@autopilot-harness/cli 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,446 +0,0 @@
1
- /**
2
- * Autopilot hook entry — marker: autopilot-harness
3
- * Installed at .autopilot/bin/autopilot-harness-hook.mjs (copy, not symlink).
4
- *
5
- * Prefers bundled vendor/runtime.mjs (shipped by init/upgrade) so empty
6
- * consumer projects work without @autopilot-harness/* in node_modules.
7
- * Falls back to project-local packages, then fail-open.
8
- *
9
- * Events:
10
- * Cursor: beforeSubmitPrompt | afterFileEdit | stop
11
- * Claude Code: UserPromptSubmit | PostToolUse | Stop | StopFailure
12
- */
13
- import fs from "node:fs";
14
- import path from "node:path";
15
- import { createRequire } from "node:module";
16
- import { fileURLToPath, pathToFileURL } from "node:url";
17
-
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
- const projectRoot = (() => {
20
- const resolved = path.resolve(__dirname, "..", "..");
21
- try {
22
- return fs.realpathSync(resolved);
23
- } catch {
24
- return resolved;
25
- }
26
- })();
27
-
28
- const CURSOR_EVENTS = new Set([
29
- "beforeSubmitPrompt",
30
- "afterFileEdit",
31
- "stop",
32
- ]);
33
- const CLAUDE_EVENTS = new Set([
34
- "UserPromptSubmit",
35
- "PostToolUse",
36
- "Stop",
37
- "StopFailure",
38
- ]);
39
- const KNOWN_PLATFORMS = new Set(["cursor", "claude-code"]);
40
-
41
- function parseArgs(argv) {
42
- const allowed = new Set([...CURSOR_EVENTS, ...CLAUDE_EVENTS]);
43
- const out = { event: "beforeSubmitPrompt", platform: null };
44
- for (let i = 0; i < argv.length; i++) {
45
- if (argv[i] === "--event" && argv[i + 1]) {
46
- const ev = String(argv[i + 1]);
47
- // Do not consume the next flag as a value (`--event --platform …`).
48
- if (ev.startsWith("--")) continue;
49
- i += 1;
50
- out.event = allowed.has(ev) ? ev : "beforeSubmitPrompt";
51
- } else if (argv[i] === "--platform" && argv[i + 1]) {
52
- const raw = String(argv[i + 1]);
53
- if (raw.startsWith("--")) continue;
54
- i += 1;
55
- const p = raw.trim().toLowerCase();
56
- out.platform = KNOWN_PLATFORMS.has(p) ? p : null;
57
- }
58
- }
59
- return out;
60
- }
61
-
62
- function isClaudeEvent(event) {
63
- return CLAUDE_EVENTS.has(event);
64
- }
65
-
66
- /** Strong Claude Stop markers (override a lying `--platform cursor`). */
67
- function isClaudeShapedStopPayload(payload) {
68
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
69
- return false;
70
- }
71
- const hookName = String(
72
- payload.hook_event_name ?? payload.hookEventName ?? "",
73
- ).trim();
74
- if (hookName === "Stop" || /^stopfailure$/i.test(hookName)) return true;
75
- if (
76
- typeof payload.stop_hook_active === "boolean" ||
77
- typeof payload.stopHookActive === "boolean"
78
- ) {
79
- return true;
80
- }
81
- return false;
82
- }
83
-
84
- async function readStdin() {
85
- const chunks = [];
86
- for await (const chunk of process.stdin) chunks.push(chunk);
87
- const raw = Buffer.concat(chunks).toString("utf8").trim();
88
- if (!raw) return {};
89
- try {
90
- return JSON.parse(raw);
91
- } catch {
92
- return {};
93
- }
94
- }
95
-
96
- async function tryImport(specifier) {
97
- try {
98
- return await import(specifier);
99
- } catch {
100
- return null;
101
- }
102
- }
103
-
104
- function isSymlinkOrUnreadable(filePath) {
105
- try {
106
- return fs.lstatSync(filePath).isSymbolicLink();
107
- } catch {
108
- // Cannot verify — refuse vendor load (fail-closed, match CLI policy).
109
- return true;
110
- }
111
- }
112
-
113
- /** Refuse vendor paths whose realpath escapes the project root. */
114
- function realpathEscapesProject(filePath) {
115
- try {
116
- const realRoot = fs.realpathSync(projectRoot);
117
- const real = fs.realpathSync(filePath);
118
- return real !== realRoot && !real.startsWith(realRoot + path.sep);
119
- } catch {
120
- return true;
121
- }
122
- }
123
-
124
- async function loadVendorRuntime() {
125
- const vendorDir = path.join(__dirname, "vendor");
126
- const vendor = path.join(vendorDir, "runtime.mjs");
127
- const migDir = path.join(vendorDir, "migrations");
128
- const mig = path.join(migDir, "001_initial.sql");
129
- if (!fs.existsSync(vendor) || !fs.existsSync(mig)) return null;
130
- // Refuse symlink escape / unreadable lstat (same policy as CLI).
131
- if (
132
- isSymlinkOrUnreadable(vendorDir) ||
133
- isSymlinkOrUnreadable(vendor) ||
134
- isSymlinkOrUnreadable(migDir) ||
135
- isSymlinkOrUnreadable(mig)
136
- ) {
137
- return null;
138
- }
139
- if (realpathEscapesProject(vendor) || realpathEscapesProject(mig)) {
140
- return null;
141
- }
142
- return tryImport(pathToFileURL(vendor).href);
143
- }
144
-
145
- async function loadPortPackage(pkgName) {
146
- try {
147
- const require = createRequire(path.join(projectRoot, "package.json"));
148
- const resolved = require.resolve(pkgName);
149
- return tryImport(pathToFileURL(resolved).href);
150
- } catch {
151
- return null;
152
- }
153
- }
154
-
155
- async function loadCoreFromNodeModules() {
156
- return loadPortPackage("@autopilot-harness/core");
157
- }
158
-
159
- /**
160
- * Fail-open shapes must match the host:
161
- * - Cursor submit → { continue: true }
162
- * - Claude UserPromptSubmit → {} (allow; no decision:block)
163
- * - other events → {}
164
- */
165
- function failOpen(event) {
166
- if (event === "beforeSubmitPrompt") {
167
- writeReply(JSON.stringify({ continue: true }));
168
- } else {
169
- writeReply("{}");
170
- }
171
- }
172
-
173
- /** At-most-once stdout so fail-open cannot append a second JSON blob. */
174
- let replied = false;
175
- function writeReply(text) {
176
- if (replied) return;
177
- process.stdout.write(text);
178
- // Set only after a successful write so failOpen can still retry on throw.
179
- replied = true;
180
- }
181
-
182
- function createEngine(coreMod, store) {
183
- return typeof coreMod.createConfiguredReviewEngine === "function"
184
- ? coreMod.createConfiguredReviewEngine(store, projectRoot)
185
- : new coreMod.ReviewEngine(store, {
186
- confirmRounds: 5,
187
- reviewScope: "executing_only",
188
- verifyEnabled: false,
189
- verifyCommands: [],
190
- maxIdleStops: 5,
191
- maxErrorsBeforePause: 0,
192
- projectRoot,
193
- });
194
- }
195
-
196
- function cursorStopHandler(port) {
197
- if (typeof port.handleCursorStop === "function") {
198
- return port.handleCursorStop;
199
- }
200
- // Dual/legacy vendor: deprecated handleStop === Cursor only when Cursor
201
- // submit exists. Never fall through to Claude-only package handleStop.
202
- if (
203
- typeof port.handleStop === "function" &&
204
- typeof port.handleBeforeSubmitPrompt === "function"
205
- ) {
206
- return port.handleStop;
207
- }
208
- return undefined;
209
- }
210
-
211
- /**
212
- * Resolve Claude Stop handler without falling through to Cursor's handleStop
213
- * on the dual-port vendor (where deprecated `handleStop` === handleCursorStop).
214
- * node_modules `@autopilot-harness/port-claude-code` exports Claude as handleStop
215
- * and has no Cursor submit handler.
216
- */
217
- function claudeStopHandler(port) {
218
- if (typeof port.handleClaudeStop === "function") {
219
- return port.handleClaudeStop;
220
- }
221
- if (
222
- typeof port.handleStop === "function" &&
223
- typeof port.handleBeforeSubmitPrompt !== "function"
224
- ) {
225
- return port.handleStop;
226
- }
227
- return undefined;
228
- }
229
-
230
- /**
231
- * Cursor IDE may also execute `.claude/settings.json` Stop hooks ("claude-project
232
- * config") on the same user Stop. Those payloads are Cursor-shaped (`status`,
233
- * lowercase `hook_event_name: "stop"`). Route them to the Cursor port so abort
234
- * halts instead of Claude recover (decision:block), which Cursor merges back
235
- * into followup and fights the real abort path.
236
- *
237
- * Heuristic (order matters):
238
- * 1) Explicit Claude hook names (`Stop` / `StopFailure`) → not Cursor
239
- * 2) Lowercase `stop` → Cursor
240
- * 3) `stop_hook_active` present (Claude continuum) → not Cursor
241
- * 4) Cursor status vocab + `conversation_id` → Cursor; bare `session_id` → Claude
242
- */
243
- function isCursorShapedStopPayload(payload) {
244
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
245
- return false;
246
- }
247
- const hookName = String(
248
- payload.hook_event_name ?? payload.hookEventName ?? "",
249
- ).trim();
250
- if (hookName === "Stop" || /^stopfailure$/i.test(hookName)) return false;
251
- if (hookName === "stop") return true;
252
-
253
- // Claude Stop threads stop_hook_active (bool); Cursor uses loop_count.
254
- if (
255
- typeof payload.stop_hook_active === "boolean" ||
256
- typeof payload.stopHookActive === "boolean"
257
- ) {
258
- return false;
259
- }
260
-
261
- const statusRaw = String(payload.status ?? "")
262
- .toLowerCase()
263
- .trim();
264
- const cursorStatus =
265
- statusRaw === "aborted" ||
266
- statusRaw === "cancelled" ||
267
- statusRaw === "canceled" ||
268
- statusRaw === "completed" ||
269
- statusRaw === "error" ||
270
- statusRaw === "failed";
271
- if (!cursorStatus) return false;
272
-
273
- const conversationId = String(
274
- payload.conversation_id ?? payload.conversationId ?? "",
275
- ).trim();
276
- if (conversationId) return true;
277
-
278
- const sessionId = String(
279
- payload.session_id ?? payload.sessionId ?? "",
280
- ).trim();
281
- // Claude-shaped id without conversation_id → keep Claude path
282
- if (sessionId) return false;
283
-
284
- // Abort/cancel with no ids: prefer Cursor halt (no-op {}) over Claude recover
285
- return (
286
- statusRaw === "aborted" ||
287
- statusRaw === "cancelled" ||
288
- statusRaw === "canceled"
289
- );
290
- }
291
-
292
- let bootEvent = "beforeSubmitPrompt";
293
-
294
- async function main() {
295
- const { event, platform: declaredPlatform } = parseArgs(
296
- process.argv.slice(2),
297
- );
298
- bootEvent = event;
299
- try {
300
- const payload = await readStdin();
301
- // Layer A: --platform; fall back to event-name heuristics for legacy installs.
302
- const preferClaudePort =
303
- declaredPlatform === "claude-code"
304
- ? true
305
- : declaredPlatform === "cursor"
306
- ? false
307
- : isClaudeEvent(event);
308
- const claude = preferClaudePort;
309
-
310
- const vendor = await loadVendorRuntime();
311
- const port = vendor
312
- ? vendor
313
- : claude
314
- ? await loadPortPackage("@autopilot-harness/port-claude-code")
315
- : await loadPortPackage("@autopilot-harness/port-cursor");
316
- const coreMod = vendor ?? (await loadCoreFromNodeModules());
317
-
318
- const portReady = claude
319
- ? typeof port?.handleUserPromptSubmit === "function"
320
- : typeof port?.handleBeforeSubmitPrompt === "function";
321
- if (!portReady || !coreMod?.StateStore) {
322
- failOpen(event);
323
- return;
324
- }
325
-
326
- const store = new coreMod.StateStore(projectRoot);
327
- try {
328
- if (event === "beforeSubmitPrompt") {
329
- const result = port.handleBeforeSubmitPrompt(
330
- store,
331
- payload,
332
- projectRoot,
333
- );
334
- writeReply(JSON.stringify(result ?? {}));
335
- return;
336
- }
337
- if (event === "afterFileEdit") {
338
- port.handleAfterFileEdit?.(store, payload, projectRoot);
339
- writeReply("{}");
340
- return;
341
- }
342
- if (event === "stop") {
343
- const stopFn = cursorStopHandler(port);
344
- if (typeof stopFn !== "function") {
345
- failOpen(event);
346
- return;
347
- }
348
- const result = stopFn(createEngine(coreMod, store), payload);
349
- writeReply(JSON.stringify(result ?? {}));
350
- return;
351
- }
352
- if (event === "UserPromptSubmit") {
353
- const result = port.handleUserPromptSubmit(
354
- store,
355
- payload,
356
- projectRoot,
357
- );
358
- writeReply(JSON.stringify(result ?? {}));
359
- return;
360
- }
361
- if (event === "PostToolUse") {
362
- port.handlePostToolUse?.(store, payload, projectRoot);
363
- writeReply("{}");
364
- return;
365
- }
366
- if (event === "Stop") {
367
- // Layer C: payload shape vs declared --platform (cross-fire / lying argv).
368
- let useCursorStop = false;
369
- if (isCursorShapedStopPayload(payload)) {
370
- useCursorStop = true;
371
- } else if (isClaudeShapedStopPayload(payload)) {
372
- useCursorStop = false;
373
- } else if (declaredPlatform === "cursor") {
374
- useCursorStop = true;
375
- } else {
376
- useCursorStop = false;
377
- }
378
- if (useCursorStop) {
379
- let stopFn = cursorStopHandler(port);
380
- // Non-vendor Claude-only load + Cursor-shaped cross-fire needs Cursor port.
381
- if (typeof stopFn !== "function") {
382
- const cursorPort = await loadPortPackage(
383
- "@autopilot-harness/port-cursor",
384
- );
385
- if (cursorPort) stopFn = cursorStopHandler(cursorPort);
386
- }
387
- if (typeof stopFn !== "function") {
388
- failOpen(event);
389
- return;
390
- }
391
- const result = stopFn(createEngine(coreMod, store), payload);
392
- writeReply(JSON.stringify(result ?? {}));
393
- return;
394
- }
395
- let stopFn = claudeStopHandler(port);
396
- // Non-vendor Cursor-only load + Claude-shaped Stop needs Claude port.
397
- if (typeof stopFn !== "function") {
398
- const claudePort = await loadPortPackage(
399
- "@autopilot-harness/port-claude-code",
400
- );
401
- if (claudePort) stopFn = claudeStopHandler(claudePort);
402
- }
403
- if (typeof stopFn !== "function") {
404
- failOpen(event);
405
- return;
406
- }
407
- const result = stopFn(createEngine(coreMod, store), payload);
408
- writeReply(JSON.stringify(result ?? {}));
409
- return;
410
- }
411
- if (event === "StopFailure") {
412
- let failFn = port.handleStopFailure;
413
- if (typeof failFn !== "function") {
414
- const stopFn = claudeStopHandler(port);
415
- if (typeof stopFn === "function") {
416
- failFn = (engine, p) => stopFn(engine, p, { status: "error" });
417
- }
418
- }
419
- if (typeof failFn !== "function") {
420
- failOpen(event);
421
- return;
422
- }
423
- const result = failFn(createEngine(coreMod, store), payload);
424
- writeReply(JSON.stringify(result ?? {}));
425
- return;
426
- }
427
- writeReply("{}");
428
- } finally {
429
- try {
430
- store.close();
431
- } catch {
432
- /* ignore */
433
- }
434
- }
435
- } catch (err) {
436
- console.error("[autopilot-harness] hook error:", err?.message ?? err);
437
- failOpen(event);
438
- }
439
- }
440
-
441
- main().catch((err) => {
442
- console.error("[autopilot-harness] hook error:", err?.message ?? err);
443
- // Prefer the parsed event when main() assigned it; else Cursor-safe default.
444
- failOpen(bootEvent);
445
- process.exitCode = 0;
446
- });
@@ -1,92 +0,0 @@
1
- # Autopilot — paths that do NOT trigger self-review (gitignore syntax).
2
- #
3
- # What this file is:
4
- # - Controls whether an afterFileEdit counts as "product code" (opens fix/confirm).
5
- # - Does NOT change `git diff` / `git status` output (that is `.gitignore`).
6
- # - Review followups ask the agent to skip these paths when reading diffs (soft).
7
- #
8
- # Semantics:
9
- # - Same glob rules as gitignore; last matching pattern wins.
10
- # - Use `!` to force-include an exception (e.g. `!docs/feed/**/*.yml`).
11
- # - Markdown (*.md / *.mdx) is NOT ignored by default — design docs can be reviewed.
12
- # - `docs/**` is NOT ignored by default.
13
- # - Also skip untracked paths ignored by `.gitignore` (tracked files still count).
14
- #
15
- # Later (not implemented): hard-filtered review-diff / path ledger — see
16
- # docs/autopilot/workflows/autopilot-executing.md (B2 strong).
17
-
18
- # Runtime / editor (prefer also listing these in .gitignore)
19
- .autopilot/**
20
- .cursor/**
21
- .claude/**
22
-
23
- # Planning artifacts
24
- plans/**
25
-
26
- # Common build / vendor trees
27
- node_modules/**
28
- dist/**
29
- build/**
30
- out/**
31
- target/**
32
- .target/**
33
- coverage/**
34
- .venv/**
35
- venv/**
36
- __pycache__/**
37
-
38
- # Lockfiles / package manager noise
39
- package-lock.json
40
- pnpm-lock.yaml
41
- yarn.lock
42
- bun.lock
43
- bun.lockb
44
- Cargo.lock
45
- poetry.lock
46
- composer.lock
47
-
48
- # Media / binary (do not trigger self-review)
49
- *.png
50
- *.jpg
51
- *.jpeg
52
- *.gif
53
- *.webp
54
- *.ico
55
- *.svg
56
- *.bmp
57
- *.mp3
58
- *.mp4
59
- *.wav
60
- *.webm
61
- *.mov
62
- *.woff
63
- *.woff2
64
- *.ttf
65
- *.otf
66
- *.eot
67
- *.pdf
68
- *.zip
69
- *.gz
70
- *.tgz
71
- *.7z
72
- *.rar
73
- *.jar
74
- *.class
75
- *.o
76
- *.a
77
- *.so
78
- *.dylib
79
- *.dll
80
- *.exe
81
- *.wasm
82
-
83
- # Prose / data noise
84
- *.txt
85
- *.html
86
- *.htm
87
- *.csv
88
- *.tsv
89
- *.log
90
- *.map
91
- *.min.js
92
- *.min.css
@@ -1,8 +0,0 @@
1
- ---
2
- name: autopilot-off
3
- description: "{{description}}"
4
- ---
5
-
6
- The submit hook has already disarmed Autopilot for this conversation (paused; phase unchanged unless done→idle).
7
-
8
- Acknowledge pause. Do not auto-advance. Suggest Autopilot RESUME or Autopilot RUN · <slug> when ready.
@@ -1,14 +0,0 @@
1
- ---
2
- name: autopilot-on
3
- description: "{{description}}"
4
- ---
5
-
6
- The submit hook has already set phase=planning for this conversation.
7
-
8
- Follow **autopilot-planning** workflow (docs/autopilot/workflows/autopilot-planning.md).
9
-
10
- - initial_brief from text after /autopilot-on → seed Round 1
11
- - Optional slug: alone after the command, or after `·`, matching `[a-z0-9]+([.-][a-z0-9]+)*` and ≤128 chars (same as RUN); other text is initial_brief. Unsafe explicit slugs (e.g. from API) are rejected by the hook.
12
- - Look up repo facts with platform tools; do not ask the user for what you can inspect
13
- - Write plans/<slug>/ artifacts (slug rule above); no product code until /autopilot-run
14
- - User-visible replies must match the user's language
@@ -1,9 +0,0 @@
1
- ---
2
- name: autopilot-replan
3
- description: "{{description}}"
4
- ---
5
-
6
- The submit hook has set phase=planning and reset the review chain for this track.
7
-
8
- Revise plan.md and unchecked checklist items only. Do not silently delete completed `[x]` items.
9
- When ready, prompt `/autopilot-run`.
@@ -1,10 +0,0 @@
1
- ---
2
- name: autopilot-resume
3
- description: "{{description}}"
4
- ---
5
-
6
- The submit hook has resumed Autopilot for **this** conversation (cleared pause if any; review chain preserved).
7
-
8
- If this chat had no session, the hook may have **claimed** an executing track from another conversation (same project) onto this one — including when the old Cursor chat is dead/unreadable. Optional: `/autopilot-resume <slug>` to pick the track when several are executing.
9
-
10
- Continue from checklist progress and current phase. Do not reset review confirm rounds unless asked.
@@ -1,12 +0,0 @@
1
- ---
2
- name: autopilot-run
3
- description: "{{description}}"
4
- ---
5
-
6
- The submit hook has already set phase=executing (or will after track pick) for this conversation.
7
-
8
- Follow **autopilot-executing** workflow (docs/autopilot/workflows/autopilot-executing.md).
9
-
10
- - Read plans/<slug>/checklist.md; implement the first unchecked item
11
- - Obey fix/confirm/advance followups from the stop hook
12
- - User-visible replies must match the user's language
@@ -1,57 +0,0 @@
1
- # Autopilot Executing
2
-
3
- Implement the current unchecked checklist item, then obey stop-hook followups.
4
-
5
- ## Per-item flow
6
-
7
- 1. Read `plans/<slug>/checklist.md` — work only on `firstUnchecked()` (`- [ ] <id> — <title>`).
8
- 2. Implement within that item's scope (align with `plan.md`).
9
- 3. Machine verify / completion evidence: write `.autopilot/verify-last.json` with matching `itemId` (and `ok: true` when using a hand-written report). Run configured verify commands when present.
10
- 4. Stop hook injects **fix** / **confirm** / **advance** / **done** — follow the injected message; do **not** invent your own review lens.
11
-
12
- ### Product code vs no-code items
13
-
14
- | Situation | Stop behavior |
15
- |-----------|----------------|
16
- | You edited product code this item | **fix → confirm →** then verify / advance |
17
- | No product-code diff (env, ops, paths listed in `.autopilotignore`, or untracked + `.gitignore`) | Skip fix/confirm when `verify-last.json` `itemId` matches the current item (or required verify **pass**); then **advance** / **done** |
18
- | Required verify **fail** | `verify_fix` — fix env/report or code; if you edit product code next, fix chain runs first |
19
-
20
- **What counts as product code (trigger):** any edited path that is **not** matched by `.autopilotignore`, and is **not** an untracked path ignored by `.gitignore`. There is no hardcoded extension allowlist — configure exclusions in `.autopilotignore` (comments in that file explain defaults). Markdown is reviewable by default; `docs/**` is not blocked by default.
21
-
22
- **Agent review scope (B2 weak):** fix/confirm followups ask the agent to skip `.autopilotignore` hits and untracked `.gitignore` paths when reading `git diff` / `git status`. This is prompt guidance only (soft).
23
-
24
- **B2 strong (not implemented — future):** harness could emit a filtered diff command or a per-chain product-path ledger so review scope is hard-enforced without relying on the agent. Revisit if soft guidance is insufficient.
25
-
26
- ## Fix vs confirm
27
-
28
- | Mode | Behavior |
29
- |------|----------|
30
- | Fix round | Defect-first on the in-scope diff; fix CRITICAL/HIGH; run relevant tests; **no commit** |
31
- | Confirm rounds | Only the **injected lens**; CRITICAL/HIGH may fix (returns to fix); final lens is **read-only** |
32
- | Confirm 1–N | **Never commit** |
33
-
34
- ## Advance / done turn (mandatory order)
35
-
36
- When followup is advance or done:
37
-
38
- 1. Mark **only** the completed current item named in the followup `[x]` in `checklist.md`. Do **not** mark the next item.
39
- 2. Scoped conventional commit if the working tree has this item's changes — **include `checklist.md`** when `plans/` is committed (no `git add -A`, no secrets / `.autopilot/state.db`).
40
- 3. **Then** start the next unchecked item named in the followup (next turn is OK for large code).
41
-
42
- ### Checklist `[x]` timing (hard)
43
-
44
- - **Do not** mark the item you are still implementing `[x]` mid-work or mid-review.
45
- - Only Advance/Done followups check off the **completed** current item.
46
- - Premature `[x]` used to make the stop-hook name the wrong "next" item; the harness now sticks `reviewing_item_id`, but agents must still obey this rule.
47
-
48
- If you write next-item code before checking off, `itemId` / verify binding will be wrong.
49
-
50
- Advance leaves `chain_pending=0` so a docs-only / ignore-only next item does not open a phantom confirm chain; product edits still arm review via `afterFileEdit`.
51
-
52
- ## Hard rules
53
-
54
- - Do not advance while verify required commands FAIL (hook blocks; rewrite `verify-last.json` after fixing).
55
- - Configure verify under `.autopilot/config.yml` → `review.verify.commands` (`id` / `run` / `required`).
56
- - User-visible replies match the user's language.
57
- - No push / `--no-verify` / amend unless the user explicitly asks in this conversation.
@@ -1,42 +0,0 @@
1
- # Autopilot Planning
2
-
3
- Built-in grill / design-tree workflow. Do **not** write product code until `/autopilot-run`.
4
-
5
- ## Frontier format (every round)
6
-
7
- List every decision you can ask **now** (premises already settled):
8
-
9
- ```markdown
10
- ❓ **Q1** - **<title>**: <body; options if useful>
11
-
12
- ➡️ <recommended answer>
13
- ```
14
-
15
- Wait for the user to answer the round, then open the next frontier. Round 1 usually covers goal / scope / acceptance. Later rounds go block → detail.
16
-
17
- ## Brownfield (existing repo)
18
-
19
- 1. Read README and manifests (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, …).
20
- 2. Search / skim modules related to the request.
21
- 3. Put constraints under **Existing context** in `brief.md`.
22
- 4. Cite **real repo paths** in questions — do not ask the user for facts you can inspect.
23
-
24
- ## Greenfield
25
-
26
- Skip repo survey; start from goals and constraints.
27
-
28
- ## Artifact timing
29
-
30
- | When | Write |
31
- |------|--------|
32
- | Title is clear | Create `plans/<slug>/` (`brief.md`, `plan.md`, `checklist.md`); update `plans/README.md`. **Slug** = `[a-z0-9]+([.-][a-z0-9]+)*`, length 1–128 (kebab; single dots OK, e.g. `v0.1-npm-release`; no `..`, `/`, `\`, `_`) — same rule as `/autopilot-on|run <slug>` |
33
- | Frontier nearly empty | Checklist **draft** (`- [ ]`) |
34
- | User confirms the plan | Finalize checklist: `- [ ] <id> — <title>` (**item id** kebab-case letters/digits/hyphens only — **no dots**) |
35
- | Ready to build | Prompt **`/autopilot-run`** (or `/autopilot-run <slug>`) |
36
-
37
- ## Hard rules
38
-
39
- - Planning may only edit `plans/**` and docs — **no product code**.
40
- - Directory `<slug>` must match `[a-z0-9]+([.-][a-z0-9]+)*` and ≤128 chars (same gate as `/autopilot-on|run <slug>`); checklist **item** ids stay `[a-z0-9]+(-[a-z0-9]+)*` (no dots).
41
- - User shortcuts: “直接定稿 / skip grill / use your recommendations” may shorten rounds; still produce the three artifacts.
42
- - User-visible replies match the user's language. Workflow procedure stays English.