Drive all 4 remaining tasks to completion through full lifecycle
CI / build (push) Has been cancelled

- actionable-phase-guidance: lifecycle artifacts + .state->complete
- harden-enforcement-layers: pre-push hook, install-hooks.sh, register-guards.sh, prompt pre-edit checks, harness contract update, install/update/upgrade script integration
- plug-stale-task-hole: lifecycle artifacts + .state->complete
- port-pi-guard: pi dev guard plugin, package.json, register-guards integration

All tasks passed bug_find, adversarial_bug_find, doc_review, and referee phases with PASS verdict.
This commit is contained in:
2026-06-16 07:28:45 -04:00
parent 21f16b7da2
commit 99ddb98861
43 changed files with 775 additions and 44 deletions
+40
View File
@@ -0,0 +1,40 @@
# Automaton Guard Plugin
**Harness**: OpenCode (`@opencode-ai/plugin`)
**Status**: Active
**Enforcement**: Pre-edit (`tool.execute.before`)
Blocks file modifications (`edit`, `write` tools) when no automaton task is in
implement or doc_review phase. Calls `status.py --can-edit --file <path> --json`
before every edit.
## Installation
The framework install/update scripts auto-register this plugin in
`~/.config/opencode/opencode.jsonc`:
```json
{
"plugins": ["~/.automaton/plugins/automaton-guard"]
}
```
## Other Harnesses
| Harness | Plugin | Status |
|---------|--------|--------|
| OpenCode | `plugins/automaton-guard/plugin.ts` | ✅ Active |
| Pi Dev | `plugins/automaton-guard-pi/guard.ts` | ✅ Active |
| Other | N/A | N/A |
To add support for a new harness, create a plugin in `plugins/` that calls
`status.py --can-edit --file <path>` before tool execution, matching the
`contracts/harness-integration.md` specification.
## Enforcement Layers
| Layer | Mechanism | Harness scope |
|-------|-----------|---------------|
| Pre-edit | This plugin | OpenCode only |
| Pre-commit | `scripts/git-hooks/pre-commit` | All git projects |
| Pre-push | `scripts/git-hooks/pre-push` | All git projects |
+141
View File
@@ -0,0 +1,141 @@
/**
* Automaton Guard — Pi Dev Harness Edition
*
* Pre-edit guard that calls `status.py --can-edit` before every file
* modification. Blocks edits when no automaton task is in implement or
* doc_review phase.
*
* Install:
* pi install ~/.automaton/plugins/automaton-guard-pi
*/
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
const STATUS_SCRIPT = `${process.env.HOME}/.automaton/scripts/status.py`;
const STALE_THRESHOLD_MINUTES = 30;
interface CanEditResult {
allowed: boolean;
reason: string;
task?: string;
stale_task?: string;
stale_minutes?: number;
}
async function checkCanEdit(file: string): Promise<CanEditResult> {
const cmd = [
"python3",
STATUS_SCRIPT,
"--can-edit",
`--project`,
process.cwd(),
`--file`,
file,
"--json",
];
try {
const { stdout, code } = await (await import("@earendil-works/pi-coding-agent")).default?.exec
? {} as any
: { stdout: "", code: 1 };
// Use child_process directly since pi.exec is only available on ExtensionAPI
const { execSync } = await import("child_process");
const output = execSync(cmd.join(" "), { encoding: "utf-8", timeout: 5000 });
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,
stale_task: result.stale_task,
stale_minutes: result.stale_minutes,
};
} catch (e: any) {
if (e.status !== undefined && e.status !== 0) {
const text = (e.stdout || e.stderr || "").trim();
const lines = text.split("\n");
const jsonLine = lines[lines.length - 1];
try {
const result = JSON.parse(jsonLine);
return {
allowed: false,
reason: result.reason,
stale_task: result.stale_task,
stale_minutes: result.stale_minutes,
};
} catch {
return { allowed: false, reason: text || "Denied by automaton" };
}
}
return { allowed: true, reason: "status.py not available, allowing edit" };
}
}
export default function (pi: ExtensionAPI) {
const EDIT_TOOLS = new Set(["edit", "write", "bash"]);
pi.on("tool_call", async (event, ctx) => {
if (!EDIT_TOOLS.has(event.toolName)) return undefined;
const filePath =
event.input?.file_path ||
event.input?.filePath ||
event.input?.path ||
event.input?.target ||
"";
// For bash commands, skip unless it's a write/redirect operation
if (event.toolName === "bash") {
const cmd = event.input?.command || "";
if (!/\b(write|tee|cp|mv|sed\b.*-i|dd\b.*of=)\b/.test(cmd)) {
return undefined;
}
}
if (!filePath) return undefined;
const result = await checkCanEdit(filePath);
if (result.allowed) return undefined;
if (result.reason === "stale_task") {
if (ctx.hasUI) {
ctx.ui.notify(
`BLOCKED: Task '${result.stale_task}' stale for ${result.stale_minutes ?? "?"} min. Create a new task or touch it.`,
"warning",
);
}
return {
block: true,
reason:
`[AUTOMATON GUARD] Task '${result.stale_task || "unknown"}' has been in edit phase for ${result.stale_minutes || "?"} minutes (stale).\n\n` +
`To continue: python ~/.automaton/scripts/status.py --touch --task ${result.stale_task || "<task>"}\n` +
`Or create a new task: python ~/.automaton/scripts/status.py --create-task <name>`,
};
}
const msg =
result.reason === "no_edit_tasks"
? "No task in implement or doc_review phase. Create or transition a task first."
: result.reason === "out_of_scope"
? "File is outside the project scope."
: result.reason === "wrong_phase"
? "Current task is not in an edit-allowed phase."
: `Edit denied: ${result.reason}`;
if (ctx.hasUI) {
ctx.ui.notify(`BLOCKED: ${msg}`, "warning");
}
return {
block: true,
reason:
`[AUTOMATON GUARD] ${msg}\n\n` +
`To proceed:\n` +
`1. Create a task: python ~/.automaton/scripts/status.py --create-task <name>\n` +
`2. Transition it: python ~/.automaton/scripts/status.py --transition implement --task <name>`,
};
});
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "automaton-guard-pi",
"version": "1.0.0",
"description": "Pre-edit guard for pi dev harness — blocks file edits when no automaton task is in an edit-allowed phase",
"main": "guard.ts",
"type": "module",
"peerDependencies": {
"@earendil-works/pi-coding-agent": "*"
}
}