- task.py: remove naive substring fallback from parse_verdict_status(), only parse ## Status: header; no header → ambiguous (REFEREE) - status.py: add _auto_update_verdict_on_complete() — when transitioning human_intervention→complete, auto-update VERDICT.md to PASS - status.py: add stale-task detection to --can-edit — deny edits if all edit tasks have .state mtime >30 min old (reason: stale_task) - status.py: add --touch command to reset task activity clock - guard plugin: handle stale_task reason with specific error message - Update test to match new verdict parsing behavior
This commit is contained in:
@@ -3,7 +3,7 @@ import type { Plugin, PluginInput, Hooks } from "@opencode-ai/plugin"
|
||||
const STATUS_SCRIPT = process.env.HOME + "/.automaton/scripts/status.py"
|
||||
const PROJECT_ROOT = process.cwd()
|
||||
|
||||
async function checkCanEdit(file?: string): Promise<{ allowed: boolean; reason: string; task?: string }> {
|
||||
async function checkCanEdit(file?: string): Promise<{ allowed: boolean; reason: string; task?: string; staleTask?: string; staleMinutes?: number }> {
|
||||
const { execSync } = await import("child_process")
|
||||
let cmd = `python3 "${STATUS_SCRIPT}" --can-edit --project "${PROJECT_ROOT}"`
|
||||
if (file) {
|
||||
@@ -16,7 +16,13 @@ async function checkCanEdit(file?: string): Promise<{ allowed: boolean; reason:
|
||||
const lines = output.trim().split("\n")
|
||||
const jsonLine = lines[lines.length - 1]
|
||||
const result = JSON.parse(jsonLine)
|
||||
return { allowed: result.allowed, reason: result.reason, task: result.primary_task?.task }
|
||||
return {
|
||||
allowed: result.allowed,
|
||||
reason: result.reason,
|
||||
task: result.primary_task?.task,
|
||||
staleTask: result.stale_task,
|
||||
staleMinutes: result.stale_minutes,
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.status === 1) {
|
||||
const stderr = (e.stderr || "").trim()
|
||||
@@ -25,7 +31,12 @@ async function checkCanEdit(file?: string): Promise<{ allowed: boolean; reason:
|
||||
const jsonLine = lines[lines.length - 1]
|
||||
try {
|
||||
const result = JSON.parse(jsonLine)
|
||||
return { allowed: false, reason: result.reason }
|
||||
return {
|
||||
allowed: false,
|
||||
reason: result.reason,
|
||||
staleTask: result.stale_task,
|
||||
staleMinutes: result.stale_minutes,
|
||||
}
|
||||
} catch {
|
||||
return { allowed: false, reason: stderr || "Denied by automaton" }
|
||||
}
|
||||
@@ -46,9 +57,20 @@ export default (async ({ client, project, directory }: PluginInput): Promise<Hoo
|
||||
return
|
||||
}
|
||||
|
||||
const { allowed, reason, task } = await checkCanEdit(filePath)
|
||||
const { allowed, reason, task, staleTask, staleMinutes } = await checkCanEdit(filePath)
|
||||
|
||||
if (!allowed) {
|
||||
if (reason === "stale_task") {
|
||||
throw new Error(
|
||||
`[AUTOMATON GUARD] BLOCKED: Task '${staleTask || "unknown"}' has been in edit phase for ${staleMinutes || "?"} minutes (stale).\n\n` +
|
||||
`To continue work on this task, touch it first:\n` +
|
||||
` python ~/.automaton/scripts/status.py --touch --task ${staleTask || "<task>"} --project ${directory}\n\n` +
|
||||
`Or create a new task for new work:\n` +
|
||||
` python ~/.automaton/scripts/status.py --create-task <name> --project ${directory}\n` +
|
||||
` python ~/.automaton/scripts/status.py --transition implement --task <name> --project ${directory}`
|
||||
);
|
||||
}
|
||||
|
||||
const msg = reason === "no_edit_tasks"
|
||||
? `BLOCKED: No task in implement or doc_review phase. Create or transition a task first.`
|
||||
: reason === "out_of_scope"
|
||||
|
||||
Reference in New Issue
Block a user