@aperant/framework 0.16.0 → 0.19.0

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 (174) hide show
  1. package/CHANGELOG.md +185 -1
  2. package/dist/cli/commands/ci-watch.mjs +49 -2
  3. package/dist/cli/commands/ci-watch.mjs.map +1 -1
  4. package/dist/cli/commands/commit.mjs +3 -3
  5. package/dist/cli/commands/commit.mjs.map +1 -1
  6. package/dist/cli/commands/event.mjs +16 -16
  7. package/dist/cli/commands/event.mjs.map +1 -1
  8. package/dist/cli/commands/merge-integrate.mjs +3 -3
  9. package/dist/cli/commands/merge-integrate.mjs.map +1 -1
  10. package/dist/cli/commands/produce.d.mts +9 -0
  11. package/dist/cli/commands/produce.d.mts.map +1 -0
  12. package/dist/cli/commands/produce.mjs +1345 -0
  13. package/dist/cli/commands/produce.mjs.map +1 -0
  14. package/dist/cli/commands/spar-resolve-partner.d.mts +9 -0
  15. package/dist/cli/commands/spar-resolve-partner.d.mts.map +1 -0
  16. package/dist/cli/commands/spar-resolve-partner.mjs +82 -0
  17. package/dist/cli/commands/spar-resolve-partner.mjs.map +1 -0
  18. package/dist/cli/commands/task.d.mts +16 -1
  19. package/dist/cli/commands/task.d.mts.map +1 -1
  20. package/dist/cli/commands/task.mjs +434 -266
  21. package/dist/cli/commands/task.mjs.map +1 -1
  22. package/dist/cli/config/gitignore-drift.d.mts.map +1 -1
  23. package/dist/cli/config/gitignore-drift.mjs +1 -0
  24. package/dist/cli/config/gitignore-drift.mjs.map +1 -1
  25. package/dist/cli/config/post-merge-sweep.d.mts.map +1 -1
  26. package/dist/cli/config/post-merge-sweep.mjs +1 -0
  27. package/dist/cli/config/post-merge-sweep.mjs.map +1 -1
  28. package/dist/cli/coordination/auto-emit-artifact-ready.mjs +5 -5
  29. package/dist/cli/coordination/auto-emit-artifact-ready.mjs.map +1 -1
  30. package/dist/cli/coordination/event-schema.d.mts +5 -3
  31. package/dist/cli/coordination/event-schema.d.mts.map +1 -1
  32. package/dist/cli/coordination/event-schema.mjs +245 -21
  33. package/dist/cli/coordination/event-schema.mjs.map +1 -1
  34. package/dist/cli/coordination/store.d.mts +2 -2
  35. package/dist/cli/coordination/store.mjs +4 -4
  36. package/dist/cli/coordination/store.mjs.map +1 -1
  37. package/dist/cli/dispatch.d.mts.map +1 -1
  38. package/dist/cli/dispatch.mjs +4 -0
  39. package/dist/cli/dispatch.mjs.map +1 -1
  40. package/dist/cli/help.d.mts.map +1 -1
  41. package/dist/cli/help.mjs +31 -0
  42. package/dist/cli/help.mjs.map +1 -1
  43. package/dist/cli/host/codex-config.d.mts +78 -0
  44. package/dist/cli/host/codex-config.d.mts.map +1 -0
  45. package/dist/cli/host/codex-config.mjs +169 -0
  46. package/dist/cli/host/codex-config.mjs.map +1 -0
  47. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  48. package/dist/cli/install/legacy-paths.mjs +2 -0
  49. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  50. package/dist/cli/produce/blind-probe.d.mts +85 -0
  51. package/dist/cli/produce/blind-probe.d.mts.map +1 -0
  52. package/dist/cli/produce/blind-probe.mjs +217 -0
  53. package/dist/cli/produce/blind-probe.mjs.map +1 -0
  54. package/dist/cli/produce/claim.d.mts +188 -0
  55. package/dist/cli/produce/claim.d.mts.map +1 -0
  56. package/dist/cli/produce/claim.mjs +518 -0
  57. package/dist/cli/produce/claim.mjs.map +1 -0
  58. package/dist/cli/produce/done-gate.d.mts +87 -0
  59. package/dist/cli/produce/done-gate.d.mts.map +1 -0
  60. package/dist/cli/produce/done-gate.mjs +200 -0
  61. package/dist/cli/produce/done-gate.mjs.map +1 -0
  62. package/dist/cli/produce/events.d.mts +77 -0
  63. package/dist/cli/produce/events.d.mts.map +1 -0
  64. package/dist/cli/produce/events.mjs +126 -0
  65. package/dist/cli/produce/events.mjs.map +1 -0
  66. package/dist/cli/produce/evidence-oracle.d.mts +63 -0
  67. package/dist/cli/produce/evidence-oracle.d.mts.map +1 -0
  68. package/dist/cli/produce/evidence-oracle.mjs +122 -0
  69. package/dist/cli/produce/evidence-oracle.mjs.map +1 -0
  70. package/dist/cli/produce/ledger.d.mts +140 -0
  71. package/dist/cli/produce/ledger.d.mts.map +1 -0
  72. package/dist/cli/produce/ledger.mjs +272 -0
  73. package/dist/cli/produce/ledger.mjs.map +1 -0
  74. package/dist/cli/produce/probe-family.d.mts +53 -0
  75. package/dist/cli/produce/probe-family.d.mts.map +1 -0
  76. package/dist/cli/produce/probe-family.mjs +160 -0
  77. package/dist/cli/produce/probe-family.mjs.map +1 -0
  78. package/dist/cli/produce/projection.d.mts +55 -0
  79. package/dist/cli/produce/projection.d.mts.map +1 -0
  80. package/dist/cli/produce/projection.mjs +97 -0
  81. package/dist/cli/produce/projection.mjs.map +1 -0
  82. package/dist/cli/produce/run-id.d.mts +42 -0
  83. package/dist/cli/produce/run-id.d.mts.map +1 -0
  84. package/dist/cli/produce/run-id.mjs +79 -0
  85. package/dist/cli/produce/run-id.mjs.map +1 -0
  86. package/dist/cli/produce/saga.d.mts +180 -0
  87. package/dist/cli/produce/saga.d.mts.map +1 -0
  88. package/dist/cli/produce/saga.mjs +290 -0
  89. package/dist/cli/produce/saga.mjs.map +1 -0
  90. package/dist/cli/produce/scheduler.d.mts +165 -0
  91. package/dist/cli/produce/scheduler.d.mts.map +1 -0
  92. package/dist/cli/produce/scheduler.mjs +399 -0
  93. package/dist/cli/produce/scheduler.mjs.map +1 -0
  94. package/dist/cli/produce/setpoint.d.mts +52 -0
  95. package/dist/cli/produce/setpoint.d.mts.map +1 -0
  96. package/dist/cli/produce/setpoint.mjs +113 -0
  97. package/dist/cli/produce/setpoint.mjs.map +1 -0
  98. package/dist/cli/produce/verification-ttl.d.mts +75 -0
  99. package/dist/cli/produce/verification-ttl.d.mts.map +1 -0
  100. package/dist/cli/produce/verification-ttl.mjs +169 -0
  101. package/dist/cli/produce/verification-ttl.mjs.map +1 -0
  102. package/dist/cli/roadmap/{conductor-view.d.mts → showrunner-view.d.mts} +3 -3
  103. package/dist/cli/roadmap/showrunner-view.d.mts.map +1 -0
  104. package/dist/cli/roadmap/{conductor-view.mjs → showrunner-view.mjs} +7 -7
  105. package/dist/cli/roadmap/showrunner-view.mjs.map +1 -0
  106. package/dist/cli/wfrun/run.d.mts.map +1 -1
  107. package/dist/cli/wfrun/run.mjs +1 -0
  108. package/dist/cli/wfrun/run.mjs.map +1 -1
  109. package/dist/plugin/.claude-plugin/plugin.json +2 -1
  110. package/dist/plugin/skills/apt/SKILL.md +112 -38
  111. package/dist/plugin/skills/apt-debug/SKILL.md +14 -24
  112. package/dist/plugin/skills/apt-fan-out/SKILL.md +4 -4
  113. package/dist/plugin/skills/apt-handoff/SKILL.md +1 -1
  114. package/dist/plugin/skills/apt-plan/SKILL.md +5 -5
  115. package/dist/plugin/skills/apt-plan/adapters/{conductor.md → showrunner.md} +16 -16
  116. package/dist/plugin/skills/apt-produce/SKILL.md +606 -0
  117. package/dist/plugin/skills/apt-quick/SKILL.md +14 -22
  118. package/dist/plugin/skills/apt-run/SKILL.md +126 -3
  119. package/dist/plugin/skills/apt-ship/SKILL.md +2 -0
  120. package/dist/plugin/skills/apt-spar/SKILL.md +19 -9
  121. package/dist/plugin/skills/apt-watch-ci/SKILL.md +4 -4
  122. package/package.json +4 -4
  123. package/prompts/{conductor-framework-context.md → showrunner-framework-context.md} +1 -1
  124. package/prompts/{conductor-status-check.md → showrunner-status-check.md} +1 -1
  125. package/prompts/{conductor-sub-agent.md → showrunner-sub-agent.md} +6 -6
  126. package/prompts/{conductor-system.md → showrunner-system.md} +8 -8
  127. package/skills/apt/SKILL.md +112 -38
  128. package/skills/apt-debug/SKILL.md +14 -24
  129. package/skills/apt-fan-out/SKILL.md +4 -4
  130. package/skills/apt-handoff/SKILL.md +1 -1
  131. package/skills/apt-plan/SKILL.md +5 -5
  132. package/skills/apt-plan/adapters/{conductor.md → showrunner.md} +16 -16
  133. package/skills/apt-produce/SKILL.md +606 -0
  134. package/skills/apt-quick/SKILL.md +14 -22
  135. package/skills/apt-run/SKILL.md +126 -3
  136. package/skills/apt-ship/SKILL.md +2 -0
  137. package/skills/apt-spar/SKILL.md +19 -9
  138. package/skills/apt-watch-ci/SKILL.md +4 -4
  139. package/src/cli/commands/ci-watch.mjs +51 -2
  140. package/src/cli/commands/commit.mjs +3 -3
  141. package/src/cli/commands/event.mjs +16 -16
  142. package/src/cli/commands/merge-integrate.mjs +3 -3
  143. package/src/cli/commands/produce.mjs +1466 -0
  144. package/src/cli/commands/spar-resolve-partner.mjs +85 -0
  145. package/src/cli/commands/task.mjs +482 -285
  146. package/src/cli/config/gitignore-drift.mjs +1 -0
  147. package/src/cli/config/post-merge-sweep.mjs +1 -0
  148. package/src/cli/coordination/auto-emit-artifact-ready.mjs +5 -5
  149. package/src/cli/coordination/event-schema.d.ts +4 -2
  150. package/src/cli/coordination/event-schema.mjs +276 -21
  151. package/src/cli/coordination/store.mjs +4 -4
  152. package/src/cli/dispatch.mjs +4 -0
  153. package/src/cli/help.mjs +31 -0
  154. package/src/cli/host/codex-config.mjs +165 -0
  155. package/src/cli/install/legacy-paths.mjs +2 -0
  156. package/src/cli/produce/blind-probe.mjs +245 -0
  157. package/src/cli/produce/claim.mjs +543 -0
  158. package/src/cli/produce/done-gate.mjs +238 -0
  159. package/src/cli/produce/events.mjs +131 -0
  160. package/src/cli/produce/evidence-oracle.mjs +133 -0
  161. package/src/cli/produce/ledger.mjs +284 -0
  162. package/src/cli/produce/probe-family.mjs +168 -0
  163. package/src/cli/produce/projection.mjs +105 -0
  164. package/src/cli/produce/run-id.mjs +84 -0
  165. package/src/cli/produce/saga.mjs +303 -0
  166. package/src/cli/produce/scheduler.mjs +423 -0
  167. package/src/cli/produce/setpoint.mjs +122 -0
  168. package/src/cli/produce/verification-ttl.mjs +191 -0
  169. package/src/cli/roadmap/showrunner-view.d.ts +10 -0
  170. package/src/cli/roadmap/{conductor-view.mjs → showrunner-view.mjs} +6 -6
  171. package/src/cli/wfrun/run.mjs +1 -0
  172. package/dist/cli/roadmap/conductor-view.d.mts.map +0 -1
  173. package/dist/cli/roadmap/conductor-view.mjs.map +0 -1
  174. package/src/cli/roadmap/conductor-view.d.ts +0 -10
@@ -0,0 +1,606 @@
1
+ ---
2
+ name: apt:produce
3
+ description: "Outer loop (M1+M2) — freeze an outcome as an immutable setpoint, project a crash-safe roadmap from the append-only event log, one wake tick per invocation, gate `done` behind a cross-family oracle + a held-out blind probe."
4
+ apt-skill-version: {{APT_VERSION}}
5
+ stage: ops
6
+ intent: x-produce
7
+ when_to_use: "You have ONE outcome to drive over time and want a frozen acceptance setpoint plus a crash-safe, event-sourced roadmap that a host wake-beat re-reads from disk one tick at a time — no daemon, no in-skill polling loop. M2 unlocks `done` behind a two-gate adjudication: the visible oracle (verify-proof `approved` + an independent cross-family evidence-quality judge) AND a held-out blind cross-family probe must both pass; otherwise the milestone stays open and a reward-hacking canary logs the gap."
8
+ user_invocable: true
9
+ internal: false
10
+ spawns_agent: false
11
+ agent_name: null
12
+ task_context: self-managed
13
+ default_execution_mode: auto
14
+ execution_modes:
15
+ - auto
16
+ - step
17
+ allowed-tools: "Bash, Read, Write, Edit, Grep, Glob"
18
+ argument-hint: "apt:produce \"<outcome>\" | apt:produce --tick --run-id <id>"
19
+ gates: []
20
+ ---
21
+
22
+ # /apt:produce — Outer loop (M1 + M2 + M3 + M4 + M5 + M6)
23
+
24
+ You take **one outcome** and stand up the minimum viable outer loop: a **frozen
25
+ acceptance setpoint** written once and never mutated, and an **event-sourced
26
+ roadmap** projected from the existing append-only substrate. The long run is a
27
+ **host wake-beat** — one tick per invocation. No daemon. No in-skill polling
28
+ loop.
29
+
30
+ This skill is **one-tick-per-invocation** (ID-05). It MUST NOT enter a polling
31
+ loop. The recurring beat is delivered externally — by Claude Code's
32
+ `CronCreate` on cron-capable hosts, or by the user re-invoking
33
+ `/apt:produce --tick --run-id <id>` on non-cron hosts. Each wake invocation does
34
+ exactly one tick, then exits.
35
+
36
+ **Scope.** M1 freezes the setpoint, seeds + projects the roadmap crash-safely,
37
+ and re-reads from disk on each wake tick. **M2** unlocks `done` behind a
38
+ two-gate adjudication — an independent cross-family evidence-quality oracle over
39
+ verify-proof's captured evidence AND a held-out blind cross-family probe the
40
+ implementer never sees. **M3** adds closed-loop convergence: an
41
+ acceptance-criteria ledger whose open count **V** must strictly decrease across
42
+ shipped milestones (the Lyapunov property), with an anti-windup guard that
43
+ rejects any un-sanctioned increase. **M4** is the anti-degradation layer: the
44
+ loop is a **saga** (plan→execute→verify→review→ship) where each phase declares a
45
+ **FORWARD compensator** (a failed verify **ships a revert** + reopens + re-plans;
46
+ you NEVER roll back a merged PR), a **verification-TTL** re-verifies a rotating
47
+ sample of OLD done milestones against HEAD **each tick** and flags regressions,
48
+ and **provider-diverse review** is a RULE (reviewer family ≠ implementer). **M5**
49
+ is the **WSJF/WIP scheduler**: the wake tick now advances the highest-leverage
50
+ READY milestone under a WIP budget — ranked by `WSJF = cost-of-delay / job-size`
51
+ (regression > blocking > open-value > priority), dependency-aware (a milestone
52
+ whose deps aren't `done` is `blocked`, a cycle is surfaced, never a silent idle),
53
+ and M4-regression-aware (a regressed `done` milestone re-enters the ready queue).
54
+ **M6** is the **Claim Envelope**: a durable, bounded, expiring way to CAPTURE a
55
+ review-lesson as a first-class claim and CONSUME it as a concrete AC / subtask /
56
+ proof-obligation DELTA — never a lesson-text context paste. It is interop, not a
57
+ 4th store (a fold + a per-record content file on the SAME substrate), it
58
+ generalizes past `lesson` as a data-driven profile registry, and expiry + HEAD
59
+ re-validation keep the corrective loop from corroding. The outer loop is now
60
+ **feature-complete** (M1–M6). See §5 (done-adjudication) + §6 (cross-family
61
+ dispatch) + §7 (probe-blindness) + §8 (Lyapunov monotone progress) + §9 (saga +
62
+ forward compensators) + §10 (provider-diverse review) + §11 (verification-TTL) +
63
+ §12 (WSJF/WIP scheduler) + §13 (Claim Envelope).
64
+
65
+ ---
66
+
67
+ ## Your environment
68
+
69
+ - **Working directory:** the project root (or any worktree — the run dir
70
+ resolves to the shared main repo).
71
+ - **apt-tools path:** `node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs`
72
+ (or `node packages/framework/bin/apt-tools.mjs` when developing the framework).
73
+ - **Run artifacts:** `.aperant/produce/<run-id>/setpoint.json` (frozen,
74
+ immutable) + `.aperant/produce/<run-id>/roadmap.json` (a DERIVED cache,
75
+ regenerable from the log — never the authority).
76
+ - **Events:** every mutation appends one line to `.aperant/events/{today}.jsonl`
77
+ — `produce.run.started` + `produce.setpoint.frozen` at start,
78
+ `produce.roadmap.milestone_added` / `produce.roadmap.milestone_status_changed`
79
+ on roadmap edits, `produce.tick` on each wake beat. M2 adds the oracle/probe
80
+ forensic trail: `produce.milestone.implemented`, `produce.probe.frozen`
81
+ (hash + author family only — NEVER probe content), `produce.probe.run`
82
+ (result/family/head_sha only), `produce.oracle.judged`, `produce.oracle.verdict`,
83
+ `produce.oracle.probe_gap`. M3 adds the Lyapunov acceptance-criteria ledger:
84
+ `produce.criteria.declared` / `produce.criteria.closed` / `produce.scope.changed`.
85
+ M4 adds the saga + verification-TTL trail: `produce.saga.phase_entered` /
86
+ `produce.saga.phase_completed` / `produce.saga.compensated` /
87
+ `produce.saga.reviewed` / `produce.ttl.reverified` /
88
+ `produce.ttl.regression_detected` (the saga-phase ops enforce a closed
89
+ `plan|execute|verify|review|ship` vocabulary; no op carries probe/review
90
+ content). M5 adds `produce.schedule.computed` (emitted once per `tick` — the
91
+ scheduler's recorded pick; content-blind: `picked` ids + `wip_limit` only, never
92
+ folded by the roadmap projection). Schema-gated (`event-schema.mjs`) so a
93
+ malformed event is rejected fail-closed.
94
+ - **CLI surface:** `apt-tools produce <start|add-milestone|set-milestone-status|status|tick>`
95
+ (M1) + `<record-implementer|resolve-probe-family|oracle prepare|oracle record|probe freeze|probe record|probe sweep|canary>`
96
+ (M2) + `<declare-criteria|progress>` (M3) +
97
+ `<saga enter|saga complete|saga compensate|saga review|saga resolve-reviewer|saga status|ttl select|ttl reverify>`
98
+ (M4) + `<schedule>` (M5 — the WSJF/WIP ranking + WIP-limited pick; `add-milestone`
99
+ gains OPTIONAL `--depends-on`/`--priority`/`--size`). The subcommands do the
100
+ deterministic work — DO NOT re-implement the projection, the setpoint freeze, the
101
+ probe hash, the gate, the ledger fold, the compensator table, the TTL rotation,
102
+ or the WSJF math in your prompt.
103
+ - **Probe artifacts:** `.aperant/produce/<run-id>/probes/<milestone>.json` (the
104
+ frozen blind-probe set — immutable, hash-stamped; its CONTENT is blind to the
105
+ event log + roadmap projection).
106
+
107
+ ---
108
+
109
+ ## §1. First invocation — freeze the setpoint + seed the roadmap
110
+
111
+ Given one outcome string:
112
+
113
+ 1. **Freeze the setpoint.** Run:
114
+
115
+ ```bash
116
+ apt-tools produce start . --outcome "<outcome>"
117
+ ```
118
+
119
+ This writes an immutable `setpoint.json` (hash-stamped, exclusive-create) and
120
+ returns `{ run_id, setpoint_hash, setpoint_path }`. **Record the `run_id`** —
121
+ every later invocation needs it. Re-running `start` with `--run-id <id>` is
122
+ idempotent-reuse: it returns the byte-identical setpoint, never an overwrite
123
+ (ID-02). The setpoint is the fixed external reference — it cannot drift.
124
+
125
+ 2. **Seed milestones.** For each milestone the outcome decomposes into:
126
+
127
+ ```bash
128
+ apt-tools produce add-milestone . --run-id <id> --title "<milestone title>"
129
+ ```
130
+
131
+ Each append lands a `produce.roadmap.milestone_added` event and refreshes the
132
+ derived roadmap projection. Milestones seed at status `planned`.
133
+
134
+ 3. **Reschedule the first wake tick** — see §3.
135
+
136
+ ---
137
+
138
+ ## §2. Wake beat — one tick
139
+
140
+ On each wake beat run exactly ONE tick and then exit:
141
+
142
+ ```bash
143
+ apt-tools produce tick . --run-id <id>
144
+ ```
145
+
146
+ The `tick` subcommand does exactly one of the following, once, then returns:
147
+
148
+ - re-projects the roadmap from the append-only event log (ID-01 — no in-memory
149
+ authority, re-read from disk every time);
150
+ - folds the M5 WSJF/WIP **schedule** (dependency-aware, regression-aware) under
151
+ the resolved WIP budget (`--wip N` → config `produce.wip` → default 1);
152
+ - appends a `produce.tick` event + ONE `produce.schedule.computed`;
153
+ - computes `next_action` from the schedule (§12): `continue_milestone` for an
154
+ in-progress one (unconditional), else `start_milestone` for the **top WSJF ready
155
+ pick under the WIP budget**, else `idle` (carrying the schedule's reason);
156
+ - returns `{ run_id, roadmap, next_action, schedule, next_tick_hint }`.
157
+
158
+ Act on `next_action` (advance the named milestone via `set-milestone-status`, or
159
+ dispatch its work), then **reschedule** per §3 and **exit**. You may also mark a
160
+ milestone `in_progress` when you begin it:
161
+
162
+ ```bash
163
+ apt-tools produce set-milestone-status . --run-id <id> --milestone <mid> --status in_progress
164
+ ```
165
+
166
+ ---
167
+
168
+ ## §3. Reschedule — the host beat (two branches)
169
+
170
+ The recurring beat is the HOST's, never yours. Pick the branch for your host:
171
+
172
+ - **Cron-capable host (Claude Code).** Refresh the `CronCreate` entry's
173
+ next-fire timestamp so the next tick fires automatically (CronCreate is
174
+ idempotent on the same payload). This mirrors `apt-watch-ci` §3 step 4. The
175
+ cron payload's invocation form is `/apt:produce --tick --run-id <id>`.
176
+
177
+ - **Non-cron host.** Print the honest manual line and stop — do NOT register a
178
+ cron the host cannot honor, and do NOT fake a scheduler (the framework would
179
+ be lying). This mirrors `apt-watch-ci` §8:
180
+
181
+ ```
182
+ Next tick: run `apt-tools produce tick . --run-id <id>` again when you return.
183
+ ```
184
+
185
+ ---
186
+
187
+ ## §4. Single-execution-mode contract (the most easily-broken invariant)
188
+
189
+ - One tick = one invocation. Do NOT wrap the §2 tick in a polling loop.
190
+ - Do NOT sleep and then re-invoke yourself inside a single session.
191
+ - The recurring beat is delivered by host cron OR by the user re-invoking the
192
+ skill manually. The loop's value comes from the host scheduler — it does not
193
+ impersonate one.
194
+
195
+ If you find yourself wanting to add "and then wait N minutes and run again",
196
+ STOP. The skill is finished after one tick. Exit.
197
+
198
+ ---
199
+
200
+ ## §5. Done-adjudication (M2) — the Showrunner never self-attests "done" (ID-03)
201
+
202
+ The Showrunner is planner + prosecutor, **never** the judge. `done` is reachable
203
+ ONLY through recorded external evidence + a blind cross-family probe — the skill
204
+ physically cannot let the Showrunner's prose mark a milestone done. A milestone
205
+ flips to `done` ONLY when the **visible gate** AND the **blind gate** both pass;
206
+ otherwise it stays open and a reward-hacking canary logs the gap.
207
+
208
+ When a milestone's implementation is claimed done, walk these steps IN ORDER:
209
+
210
+ 1. **Record who implemented it** (the cross-family disjointness anchor, ID-M2-02):
211
+
212
+ ```bash
213
+ apt-tools produce record-implementer . --run-id <id> --milestone <mid> --implementer-family <fam>
214
+ ```
215
+
216
+ `<fam>` is one of `claude | codex | gemini` (the family that did the work).
217
+ Every later cross-family check reads this recorded fact; if it is absent,
218
+ adjudication is impossible (fail-closed).
219
+
220
+ 2. **Resolve a probe/oracle family DISJOINT from the implementer** (§6):
221
+
222
+ ```bash
223
+ apt-tools produce resolve-probe-family . --run-id <id> --milestone <mid> [--host-family <fam>]
224
+ ```
225
+
226
+ Returns a `probe_family` ≠ the implementer family plus a LIVE-resolved model
227
+ (never a hardcoded id) + a `transport_hint`. `available:false` when no
228
+ disjoint family exists (a single-family setup cannot satisfy the rule by
229
+ design — it CANNOT reach done).
230
+
231
+ 3. **Author 10–20 behavioral probes in that disjoint family + freeze them**
232
+ (probe-blindness, §7):
233
+
234
+ ```bash
235
+ apt-tools produce probe freeze . --run-id <id> --milestone <mid> --probe-file <path> --author-family <probe_family>
236
+ ```
237
+
238
+ The probe set is immutable (a second freeze returns the byte-identical set)
239
+ and its CONTENT never enters the event log or the roadmap projection.
240
+
241
+ 4. **Run the cross-family evidence-quality oracle over verify-proof's
242
+ `verification.json` + record it:**
243
+
244
+ ```bash
245
+ apt-tools produce oracle prepare . --run-id <id> --milestone <mid> --verdict-path <path/to/verification.json>
246
+ ```
247
+
248
+ `prepare` extracts the v2 verdict (must be EXACTLY `approved`) + the tests[]
249
+ evidence into a judge payload. Dispatch that payload to the disjoint family
250
+ (§6) for a pass/fail evidence-quality judgement, then record it:
251
+
252
+ ```bash
253
+ apt-tools produce oracle record . --run-id <id> --milestone <mid> --verdict <pass|fail> --oracle-family <probe_family>
254
+ ```
255
+
256
+ `record` is REFUSED when `oracle_family` equals the implementer family.
257
+
258
+ 5. **Run the frozen blind probe against HEAD in the disjoint family + record the
259
+ result** (result/family/head-sha only — never content):
260
+
261
+ ```bash
262
+ apt-tools produce probe record . --run-id <id> --milestone <mid> --result <pass|fail> --probe-family <probe_family> --head-sha <sha>
263
+ ```
264
+
265
+ 6. **Adjudicate `done`:**
266
+
267
+ ```bash
268
+ apt-tools produce set-milestone-status . --run-id <id> --milestone <mid> --status done --verdict-path <path/to/verification.json>
269
+ ```
270
+
271
+ Accepted (flips to `done` + appends `produce.oracle.verdict{done}`) ONLY when
272
+ the **visible gate** (verify-proof `approved` AND a recorded cross-family
273
+ oracle pass) AND the **blind gate** (a recorded cross-family probe pass + a
274
+ cross-family frozen probe set) both pass. A visible-pass + blind-fail is
275
+ REFUSED — the milestone stays open and a `produce.oracle.probe_gap` canary
276
+ event is logged (the reward-hacking near-miss).
277
+
278
+ **Canary + rotating blind re-probe:**
279
+
280
+ ```bash
281
+ apt-tools produce canary . --run-id <id> # green-on-visible MINUS green-on-blind + per-milestone breakdown
282
+ apt-tools produce probe sweep . --run-id <id> --sample N # deterministic rotating sample of already-done milestones to blind-re-probe against HEAD
283
+ ```
284
+
285
+ `produce probe sweep` selects already-`done` milestones (fewest / oldest
286
+ last-probe-run first) so a milestone that silently regressed after done is
287
+ caught; re-run the blind probe (steps 3+5) against those, and a fresh failure is
288
+ a real regression.
289
+
290
+ ---
291
+
292
+ ## §6. Cross-family dispatch recipe — REUSE the apt-spar mechanism (ID-M2-01)
293
+
294
+ M2 does NOT invent a second cross-family path. The probe/oracle family + model
295
+ resolve through the SAME primitives `apt:spar` uses — provider is an OUTPUT,
296
+ Aperant holds ZERO model ids:
297
+
298
+ - `apt-tools produce resolve-probe-family` picks a family DISJOINT from the
299
+ implementer (the one new rule) using the apt-spar §3 partner-map shape, and
300
+ delegates model resolution to `spar-resolve-partner` /
301
+ `resolveCodexSparModel` — for `codex` it reads the model LIVE from
302
+ `~/.codex/config.toml`; `claude` / `gemini` self-resolve (`model: null`).
303
+ - Dispatch the oracle-judge + probe-run to the resolved family via the
304
+ **transport ladder** (MCP tool → plugin shim → raw CLI), following the
305
+ returned `transport_hint.recommended_walk` (codex prefers the self-resolving
306
+ plugin/CLI rungs — see `apt:spar` §3). NEVER name a model id yourself; NEVER
307
+ hardcode one in a prompt (the `produce-no-hardcoded-models.guard` test fails
308
+ CI on any `gpt-*` / `claude-*` / `gemini-*` / `grok-*` / `glm-*` literal in a
309
+ produce module or this SKILL).
310
+
311
+ ## §7. Probe-blindness — the invariant you must never break
312
+
313
+ The held-out probe set is what makes the blind gate meaningful. The probe
314
+ CONTENT lives ONLY in the frozen `probes/<milestone>.json` file; the event log +
315
+ roadmap projection carry ONLY the probe-set hash + families. **NEVER paste probe
316
+ content into any Showrunner or implementer context** — not into a plan, a prompt,
317
+ a commit, or a chat message. A Showrunner re-anchoring from disk each tick
318
+ physically cannot see the probe questions (the projection folds
319
+ `produce.roadmap.*` ONLY); do not defeat that by hand. Author the probes in the
320
+ disjoint family, freeze them, and reference them only by running them
321
+ cross-family.
322
+
323
+ ---
324
+
325
+ ## §8. Lyapunov monotone progress (M3) — V must strictly decrease
326
+
327
+ M3 adds the closed-loop convergence measure: **V = open acceptance criteria**
328
+ (declared − closed), folded from the append-only log on every read. The run
329
+ converges when V reaches 0. Two guarantees, enforced at two different times.
330
+
331
+ 1. **Declare a milestone's acceptance criteria at seed time — BEFORE it ships:**
332
+
333
+ ```bash
334
+ apt-tools produce declare-criteria . --run-id <id> --milestone <mid> --criterion "<text>"
335
+ ```
336
+
337
+ Each declaration opens one criterion bound to the milestone (V += 1). Declare
338
+ a milestone's criteria while defining/seeding it (§1) so they land in the
339
+ baseline scope-definition window — before the run has closed anything.
340
+
341
+ 2. **Monotone-decrease is enforced at WRITE time (the anti-windup guard).** Once
342
+ the run has begun closing criteria (it is **in descent**), a `declare-criteria`
343
+ that would raise V is **REJECTED fail-closed** — the criterion is not added —
344
+ unless it carries the scope-change escape hatch:
345
+
346
+ ```bash
347
+ apt-tools produce declare-criteria . --run-id <id> --milestone <mid> --criterion "<text>" --scope-change "<why the outcome genuinely grew>"
348
+ ```
349
+
350
+ `--scope-change "<reason>"` is the **ONLY** sanctioned way V may rise after
351
+ descent — it appends a `produce.scope.changed` event immediately before the new
352
+ criterion, so every post-descent increase carries its own logged justification
353
+ on the trail. There is NO permanent unlock: the flag justifies exactly the
354
+ criteria declared in that one call. Honest scope growth is allowed; it is
355
+ ALWAYS audited.
356
+
357
+ 3. **Criteria close as a CONSEQUENCE of a milestone shipping.** When a milestone
358
+ flips to `done` (the §5 two-gate), its still-open criteria auto-close, so V
359
+ strictly decreases. A milestone with zero declared criteria closes zero
360
+ (byte-identical to M2).
361
+
362
+ 4. **Monotone-progress is REPORTED at READ time:**
363
+
364
+ ```bash
365
+ apt-tools produce progress . --run-id <id>
366
+ ```
367
+
368
+ `progress` re-reads the ledger from disk every invocation (no in-memory
369
+ authority) and returns `{ v_open_now, v_series:[{milestone_id, v_before,
370
+ v_after, delta, decreased}], strictly_decreasing, lyapunov_ok,
371
+ lyapunov_violations[], scope_changes[] }`. The V-series must be **strictly
372
+ decreasing** across shipped milestones. A milestone that shipped without
373
+ closing any criterion (`delta === 0`) is a soft **stall**
374
+ (`strictly_decreasing: false`), not a hard failure. A `produce.criteria.declared`
375
+ that raised V after descent WITHOUT an accompanying `produce.scope.changed` is
376
+ a hard **Lyapunov violation** (`lyapunov_ok: false`). `progress` FLAGS these —
377
+ it never rejects (the rejection is the write-time guard in step 2); a
378
+ scope-changed increase is audited, NOT a hard violation.
379
+
380
+ ---
381
+
382
+ ## §9. Saga + forward compensators (M4) — ship a revert, never roll back a merged PR
383
+
384
+ M4 models the loop as a **saga**: `plan → execute → verify → review → ship`. Each
385
+ phase transition is recorded; each phase declares a **FORWARD compensator** that
386
+ recovers the run WITHOUT a destructive rollback. Record the transitions as you
387
+ drive a milestone:
388
+
389
+ ```bash
390
+ apt-tools produce saga enter . --run-id <id> --milestone <mid> --phase <plan|execute|verify|review|ship>
391
+ apt-tools produce saga complete . --run-id <id> --milestone <mid> --phase <plan|execute|verify|review|ship>
392
+ apt-tools produce saga status . --run-id <id>
393
+ ```
394
+
395
+ When a phase FAILS, fire its compensator:
396
+
397
+ ```bash
398
+ apt-tools produce saga compensate . --run-id <id> --milestone <mid> --phase <p> --reason "<why it failed>"
399
+ ```
400
+
401
+ The **load-bearing rule (ID-10 / ID-M4-02):** EVERY compensator is FORWARD —
402
+ `rolls_back_merged_pr: false` on every phase. `plan`/`execute` (pre-merge) reopen
403
+ + re-plan without a revert; `verify`/`review`/`ship` are `revert_reopen_replan` —
404
+ they **ship a revert** (a new forward commit/branch) + reopen the task + re-plan.
405
+ **You NEVER roll back a merged PR** — you cannot un-merge history other agents or
406
+ humans already built on. The CLI RECORDS the forward-compensator descriptor; it
407
+ does NOT execute the revert / reopen / re-plan (that is the dispatch layer's job)
408
+ and it NEVER mutates the roadmap milestone status (the saga fold is SEPARATE from
409
+ the roadmap projection — blindness preserved).
410
+
411
+ ## §10. Provider-diverse review (M4) — reviewer family ≠ implementer (ID-08)
412
+
413
+ The saga's REVIEW phase reviewer must be a provider family DISJOINT from the
414
+ implementer's — a same-family reviewer rubber-stamps its own family's work. This
415
+ REUSES the SAME `resolveProbeFamily` M2 uses on the probe/oracle path (no second
416
+ cross-family mechanism; provider is an OUTPUT; the model resolves LIVE, never a
417
+ hardcoded id):
418
+
419
+ ```bash
420
+ apt-tools produce saga resolve-reviewer . --run-id <id> --milestone <mid> [--host-family <fam>] [--available-families a,b,c]
421
+ ```
422
+
423
+ Returns a `reviewer_family` DISJOINT from the recorded implementer + a
424
+ LIVE-resolved model + a `transport_hint`; `available:false` when no disjoint
425
+ family exists (single-family setup — the review-diversity rule cannot be
426
+ satisfied by design). Dispatch the review to that family via the same **transport
427
+ ladder** as §6, then record the verdict:
428
+
429
+ ```bash
430
+ apt-tools produce saga review . --run-id <id> --milestone <mid> --verdict <pass|fail> --reviewer-family <reviewer_family>
431
+ ```
432
+
433
+ `saga review` is REFUSED fail-closed when `reviewer_family` equals the recorded
434
+ implementer family (or when no implementer family is recorded, ID-M2-02) — the
435
+ review-path analog of `oracle record`.
436
+
437
+ ## §11. Verification-TTL (M4) — re-verify OLD done milestones against HEAD each tick
438
+
439
+ Verification is not permanent: a milestone marked `done` at HEAD-sha `A` can
440
+ silently regress as HEAD advances to `B`. **Each tick**, re-verify a rotating
441
+ sample of OLD done milestones against the current HEAD. Pass the current HEAD sha
442
+ on the tick to surface the sample (additive — a tick WITHOUT `--head-sha` is
443
+ byte-identical to M1/M2/M3, still one tick, no loop):
444
+
445
+ ```bash
446
+ apt-tools produce tick . --run-id <id> --head-sha <sha> # surfaces the additive ttl_reverify sample
447
+ apt-tools produce ttl select . --run-id <id> --head-sha <sha> [--sample N]
448
+ ```
449
+
450
+ `ttl select` REUSES the M2 rotating-sample primitive and returns a deterministic
451
+ sample of already-`done` milestones **STALE** against HEAD — a milestone last
452
+ re-verified against the current HEAD is EXCLUDED as fresh. Re-run the frozen blind
453
+ probe (§5 steps 3+5) against each, then record the re-verify result cross-family:
454
+
455
+ ```bash
456
+ apt-tools produce ttl reverify . --run-id <id> --milestone <mid> --result <pass|fail> --probe-family <fam> --head-sha <sha>
457
+ ```
458
+
459
+ `ttl reverify` REUSES the M2 cross-family guard (a same-family re-verify is
460
+ refused). A `pass` refreshes verification against the new HEAD. A **`fail`** flags
461
+ the milestone — it appends `produce.ttl.regression_detected` AND **fires the
462
+ FORWARD compensator** (`saga compensate --phase verify` → `revert_reopen_replan`:
463
+ ship a revert + reopen + re-plan, never a merged-PR rollback). The re-verify RUN +
464
+ the revert-ship/reopen/re-plan EXECUTION is the dispatch layer's job; the CLI
465
+ records + computes.
466
+
467
+ ---
468
+
469
+ ## §12. WSJF/WIP scheduler (M5) — what to advance next, under budget
470
+
471
+ M5 replaces the naive "first planned" tick heuristic with a deterministic
472
+ **WSJF** (Weighted Shortest Job First) ranking of the READY milestones + a
473
+ **WIP-limited pick**. Ask the scheduler what to advance next:
474
+
475
+ ```bash
476
+ apt-tools produce schedule . --run-id <id> [--wip N]
477
+ ```
478
+
479
+ Read-only (emits nothing — mirrors `status`/`progress`/`canary`). It returns
480
+ `{ wip_limit, wip_in_use, wip_available, ready_queue[], blocked[], in_progress[],
481
+ pick[], reason, cycle }`.
482
+
483
+ **The formula (fixed integer weights — deterministic, no LLM):**
484
+
485
+ ```
486
+ WSJF(m) = CostOfDelay(m) / JobSize(m)
487
+ CostOfDelay(m) = 5·regression_flag(m) // W_REGRESS — user-facing breakage, most urgent
488
+ + 3·blocking_count(m) // W_BLOCK — # milestones depending on m
489
+ + 2·open_criteria(m) // W_VALUE — M3 open criteria bound to m
490
+ + priority(m) // optional declared --priority (default 0)
491
+ JobSize(m) = declared_size(m) > 0 ? declared_size(m) : max(open_criteria(m), 1)
492
+ ```
493
+
494
+ `open_criteria` deliberately appears in BOTH the value term of CoD and (as the
495
+ fallback) in JobSize — standard WSJF (prefer small, high-value jobs); a declared
496
+ `--size` decouples them. `ready_queue` is sorted **WSJF desc → roadmap first-seen
497
+ order → milestone_id** — a total, stable order, so two folds of the same log are
498
+ byte-identical.
499
+
500
+ **Ready-set membership + WIP budget:**
501
+
502
+ - A `planned` milestone is READY iff every `depends_on` id resolves to a roadmap
503
+ milestone with status `done` (an unknown dep fails closed = unmet); otherwise it
504
+ is in `blocked` with `blocked_by` = the not-done deps. A dependency **cycle**
505
+ among not-done milestones surfaces `reason: "dependency_cycle_detected"` + the
506
+ offending `cycle` — never a silent idle.
507
+ - A `done` milestone with an **unresolved M4 regression** (§11) re-enters the ready
508
+ queue tagged `regressed: true` with the `W_REGRESS=5` weight — the loop defends
509
+ what it shipped. A subsequent `ttl reverify --result pass` resolves it.
510
+ - The WIP cap resolves `--wip N` → `.aperant/config.json :: produce.wip` →
511
+ default **1**. `wip_available = max(0, wip_limit − in_progress_count)`; `pick` is
512
+ the top `wip_available` of `ready_queue`. WIP-saturated → empty `pick`,
513
+ `reason: "wip_saturated"`. WIP counts roadmap `in_progress` status ONLY (a
514
+ regressed `done` candidate is not counted as in_progress).
515
+
516
+ Declare a milestone's dependencies / priority / size at seed time (additive on the
517
+ existing `milestone_added` event — an M1-style add is byte-identical):
518
+
519
+ ```bash
520
+ apt-tools produce add-milestone . --run-id <id> --title "<t>" [--depends-on a,b] [--priority <int>] [--size <positive int>]
521
+ ```
522
+
523
+ **Scheduler-driven tick.** The §2 `tick` folds this schedule and sets
524
+ `next_action` to the scheduler's pick (top WSJF ready under the WIP budget), still
525
+ one tick per invocation. The `pick` is **advisory scheduling input** — the M5 CLI
526
+ does NOT dispatch plan→execute→verify→review→ship of the picked milestone (that
527
+ closed-loop dispatch is out of scope; the scheduler tells the loop *what* to
528
+ advance next). A **token/cost budget** is deferred — the substrate records no cost
529
+ data; WIP is the honest, substrate-native budget.
530
+
531
+ ---
532
+
533
+ ## §13. Claim Envelope (M6) — capture a lesson, consume it as a plan delta
534
+
535
+ M6 turns "we learned this once" into "every future plan in this family is
536
+ pre-corrected." A **Claim Envelope** is a durable, provider-neutral, bounded,
537
+ expiring claim (the `lesson` profile is the only one shipped). Four subcommands
538
+ drive its lifecycle:
539
+
540
+ ```bash
541
+ apt-tools produce claim declare . --run-id <id> --profile lesson --claim-file <path> [--claim-id <id>]
542
+ apt-tools produce claim list . --run-id <id>
543
+ apt-tools produce claim expire . --run-id <id> --claim-id <id> [--reason "<r>"]
544
+ apt-tools produce claim consume . --run-id <id> [--milestone <mid>]
545
+ ```
546
+
547
+ - **`declare`** VALIDATES the envelope (the `lesson` profile requires
548
+ `provenance` + a non-empty `preconditions` trigger + a **bounded** `scope`
549
+ (`global !== true`) + an `expiry` (`expires_at` OR `revalidate_against_head`) +
550
+ a `negative_evidence` field + a non-empty `resolution_verifier`), then FREEZES
551
+ it once to `claims/<claim-id>.json` (exclusive-create, immutable) and logs
552
+ `produce.claim.declared` carrying the **content_hash only** — the claim content
553
+ NEVER touches the event log. A global/unbounded scope, an empty precondition,
554
+ or a missing expiry is REJECTED fail-closed with a reason.
555
+ - **`list`** re-projects the claim ledger (`declared − expired`) folded from the
556
+ append-only log. The `produce.claim.*` ops are a SEPARATE fold — they NEVER
557
+ enter the roadmap projection the Showrunner re-reads each tick.
558
+ - **`expire`** logs `produce.claim.expired`; the claim leaves the active set.
559
+ - **`consume`** emits a STRUCTURED delta
560
+ `{claim_id, targets:[{kind: acceptance_criterion|subtask|proof_obligation, requirement, ref?}]}`
561
+ derived FROM the claim's `resolution_verifier`.
562
+
563
+ **Consumption is a DELTA, never a context paste (the M6 invariant).** "Consuming
564
+ a lesson" means applying a concrete AC / subtask / proof-obligation requirement to
565
+ a plan — it does NOT mean pasting the lesson's prose (`provenance` /
566
+ `negative_evidence` / body) into the planner's context. Pasting lesson text
567
+ re-introduces the context-rot the loop is trying to escape and is FORBIDDEN. The
568
+ consume output is a projection DOWN to `{claim_id, targets}` and carries none of
569
+ the envelope's non-delta fields.
570
+
571
+ **Anti-corrosion (expiry + HEAD re-validation).** A stale lesson that no longer
572
+ matches HEAD silently degrades every future plan. `consume` SKIPS (never applies)
573
+ a claim that is time-expired, whose `revalidate_against_head` check no longer
574
+ matches the working tree, or whose precondition does not match — surfacing each
575
+ under `skipped[]` with a reason. The HEAD check is a DETERMINISTIC path-exists /
576
+ grep, never an LLM judgment.
577
+
578
+ **Bounded critic (anti-windup).** A lesson fires only behind a matching
579
+ precondition and a bounded scope, and consumption emits ONE delta per claim — one
580
+ incident fixes ITS task, it never ratchets a global always-on policy.
581
+
582
+ **Interop, not a 4th store; generalizes as data.** The claim ledger is a fold +
583
+ a per-record content file on the SAME `.aperant/produce/<run-id>/` substrate M1/M2
584
+ already use — no new DB, no schema, no migration, and it touches none of the 3
585
+ adjudication engines (verify-proof / oracle / review). Generalization past
586
+ `lesson` is a data-driven **profile registry** (`CLAIM_PROFILES`): adding a second
587
+ profile is one map entry, not a code fork. The outer loop is now
588
+ **feature-complete** (M1–M6).
589
+
590
+ ---
591
+
592
+ ## Notes for the orchestrator
593
+
594
+ - **After editing this SKILL** (or any shipped `apt-*/SKILL.md`), re-run
595
+ `apt-tools init . --<runtime>` (e.g. `apt-tools init . --solo --claude`, and
596
+ once per other installed runtime) — or `/apt:update` — so each runtime's
597
+ `apt-file-manifest.json` + `apt-skill-version` headers stay in sync.
598
+
599
+ - The event log is the authority; `roadmap.json` is a derived cache, always
600
+ regenerable by folding the log. A corrupt/stale cache is repaired by the next
601
+ `status`/`tick` re-fold — never trust it as the source of truth (ID-01).
602
+ - Concurrent `add-milestone` writers are crash-safe by construction: the
603
+ mutation is a pure O_APPEND to the shared log (atomic under PIPE_BUF), and the
604
+ cache is a fold-under-lock single-writer regenerate — no lost updates.
605
+ - M1 introduces no new secret material, no daemon, and no new per-mode
606
+ persistence: it is framework filesystem substrate, cross-mode by construction.
@@ -29,7 +29,7 @@ Execute a small task quickly without full pipeline overhead. For small fixes, do
29
29
  - **Working directory:** The project root (where you were invoked) UNLESS an active task has a task-level worktree (see below).
30
30
  - **Constitution:** Read `AGENTS.md` in the project root if it exists
31
31
  - **State directory:** `.aperant/quick/{task-id}/`
32
- - **Task-level worktree isolation:** Use the `worktree_path` the router passed in skill context (router path) OR that §1a parsed from `task create`'s envelope (direct path) — this is the worktree `task create` literally just provisioned for this task. When present, run all editing Bash commands with `cd {worktree_path} &&` prefix so the fix lands on the task branch. `apt-tools` calls still take the project root as `<project-dir>` so they coordinate through the main repo's shared `.aperant/`. The quick task's `.aperant/quick/{task-id}/` directory lives in the main repo, not the worktree. (Last-resort fallback only: if neither source supplied a `worktree_path` but `.aperant/state.json` has an active task with one, you may use that.)
32
+ - **Task-level worktree isolation:** Use the `worktree_path` the router passed in skill context (router path) OR that §1a parsed from `task ensure`'s envelope (direct path) — this is the worktree `task ensure` literally just provisioned for this task. When present, run all editing Bash commands with `cd {worktree_path} &&` prefix so the fix lands on the task branch. `apt-tools` calls still take the project root as `<project-dir>` so they coordinate through the main repo's shared `.aperant/`. The quick task's `.aperant/quick/{task-id}/` directory lives in the main repo, not the worktree. (Last-resort fallback only: if neither source supplied a `worktree_path` but `.aperant/state.json` has an active task with one, you may use that.)
33
33
  </your_environment>
34
34
 
35
35
  <state_files>
@@ -54,35 +54,27 @@ Extract the task description from `$ARGUMENTS`.
54
54
 
55
55
  ### 1a. Resolve canonical task id (idempotent)
56
56
 
57
- QUICK now produces a canonical state.active_tasks record so `/apt:ship` can find the task via the lite ship profile. The body owns idempotency because `apt-tools task create` does NOT guard against existing ids (see spec §3 R8).
57
+ QUICK now produces a canonical state.active_tasks record so `/apt:ship` can find the task via the lite ship profile. Idempotency is owned by the deterministic allocator `apt-tools task ensure --intent create-new` (NOT raw `task create`) re-running returns the SAME task + worktree (`reused: true`), so the old `task get` guard dance is gone.
58
58
 
59
59
  Two invocation paths converge here:
60
60
 
61
- - **Router path** (`/apt` → `/apt:quick`): the router has already called `task create` and injected `task_id` into your skill context.
62
- - **Direct path** (`/apt:quick foo`): no `task_id` in context — you create one now.
63
-
64
- Sequence:
61
+ - **Router path** (`/apt` → `/apt:quick`): the router has already run `task ensure --intent create-new` and injected `task_id` (+ `worktree_path`) into your skill context. Re-affirm idempotently with the same id:
62
+ ```bash
63
+ apt-tools task ensure . --description "$ARGUMENTS" --intent create-new --track QUICK --id {task_id}
64
+ ```
65
+ The `--id {task_id}` makes ensure resolve to the EXACT record the router just provisioned (`reused: true`) — no second task, no second worktree.
65
66
 
66
- 1. **If `task_id` is present in skill context** (router path):
67
- ```bash
68
- apt-tools task get . --id {task_id}
69
- ```
70
- - If exit 0 AND the returned record has `track === "QUICK"` reuse, skip the create call.
71
- - Else (record missing OR wrong track) → call:
72
- ```bash
73
- apt-tools task create . --description "$ARGUMENTS" --track QUICK --id {task_id}
74
- ```
75
- Parse `task_id` from the returned envelope (it will match the id you passed).
67
+ - **Direct path** (`/apt:quick foo`): no `task_id` in context allocate now:
68
+ ```bash
69
+ apt-tools task ensure . --description "$ARGUMENTS" --intent create-new --track QUICK
70
+ ```
71
+ ensure is keyed on the normalized description + scope, so a retry returns the SAME canonical `{slug}_YY-MM-DD` `task_id`.
76
72
 
77
- 2. **If no `task_id` in skill context** (direct path):
78
- ```bash
79
- apt-tools task create . --description "$ARGUMENTS" --track QUICK
80
- ```
81
- Parse the generated `task_id` from the envelope — it will be the canonical `{slug}_YY-MM-DD` shape.
73
+ Parse `task_id` from the returned envelope.
82
74
 
83
75
  The task id is now canonical (no more `quick-YYYYMMDD-*` invention). `QuickTaskSchema` accepts both the canonical `{slug}_YY-MM-DD` form and the legacy `quick-YYYYMMDD-{slug}` form for back-compat with already-shipped task records on disk.
84
76
 
85
- **Worktree isolation (both paths).** `task create` provisions a worktree when `task_isolation.worktree_per_task` is enabled (track-agnostic — QUICK gets one too). Parse `worktree.worktree_path`, `worktree.branch`, and `worktree.base_branch` from the `task create` envelope (direct path); on the router path the orchestrator already passed `worktree_path` into your skill context — use that. When a `worktree` block is present, print the banner and run all subsequent editing Bash commands with `cd {worktree_path} &&`:
77
+ **Worktree isolation (both paths).** `task ensure --intent create-new` provisions a worktree when `task_isolation.worktree_per_task` is enabled (track-agnostic — QUICK gets one too). Parse `worktree.worktree_path`, `worktree.branch`, and `worktree.base_branch` from the `task ensure` envelope (direct path); on the router path the orchestrator already passed `worktree_path` into your skill context — use that. When a `worktree` block is present, print the banner and run all subsequent editing Bash commands with `cd {worktree_path} &&`:
86
78
 
87
79
  ```
88
80
  [APT] Working in isolated worktree: {worktree_path}