@autopilot-harness/cli 0.2.2 → 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.
@@ -0,0 +1,92 @@
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
@@ -0,0 +1,8 @@
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.
@@ -0,0 +1,14 @@
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
@@ -0,0 +1,9 @@
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`.
@@ -0,0 +1,10 @@
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.
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,59 @@
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
+ - **Required for every item before you stop** — especially no-code / ops / verify-only items. A stale `itemId` from a prior item blocks advance.
11
+ - After writing the report, **end the turn** so the stop hook can inject advance/done. Do not ask the user to continue; do not invent your own Advance/Done.
12
+ 4. Stop hook injects **fix** / **confirm** / **need_evidence** / **advance** / **done** — follow the injected message; do **not** invent your own review lens.
13
+
14
+ ### Product code vs no-code items
15
+
16
+ | Situation | Stop behavior |
17
+ |-----------|----------------|
18
+ | You edited product code this item | **fix → confirm →** then verify / advance |
19
+ | 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**. If the report is missing, stale, or `ok: false`, stop hook injects **need_evidence** — write the matching report and end the turn; do not wait for the user. |
20
+ | Required verify **fail** | `verify_fix` — fix env/report or code; if you edit product code next, fix chain runs first |
21
+
22
+ **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.
23
+
24
+ **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).
25
+
26
+ **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.
27
+
28
+ ## Fix vs confirm
29
+
30
+ | Mode | Behavior |
31
+ |------|----------|
32
+ | Fix round | Defect-first on the in-scope diff; fix CRITICAL/HIGH; run relevant tests; **no commit** |
33
+ | Confirm rounds | Only the **injected lens**; CRITICAL/HIGH may fix (returns to fix); final lens is **read-only** |
34
+ | Confirm 1–N | **Never commit** |
35
+
36
+ ## Advance / done turn (mandatory order)
37
+
38
+ When followup is advance or done:
39
+
40
+ 1. Mark **only** the completed current item named in the followup `[x]` in `checklist.md`. Do **not** mark the next item.
41
+ 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`).
42
+ 3. **Then** start the next unchecked item named in the followup (next turn is OK for large code).
43
+
44
+ ### Checklist `[x]` timing (hard)
45
+
46
+ - **Do not** mark the item you are still implementing `[x]` mid-work or mid-review.
47
+ - Only Advance/Done followups check off the **completed** current item.
48
+ - 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.
49
+
50
+ If you write next-item code before checking off, `itemId` / verify binding will be wrong.
51
+
52
+ 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`.
53
+
54
+ ## Hard rules
55
+
56
+ - Do not advance while verify required commands FAIL (hook blocks; rewrite `verify-last.json` after fixing).
57
+ - Configure verify under `.autopilot/config.yml` → `review.verify.commands` (`id` / `run` / `required`).
58
+ - User-visible replies match the user's language.
59
+ - No push / `--no-verify` / amend unless the user explicitly asks in this conversation.
@@ -0,0 +1,42 @@
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.
@@ -43,6 +43,7 @@ var en_default = {
43
43
  review_complete: "Review complete. All {total} confirm rounds passed; the review chain has ended. If the working tree still has uncommitted changes from this session, local commit only per the safe checklist (never stage .env/secrets/.autopilot runtime; no push unless the user asks); if clean, briefly confirm only.",
44
44
  stuck: "Stuck: no progress for several stops. Change strategy or send Autopilot RESUME after fixing.",
45
45
  verify_fix: "Verify failed ({reason}). Fix verify commands and rewrite verify-last.json; do not advance.",
46
+ need_evidence: `Need evidence: no-code item {currentId}{currentTitleSuffix} cannot advance without matching soft completion evidence. Write .autopilot/verify-last.json with itemId "{currentId}" and ok: true (only after this item's work is done). Then end the turn so the stop hook can advance/done. Do not ask the user to continue; do not invent Advance/Done.`,
46
47
  track_pick: "Select a plan by number or slug."
47
48
  },
48
49
  error: {
@@ -118,6 +119,7 @@ var zh_CN_default = {
118
119
  review_complete: "\u81EA\u5BA1\u5B8C\u6210\u3002\u8FDE\u7EED {total} \u8F6E\u786E\u8BA4\u5DF2\u901A\u8FC7\uFF0C\u81EA\u5BA1\u94FE\u5DF2\u7ED3\u675F\u3002\u82E5\u5DE5\u4F5C\u533A\u4ECD\u6709\u672C\u4F1A\u8BDD\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u6309\u5B89\u5168\u6E05\u5355\u672C\u5730 commit\uFF08\u52FF stage .env/\u5BC6\u94A5/.autopilot \u8FD0\u884C\u65F6\uFF1B\u52FF push\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\uFF09\uFF1B\u5DF2\u5E72\u51C0\u5219\u53EA\u7B80\u77ED\u786E\u8BA4\u5373\u53EF\u3002",
119
120
  stuck: "\u5361\u4F4F\uFF1A\u8FDE\u7EED\u591A\u8F6E\u65E0\u8FDB\u5C55\u3002\u8BF7\u6362\u7B56\u7565\uFF0C\u6216\u4FEE\u597D\u540E\u53D1\u9001 Autopilot RESUME\u3002",
120
121
  verify_fix: "\u6821\u9A8C\u5931\u8D25\uFF08{reason}\uFF09\u3002\u8BF7\u4FEE\u590D verify \u547D\u4EE4\u5E76\u91CD\u5199 verify-last.json\uFF1B\u4E0D\u8981\u63A8\u8FDB\u3002",
122
+ need_evidence: '\u9700\u8981\u5B8C\u6210\u8BC1\u636E\uFF1A\u65E0\u4EE3\u7801\u6539\u52A8\u9879 {currentId}{currentTitleSuffix} \u7F3A\u5C11\u5339\u914D\u7684 soft \u5B8C\u6210\u8BC1\u636E\uFF0C\u65E0\u6CD5\u63A8\u8FDB\u3002\u8BF7\u5199\u5165 .autopilot/verify-last.json\uFF08itemId \u4E3A "{currentId}"\uFF0Cok: true\uFF1B\u987B\u5728\u8BE5\u9879\u5DE5\u4F5C\u5B8C\u6210\u540E\uFF09\u3002\u7136\u540E\u7ED3\u675F\u672C\u56DE\u5408\uFF0C\u7531 stop hook \u63A8\u8FDB/\u5B8C\u6210\u3002\u4E0D\u8981\u8BA9\u7528\u6237\u8BF4\u300C\u7EE7\u7EED\u300D\uFF1B\u4E0D\u8981\u81EA\u884C\u53D1\u660E\u63A8\u8FDB/\u5B8C\u6210\u6307\u4EE4\u3002',
121
123
  track_pick: "\u8BF7\u7528\u6570\u5B57\u6216 slug \u9009\u62E9\u8981\u6267\u884C\u7684 plan\u3002"
122
124
  },
123
125
  error: {
@@ -1236,6 +1238,8 @@ var StateStore = class _StateStore {
1236
1238
  OR trim(pending_followup) GLOB '\u81EA\u5BA1\u5B8C\u6210*'
1237
1239
  OR trim(pending_followup) GLOB 'Advance*'
1238
1240
  OR trim(pending_followup) GLOB '\u63A8\u8FDB*'
1241
+ OR trim(pending_followup) GLOB 'Need evidence*'
1242
+ OR trim(pending_followup) GLOB '\u9700\u8981\u5B8C\u6210\u8BC1\u636E*'
1239
1243
  )`;
1240
1244
  /**
1241
1245
  * SQL predicate: pending is absent or not a recover automation prompt.
@@ -1877,12 +1881,15 @@ var HARNESS_FOLLOWUP_PREFIXES = [
1877
1881
  "Recover:",
1878
1882
  "Review complete",
1879
1883
  "Verify failed",
1884
+ "Need evidence:",
1880
1885
  "\u81EA\u5BA1\u4FEE\u590D",
1881
1886
  "\u81EA\u5BA1\u786E\u8BA4",
1882
1887
  "\u81EA\u5BA1\u5B8C\u6210",
1883
1888
  "\u63A8\u8FDB\u4E0B\u4E00\u9879",
1884
1889
  "\u5168\u90E8\u5B8C\u6210",
1885
1890
  "\u6821\u9A8C\u5931\u8D25",
1891
+ "\u9700\u8981\u5B8C\u6210\u8BC1\u636E\uFF1A",
1892
+ "\u9700\u8981\u5B8C\u6210\u8BC1\u636E:",
1886
1893
  // Match zh recover/stuck templates (fullwidth colon) — bare「恢复」is too broad.
1887
1894
  "\u6062\u590D\uFF1A",
1888
1895
  "\u5361\u4F4F\uFF1A",
@@ -2284,6 +2291,8 @@ function defaultRender(kind, vars) {
2284
2291
  return `Stuck: no progress for several stops. Change strategy or send Autopilot RESUME after fixing.`;
2285
2292
  case "verify_fix":
2286
2293
  return `Verify failed (${vars.reason ?? "unknown"}). Fix verify commands and rewrite verify-last.json; do not advance.`;
2294
+ case "need_evidence":
2295
+ return `Need evidence: no-code item ${vars.currentId ?? ""}${vars.currentTitle ? ` \u2014 ${vars.currentTitle}` : ""} cannot advance without matching soft completion evidence. Write .autopilot/verify-last.json with itemId "${vars.currentId ?? ""}" and ok: true (only after this item's work is done). Then end the turn so the stop hook can advance/done. Do not ask the user to continue; do not invent Advance/Done.`;
2287
2296
  default:
2288
2297
  return "";
2289
2298
  }
@@ -2575,6 +2584,9 @@ var ReviewEngine = class {
2575
2584
  if (m.startsWith("Recover") || m.startsWith("\u6062\u590D")) return "recover";
2576
2585
  if (m.startsWith("Stuck") || m.startsWith("\u5361\u4F4F")) return "stuck";
2577
2586
  if (m.startsWith("Verify failed") || m.startsWith("\u6821\u9A8C\u5931\u8D25")) return "verify_fix";
2587
+ if (m.startsWith("Need evidence") || m.startsWith("\u9700\u8981\u5B8C\u6210\u8BC1\u636E")) {
2588
+ return "need_evidence";
2589
+ }
2578
2590
  return "review.confirm";
2579
2591
  }
2580
2592
  emit(conversationId, action) {
@@ -2952,6 +2964,9 @@ var ReviewEngine = class {
2952
2964
  if (line.startsWith("Verify failed") || line.startsWith("\u6821\u9A8C\u5931\u8D25")) {
2953
2965
  return "verify";
2954
2966
  }
2967
+ if (line.startsWith("Need evidence") || line.startsWith("\u9700\u8981\u5B8C\u6210\u8BC1\u636E")) {
2968
+ return "need_evidence";
2969
+ }
2955
2970
  if (line.startsWith("All checklist") || line.startsWith("\u5168\u90E8\u5B8C\u6210") || line.startsWith("Review complete") || line.startsWith("\u81EA\u5BA1\u5B8C\u6210")) {
2956
2971
  return "terminal";
2957
2972
  }
@@ -3292,9 +3307,26 @@ var ReviewEngine = class {
3292
3307
  projectRoot: trustRoot ?? void 0
3293
3308
  });
3294
3309
  if (evalResult.outcome === "skip") {
3295
- return this.e0DirectAdvance(session, reportPath, currentItem.id, {
3296
- kind: "soft"
3297
- });
3310
+ const trySoftAdvance = (itemId) => this.e0DirectAdvance(session, reportPath, itemId, { kind: "soft" });
3311
+ const advanced = trySoftAdvance(currentItem.id);
3312
+ if (advanced) return advanced;
3313
+ const nudged = this.e0EmitNeedEvidence(session, currentItem);
3314
+ if (nudged) return nudged;
3315
+ const liveChain = this.store.getReviewChain(session.conversation_id);
3316
+ const liveParsed = this.parseSessionChecklist(session);
3317
+ const liveId = liveParsed?.checklist && this.resolveReviewingItemId(
3318
+ liveChain,
3319
+ liveParsed.checklist,
3320
+ currentItem.id
3321
+ ) || currentItem.id;
3322
+ if (!hasNoCodeCompletionEvidence({
3323
+ reportPath,
3324
+ currentItemId: liveId,
3325
+ projectRoot: trustRoot ?? void 0
3326
+ })) {
3327
+ return null;
3328
+ }
3329
+ return trySoftAdvance(liveId);
3298
3330
  }
3299
3331
  if (evalResult.outcome === "pass") {
3300
3332
  return this.e0DirectAdvance(session, reportPath, currentItem.id, {
@@ -3435,6 +3467,96 @@ var ReviewEngine = class {
3435
3467
  }
3436
3468
  return action;
3437
3469
  }
3470
+ /**
3471
+ * E0 soft path without completion evidence: inject an actionable nudge instead
3472
+ * of returning null (silent stall). Keeps chain_pending=0 so the next stop
3473
+ * can soft-advance once verify-last.json matches — arming would force phantom E3.
3474
+ * Shared by all platforms (Cursor / Claude Code) via ReviewEngine.
3475
+ *
3476
+ * Do **not** call afterFollowupCommitted here: that resets idle_stop_count and
3477
+ * would undo the stuck progression (same pattern as E5c verify_fix).
3478
+ */
3479
+ e0EmitNeedEvidence(session, expectedItem) {
3480
+ const cid2 = session.conversation_id;
3481
+ const trustRoot = this.trustedProjectRoot();
3482
+ const reportPath = this.config.verifyReportPath ?? defaultVerifyReportPath(trustRoot ?? "");
3483
+ return this.store.exclusiveWrite(() => {
3484
+ if (!this.sessionRunnable(cid2)) {
3485
+ return { commit: false, value: null };
3486
+ }
3487
+ const sess = this.store.getSession(cid2);
3488
+ if (!sess || !isChecklistExecuting(sess)) {
3489
+ return { commit: false, value: null };
3490
+ }
3491
+ const fresh = this.store.getReviewChain(cid2);
3492
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1) {
3493
+ return { commit: false, value: null };
3494
+ }
3495
+ const refreshed = this.parseSessionChecklist(sess);
3496
+ if (!refreshed?.checklist) {
3497
+ return { commit: false, value: null };
3498
+ }
3499
+ const reviewingId = this.resolveReviewingItemId(
3500
+ fresh,
3501
+ refreshed.checklist,
3502
+ expectedItem.id
3503
+ );
3504
+ const lockedItem = reviewingId && refreshed.checklist.items.find((i) => i.id === reviewingId) || refreshed.currentItem;
3505
+ if (!lockedItem) {
3506
+ return { commit: false, value: null };
3507
+ }
3508
+ if (hasNoCodeCompletionEvidence({
3509
+ reportPath,
3510
+ currentItemId: lockedItem.id,
3511
+ projectRoot: trustRoot ?? void 0
3512
+ })) {
3513
+ return { commit: false, value: null };
3514
+ }
3515
+ const nextIdle = sess.idle_stop_count + 1;
3516
+ const nowStuck = nextIdle >= this.config.maxIdleStops;
3517
+ if (nowStuck) {
3518
+ this.store.upsertSession({
3519
+ conversation_id: cid2,
3520
+ project_root: sess.project_root,
3521
+ code_root: sess.code_root,
3522
+ idle_stop_count: nextIdle,
3523
+ paused: 1,
3524
+ paused_reason: "stuck",
3525
+ armed: 0
3526
+ });
3527
+ } else {
3528
+ this.store.upsertSession({
3529
+ conversation_id: cid2,
3530
+ project_root: sess.project_root,
3531
+ code_root: sess.code_root,
3532
+ idle_stop_count: nextIdle
3533
+ });
3534
+ }
3535
+ const kind = nowStuck ? "stuck" : "need_evidence";
3536
+ const title = (lockedItem.title ?? "").trim();
3537
+ const message = this.render(
3538
+ kind,
3539
+ nowStuck ? {} : {
3540
+ currentId: lockedItem.id,
3541
+ currentTitle: title,
3542
+ currentTitleSuffix: title ? ` \u2014 ${title}` : ""
3543
+ }
3544
+ );
3545
+ this.store.updateReviewChain(cid2, {
3546
+ chain_pending: 0,
3547
+ pending_followup: message,
3548
+ pending_followup_at: (/* @__PURE__ */ new Date()).toISOString(),
3549
+ pending_redeliver_at: null
3550
+ });
3551
+ const out = {
3552
+ kind,
3553
+ message,
3554
+ loop: true,
3555
+ meta: nowStuck ? void 0 : { currentId: lockedItem.id }
3556
+ };
3557
+ return { commit: true, value: out };
3558
+ });
3559
+ }
3438
3560
  /** Arm confirm_left=0 + ICC=1 only when still idle on the no-code path. */
3439
3561
  armAtE5ForNoCode(conversationId) {
3440
3562
  return this.store.exclusiveWrite(() => {
@@ -4413,6 +4535,11 @@ function createRenderFollowup(bundle) {
4413
4535
  f.verify_fix ?? "Verify failed ({reason}). Fix verify commands and rewrite verify-last.json; do not advance.",
4414
4536
  vars
4415
4537
  );
4538
+ case "need_evidence":
4539
+ return renderTemplate(
4540
+ f.need_evidence ?? `Need evidence: no-code item {currentId}{currentTitleSuffix} cannot advance without matching soft completion evidence. Write .autopilot/verify-last.json with itemId "{currentId}" and ok: true (only after this item's work is done). Then end the turn so the stop hook can advance/done. Do not ask the user to continue; do not invent Advance/Done.`,
4541
+ vars
4542
+ );
4416
4543
  default:
4417
4544
  return "";
4418
4545
  }
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/init/install.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAGV,UAAU,EACV,cAAc,EACf,MAAM,YAAY,CAAC;AAyCpB,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,qBAAqB,EACrB,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,8BAA8B,EAC9B,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIxE,kEAAkE;AAClE,eAAO,MAAM,qBAAqB,qGAMxB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,8DAG3B,CAAC;AAgVX;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,MAAM,GAAG,IAAI,CA6Df;AAwMD,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA8C1E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAuhB/D"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/init/install.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAGV,UAAU,EACV,cAAc,EACf,MAAM,YAAY,CAAC;AA0CpB,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,qBAAqB,EACrB,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,8BAA8B,EAC9B,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIxE,kEAAkE;AAClE,eAAO,MAAM,qBAAqB,qGAMxB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,8DAG3B,CAAC;AAyUX;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,MAAM,GAAG,IAAI,CA6Df;AAwMD,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA8C1E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAuhB/D"}
@@ -12,6 +12,7 @@ import { DEFAULT_AUTOPILOT_IGNORE_TEXT } from "@autopilot-harness/core";
12
12
  import { readConfigInstallHints, readConfigPlatformsOrThrow } from "./config-merge.js";
13
13
  import { applyPlatformsToConfigYaml, assertInstallablePlatforms, isInstallableBinding, MAX_PLATFORM_BINDINGS, mergePlatformBindings, mergedIncludesAllRequested, normalizeBinding, primaryBinding, sanitizePlatformId, } from "./platforms.js";
14
14
  import { MAX_UNTRUSTED_TEXT_BYTES, readUntrustedUtf8File, copyFileReplaceSync, copyFileNoFollowExclSync, writeFileReplaceSync, renameReplaceSync, } from "../read-untrusted-file.js";
15
+ import { resolveTemplatesRoot } from "../template-paths.js";
15
16
  export { mergeHooksJson, stripAutopilotHooks, isAutopilotCommand, countAutopilotDuplicates, validateHooksShape, hasCompleteAutopilotHooks, summarizeAutopilotHooks, autopilotStopHasUnlimitedLoop, autopilotHookCommand, } from "./hooks-merge.js";
16
17
  export { mergeClaudeSettings, validateClaudeSettingsShape, hasClaudeBlockCapZero, hasCompleteClaudeAutopilotHooks, summarizeClaudeAutopilotHooks, stripAutopilotClaudeSettings, claudeSettingsContainAutopilot, CLAUDE_AUTOPILOT_EVENTS, CLAUDE_BLOCK_CAP_ENV, } from "./claude-settings-merge.js";
17
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -33,13 +34,7 @@ const WORKFLOW_FILES = AUTOPILOT_WORKFLOW_FILES;
33
34
  function resolvePackageRoots() {
34
35
  // src/init → ../../ = packages/cli; dist/init → ../../ = packages/cli
35
36
  const cliRoot = path.resolve(__dirname, "../..");
36
- const candidates = [
37
- path.resolve(cliRoot, "../templates"),
38
- path.resolve(cliRoot, "node_modules/@autopilot-harness/templates"),
39
- ];
40
- const templatesRoot = candidates.find((p) => isRealDirectory(path.join(p, "skills"))) ??
41
- candidates[0];
42
- return { cliRoot, templatesRoot };
37
+ return { cliRoot, templatesRoot: resolveTemplatesRoot(cliRoot) };
43
38
  }
44
39
  function resolveHookAsset(cliRoot) {
45
40
  const candidates = [
@@ -78,7 +73,7 @@ function resolveVendorRoot(cliRoot) {
78
73
  function copyVendorDir(cliRoot, destBin, projectRoot) {
79
74
  const vendorRoot = resolveVendorRoot(cliRoot);
80
75
  if (!vendorRoot) {
81
- throw new Error("Missing assets/vendor/runtime.mjs or migrations — run pnpm bundle-vendor (or pnpm build)");
76
+ throw new Error("Missing assets/vendor (or dist/assets/vendor) runtime.mjs/migrations — run pnpm bundle-vendor (or pnpm build)");
82
77
  }
83
78
  const runtimeSrc = path.join(vendorRoot, "runtime.mjs");
84
79
  const migSrcDir = path.join(vendorRoot, "migrations");
@@ -537,7 +532,7 @@ export function preflightForceRefresh(projectRoot) {
537
532
  if (!resolveVendorRoot(cliRoot)) {
538
533
  return {
539
534
  ok: false,
540
- error: "Missing assets/vendor/runtime.mjs or migrations — run pnpm bundle-vendor (or pnpm build)",
535
+ error: "Missing assets/vendor (or dist/assets/vendor) runtime.mjs/migrations — run pnpm bundle-vendor (or pnpm build)",
541
536
  };
542
537
  }
543
538
  // Host settings (`.cursor/hooks.json` / `.claude/settings.json`) are validated