@aperant/framework 0.6.5 → 0.6.6

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.
Files changed (64) hide show
  1. package/README.md +17 -251
  2. package/dist/cli/commands/commit.d.mts.map +1 -1
  3. package/dist/cli/commands/commit.mjs +25 -8
  4. package/dist/cli/commands/commit.mjs.map +1 -1
  5. package/dist/cli/commands/event.d.mts.map +1 -1
  6. package/dist/cli/commands/event.mjs +66 -0
  7. package/dist/cli/commands/event.mjs.map +1 -1
  8. package/dist/cli/commands/modes.d.mts.map +1 -1
  9. package/dist/cli/commands/modes.mjs +2 -1
  10. package/dist/cli/commands/modes.mjs.map +1 -1
  11. package/dist/cli/commands/task.d.mts.map +1 -1
  12. package/dist/cli/commands/task.mjs +285 -139
  13. package/dist/cli/commands/task.mjs.map +1 -1
  14. package/dist/cli/commands/triage.d.mts.map +1 -1
  15. package/dist/cli/commands/triage.mjs +9 -5
  16. package/dist/cli/commands/triage.mjs.map +1 -1
  17. package/dist/cli/coordination/auto-emit-artifact-ready.d.mts +16 -0
  18. package/dist/cli/coordination/auto-emit-artifact-ready.d.mts.map +1 -0
  19. package/dist/cli/coordination/auto-emit-artifact-ready.mjs +88 -0
  20. package/dist/cli/coordination/auto-emit-artifact-ready.mjs.map +1 -0
  21. package/dist/cli/coordination/event-schema.d.mts +16 -0
  22. package/dist/cli/coordination/event-schema.d.mts.map +1 -0
  23. package/dist/cli/coordination/event-schema.mjs +161 -0
  24. package/dist/cli/coordination/event-schema.mjs.map +1 -0
  25. package/dist/cli/coordination/store.d.mts +6 -0
  26. package/dist/cli/coordination/store.d.mts.map +1 -1
  27. package/dist/cli/coordination/store.mjs +14 -0
  28. package/dist/cli/coordination/store.mjs.map +1 -1
  29. package/dist/cli/help.mjs +2 -2
  30. package/dist/cli/help.mjs.map +1 -1
  31. package/dist/cli/roadmap/conductor-view.d.mts +13 -0
  32. package/dist/cli/roadmap/conductor-view.d.mts.map +1 -0
  33. package/dist/cli/roadmap/conductor-view.mjs +31 -0
  34. package/dist/cli/roadmap/conductor-view.mjs.map +1 -0
  35. package/dist/cli/route/skill-discover.d.mts.map +1 -1
  36. package/dist/cli/route/skill-discover.mjs +2 -1
  37. package/dist/cli/route/skill-discover.mjs.map +1 -1
  38. package/dist/cli/task/ids.d.mts +7 -4
  39. package/dist/cli/task/ids.d.mts.map +1 -1
  40. package/dist/cli/task/ids.mjs +11 -10
  41. package/dist/cli/task/ids.mjs.map +1 -1
  42. package/package.json +133 -125
  43. package/prompts/conductor-status-check.md +23 -0
  44. package/prompts/conductor-sub-agent.md +57 -0
  45. package/prompts/conductor-system.md +172 -0
  46. package/skills/apt-plan/SKILL.md +12 -0
  47. package/skills/apt-plan/adapters/conductor.md +98 -0
  48. package/skills/apt-setup/SKILL.md +2 -0
  49. package/skills/apt-ship/SKILL.md +27 -12
  50. package/skills/apt-spar/SKILL.md +290 -0
  51. package/src/cli/commands/commit.mjs +27 -8
  52. package/src/cli/commands/event.mjs +68 -0
  53. package/src/cli/commands/modes.mjs +2 -1
  54. package/src/cli/commands/task.mjs +305 -152
  55. package/src/cli/commands/triage.mjs +14 -5
  56. package/src/cli/coordination/auto-emit-artifact-ready.mjs +96 -0
  57. package/src/cli/coordination/event-schema.d.ts +13 -0
  58. package/src/cli/coordination/event-schema.mjs +174 -0
  59. package/src/cli/coordination/store.mjs +14 -0
  60. package/src/cli/help.mjs +2 -2
  61. package/src/cli/roadmap/conductor-view.d.ts +10 -0
  62. package/src/cli/roadmap/conductor-view.mjs +31 -0
  63. package/src/cli/route/skill-discover.mjs +2 -1
  64. package/src/cli/task/ids.mjs +15 -13
@@ -0,0 +1,172 @@
1
+ # Conductor — Master Orchestrator
2
+
3
+ You orchestrate up to 12 parallel agent terminals. You do not write code.
4
+ You think a lot. You output little. You replace human babysitting.
5
+
6
+ ## Identity
7
+
8
+ - Frontier model only. No cheap-model fallback.
9
+ - You sit ABOVE terminal agents. They execute. You direct.
10
+ - You never produce diffs. If a sub-agent emits a diff, reject it.
11
+
12
+ ## Voice
13
+
14
+ Caveman-terse. Drop articles, filler, pleasantries. Fragments OK. No
15
+ "I'll just go ahead and...". State decisions, not deliberation. Reason
16
+ deeply in private. Speak rarely.
17
+
18
+ Bad: "Sure! I'll now spawn a fresh terminal and run /apt:plan on it..."
19
+ Good: "Spawning T3 for task X."
20
+
21
+ ## Mission
22
+
23
+ Human gives goal. You take it end-to-end across the agent fleet without
24
+ asking for approval at each step. Default mode: full-auto. Ask only when
25
+ the goal itself is ambiguous, not when execution is.
26
+
27
+ Example goal: "Take next 12 roadmap phases, execute end-to-end."
28
+ Your job: spawn terminals, plan each, review plans, realign or approve,
29
+ execute, verify, review, ship, pr-review, verify-proof. Report only
30
+ exceptions + final result.
31
+
32
+ ## The pipeline you drive per task
33
+
34
+ For each task you take responsibility for, drive ALL phases yourself:
35
+
36
+ 1. `/apt:plan <task>` — agent writes spec.md + implementation_plan.json
37
+ 2. Wait for `artifact.ready{kind:'plan'}`
38
+ 3. `reviewPlan(taskId)` — fetch spec + plan + coverage. Read against:
39
+ - the task brief you gave
40
+ - product vision / roadmap goal
41
+ - known architecture (use `getRoadmap`, `getTaskState`)
42
+ 4. Decide:
43
+ - **Approve** → `sendToTerminal('/apt:execute\r')` to that terminal
44
+ - **Realign** → `sendToTerminal('[Conductor realignment] <terse note>\r')`.
45
+ Wait for fresh `artifact.ready`. Re-review. Loop up to 3 attempts.
46
+ After 3, escalate (respawn or surface to human).
47
+ - **Reject** → `sendToTerminal('abort\r')`; spawn replacement if task
48
+ still in scope.
49
+ 5. After execute completes (`task-completed` event):
50
+ - `sendToTerminal('/apt:verify\r')`
51
+ 6. After verify completes:
52
+ - `sendToTerminal('/apt:review\r')`
53
+ 7. After review completes:
54
+ - `sendToTerminal('/apt:ship\r')`
55
+ 8. After PR opens:
56
+ - `sendToTerminal('/apt:pr-review\r')`
57
+ 9. After pr-review completes:
58
+ - `sendToTerminal('/apt:verify-proof\r')`
59
+ 10. Mark task done in your internal state. Move to next task.
60
+
61
+ Run multiple terminals in parallel. Track each by terminal_id.
62
+
63
+ ## Slacker discipline
64
+
65
+ You are the only thing standing between agents and silent failure. Catch:
66
+
67
+ - **Idle**: terminal `phase.changed{to:'idle'}` with no fresh artifact
68
+ for > 5 min after a /apt:* dispatch. Poll: `sendToTerminal('[conductor]
69
+ status: phase? blocker?\r')`. If still idle after another 5 min,
70
+ escalate (realign with explicit next step, or respawn).
71
+ - **Slacking**: agent reports completion but artifacts don't exist on
72
+ disk OR don't match the spec. Use `crossCheck` + `reviewPlan` to verify.
73
+ Never trust self-reports.
74
+ - **Looping**: same `phase.changed` toggling > 6 times in 10 min. Send
75
+ `[Conductor realignment] You are looping. State the blocker and stop.\r`
76
+ - **Cost runaway**: a terminal's `budget.exceeded` event arrives. Halt
77
+ that terminal with `sendToTerminal('exit\r')` after persisting state.
78
+
79
+ When you nudge, send ONE structured line. Not paragraphs.
80
+
81
+ ## Tools (use, don't narrate)
82
+
83
+ Read-only:
84
+ - `getRoadmap()` — overlay-aware roadmap. Call when picking next task.
85
+ - `getTaskState(taskId)` — task lifecycle record.
86
+ - `reviewPlan(taskId)` — spec + plan + coverage bundle for self-review.
87
+ - `readEventLog({op?, terminalId?, sinceIso?, limit?})` — event tail.
88
+ - `getEnrolledTerminals()` — enrolled fleet view.
89
+ - `crossCheck({kind, ...})` — deterministic shell verify (file diff,
90
+ phase commit, PR state). Never trust agent claims without this.
91
+
92
+ Write (use when needed, no human approval gating):
93
+ - `setEnrollment(terminalId, true, tier)` — enroll a terminal.
94
+ - `spawnTerminal({projectPath, cwd, cliCommand, requestId, reasoning})`
95
+ — create PTY. For agent runs use `cliCommand: 'claude
96
+ --dangerously-skip-permissions'` then `sendToTerminal` the slash command.
97
+ - `listTerminals()` — fleet snapshot (cwd, busyState, isCLIMode).
98
+ - `sendToTerminal(terminalId, input, requestId, reasoning)` — tap-on-
99
+ shoulder. Always one line. Append `\r` to submit. NEVER multi-line essay.
100
+ - `destroyTerminal(terminalId, requestId, reasoning)` — irreversible.
101
+ Prefer graceful `sendToTerminal('exit\r')`.
102
+ - `emitIntent({...})` — audit envelope for non-tool decisions.
103
+
104
+ Tool surface depends on host. If `spawnTerminal` is not in your tools,
105
+ you are in observe-only mode — do not promise spawns; describe what you
106
+ WOULD do.
107
+
108
+ ## Tier classification (verify on enrollment)
109
+
110
+ - **Tier A** — native structured event source (Claude `claude -p
111
+ --output-format stream-json`). Full autonomy.
112
+ - **Tier B** — APT skill wrapping any CLI (`/apt:plan`, `/apt:execute`,
113
+ etc.). Full autonomy. THIS IS YOUR PRIMARY TARGET.
114
+ - **Tier C** — bare CLI / raw PTY. Capped to suggest-only regardless of
115
+ user autonomy setting. Mark intents `tier_capped: true`.
116
+
117
+ ## Cross-check is canonical
118
+
119
+ Before recording any intent that depends on agent self-report, call
120
+ `crossCheck`:
121
+ - "I finished spec.md" → `crossCheck({kind:'phase', taskDir})`
122
+ - "I edited file X" → `crossCheck({kind:'file', file, sinceRef:'HEAD~1'})`
123
+ - "I opened PR" → `crossCheck({kind:'pr', prUrl})`
124
+
125
+ Sub-agent reviewers can call cross-check too. When uncertain, demand
126
+ evidence.
127
+
128
+ ## Event log
129
+
130
+ Canonical envelope at `.aperant/events/YYYY-MM-DD.jsonl`. Schema gate
131
+ enforces required fields per op (see `coordination/event-schema.mjs`).
132
+
133
+ Locked op set:
134
+ `terminal.enrolled` `terminal.unenrolled` `phase.changed` `awaiting-input`
135
+ `input-rescinded` `artifact.ready` `heartbeat` `budget.exceeded`
136
+ `conductor.intent` `conductor.cross_check` `conductor.realignment`
137
+
138
+ Reserved meanings:
139
+ - `phase.changed.to` ∈ {busy, idle} ONLY (PTY state). NEVER use for plan
140
+ completion.
141
+ - `artifact.ready{kind:'plan'}` IS the plan-completion signal. Auto-
142
+ emitted by `apt-tools commit` when planner runs under a Conductor PTY
143
+ (`APERANT_TERMINAL_ID` set).
144
+
145
+ Every action you take appends an audit envelope. The framework writes
146
+ most of them for you when you call its tools.
147
+
148
+ ## Output discipline
149
+
150
+ User-facing text per turn: one line per terminal you acted on, plus one
151
+ final summary. Examples:
152
+
153
+ - `T1: plan reviewed, 2 gaps → realignment sent (attempt 1).`
154
+ - `T3: plan approved → /apt:execute dispatched.`
155
+ - `T5: idle 8m, polling.`
156
+ - `Done. 4 tasks shipped, 1 wedged at T7 (handed back to you).`
157
+
158
+ NOT examples (forbidden):
159
+ - "I'll go ahead and spawn..."
160
+ - "Great question! Let me think through..."
161
+ - Multi-paragraph narration of your reasoning. Reason internally.
162
+
163
+ The chat thread is a control surface, not a journal.
164
+
165
+ ## First turn
166
+
167
+ On first turn in a NEW thread (no prior assistant messages), one line:
168
+
169
+ `Conductor ready. Goal?`
170
+
171
+ Nothing else. No greeting paragraph. No tool list. The user knows what
172
+ you are.
@@ -416,6 +416,18 @@ The coverage check ensures every acceptance criterion has at least one subtask w
416
416
  node packages/framework/bin/apt-tools.mjs commit "plan: create implementation plan for {task}" --files spec.md implementation_plan.json
417
417
  ```
418
418
 
419
+ ## 8a. Runtime-context adapters
420
+
421
+ If `APERANT_TERMINAL_ID` is set (you are running under the Aperant
422
+ Conductor — a master-orchestrator agent inside the Aperant app), see
423
+ `adapters/conductor.md` for the plan-review handshake convention
424
+ (`proceed` / `abort` / `[Conductor realignment]` stdin strings) the
425
+ Conductor's drawer uses post-commit. The `artifact.ready{kind:'plan'}`
426
+ signal itself is now emitted automatically by the `apt-tools commit`
427
+ postcondition — no manual emit step required. Native invocations
428
+ (Claude Code, Gemini, OpenCode, Codex with no Conductor context) can
429
+ skip the adapter entirely and proceed to Section 9.
430
+
419
431
  ## 9. Report
420
432
 
421
433
  Output a summary:
@@ -0,0 +1,98 @@
1
+ # apt-plan adapter — Aperant Conductor
2
+
3
+ This adapter is **only loaded** when the planner runs under the Aperant
4
+ Conductor (a master-orchestrator agent inside the Aperant desktop / web
5
+ app). Native invocations of `/apt:plan` (from plain Claude Code, Gemini
6
+ CLI, OpenCode, or Codex) IGNORE this file entirely — the framework
7
+ behavior in `SKILL.md` is the source of truth for them.
8
+
9
+ ## When to load this adapter
10
+
11
+ Detect Conductor context by env var:
12
+
13
+ ```bash
14
+ [ -n "$APERANT_TERMINAL_ID" ]
15
+ ```
16
+
17
+ The Conductor injects `APERANT_TERMINAL_ID` into every PTY it spawns.
18
+ If the var is unset, you are NOT under the Conductor — stop reading.
19
+
20
+ ## What changes under the Conductor
21
+
22
+ The planner's filesystem outputs (`spec.md`, `implementation_plan.json`)
23
+ are unchanged. The ADDITION is one extra step after Section 8 (Persist
24
+ State) of `SKILL.md`:
25
+
26
+ ### Plan-Ready Signal (framework-owned, no action required)
27
+
28
+ The `artifact.ready{kind:'plan'}` signal is emitted **automatically** by
29
+ the framework's `apt-tools commit` postcondition. When the planner
30
+ commits `spec.md` + `implementation_plan.json` via the standard:
31
+
32
+ ```bash
33
+ node packages/framework/bin/apt-tools.mjs commit "plan: ..." \
34
+ --files spec.md implementation_plan.json
35
+ ```
36
+
37
+ …and the process is running under a Conductor PTY (env
38
+ `APERANT_TERMINAL_ID` is set), `apt-tools commit` auto-emits a fully-
39
+ formed `artifact.ready` envelope to `.aperant/events/{today}.jsonl`.
40
+ The envelope carries the `task_id`, plan path, sha256 hash, and the
41
+ auto-stamped `terminal_id`.
42
+
43
+ This replaces the earlier markdown-discipline emit step. The earlier
44
+ step was correctly skipped on QUICK / SIMPLE classified tasks (Fast
45
+ Path Guarantee, Pocock adoption ID-05) — moving the emit to a
46
+ framework postcondition makes it work uniformly across QUICK, STANDARD,
47
+ DEEP, and COMPLEX without violating the Fast Path Guarantee.
48
+
49
+ **Planner authors don't need to do anything.** As long as you call
50
+ `apt-tools commit` to land your plan artifacts, the Conductor will
51
+ see `artifact.ready` and the drawer's PlanReadyCard will auto-promote.
52
+
53
+ If the planner does NOT use `apt-tools commit` (it lands the plan via
54
+ raw `git commit` or `git add`), the auto-emit will not fire. In that
55
+ case the Conductor falls back to filesystem polling of the task dir
56
+ (slightly higher latency, same outcome).
57
+
58
+ ### Plan-review handshake (Step 6 + 7 of Conductor v2)
59
+
60
+ After emitting `artifact.ready{kind:'plan'}` the planner SHOULD pause at
61
+ an idle prompt and listen for ONE of three Conductor-driven inputs:
62
+
63
+ - `proceed\r` — the human (via the PlanReadyCard's **Approve** button)
64
+ approves the plan. Continue to `/apt:execute` as you would in the
65
+ normal flow.
66
+ - `abort\r` — the human rejected. Abandon this plan; do not start
67
+ execution. Optionally exit the session cleanly.
68
+ - `[Conductor realignment] <note>\r` — the human pressed **Realign**.
69
+ Treat the `<note>` as additional planning context: re-open spec.md /
70
+ implementation_plan.json, address the note's gaps, commit a new
71
+ revision, and emit a fresh `artifact.ready{kind:'plan'}` (with the
72
+ same task_id but a NEW request_id — the drawer keys cards on
73
+ request_id, so re-using it would suppress the new card). The
74
+ realignment loop can repeat; after attempt_n >= 3 the Conductor may
75
+ decide to respawn the terminal entirely.
76
+
77
+ These exact strings are pinned by the Conductor's `ConductorContextHost`
78
+ handlers (`packages/ui/src/sections/conductor/ConductorContextHost.tsx`).
79
+ Native CLI invocations of `/apt:plan` (no `APERANT_TERMINAL_ID`) skip
80
+ this handshake and follow the normal report → `/apt:execute` flow.
81
+
82
+ ## Why this lives in an adapter (not in SKILL.md)
83
+
84
+ The Aperant Framework is consumed by multiple CLIs (Claude Code, Gemini,
85
+ OpenCode, Codex) and by the Aperant app itself. The vast majority of
86
+ `/apt:plan` invocations come from native CLIs — those should not see
87
+ or execute Conductor-specific machinery. By isolating the Conductor's
88
+ event-emit obligations to this adapter, the public skill stays minimal
89
+ and the framework remains CLI-agnostic. The pattern mirrors
90
+ `appendices/` (reasoning-stance loaders) but with a different semantic:
91
+ **runtime-context loader** — load when the runtime environment matches.
92
+
93
+ ## Adding more adapters
94
+
95
+ Future runtime contexts (e.g. a hypothetical CI runner, another
96
+ orchestrator) can add `adapters/{context}.md` files using the same
97
+ pattern: detect via env or filesystem signal, document the additional
98
+ obligations, and add a one-line conditional reference in `SKILL.md`.
@@ -1017,6 +1017,8 @@ Map the user's selections to config values:
1017
1017
  - "Ask me" → `pr_review.suggest_after_ship: "ask"`
1018
1018
  - "Never" → `pr_review.suggest_after_ship: "never"`
1019
1019
 
1020
+ Note: task ids are slug-first since v0.6.6 — the date-format setting controls the trailing date component only.
1021
+
1020
1022
  **Date Format:**
1021
1023
  - "DD-MM-YY" → `preferences.date_format: "DD-MM-YY"`
1022
1024
  - "MM-DD-YY" → `preferences.date_format: "MM-DD-YY"`
@@ -289,11 +289,13 @@ Capture the PR URL from the output into `pr_url` (for example, the last line of
289
289
  **Record `pr_url` on the task record (C56 A2).** `/apt:close-task` and the auto-close-phase path in `task close` both resolve the PR number from this URL to poll `gh pr view` for merge state — without it, the closer side cannot run:
290
290
 
291
291
  ```bash
292
- node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --pr-url {PR_URL}
292
+ ship_update=$(node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --pr-url {PR_URL})
293
293
  ```
294
294
 
295
295
  This same call **atomically flips `lifecycle_phase` from `reviewing` to `shipped-pending-merge`** when the task is in `reviewing` and no explicit `--lifecycle-phase` flag is passed. Section 6 is now the single load-bearing step that both records the PR and transitions the lifecycle; Section 8 below documents the semantics but no longer issues a separate command.
296
296
 
297
+ It also evaluates `ci_watch.after_ship` and may arm the post-ship CI watcher. Preserve the JSON envelope in `ship_update`; Section 7.2 reads `ci_watch_armed` and `ci_watch_skip_reason` from it instead of issuing a second auto-start command.
298
+
297
299
  Display the resolution so users see which branch/base was used:
298
300
 
299
301
  ```
@@ -352,29 +354,31 @@ suggest_mode=$(jq -r '.pr_review.suggest_after_ship // "always"' .aperant/config
352
354
 
353
355
  ### 7.2 Post-ship /apt:watch-ci hook
354
356
 
355
- After Section 7.1's hint resolves (whether it ran the deep review or skipped), conditionally arm a post-ship CI watcher. The hook is controlled by `ci_watch.after_ship` in `.aperant/config.json` (tri-state: `auto` | `ask` | `never`). The default depends on host capability — `auto` on cron-capable hosts (Claude Code, Pi), `never` elsewhere.
357
+ After Section 7.1's hint resolves (whether it ran the deep review or skipped), report or interactively arm the post-ship CI watcher. The hook is controlled by `ci_watch.after_ship` in `.aperant/config.json` (tri-state: `auto` | `ask` | `never`). The default depends on host capability — `auto` on cron-capable hosts (Claude Code, Pi), `never` elsewhere.
356
358
 
357
359
  **Skip entirely** when no PR URL was captured in Section 6 (library `npm publish`-only path — same skip rule as 7.1).
358
360
 
359
- **Read host cron capability + the after-ship mode:**
361
+ **Read the Section 6 task-update envelope first:**
360
362
 
361
363
  ```bash
364
+ ci_watch_armed=$(echo "$ship_update" | jq -r '.ci_watch_armed // false')
365
+ ci_watch_skip_reason=$(echo "$ship_update" | jq -r '.ci_watch_skip_reason // empty')
366
+ ci_watch_host=$(echo "$ship_update" | jq -r '.ci_watch.host // "unknown"')
367
+ ci_watch_deadline=$(echo "$ship_update" | jq -r '.ci_watch.deadline // empty')
362
368
  host_cron=$(node packages/framework/bin/apt-tools.mjs host-detect | jq -r '.host.capabilities.cron')
363
- after_ship_default=$([ "$host_cron" = "true" ] && echo "auto" || echo "never")
364
- after_ship_mode=$(jq -r --arg d "$after_ship_default" '.ci_watch.after_ship // $d' .aperant/config.json)
365
369
  ```
366
370
 
367
371
  **Autonomy interaction** (apply BEFORE branching, same rule as 7.1): when `autonomy === 3` (YOLO), degrade `ask` → `auto`.
368
372
 
369
- **Branch on `after_ship_mode`:**
373
+ **Branch on the task-update envelope:**
370
374
 
371
- - **`auto` AND `host_cron=true`** — call `apt-tools ci-watch start --pr {pr_number} --task-id {task-id} --host {cli}`. On success, emit:
375
+ - **`ci_watch_armed=true` AND `host_cron=true`** — emit:
372
376
 
373
377
  ```
374
- [APT:ship] CI watcher armed (host cron: {cli}). Deadline: {iso}. Stop: /apt:watch-ci --stop --pr {N}.
378
+ [APT:ship] CI watcher armed (host cron: {ci_watch_host}). Deadline: {ci_watch_deadline}. Stop: /apt:watch-ci --stop --pr {N}.
375
379
  ```
376
380
 
377
- - **`auto` AND `host_cron=false`** — call `apt-tools ci-watch start --pr {pr_number} --task-id {task-id} --host {cli}` (creates state file, sets `scheduler_handle: null`), then emit:
381
+ - **`ci_watch_armed=true` AND `host_cron=false`** — emit:
378
382
 
379
383
  ```
380
384
  [APT:ship] CI watcher state created (host has no cron; advance manually: /apt:watch-ci --tick --pr {N}).
@@ -382,15 +386,26 @@ after_ship_mode=$(jq -r --arg d "$after_ship_default" '.ci_watch.after_ship // $
382
386
 
383
387
  This is the honest-gap surface per D-03 — we never lie about cron-capability.
384
388
 
385
- - **`ask`** — emit:
389
+ - **`ci_watch_skip_reason=ask-deferred`** — emit:
386
390
 
387
391
  ```
388
392
  [APT:ship] Arm CI watcher for PR {N} now? [Y/n]
389
393
  ```
390
394
 
391
- On `Y` (or empty enter), invoke the `auto` branch logic above (with the same host-cron derivation). On `N` or any other response, proceed silently to Section 8.
395
+ On `Y` (or empty enter), call:
392
396
 
393
- - **`never`** — silent. Proceed directly to Section 8.
397
+ ```bash
398
+ node packages/framework/bin/apt-tools.mjs ci-watch start . --pr {pr_number} --task-id {task-id} --host {cli}
399
+ ```
400
+
401
+ Then emit the same host-cron-aware success banner used for `ci_watch_armed=true`. On `N` or any other response, proceed silently to Section 8.
402
+
403
+ - **`ci_watch_skip_reason=disabled`** — silent. Proceed directly to Section 8.
404
+
405
+ - **Any other skip reason** — emit:
406
+ ```
407
+ [APT:ship] CI watcher not armed: {ci_watch_skip_reason}
408
+ ```
394
409
 
395
410
  Section 7.2 is purely additive — it does not affect Section 7.1's `pr_review.suggest_after_ship` policy or any other config block.
396
411