ahead-pi 0.2.1 → 0.3.1

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 (101) hide show
  1. package/README.md +26 -41
  2. package/dist/ahead_wasm.wasm +0 -0
  3. package/generated/corrective-debugging/ai-audit.md +39 -0
  4. package/generated/corrective-debugging/ai-review.md +46 -0
  5. package/generated/corrective-debugging/characterize.md +53 -0
  6. package/generated/corrective-debugging/conclude.md +59 -0
  7. package/generated/corrective-debugging/correction.md +53 -0
  8. package/generated/corrective-debugging/deploy.md +38 -0
  9. package/generated/corrective-debugging/human-review.md +45 -0
  10. package/generated/corrective-debugging/implement.md +42 -0
  11. package/generated/corrective-debugging/investigate.md +59 -0
  12. package/generated/corrective-debugging/manifest.json +30 -0
  13. package/generated/corrective-debugging/model.md +53 -0
  14. package/generated/corrective-debugging/outcome.md +38 -0
  15. package/generated/corrective-debugging/plan.md +53 -0
  16. package/generated/corrective-debugging/verify.md +47 -0
  17. package/generated/decision/compare.md +45 -0
  18. package/generated/decision/criteria.md +45 -0
  19. package/generated/decision/decide.md +45 -0
  20. package/generated/decision/frame.md +45 -0
  21. package/generated/decision/manifest.json +21 -0
  22. package/generated/decision/options.md +47 -0
  23. package/generated/decision/publish.md +38 -0
  24. package/generated/decision/research.md +45 -0
  25. package/generated/internal-improvement/ai-audit.md +39 -0
  26. package/generated/internal-improvement/ai-review.md +46 -0
  27. package/generated/internal-improvement/baseline.md +46 -0
  28. package/generated/internal-improvement/decision.md +45 -0
  29. package/generated/internal-improvement/deploy.md +38 -0
  30. package/generated/internal-improvement/human-review.md +45 -0
  31. package/generated/internal-improvement/implement.md +42 -0
  32. package/generated/internal-improvement/invariants.md +38 -0
  33. package/generated/internal-improvement/manifest.json +29 -0
  34. package/generated/internal-improvement/options.md +47 -0
  35. package/generated/internal-improvement/outcome.md +38 -0
  36. package/generated/internal-improvement/plan.md +53 -0
  37. package/generated/internal-improvement/target.md +45 -0
  38. package/generated/internal-improvement/verify.md +45 -0
  39. package/generated/investigation/bound.md +45 -0
  40. package/generated/investigation/conclude.md +45 -0
  41. package/generated/investigation/explore.md +60 -0
  42. package/generated/investigation/frame.md +45 -0
  43. package/generated/investigation/gather.md +45 -0
  44. package/generated/investigation/manifest.json +21 -0
  45. package/generated/investigation/synthesize.md +51 -0
  46. package/generated/operational-stabilization/assess.md +46 -0
  47. package/generated/operational-stabilization/execute-observe.md +45 -0
  48. package/generated/operational-stabilization/manifest.json +19 -0
  49. package/generated/operational-stabilization/monitor.md +45 -0
  50. package/generated/operational-stabilization/outcome.md +38 -0
  51. package/generated/operational-stabilization/respond.md +40 -0
  52. package/generated/operational-stabilization/verify-recovery.md +45 -0
  53. package/generated/product-change/ai-audit.md +7 -4
  54. package/generated/product-change/ai-review.md +15 -5
  55. package/generated/product-change/decision.md +11 -2
  56. package/generated/product-change/define.md +4 -2
  57. package/generated/product-change/deploy.md +4 -2
  58. package/generated/product-change/human-review.md +11 -2
  59. package/generated/product-change/implement.md +4 -2
  60. package/generated/product-change/manifest.json +8 -3
  61. package/generated/product-change/options.md +11 -2
  62. package/generated/product-change/outcome.md +4 -2
  63. package/generated/product-change/plan.md +17 -2
  64. package/generated/product-change/questions.md +17 -2
  65. package/generated/product-change/research.md +11 -2
  66. package/generated/product-change/verify.md +4 -2
  67. package/generated/recommended-skills.json +24 -0
  68. package/generated/reference/CONSTITUTION.md +2 -0
  69. package/generated/reference/docs/evidence/README.md +17 -0
  70. package/generated/reference/docs/evidence/evidence-standard.md +2 -0
  71. package/generated/reference/docs/evidence/research-map.md +2 -0
  72. package/generated/reference/docs/{references → evidence/sources}/pragmatic-programmer-page-index.md +3 -1
  73. package/generated/reference/docs/{references → evidence/sources}/submitted-engineering-notes.md +3 -1
  74. package/generated/reference/docs/guide/README.md +28 -0
  75. package/generated/reference/docs/{acceptable-ai-use.md → guide/acceptable-ai-use.md} +4 -2
  76. package/generated/reference/docs/{engineering-practice.md → guide/engineering-practice.md} +5 -3
  77. package/generated/reference/docs/{rationale.md → guide/rationale.md} +3 -1
  78. package/generated/reference/docs/guide/recommended-skills.md +21 -0
  79. package/generated/reference/docs/{workflows → guide/workflows}/README.md +6 -4
  80. package/generated/reference/docs/{workflows → guide/workflows}/corrective-debugging.md +39 -19
  81. package/generated/reference/docs/{workflows → guide/workflows}/decision.md +4 -2
  82. package/generated/reference/docs/{workflows → guide/workflows}/internal-improvement.md +37 -23
  83. package/generated/reference/docs/{workflows → guide/workflows}/investigation.md +5 -1
  84. package/generated/reference/docs/{workflows → guide/workflows}/operational-stabilization.md +16 -12
  85. package/generated/reference/docs/{workflows → guide/workflows}/product-change.md +16 -3
  86. package/generated/reference/index.json +200 -87
  87. package/package.json +34 -25
  88. package/src/engine.ts +27 -8
  89. package/src/flow-guides.ts +168 -0
  90. package/src/guidance.ts +220 -78
  91. package/src/index.ts +852 -189
  92. package/src/reference-viewer.ts +20 -18
  93. package/src/reference.ts +76 -14
  94. package/src/review.ts +360 -0
  95. package/src/skills.ts +133 -0
  96. package/src/storage.ts +139 -15
  97. package/src/types.ts +1 -0
  98. package/generated/reference/docs/design/debugging-and-operations.md +0 -119
  99. package/generated/reference/docs/design/executable-workflows.md +0 -110
  100. package/generated/reference/docs/design/process-taxonomy.md +0 -144
  101. package/generated/reference/docs/releasing-pi.md +0 -89
package/src/index.ts CHANGED
@@ -4,12 +4,14 @@ import type {
4
4
  ExtensionAPI,
5
5
  ExtensionCommandContext,
6
6
  ExtensionContext,
7
+ Theme,
7
8
  } from "@earendil-works/pi-coding-agent";
9
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
8
10
  import { Type } from "typebox";
9
11
  import { AheadEngine, AheadEngineError } from "./engine.js";
10
12
  import {
11
13
  buildArtifactTemplate,
12
- buildWidgetLines,
14
+ buildHeaderLines,
13
15
  nextAction,
14
16
  phaseGuide,
15
17
  phasePosition,
@@ -19,14 +21,31 @@ import {
19
21
  loadReferenceIndex,
20
22
  readReference,
21
23
  relevantReferences,
24
+ type ReferenceEntry,
22
25
  } from "./reference.js";
23
26
  import { showReferenceViewer } from "./reference-viewer.js";
27
+ import {
28
+ collectReviewSnapshot,
29
+ extractFindingIds,
30
+ extractReviewFingerprint,
31
+ openInConfiguredEditor,
32
+ reviewDispositionTemplate,
33
+ reviewRequest,
34
+ reviewSnapshotMarkdown,
35
+ validateAiReviewArtifact,
36
+ validateReviewDisposition,
37
+ } from "./review.js";
38
+ import {
39
+ loadRecommendedSkills,
40
+ recommendedSkillsMarkdown,
41
+ relevantRecommendedSkills,
42
+ } from "./skills.js";
24
43
  import { humanActor, projectRoot, RunStore } from "./storage.js";
25
44
  import type { Actor, Capability, EventAction, Run, RunState } from "./types.js";
26
45
 
27
46
  const wasmPath =
28
47
  process.env.AHEAD_WASM_PATH || fileURLToPath(new URL("../dist/ahead_wasm.wasm", import.meta.url));
29
- const instructionDirectory = fileURLToPath(new URL("../generated/product-change", import.meta.url));
48
+ const instructionDirectory = fileURLToPath(new URL("../generated", import.meta.url));
30
49
  const enginePromise = AheadEngine.load(wasmPath);
31
50
  const instructions = new Map<string, string>();
32
51
 
@@ -46,106 +65,161 @@ const RecordArtifactParams = Type.Object({
46
65
  content: Type.String({ description: "Complete Markdown artifact content", maxLength: 100_000 }),
47
66
  });
48
67
  const ReferenceParams = Type.Object({
49
- topic: Type.Optional(Type.String({ description: "Reference id, path, or title; omit to list phase-relevant references" })),
68
+ topic: Type.Optional(
69
+ Type.String({
70
+ description: "Reference id, path, or title; omit to list phase-relevant references",
71
+ }),
72
+ ),
50
73
  });
51
74
 
52
75
  export default function aheadExtension(pi: ExtensionAPI): void {
53
76
  pi.registerCommand("ahead", {
54
- description: "Enter or continue the guided AHEAD mode",
55
- handler: async (args, ctx) => command(ctx, async () => {
56
- await openAheadMode(pi, args, ctx);
57
- }),
77
+ description: "Enter AHEAD mode or open its action menu",
78
+ handler: async (args, ctx) =>
79
+ command(ctx, async () => {
80
+ await openAheadMode(pi, args, ctx);
81
+ }),
58
82
  });
59
83
 
60
84
  pi.registerCommand("ahead-guide", {
61
85
  description: "Read the AHEAD framework guidance relevant to the active phase",
62
- handler: async (args, ctx) => command(ctx, async () => {
63
- await showAheadGuide(ctx, args);
64
- }),
86
+ handler: async (args, ctx) =>
87
+ command(ctx, async () => {
88
+ await showAheadGuide(ctx, args);
89
+ }),
90
+ });
91
+
92
+ pi.registerCommand("ahead-skills", {
93
+ description: "Inspect optional skills reviewed for the active AHEAD phase",
94
+ handler: async (_args, ctx) =>
95
+ command(ctx, async () => {
96
+ await showRecommendedSkills(ctx);
97
+ }),
98
+ });
99
+
100
+ pi.registerCommand("ahead-review", {
101
+ description: "Inspect and perform the current changeset review handoff",
102
+ handler: async (_args, ctx) =>
103
+ command(ctx, async () => {
104
+ await openReviewWorkbench(pi, ctx);
105
+ }),
106
+ });
107
+
108
+ pi.registerCommand("ahead-stop", {
109
+ description: "Exit AHEAD mode; discard the unfinished record or explicitly save it",
110
+ handler: async (_args, ctx) =>
111
+ command(ctx, async () => {
112
+ await stopAheadMode(ctx);
113
+ }),
114
+ });
115
+
116
+ pi.registerCommand("ahead-resume", {
117
+ description: "Resume unfinished AHEAD work that was explicitly saved",
118
+ handler: async (args, ctx) =>
119
+ command(ctx, async () => {
120
+ await resumeSavedRun(ctx, args.trim());
121
+ }),
65
122
  });
66
123
 
67
124
  pi.registerCommand("ahead-start", {
68
- description: "Advanced: start a Product Change run directly",
69
- handler: async (args, ctx) => command(ctx, async () => {
70
- await startRun(ctx, args);
71
- }),
125
+ description: "Advanced: start directly with <workflow-id> :: <title>",
126
+ handler: async (args, ctx) =>
127
+ command(ctx, async () => {
128
+ await startRun(ctx, args);
129
+ }),
72
130
  });
73
131
 
74
132
  pi.registerCommand("ahead-status", {
75
133
  description: "Advanced: show the raw active AHEAD phase contract",
76
- handler: async (_args, ctx) => command(ctx, async () => {
77
- const run = await requireRun(ctx);
78
- const state = (await enginePromise).deriveState(run);
79
- await refreshUi(ctx, run);
80
- ctx.ui.notify(formatState(state), state.blockers.length ? "warning" : "info");
81
- }),
134
+ handler: async (_args, ctx) =>
135
+ command(ctx, async () => {
136
+ const run = await requireRun(ctx);
137
+ const state = (await enginePromise).deriveState(run);
138
+ await refreshUi(ctx, run);
139
+ ctx.ui.notify(formatState(state), state.blockers.length ? "warning" : "info");
140
+ }),
82
141
  });
83
142
 
84
143
  pi.registerCommand("ahead-record", {
85
144
  description: "Advanced: record a human-owned artifact directly",
86
- handler: async (args, ctx) => command(ctx, async () => {
87
- await recordHumanArtifact(ctx, args.trim());
88
- }),
145
+ handler: async (args, ctx) =>
146
+ command(ctx, async () => {
147
+ await recordHumanArtifact(ctx, args.trim());
148
+ }),
89
149
  });
90
150
 
91
151
  pi.registerCommand("ahead-accept", {
92
152
  description: "Advanced: accept the active gate without advancing",
93
- handler: async (_args, ctx) => command(ctx, async () => {
94
- if (!ctx.hasUI) throw new Error("/ahead-accept requires interactive or RPC UI support");
95
- const engine = await enginePromise;
96
- const store = storeFor(ctx);
97
- const run = await requireRun(ctx);
98
- const state = engine.deriveState(run);
99
- const confirmed = await ctx.ui.confirm(
100
- `Accept ${state.gate.id}?`,
101
- `${state.gate.title}\n\nThis records human acceptance as ${humanActor(store.projectRoot).identity}.`,
102
- );
103
- if (!confirmed) return;
104
- const updated = engine.applyEvent(run, humanActor(store.projectRoot), {
105
- type: "gate_accepted",
106
- phase: state.phase.id,
107
- gate: state.gate.id,
108
- });
109
- await store.save(updated);
110
- await refreshUi(ctx, updated);
111
- ctx.ui.notify(`Accepted gate ${state.gate.id}. Use /ahead-advance when ready.`, "info");
112
- }),
153
+ handler: async (_args, ctx) =>
154
+ command(ctx, async () => {
155
+ if (!ctx.hasUI) {
156
+ throw new Error("/ahead-accept requires interactive or RPC UI support");
157
+ }
158
+ const engine = await enginePromise;
159
+ const store = storeFor(ctx);
160
+ const run = await requireRun(ctx);
161
+ const state = engine.deriveState(run);
162
+ const confirmed = await ctx.ui.confirm(
163
+ `Accept ${state.gate.id}?`,
164
+ `${state.gate.title}\n\nThis records human acceptance as ${humanActor(store.projectRoot).identity}.`,
165
+ );
166
+ if (!confirmed) {
167
+ return;
168
+ }
169
+ const updated = engine.applyEvent(run, humanActor(store.projectRoot), {
170
+ type: "gate_accepted",
171
+ phase: state.phase.id,
172
+ gate: state.gate.id,
173
+ });
174
+ await store.save(updated);
175
+ await refreshUi(ctx, updated);
176
+ ctx.ui.notify(`Accepted gate ${state.gate.id}. Use /ahead-advance when ready.`, "info");
177
+ }),
113
178
  });
114
179
 
115
180
  pi.registerCommand("ahead-advance", {
116
181
  description: "Advanced: advance an already accepted gate",
117
- handler: async (_args, ctx) => command(ctx, async () => {
118
- if (!ctx.hasUI) throw new Error("/ahead-advance requires interactive or RPC UI support");
119
- const engine = await enginePromise;
120
- const store = storeFor(ctx);
121
- const run = await requireRun(ctx);
122
- const state = engine.deriveState(run);
123
- const destination = state.phase.next ?? "closed";
124
- const confirmed = await ctx.ui.confirm(
125
- state.phase.next ? `Advance to ${state.phase.next}?` : "Close this AHEAD run?",
126
- `Current phase: ${state.phase.title}\nDestination: ${destination}\nActor: ${humanActor(store.projectRoot).identity}`,
127
- );
128
- if (!confirmed) return;
129
- const action: EventAction = state.phase.next
130
- ? {
131
- type: "phase_transitioned",
132
- from: state.phase.id,
133
- to: state.phase.next,
134
- direction: "advance",
135
- }
136
- : { type: "run_closed", phase: state.phase.id };
137
- const updated = engine.applyEvent(run, humanActor(store.projectRoot), action);
138
- await store.save(updated);
139
- await refreshUi(ctx, updated);
140
- ctx.ui.notify(state.phase.next ? `Advanced to ${state.phase.next}.` : "AHEAD run closed.", "info");
141
- }),
182
+ handler: async (_args, ctx) =>
183
+ command(ctx, async () => {
184
+ if (!ctx.hasUI) {
185
+ throw new Error("/ahead-advance requires interactive or RPC UI support");
186
+ }
187
+ const engine = await enginePromise;
188
+ const store = storeFor(ctx);
189
+ const run = await requireRun(ctx);
190
+ const state = engine.deriveState(run);
191
+ const destination = state.phase.next ?? "closed";
192
+ const confirmed = await ctx.ui.confirm(
193
+ state.phase.next ? `Advance to ${state.phase.next}?` : "Close this AHEAD run?",
194
+ `Current phase: ${state.phase.title}\nDestination: ${destination}\nActor: ${humanActor(store.projectRoot).identity}`,
195
+ );
196
+ if (!confirmed) {
197
+ return;
198
+ }
199
+ const action: EventAction = state.phase.next
200
+ ? {
201
+ type: "phase_transitioned",
202
+ from: state.phase.id,
203
+ to: state.phase.next,
204
+ direction: "advance",
205
+ }
206
+ : { type: "run_closed", phase: state.phase.id };
207
+ const updated = engine.applyEvent(run, humanActor(store.projectRoot), action);
208
+ await store.save(updated);
209
+ await refreshUi(ctx, updated);
210
+ ctx.ui.notify(
211
+ state.phase.next ? `Advanced to ${state.phase.next}.` : "AHEAD run closed.",
212
+ "info",
213
+ );
214
+ }),
142
215
  });
143
216
 
144
217
  pi.registerCommand("ahead-return", {
145
218
  description: "Advanced: return to an earlier phase with a reason",
146
- handler: async (args, ctx) => command(ctx, async () => {
147
- await returnToEarlierPhase(ctx, args);
148
- }),
219
+ handler: async (args, ctx) =>
220
+ command(ctx, async () => {
221
+ await returnToEarlierPhase(ctx, args);
222
+ }),
149
223
  });
150
224
 
151
225
  pi.registerCommand("ahead-help", {
@@ -153,12 +227,16 @@ export default function aheadExtension(pi: ExtensionAPI): void {
153
227
  handler: async (_args, ctx) => {
154
228
  ctx.ui.notify(
155
229
  [
156
- "/ahead [title] — enter, resume, or act in guided AHEAD mode",
230
+ "/ahead [title] — choose a workflow for new work, or open the active action menu",
157
231
  "/ahead-guide [topic] — read the applicable AHEAD framework Markdown",
232
+ "/ahead-skills — inspect optional reviewed skills relevant to this phase",
233
+ "/ahead-review — inspect the exact changeset and review handoff",
234
+ "/ahead-stop — leave AHEAD mode; discard the unfinished record by default or explicitly save it",
235
+ "/ahead-resume [run-id] — resume an unfinished run that you explicitly saved",
158
236
  "",
159
- "Once started, the repository run remains in AHEAD mode until an accountable human closes the outcome.",
160
- "Use normal conversation to think and work with AI. Run /ahead whenever you want the next valid action.",
161
- "The persistent guide explains what you own, what AI may do, required evidence, and what happens next.",
237
+ "Once started, the repository run remains in AHEAD mode until an accountable human closes it or uses /ahead-stop.",
238
+ "Use normal conversation to think and work with AI. AHEAD remains active and guides every turn.",
239
+ "The compact header shows the goal, required evidence, and next owner. /ahead reopens the action menu when you need a recorded action.",
162
240
  "",
163
241
  "Advanced fallback commands: /ahead-status, /ahead-record, /ahead-accept, /ahead-advance, /ahead-return.",
164
242
  "AI can record only AI/shared artifacts allowed in the active phase. It cannot accept gates, transition, approve, deploy, or close the run.",
@@ -171,7 +249,8 @@ export default function aheadExtension(pi: ExtensionAPI): void {
171
249
  pi.registerTool({
172
250
  name: "ahead_get_context",
173
251
  label: "AHEAD context",
174
- description: "Read the authoritative active AHEAD workflow state, phase contract, artifacts, gate, and blockers.",
252
+ description:
253
+ "Read the authoritative active AHEAD workflow state, phase contract, artifacts, gate, and blockers.",
175
254
  promptSnippet: "Read the active AHEAD workflow state and human/AI boundaries.",
176
255
  parameters: EmptyParams,
177
256
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
@@ -183,29 +262,80 @@ export default function aheadExtension(pi: ExtensionAPI): void {
183
262
  },
184
263
  });
185
264
 
265
+ pi.registerTool({
266
+ name: "ahead_get_recommended_skills",
267
+ label: "AHEAD recommended skills",
268
+ description:
269
+ "List optional third-party skills reviewed for the active phase. Never installs a skill.",
270
+ promptSnippet:
271
+ "Discover reviewed optional skills only when they may help the active AHEAD work.",
272
+ parameters: EmptyParams,
273
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
274
+ return toolResult(async () => {
275
+ const catalog = await loadRecommendedSkills();
276
+ const run = await storeFor(ctx).loadCurrent();
277
+ const state = run ? (await enginePromise).deriveState(run) : undefined;
278
+ const phaseId = state && !state.closed ? state.phase.id : undefined;
279
+ const workflowId = state && !state.closed ? state.workflow_id : undefined;
280
+ return {
281
+ reviewed_at: catalog.reviewed_at,
282
+ phase: phaseId ?? null,
283
+ workflow: workflowId ?? null,
284
+ recommended: relevantRecommendedSkills(catalog, workflowId, phaseId),
285
+ available: catalog.skills,
286
+ instruction:
287
+ "Do not install automatically. Explain why a skill applies, show the pinned source and command, and let the human opt in. AHEAD remains authoritative.",
288
+ };
289
+ });
290
+ },
291
+ });
292
+
293
+ pi.registerTool({
294
+ name: "ahead_get_review_snapshot",
295
+ label: "AHEAD review snapshot",
296
+ description:
297
+ "Capture the exact current Git changeset, merge base, status, paths, diff, and stable fingerprint without modifying it.",
298
+ promptSnippet: "Bind review findings to the exact current AHEAD changeset fingerprint.",
299
+ parameters: EmptyParams,
300
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
301
+ return toolResult(async () => collectReviewSnapshot(storeFor(ctx).projectRoot));
302
+ },
303
+ });
304
+
186
305
  pi.registerTool({
187
306
  name: "ahead_get_reference",
188
307
  label: "AHEAD framework reference",
189
- description: "List or read packaged AHEAD Constitution, philosophy, acceptable-use, engineering-practice, workflow, and evidence Markdown.",
190
- promptSnippet: "Retrieve relevant AHEAD framework guidance when the phase or policy is unclear.",
308
+ description:
309
+ "List or read packaged AHEAD Constitution, philosophy, acceptable-use, engineering-practice, workflow, and evidence Markdown.",
310
+ promptSnippet:
311
+ "Retrieve relevant AHEAD framework guidance when the phase or policy is unclear.",
191
312
  parameters: ReferenceParams,
192
313
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
193
314
  return toolResult(async () => {
194
315
  const run = await storeFor(ctx).loadCurrent();
195
- const phase = run && !(await enginePromise).deriveState(run).closed
196
- ? (await enginePromise).deriveState(run).phase.id
197
- : undefined;
316
+ const state = run ? (await enginePromise).deriveState(run) : undefined;
317
+ const phase = state && !state.closed ? state.phase.id : undefined;
318
+ const workflowId = state && !state.closed ? state.workflow_id : undefined;
198
319
  if (!params.topic?.trim()) {
199
320
  const index = await loadReferenceIndex();
200
321
  return {
201
322
  phase: phase ?? null,
202
- recommended: await relevantReferences(phase),
203
- available: index.references.map(({ id, title, path }) => ({ id, title, path })),
323
+ workflow: workflowId ?? null,
324
+ recommended: await relevantReferences(workflowId, phase),
325
+ available: index.references.map(({ id, title, path, audience, authority }) => ({
326
+ id,
327
+ title,
328
+ path,
329
+ audience,
330
+ authority,
331
+ })),
204
332
  instruction: "Request one reference by id, path, or title. Load only what is relevant.",
205
333
  };
206
334
  }
207
335
  const entry = await findReference(params.topic);
208
- if (!entry) throw new Error(`No packaged AHEAD reference matches ${params.topic}`);
336
+ if (!entry) {
337
+ throw new Error(`No packaged AHEAD reference matches ${params.topic}`);
338
+ }
209
339
  return { reference: entry, content: await readReference(entry) };
210
340
  });
211
341
  },
@@ -214,7 +344,8 @@ export default function aheadExtension(pi: ExtensionAPI): void {
214
344
  pi.registerTool({
215
345
  name: "ahead_record_artifact",
216
346
  label: "Record AHEAD artifact",
217
- description: "Persist an AI-owned or shared artifact permitted by the active phase. Cannot record human-owned artifacts.",
347
+ description:
348
+ "Persist an AI-owned or shared artifact permitted by the active phase. Cannot record human-owned artifacts.",
218
349
  promptSnippet: "Record an AI-permitted artifact in the active AHEAD run.",
219
350
  parameters: RecordArtifactParams,
220
351
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -230,6 +361,13 @@ export default function aheadExtension(pi: ExtensionAPI): void {
230
361
  `AI cannot record ${params.kind} in phase ${state.phase.id}`,
231
362
  );
232
363
  }
364
+ if (artifact.kind === "ai-review") {
365
+ const snapshot = await collectReviewSnapshot(store.projectRoot);
366
+ const errors = validateAiReviewArtifact(params.content, snapshot.fingerprint);
367
+ if (errors.length) {
368
+ throw new AheadEngineError("invalid_ai_review", errors.join("; "));
369
+ }
370
+ }
233
371
  const path = store.artifactPath(run, state.phase.id, artifact.kind);
234
372
  const updated = engine.applyEvent(run, aiActor(ctx), {
235
373
  type: "artifact_recorded",
@@ -252,7 +390,8 @@ export default function aheadExtension(pi: ExtensionAPI): void {
252
390
  pi.registerTool({
253
391
  name: "ahead_request_transition",
254
392
  label: "Request AHEAD transition",
255
- description: "Report whether a human can advance the active AHEAD phase. This tool never accepts a gate or transitions state.",
393
+ description:
394
+ "Report whether a human can advance the active AHEAD phase. This tool never accepts a gate or transitions state.",
256
395
  parameters: EmptyParams,
257
396
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
258
397
  return toolResult(async () => {
@@ -272,10 +411,14 @@ export default function aheadExtension(pi: ExtensionAPI): void {
272
411
  pi.registerTool({
273
412
  name: "ahead_validate",
274
413
  label: "Validate AHEAD run",
275
- description: "Replay and validate the active AHEAD event log against the embedded workflow contract.",
414
+ description:
415
+ "Replay and validate the active AHEAD event log against the embedded workflow contract.",
276
416
  parameters: EmptyParams,
277
417
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
278
- return toolResult(async () => ({ valid: true, state: (await enginePromise).validateRun(await requireRun(ctx)) }));
418
+ return toolResult(async () => ({
419
+ valid: true,
420
+ state: (await enginePromise).validateRun(await requireRun(ctx)),
421
+ }));
279
422
  },
280
423
  });
281
424
 
@@ -286,7 +429,7 @@ export default function aheadExtension(pi: ExtensionAPI): void {
286
429
  if (run && !(await enginePromise).deriveState(run).closed && ctx.hasUI) {
287
430
  const state = (await enginePromise).deriveState(run);
288
431
  ctx.ui.notify(
289
- `AHEAD mode resumed · ${state.phase.title}. Human leads, AI assists. Run /ahead for the next guided action.`,
432
+ `AHEAD mode resumed · ${state.phase.title}. Continue in normal conversation; /ahead is available when you need the action menu.`,
290
433
  "info",
291
434
  );
292
435
  }
@@ -298,17 +441,22 @@ export default function aheadExtension(pi: ExtensionAPI): void {
298
441
 
299
442
  pi.on("before_agent_start", async (event, ctx) => {
300
443
  const run = await storeFor(ctx).loadCurrent();
301
- if (!run) return;
444
+ if (!run) {
445
+ return undefined;
446
+ }
302
447
  const engine = await enginePromise;
303
448
  const state = engine.deriveState(run);
304
- if (state.closed) return;
449
+ if (state.closed) {
450
+ return undefined;
451
+ }
305
452
  const workflow = engine.getWorkflow(run.workflow_id);
306
- const guidance = phaseGuide(state.phase.id);
453
+ const guidance = phaseGuide(run.workflow_id, state.phase.id);
307
454
  const action = nextAction(state, workflow);
308
- const phaseInstructions = await loadInstructions(state.phase.id);
455
+ const phaseInstructions = await loadInstructions(run.workflow_id, state.phase.id);
309
456
  const liveContext = [
310
457
  "# Live AHEAD run",
311
458
  `- Run: ${run.id} — ${run.title}`,
459
+ `- Workflow: ${workflow.title} (${workflow.id})`,
312
460
  `- Phase: ${state.phase.id} visit ${state.phase.visit}`,
313
461
  `- Gate accepted: ${state.gate.accepted}`,
314
462
  `- Current blockers: ${state.blockers.length ? state.blockers.join("; ") : "none"}`,
@@ -325,15 +473,22 @@ export default function aheadExtension(pi: ExtensionAPI): void {
325
473
  "- Treat AI review findings as hypotheses. Independent human review remains required for lasting engineering changes.",
326
474
  "- Humans may ask questions at any phase. During implementation, help them understand or solve the problem without taking over; if their first attempt or current model is missing, ask for it.",
327
475
  "- When AHEAD policy or rationale is unclear, use ahead_get_reference to retrieve only the applicable packaged Markdown.",
476
+ "- Use ahead_get_recommended_skills only when an optional reviewed skill could materially help. Never install one without the human's explicit choice; AHEAD remains authoritative.",
328
477
  ].join("\n");
329
478
  return { systemPrompt: `${event.systemPrompt}\n\n${phaseInstructions}\n\n${liveContext}\n` };
330
479
  });
331
480
 
332
481
  pi.on("tool_call", async (event, ctx) => {
333
- if (event.toolName.startsWith("ahead_")) return;
482
+ if (event.toolName.startsWith("ahead_")) {
483
+ return undefined;
484
+ }
334
485
  const run = await storeFor(ctx).loadCurrent();
335
- if (!run) return;
336
- if ((await enginePromise).deriveState(run).closed) return;
486
+ if (!run) {
487
+ return undefined;
488
+ }
489
+ if ((await enginePromise).deriveState(run).closed) {
490
+ return undefined;
491
+ }
337
492
  const capability = toolCapabilities[event.toolName];
338
493
  if (!capability) {
339
494
  return {
@@ -343,10 +498,13 @@ export default function aheadExtension(pi: ExtensionAPI): void {
343
498
  }
344
499
  try {
345
500
  const decision = (await enginePromise).toolAllowed(run, capability);
346
- if (!decision.allowed) return { block: true, reason: `AHEAD: ${decision.reason}` };
501
+ if (!decision.allowed) {
502
+ return { block: true, reason: `AHEAD: ${decision.reason}` };
503
+ }
347
504
  } catch (error) {
348
505
  return { block: true, reason: `AHEAD state validation failed: ${errorMessage(error)}` };
349
506
  }
507
+ return undefined;
350
508
  });
351
509
  }
352
510
 
@@ -355,31 +513,59 @@ interface GuidedAction {
355
513
  run: () => Promise<void>;
356
514
  }
357
515
 
358
- async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionCommandContext): Promise<void> {
516
+ async function openAheadMode(
517
+ pi: ExtensionAPI,
518
+ args: string,
519
+ ctx: ExtensionCommandContext,
520
+ ): Promise<void> {
359
521
  const engine = await enginePromise;
360
522
  const store = storeFor(ctx);
361
523
  let run = await store.loadCurrent();
362
524
 
363
525
  if (run && engine.deriveState(run).closed) {
364
526
  if (!ctx.hasUI) {
365
- ctx.ui.notify("The current AHEAD run is complete. Start new work in an interactive Pi session.", "info");
527
+ ctx.ui.notify(
528
+ "The current AHEAD run is complete. Start new work in an interactive Pi session.",
529
+ "info",
530
+ );
366
531
  return;
367
532
  }
368
533
  const choice = await ctx.ui.select("AHEAD work is complete", [
369
- "Start a new Product Change",
534
+ "Start new AHEAD work",
370
535
  "View the completed run",
371
536
  ]);
372
537
  if (choice === "View the completed run") {
373
538
  ctx.ui.notify(formatState(engine.deriveState(run)), "info");
374
539
  return;
375
540
  }
376
- if (choice !== "Start a new Product Change") return;
541
+ if (choice !== "Start new AHEAD work") {
542
+ return;
543
+ }
377
544
  run = undefined;
378
545
  }
379
546
 
547
+ if (!run) {
548
+ if (!args.trim() && ctx.hasUI) {
549
+ const saved = await loadResumableRuns(ctx);
550
+ if (saved.length > 0) {
551
+ const choice = await ctx.ui.select("No active AHEAD run", [
552
+ "Resume explicitly saved AHEAD work",
553
+ "Start new AHEAD work",
554
+ ]);
555
+ if (choice === "Resume explicitly saved AHEAD work") {
556
+ run = await resumeSavedRun(ctx, "", saved);
557
+ } else if (choice !== "Start new AHEAD work") {
558
+ return;
559
+ }
560
+ }
561
+ }
562
+ }
563
+
380
564
  if (!run) {
381
565
  run = await startRun(ctx, args);
382
- if (!run) return;
566
+ if (!run) {
567
+ return;
568
+ }
383
569
  }
384
570
 
385
571
  await refreshUi(ctx, run);
@@ -390,43 +576,60 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
390
576
 
391
577
  const state = engine.deriveState(run);
392
578
  const workflow = engine.getWorkflow(run.workflow_id);
393
- const guidance = phaseGuide(state.phase.id);
579
+ const guidance = phaseGuide(run.workflow_id, state.phase.id);
394
580
  const action = nextAction(state, workflow);
395
581
  const actions: GuidedAction[] = [];
396
- const missingRequired = state.artifacts.filter((artifact) => artifact.required && !artifact.present);
582
+ const missingRequired = state.artifacts.filter(
583
+ (artifact) => artifact.required && !artifact.present,
584
+ );
397
585
  if (action.artifactKind) {
586
+ const actionArtifact = state.artifacts.find(
587
+ (artifact) => artifact.kind === action.artifactKind,
588
+ );
398
589
  actions.push({
399
590
  label: action.label,
400
- run: action.actor === "ai"
401
- ? async () => requestAiAssistance(pi, state, action.artifactKind)
402
- : async () => recordHumanArtifact(ctx, action.artifactKind ?? ""),
591
+ run:
592
+ action.actor === "ai"
593
+ ? state.phase.id === "ai-review"
594
+ ? async () => openReviewWorkbench(pi, ctx)
595
+ : async () => requestAiAssistance(pi, state, action.artifactKind)
596
+ : async () => recordHumanArtifact(ctx, action.artifactKind ?? ""),
403
597
  });
598
+ if (action.actor === "ai" && actionArtifact?.actor === "any") {
599
+ actions.push({
600
+ label: `Write ${actionArtifact.title} yourself`,
601
+ run: async () => recordHumanArtifact(ctx, actionArtifact.kind),
602
+ });
603
+ }
404
604
  }
405
605
 
406
606
  if (action.optional) {
407
- const nextHumanArtifact = missingRequired.find((artifact) => artifact.actor === "human");
607
+ const nextHumanArtifact = missingRequired.find((artifact) => artifact.actor !== "ai");
408
608
  if (nextHumanArtifact) {
409
609
  actions.push({
410
610
  label: `Continue without optional AI challenge · Write ${nextHumanArtifact.title}`,
411
611
  run: async () => recordHumanArtifact(ctx, nextHumanArtifact.kind),
412
612
  });
613
+ } else if (missingRequired.length === 0) {
614
+ actions.push({
615
+ label: `Continue without optional AI contribution · Accept ${state.gate.title}`,
616
+ run: async () => acceptAndContinue(ctx),
617
+ });
413
618
  }
414
619
  }
415
620
 
416
621
  if (missingRequired.length === 0 && !action.artifactKind) {
417
622
  actions.push({
418
- label: state.gate.accepted
419
- ? action.label
420
- : `Accept and continue · ${state.gate.title}`,
623
+ label: state.gate.accepted ? action.label : `Accept and continue · ${state.gate.title}`,
421
624
  run: async () => acceptAndContinue(ctx),
422
625
  });
423
626
  }
424
627
 
425
628
  if (
426
- state.allowed_ai_capabilities.length > 0
427
- && action.actor !== "ai"
428
- && !missingRequired.some((artifact) => artifact.actor === "ai")
429
- && state.phase.id !== "implement"
629
+ state.allowed_ai_capabilities.length > 0 &&
630
+ action.actor !== "ai" &&
631
+ !missingRequired.some((artifact) => artifact.actor === "ai") &&
632
+ state.phase.id !== "implement"
430
633
  ) {
431
634
  actions.push({
432
635
  label: `Ask AI to assist · ${state.phase.title}`,
@@ -441,6 +644,13 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
441
644
  });
442
645
  }
443
646
 
647
+ if (state.phase.id === "ai-review" || state.phase.id === "human-review") {
648
+ actions.push({
649
+ label: "Open the changeset review workbench",
650
+ run: async () => openReviewWorkbench(pi, ctx),
651
+ });
652
+ }
653
+
444
654
  if (state.return_targets.length > 0) {
445
655
  actions.push({
446
656
  label: "Return to an earlier phase",
@@ -453,6 +663,11 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
453
663
  run: async () => showAheadGuide(ctx, ""),
454
664
  });
455
665
 
666
+ actions.push({
667
+ label: "Inspect optional skills reviewed for this phase",
668
+ run: async () => showRecommendedSkills(ctx),
669
+ });
670
+
456
671
  actions.push({
457
672
  label: "Explain this phase and its expectations",
458
673
  run: async () => {
@@ -469,12 +684,208 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
469
684
  },
470
685
  });
471
686
 
687
+ actions.push({
688
+ label: "Stop AHEAD mode",
689
+ run: async () => stopAheadMode(ctx),
690
+ });
691
+
472
692
  const selected = await ctx.ui.select(
473
693
  `AHEAD mode · ${state.phase.title}\nNext (${action.actor === "human" ? "you" : "AI"}): ${action.label}`,
474
694
  actions.map((candidate) => candidate.label),
475
695
  );
476
696
  const chosen = actions.find((candidate) => candidate.label === selected);
477
- if (chosen) await chosen.run();
697
+ if (chosen) {
698
+ await chosen.run();
699
+ }
700
+ }
701
+
702
+ async function stopAheadMode(ctx: ExtensionCommandContext): Promise<void> {
703
+ if (!ctx.hasUI) {
704
+ throw new Error("/ahead-stop requires interactive or RPC UI support");
705
+ }
706
+ const store = storeFor(ctx);
707
+ const run = await requireRun(ctx);
708
+ const state = (await enginePromise).deriveState(run);
709
+ if (state.closed) {
710
+ ctx.ui.notify("This AHEAD run is already complete.", "info");
711
+ return;
712
+ }
713
+
714
+ const discard = "Stop and discard the unfinished AHEAD record";
715
+ const save = "Stop and save the unfinished run for later";
716
+ const selected = await ctx.ui.select("Stop AHEAD mode", [discard, save]);
717
+ if (selected === discard) {
718
+ const confirmed = await ctx.ui.confirm(
719
+ "Discard this unfinished AHEAD record?",
720
+ [
721
+ `${run.title} · ${state.phase.title}`,
722
+ "",
723
+ "This removes only this run's .ahead workflow state and artifacts.",
724
+ "It does not delete, reset, or revert source code or other repository changes.",
725
+ ].join("\n"),
726
+ );
727
+ if (!confirmed) {
728
+ return;
729
+ }
730
+ await store.discardCurrent(run.id);
731
+ await refreshUi(ctx);
732
+ ctx.ui.notify(
733
+ "AHEAD mode stopped and its unfinished workflow record was discarded. Repository changes were left untouched.",
734
+ "info",
735
+ );
736
+ } else if (selected === save) {
737
+ await store.saveCurrentForResume(run.id);
738
+ await refreshUi(ctx);
739
+ ctx.ui.notify(
740
+ `AHEAD mode stopped and run ${run.id} was saved. Resume it with /ahead-resume ${run.id}.`,
741
+ "info",
742
+ );
743
+ }
744
+ }
745
+
746
+ async function resumeSavedRun(
747
+ ctx: ExtensionCommandContext,
748
+ requestedRunId: string,
749
+ supplied?: Run[],
750
+ ): Promise<Run | undefined> {
751
+ const store = storeFor(ctx);
752
+ const current = await store.loadCurrent();
753
+ if (current && !(await enginePromise).deriveState(current).closed) {
754
+ throw new AheadEngineError(
755
+ "active_run_exists",
756
+ `run ${current.id} is still active; stop it before resuming another run`,
757
+ );
758
+ }
759
+
760
+ const saved = supplied ?? (await loadResumableRuns(ctx));
761
+ if (saved.length === 0) {
762
+ ctx.ui.notify("No saved unfinished AHEAD runs are available.", "info");
763
+ return undefined;
764
+ }
765
+
766
+ let selected = requestedRunId
767
+ ? saved.find((candidate) => candidate.id === requestedRunId)
768
+ : undefined;
769
+ if (requestedRunId && !selected) {
770
+ throw new AheadEngineError(
771
+ "saved_run_not_found",
772
+ `saved unfinished run ${requestedRunId} was not found`,
773
+ );
774
+ }
775
+ if (!selected) {
776
+ if (!ctx.hasUI) {
777
+ throw new Error("/ahead-resume requires a run id without interactive UI");
778
+ }
779
+ const options = saved.map(savedRunOption);
780
+ const choice = await ctx.ui.select("Resume saved AHEAD work", options);
781
+ selected = saved.find((candidate) => savedRunOption(candidate) === choice);
782
+ }
783
+ if (!selected) {
784
+ return undefined;
785
+ }
786
+
787
+ const resumed = await store.resume(selected.id);
788
+ const state = (await enginePromise).deriveState(resumed);
789
+ await refreshUi(ctx, resumed);
790
+ ctx.ui.notify(
791
+ `AHEAD mode resumed · ${state.phase.title}. Existing artifacts and unmet gates were preserved.`,
792
+ "info",
793
+ );
794
+ return resumed;
795
+ }
796
+
797
+ async function loadResumableRuns(ctx: ExtensionContext): Promise<Run[]> {
798
+ const store = storeFor(ctx);
799
+ const engine = await enginePromise;
800
+ const resumable: Run[] = [];
801
+ const invalid: string[] = [];
802
+ for (const runId of await store.listRunIds()) {
803
+ try {
804
+ const run = await store.load(runId);
805
+ if (!engine.deriveState(run).closed) {
806
+ resumable.push(run);
807
+ }
808
+ } catch {
809
+ invalid.push(runId);
810
+ }
811
+ }
812
+ if (invalid.length > 0 && ctx.hasUI) {
813
+ ctx.ui.notify(`Ignored invalid saved AHEAD runs: ${invalid.join(", ")}`, "warning");
814
+ }
815
+ return resumable;
816
+ }
817
+
818
+ function savedRunOption(run: Run): string {
819
+ return `${run.title} · ${run.workflow_id} · ${run.id}`;
820
+ }
821
+
822
+ async function showRecommendedSkills(ctx: ExtensionCommandContext): Promise<void> {
823
+ if (!ctx.hasUI) {
824
+ throw new Error("Inspecting recommended skills requires interactive or RPC UI support");
825
+ }
826
+ const catalog = await loadRecommendedSkills();
827
+ const run = await storeFor(ctx).loadCurrent();
828
+ const state = run ? (await enginePromise).deriveState(run) : undefined;
829
+ const phaseId = state && !state.closed ? state.phase.id : undefined;
830
+ const workflowId = state && !state.closed ? state.workflow_id : undefined;
831
+ const relevant = relevantRecommendedSkills(catalog, workflowId, phaseId);
832
+ await showReferenceViewer(
833
+ ctx,
834
+ relevant.length ? `AHEAD skills · ${state?.phase.title}` : "AHEAD recommended skills",
835
+ recommendedSkillsMarkdown(catalog, relevant.length ? relevant : catalog.skills),
836
+ );
837
+ }
838
+
839
+ async function openReviewWorkbench(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
840
+ if (!ctx.hasUI) {
841
+ throw new Error("The review workbench requires interactive or RPC UI support");
842
+ }
843
+ const store = storeFor(ctx);
844
+ const run = await requireRun(ctx);
845
+ const state = (await enginePromise).deriveState(run);
846
+ const snapshot = await collectReviewSnapshot(store.projectRoot);
847
+ const aiReview = state.artifacts.find((artifact) => artifact.kind === "ai-review");
848
+ const disposition = state.artifacts.find((artifact) => artifact.kind === "review-disposition");
849
+ const options = ["View snapshot and changed files", "View exact terminal diff"];
850
+ if (snapshot.changed_files.length) {
851
+ options.push("Open a changed file in the configured editor");
852
+ }
853
+ if (state.phase.id === "ai-review" && !aiReview?.present) {
854
+ options.push("Request AI review of this exact snapshot");
855
+ }
856
+ if (state.phase.id === "ai-review" && aiReview?.present && !disposition?.present) {
857
+ options.push("Disposition the AI findings as the implementing human");
858
+ }
859
+ if (state.phase.id === "human-review") {
860
+ options.push("Record the independent human review of this snapshot");
861
+ }
862
+ const selected = await ctx.ui.select(
863
+ `AHEAD review · ${state.phase.title} · ${snapshot.changed_files.length} changed files`,
864
+ options,
865
+ );
866
+ if (selected === "View snapshot and changed files") {
867
+ await showReferenceViewer(ctx, "AHEAD exact review snapshot", reviewSnapshotMarkdown(snapshot));
868
+ } else if (selected === "View exact terminal diff") {
869
+ await showReferenceViewer(
870
+ ctx,
871
+ `AHEAD diff · ${snapshot.fingerprint.slice(0, 12)}`,
872
+ `${reviewSnapshotMarkdown(snapshot)}\n\n## Diff\n\n\`\`\`diff\n${snapshot.diff || "No tracked diff."}\n\`\`\``,
873
+ );
874
+ } else if (selected === "Open a changed file in the configured editor") {
875
+ const path = await ctx.ui.select("Open changed file", snapshot.changed_files);
876
+ if (path && !openInConfiguredEditor(store.projectRoot, { path })) {
877
+ ctx.ui.notify(
878
+ `No supported editor was detected. Open ${path} from the repository, or set AHEAD_EDITOR=vscode.`,
879
+ "info",
880
+ );
881
+ }
882
+ } else if (selected === "Request AI review of this exact snapshot") {
883
+ pi.sendUserMessage(reviewRequest(snapshot));
884
+ } else if (selected === "Disposition the AI findings as the implementing human") {
885
+ await recordHumanArtifact(ctx, "review-disposition");
886
+ } else if (selected === "Record the independent human review of this snapshot") {
887
+ await recordHumanArtifact(ctx, "human-review");
888
+ }
478
889
  }
479
890
 
480
891
  async function askImplementationQuestion(
@@ -482,7 +893,9 @@ async function askImplementationQuestion(
482
893
  ctx: ExtensionCommandContext,
483
894
  state: RunState,
484
895
  ): Promise<void> {
485
- if (!ctx.hasUI) throw new Error("Implementation coaching requires interactive or RPC UI support");
896
+ if (!ctx.hasUI) {
897
+ throw new Error("Implementation coaching requires interactive or RPC UI support");
898
+ }
486
899
  const question = await ctx.ui.editor(
487
900
  "AHEAD implementation help · human first",
488
901
  [
@@ -501,50 +914,72 @@ async function askImplementationQuestion(
501
914
  "",
502
915
  ].join("\n"),
503
916
  );
504
- if (!question?.trim()) return;
505
- pi.sendUserMessage([
506
- `AHEAD mode: help me with this ${state.phase.title} question while I remain the implementer.`,
507
- "Use my current model and first attempt below. Help me understand or solve the problem with questions, explanation, evidence, hints, and bounded next steps.",
508
- "Do not convert this question into autonomous implementation or author my human-owned records. If I later request a bounded mechanical edit, explain it so I can inspect and own it.",
509
- "",
510
- question.trim(),
511
- ].join("\n"));
917
+ if (!question?.trim()) {
918
+ return;
919
+ }
920
+ pi.sendUserMessage(
921
+ [
922
+ `AHEAD mode: help me with this ${state.phase.title} question while I remain the implementer.`,
923
+ "Use my current model and first attempt below. Help me understand or solve the problem with questions, explanation, evidence, hints, and bounded next steps.",
924
+ "Do not convert this question into autonomous implementation or author my human-owned records. If I later request a bounded mechanical edit, explain it so I can inspect and own it.",
925
+ "",
926
+ question.trim(),
927
+ ].join("\n"),
928
+ );
512
929
  }
513
930
 
514
931
  async function showAheadGuide(ctx: ExtensionCommandContext, requestedTopic: string): Promise<void> {
515
- if (!ctx.hasUI) throw new Error("Reading AHEAD framework guidance requires interactive or RPC UI support");
932
+ if (!ctx.hasUI) {
933
+ throw new Error("Reading AHEAD framework guidance requires interactive or RPC UI support");
934
+ }
516
935
  const run = await storeFor(ctx).loadCurrent();
517
- const phase = run && !(await enginePromise).deriveState(run).closed
518
- ? (await enginePromise).deriveState(run).phase.id
519
- : undefined;
936
+ const state = run ? (await enginePromise).deriveState(run) : undefined;
937
+ const phase = state && !state.closed ? state.phase.id : undefined;
938
+ const workflowId = state && !state.closed ? state.workflow_id : undefined;
520
939
  const index = await loadReferenceIndex();
521
- let entry = requestedTopic.trim() && requestedTopic.trim().toLowerCase() !== "all"
522
- ? await findReference(requestedTopic)
523
- : undefined;
940
+ let entry =
941
+ requestedTopic.trim() && requestedTopic.trim().toLowerCase() !== "all"
942
+ ? await findReference(requestedTopic)
943
+ : undefined;
524
944
 
525
945
  if (requestedTopic.trim() && requestedTopic.trim().toLowerCase() !== "all" && !entry) {
526
946
  throw new Error(`No packaged AHEAD reference matches ${requestedTopic.trim()}`);
527
947
  }
528
948
 
529
949
  if (!entry) {
530
- const recommended = requestedTopic.trim().toLowerCase() === "all"
531
- ? index.references
532
- : await relevantReferences(phase);
533
- const browseAll = "Browse all packaged AHEAD Markdown";
950
+ const recommended =
951
+ requestedTopic.trim().toLowerCase() === "all"
952
+ ? index.references
953
+ : await relevantReferences(workflowId, phase);
954
+ const browseAll = "Browse all practitioner and evidence Markdown";
534
955
  const selected = await ctx.ui.select(
535
956
  phase ? `AHEAD guidance · ${phase}` : "AHEAD framework guidance",
536
- [...recommended.map((candidate) => candidate.title), ...(recommended.length < index.references.length ? [browseAll] : [])],
957
+ [
958
+ ...recommended.map(referenceOption),
959
+ ...(recommended.length < index.references.length ? [browseAll] : []),
960
+ ],
537
961
  );
538
- if (!selected) return;
539
- if (selected === browseAll) return showAheadGuide(ctx, "all");
540
- entry = recommended.find((candidate) => candidate.title === selected);
962
+ if (!selected) {
963
+ return;
964
+ }
965
+ if (selected === browseAll) {
966
+ return showAheadGuide(ctx, "all");
967
+ }
968
+ entry = recommended.find((candidate) => referenceOption(candidate) === selected);
969
+ }
970
+ if (!entry) {
971
+ return;
541
972
  }
542
- if (!entry) return;
543
973
 
544
974
  await showReferenceViewer(ctx, `AHEAD reference · ${entry.title}`, await readReference(entry));
545
975
  }
546
976
 
547
- async function startRun(ctx: ExtensionCommandContext, requestedTitle: string): Promise<Run | undefined> {
977
+ function referenceOption(entry: ReferenceEntry): string {
978
+ const classification = entry.audience === "evidence" ? "evidence" : entry.authority;
979
+ return `${classification} · ${entry.title}`;
980
+ }
981
+
982
+ async function startRun(ctx: ExtensionCommandContext, request: string): Promise<Run | undefined> {
548
983
  const engine = await enginePromise;
549
984
  const store = storeFor(ctx);
550
985
  const current = await store.loadCurrent();
@@ -555,9 +990,45 @@ async function startRun(ctx: ExtensionCommandContext, requestedTitle: string): P
555
990
  );
556
991
  }
557
992
 
558
- const title = requestedTitle.trim()
559
- || (ctx.hasUI ? await ctx.ui.input("Enter AHEAD mode · Product Change", "What work are you doing?") : undefined);
560
- if (!title?.trim()) return undefined;
993
+ const workflows = engine.listWorkflows();
994
+ const parsed = parseStartRequest(
995
+ request,
996
+ workflows.map((workflow) => workflow.id),
997
+ );
998
+ let workflow = parsed.workflowId
999
+ ? workflows.find((candidate) => candidate.id === parsed.workflowId)
1000
+ : undefined;
1001
+ if (parsed.workflowId && !workflow) {
1002
+ throw new AheadEngineError(
1003
+ "unknown_workflow",
1004
+ `unknown workflow ${parsed.workflowId}; choose one of: ${workflows.map((candidate) => candidate.id).join(", ")}`,
1005
+ );
1006
+ }
1007
+ if (!workflow && ctx.hasUI) {
1008
+ const selected = await ctx.ui.select(
1009
+ "Choose the AHEAD workflow that fits this work",
1010
+ workflows.map((candidate) => candidate.title),
1011
+ );
1012
+ workflow = workflows.find((candidate) => candidate.title === selected);
1013
+ if (!workflow) {
1014
+ return undefined;
1015
+ }
1016
+ }
1017
+ if (!workflow) {
1018
+ throw new AheadEngineError(
1019
+ "workflow_required",
1020
+ `choose a workflow explicitly: ${workflows.map((candidate) => candidate.id).join(", ")}. Noninteractive usage: /ahead-start <workflow-id> :: <title>`,
1021
+ );
1022
+ }
1023
+
1024
+ const title =
1025
+ parsed.title ||
1026
+ (ctx.hasUI
1027
+ ? await ctx.ui.input(`Enter AHEAD mode · ${workflow.title}`, "What work are you doing?")
1028
+ : undefined);
1029
+ if (!title?.trim()) {
1030
+ return undefined;
1031
+ }
561
1032
 
562
1033
  const owner = humanActor(store.projectRoot);
563
1034
  const run = engine.createRun({
@@ -565,35 +1036,43 @@ async function startRun(ctx: ExtensionCommandContext, requestedTitle: string): P
565
1036
  title: title.trim(),
566
1037
  owner,
567
1038
  timestamp: new Date().toISOString(),
568
- workflow_id: "product-change",
1039
+ workflow_id: workflow.id,
569
1040
  });
570
1041
  await store.save(run);
571
1042
  await refreshUi(ctx, run);
572
1043
  ctx.ui.notify(
573
1044
  [
574
- `AHEAD mode started · ${run.title}`,
1045
+ `AHEAD mode started · ${workflow.title} · ${run.title}`,
575
1046
  "Human leads · AI assists",
576
- "This run remains active in the repository until an accountable human closes the outcome.",
577
- "Use /ahead for the next guided action; use normal conversation to think and work with AI.",
1047
+ "This run remains active until an accountable human closes the outcome or uses /ahead-stop.",
1048
+ "Continue in normal conversation. /ahead is available when you need the action menu.",
578
1049
  ].join("\n"),
579
1050
  "info",
580
1051
  );
581
1052
  return run;
582
1053
  }
583
1054
 
584
- async function recordHumanArtifact(ctx: ExtensionCommandContext, requestedKind: string): Promise<void> {
585
- if (!ctx.hasUI) throw new Error("Recording a human artifact requires interactive or RPC UI support");
1055
+ async function recordHumanArtifact(
1056
+ ctx: ExtensionCommandContext,
1057
+ requestedKind: string,
1058
+ ): Promise<void> {
1059
+ if (!ctx.hasUI) {
1060
+ throw new Error("Recording a human artifact requires interactive or RPC UI support");
1061
+ }
586
1062
  const engine = await enginePromise;
587
1063
  const store = storeFor(ctx);
588
1064
  const run = await requireRun(ctx);
589
1065
  const state = engine.deriveState(run);
590
- const allowed = state.artifacts.filter((artifact) => artifact.actor !== "ai" && !artifact.present);
1066
+ const allowed = state.artifacts.filter(
1067
+ (artifact) => artifact.actor !== "ai" && !artifact.present,
1068
+ );
591
1069
  let kind = requestedKind.trim();
592
1070
  if (!kind) {
593
- kind = (await ctx.ui.select(
594
- `AHEAD mode · Write for ${state.phase.title}`,
595
- allowed.map((artifact) => artifact.title),
596
- )) ?? "";
1071
+ kind =
1072
+ (await ctx.ui.select(
1073
+ `AHEAD mode · Write for ${state.phase.title}`,
1074
+ allowed.map((artifact) => artifact.title),
1075
+ )) ?? "";
597
1076
  kind = allowed.find((artifact) => artifact.title === kind)?.kind ?? kind;
598
1077
  }
599
1078
  const artifact = allowed.find((candidate) => candidate.kind === kind);
@@ -604,11 +1083,15 @@ async function recordHumanArtifact(ctx: ExtensionCommandContext, requestedKind:
604
1083
  );
605
1084
  }
606
1085
 
1086
+ const template = await humanArtifactTemplate(store, state, run, artifact.kind, artifact.title);
607
1087
  const content = await ctx.ui.editor(
608
1088
  `AHEAD mode · ${artifact.title} · write in your own words`,
609
- buildArtifactTemplate(run, state, artifact.kind, artifact.title),
1089
+ template,
610
1090
  );
611
- if (!content?.trim()) return;
1091
+ if (!content?.trim()) {
1092
+ return;
1093
+ }
1094
+ await validateHumanReviewArtifact(store, state, artifact.kind, content);
612
1095
  const path = store.artifactPath(run, state.phase.id, artifact.kind);
613
1096
  const action: EventAction = {
614
1097
  type: "artifact_recorded",
@@ -621,19 +1104,86 @@ async function recordHumanArtifact(ctx: ExtensionCommandContext, requestedKind:
621
1104
  await store.save(updated);
622
1105
  await refreshUi(ctx, updated);
623
1106
  ctx.ui.notify(
624
- `Saved ${artifact.title}. AHEAD mode remains active; continue the conversation or run /ahead for the next guided action.`,
1107
+ `Saved ${artifact.title}. AHEAD mode remains active; continue the conversation.`,
625
1108
  "info",
626
1109
  );
627
1110
  }
628
1111
 
1112
+ async function humanArtifactTemplate(
1113
+ store: RunStore,
1114
+ state: RunState,
1115
+ run: Run,
1116
+ kind: string,
1117
+ title: string,
1118
+ ): Promise<string> {
1119
+ if (kind === "review-disposition") {
1120
+ const reviewArtifact = state.artifacts.find((artifact) => artifact.kind === "ai-review");
1121
+ if (!reviewArtifact?.path) {
1122
+ throw new AheadEngineError(
1123
+ "ai_review_missing",
1124
+ "the AI review must be recorded before human disposition",
1125
+ );
1126
+ }
1127
+ const aiReview = await store.readArtifact(reviewArtifact.path);
1128
+ const snapshot = await collectReviewSnapshot(store.projectRoot);
1129
+ return reviewDispositionTemplate(snapshot, extractFindingIds(aiReview));
1130
+ }
1131
+ if (kind === "human-review") {
1132
+ const snapshot = await collectReviewSnapshot(store.projectRoot);
1133
+ return `${buildArtifactTemplate(run, state, kind, title)}\n\nAHEAD-Review-Snapshot: ${snapshot.fingerprint}\n`;
1134
+ }
1135
+ return buildArtifactTemplate(run, state, kind, title);
1136
+ }
1137
+
1138
+ async function validateHumanReviewArtifact(
1139
+ store: RunStore,
1140
+ state: RunState,
1141
+ kind: string,
1142
+ content: string,
1143
+ ): Promise<void> {
1144
+ if (kind !== "review-disposition" && kind !== "human-review") {
1145
+ return;
1146
+ }
1147
+ const snapshot = await collectReviewSnapshot(store.projectRoot);
1148
+ if (kind === "human-review") {
1149
+ if (extractReviewFingerprint(content) !== snapshot.fingerprint) {
1150
+ throw new AheadEngineError(
1151
+ "review_snapshot_stale",
1152
+ "the human review must identify the exact current AHEAD review snapshot",
1153
+ );
1154
+ }
1155
+ return;
1156
+ }
1157
+ const reviewArtifact = state.artifacts.find((artifact) => artifact.kind === "ai-review");
1158
+ if (!reviewArtifact?.path) {
1159
+ throw new AheadEngineError("ai_review_missing", "the AI review artifact is missing");
1160
+ }
1161
+ const aiReview = await store.readArtifact(reviewArtifact.path);
1162
+ const aiFingerprint = extractReviewFingerprint(aiReview);
1163
+ if (!aiFingerprint || aiFingerprint !== snapshot.fingerprint) {
1164
+ throw new AheadEngineError(
1165
+ "review_snapshot_stale",
1166
+ "the changeset changed after AI review; return to implementation and review the new snapshot",
1167
+ );
1168
+ }
1169
+ const errors = validateReviewDisposition(
1170
+ content,
1171
+ snapshot.fingerprint,
1172
+ extractFindingIds(aiReview),
1173
+ );
1174
+ if (errors.length) {
1175
+ throw new AheadEngineError("review_disposition_incomplete", errors.join("; "));
1176
+ }
1177
+ }
1178
+
629
1179
  function requestAiAssistance(pi: ExtensionAPI, state: RunState, requiredKind?: string): void {
630
- const guidance = phaseGuide(state.phase.id);
1180
+ const guidance = phaseGuide(state.workflow_id, state.phase.id);
631
1181
  const artifact = requiredKind
632
1182
  ? state.artifacts.find((candidate) => candidate.kind === requiredKind)
633
1183
  : undefined;
634
1184
  const request = artifact
635
1185
  ? [
636
- `AHEAD mode: perform the ${artifact.required ? "required" : "recommended"} ${state.phase.title} work for the exact current evidence and changeset.`,
1186
+ `AHEAD mode: perform the ${artifact.required ? "required" : "recommended"} ${state.phase.title} work for the exact current run and evidence.`,
637
1187
  `Produce ${artifact.title}.`,
638
1188
  `Follow the active human/AI boundary: ${guidance.ai}`,
639
1189
  `Use ahead_get_context first, then record the completed artifact as ${artifact.kind} with ahead_record_artifact.`,
@@ -649,7 +1199,9 @@ function requestAiAssistance(pi: ExtensionAPI, state: RunState, requiredKind?: s
649
1199
  }
650
1200
 
651
1201
  async function acceptAndContinue(ctx: ExtensionCommandContext): Promise<void> {
652
- if (!ctx.hasUI) throw new Error("Accepting an AHEAD gate requires interactive or RPC UI support");
1202
+ if (!ctx.hasUI) {
1203
+ throw new Error("Accepting an AHEAD gate requires interactive or RPC UI support");
1204
+ }
653
1205
  const engine = await enginePromise;
654
1206
  const store = storeFor(ctx);
655
1207
  const run = await requireRun(ctx);
@@ -663,7 +1215,8 @@ async function acceptAndContinue(ctx: ExtensionCommandContext): Promise<void> {
663
1215
  }
664
1216
 
665
1217
  const destination = state.phase.next
666
- ? engine.getWorkflow(run.workflow_id).phases.find((phase) => phase.id === state.phase.next)?.title ?? state.phase.next
1218
+ ? (engine.getWorkflow(run.workflow_id).phases.find((phase) => phase.id === state.phase.next)
1219
+ ?.title ?? state.phase.next)
667
1220
  : "close this AHEAD run";
668
1221
  const confirmed = await ctx.ui.confirm(
669
1222
  `Accept and continue from ${state.phase.title}?`,
@@ -676,7 +1229,9 @@ async function acceptAndContinue(ctx: ExtensionCommandContext): Promise<void> {
676
1229
  "This records human acceptance. AI cannot perform this action.",
677
1230
  ].join("\n"),
678
1231
  );
679
- if (!confirmed) return;
1232
+ if (!confirmed) {
1233
+ return;
1234
+ }
680
1235
 
681
1236
  const actor = humanActor(store.projectRoot);
682
1237
  let updated = run;
@@ -689,7 +1244,10 @@ async function acceptAndContinue(ctx: ExtensionCommandContext): Promise<void> {
689
1244
  state = engine.deriveState(updated);
690
1245
  }
691
1246
  if (!state.can_advance) {
692
- throw new AheadEngineError("cannot_advance", state.blockers.join("; ") || "the phase cannot advance");
1247
+ throw new AheadEngineError(
1248
+ "cannot_advance",
1249
+ state.blockers.join("; ") || "the phase cannot advance",
1250
+ );
693
1251
  }
694
1252
 
695
1253
  const action: EventAction = state.phase.next
@@ -706,33 +1264,44 @@ async function acceptAndContinue(ctx: ExtensionCommandContext): Promise<void> {
706
1264
 
707
1265
  const nextState = engine.deriveState(updated);
708
1266
  if (nextState.closed) {
709
- ctx.ui.notify("AHEAD work complete. The accountable human accepted the outcome and closed the run.", "info");
1267
+ ctx.ui.notify(
1268
+ "AHEAD work complete. The accountable human accepted the outcome and closed the run.",
1269
+ "info",
1270
+ );
710
1271
  } else if (nextState.phase.id === "human-review") {
711
1272
  ctx.ui.notify(
712
1273
  [
713
1274
  "READY FOR INDEPENDENT HUMAN REVIEW",
714
1275
  "The AI review is recorded and its material findings were disposed by a human.",
715
1276
  "A draft branch may already exist, but a human must now request review or mark the PR ready.",
716
- "The independent reviewer opens this repository, runs /ahead, and records the review.",
1277
+ "The independent reviewer opens this repository; AHEAD resumes at Human Review and guides the review record.",
717
1278
  ].join("\n"),
718
1279
  "info",
719
1280
  );
720
1281
  } else {
721
1282
  ctx.ui.notify(
722
- `Continued to ${nextState.phase.title}. AHEAD mode remains active; run /ahead for the next guided action.`,
1283
+ `Continued to ${nextState.phase.title}. AHEAD mode remains active; continue the conversation.`,
723
1284
  "info",
724
1285
  );
725
1286
  }
726
1287
  }
727
1288
 
728
- async function returnToEarlierPhase(ctx: ExtensionCommandContext, requestedTarget: string): Promise<void> {
729
- if (!ctx.hasUI) throw new Error("Returning an AHEAD phase requires interactive or RPC UI support");
1289
+ async function returnToEarlierPhase(
1290
+ ctx: ExtensionCommandContext,
1291
+ requestedTarget: string,
1292
+ ): Promise<void> {
1293
+ if (!ctx.hasUI) {
1294
+ throw new Error("Returning an AHEAD phase requires interactive or RPC UI support");
1295
+ }
730
1296
  const engine = await enginePromise;
731
1297
  const store = storeFor(ctx);
732
1298
  const run = await requireRun(ctx);
733
1299
  const state = engine.deriveState(run);
734
1300
  if (!state.return_targets.length) {
735
- throw new AheadEngineError("no_return_target", `phase ${state.phase.id} has no return transition`);
1301
+ throw new AheadEngineError(
1302
+ "no_return_target",
1303
+ `phase ${state.phase.id} has no return transition`,
1304
+ );
736
1305
  }
737
1306
  const workflow = engine.getWorkflow(run.workflow_id);
738
1307
  const targetOptions = state.return_targets.map((target) => ({
@@ -756,12 +1325,16 @@ async function returnToEarlierPhase(ctx: ExtensionCommandContext, requestedTarge
756
1325
  const reason = await ctx.ui.editor(
757
1326
  `Why return to ${workflow.phases.find((phase) => phase.id === target)?.title ?? target}?`,
758
1327
  );
759
- if (!reason?.trim()) return;
1328
+ if (!reason?.trim()) {
1329
+ return;
1330
+ }
760
1331
  const confirmed = await ctx.ui.confirm(
761
1332
  `Return to ${target}?`,
762
1333
  "This opens a new phase visit. Earlier artifacts remain as history but cannot satisfy the reopened gate.",
763
1334
  );
764
- if (!confirmed) return;
1335
+ if (!confirmed) {
1336
+ return;
1337
+ }
765
1338
  const updated = engine.applyEvent(run, humanActor(store.projectRoot), {
766
1339
  type: "phase_transitioned",
767
1340
  from: state.phase.id,
@@ -771,7 +1344,10 @@ async function returnToEarlierPhase(ctx: ExtensionCommandContext, requestedTarge
771
1344
  });
772
1345
  await store.save(updated);
773
1346
  await refreshUi(ctx, updated);
774
- ctx.ui.notify(`Returned to ${target}. AHEAD mode remains active with fresh evidence and gate requirements.`, "warning");
1347
+ ctx.ui.notify(
1348
+ `Returned to ${target}. AHEAD mode remains active with fresh evidence and gate requirements.`,
1349
+ "warning",
1350
+ );
775
1351
  }
776
1352
 
777
1353
  function storeFor(ctx: ExtensionContext): RunStore {
@@ -780,7 +1356,9 @@ function storeFor(ctx: ExtensionContext): RunStore {
780
1356
 
781
1357
  async function requireRun(ctx: ExtensionContext): Promise<Run> {
782
1358
  const run = await storeFor(ctx).loadCurrent();
783
- if (!run) throw new AheadEngineError("no_active_run", "no active AHEAD run; use /ahead [title]");
1359
+ if (!run) {
1360
+ throw new AheadEngineError("no_active_run", "no active AHEAD run; use /ahead [title]");
1361
+ }
784
1362
  return run;
785
1363
  }
786
1364
 
@@ -797,6 +1375,9 @@ async function refreshUi(ctx: ExtensionContext, supplied?: Run): Promise<void> {
797
1375
  if (!run) {
798
1376
  ctx.ui.setStatus("ahead", undefined);
799
1377
  ctx.ui.setWidget("ahead", undefined);
1378
+ if (ctx.mode === "tui") {
1379
+ ctx.ui.setHeader(undefined);
1380
+ }
800
1381
  return;
801
1382
  }
802
1383
  const engine = await enginePromise;
@@ -810,24 +1391,97 @@ async function refreshUi(ctx: ExtensionContext, supplied?: Run): Promise<void> {
810
1391
  ? `AHEAD · complete · ${state.workflow_id}`
811
1392
  : `AHEAD · ${position.current}/${position.total} · ${state.phase.id} · ${action.actor} action`,
812
1393
  );
813
- ctx.ui.setWidget(
814
- "ahead",
815
- buildWidgetLines(run, state, workflow),
816
- { placement: "aboveEditor" },
817
- );
1394
+ if (state.closed) {
1395
+ ctx.ui.setWidget("ahead", undefined);
1396
+ if (ctx.mode === "tui") {
1397
+ ctx.ui.setHeader(undefined);
1398
+ }
1399
+ return;
1400
+ }
1401
+
1402
+ const lines = buildHeaderLines(run, state, workflow);
1403
+ if (ctx.mode === "tui") {
1404
+ ctx.ui.setWidget("ahead", undefined);
1405
+ ctx.ui.setHeader((_tui, theme) => aheadHeader(lines, theme));
1406
+ } else {
1407
+ ctx.ui.setWidget("ahead", lines, { placement: "aboveEditor" });
1408
+ }
818
1409
  }
819
1410
 
820
- async function loadInstructions(phase: string): Promise<string> {
821
- const cached = instructions.get(phase);
822
- if (cached) return cached;
823
- const content = await readFile(`${instructionDirectory}/${phase}.md`, "utf8");
824
- instructions.set(phase, content);
1411
+ function aheadHeader(lines: string[], theme: Theme) {
1412
+ return {
1413
+ render(width: number): string[] {
1414
+ const [heading = "AHEAD", ...fields] = lines;
1415
+ const styledHeading = `${theme.fg("accent", theme.bold("AHEAD"))}${theme.fg("muted", heading.slice("AHEAD".length))}`;
1416
+ const ruleWidth = Math.max(0, width - visibleWidth(styledHeading) - 1);
1417
+ const headingLine = `${styledHeading}${
1418
+ ruleWidth > 0 ? ` ${theme.fg("borderMuted", "─".repeat(ruleWidth))}` : ""
1419
+ }`;
1420
+
1421
+ return [
1422
+ truncateToWidth(headingLine, width),
1423
+ ...fields.map((field) => formatAheadHeaderField(field, width, theme)),
1424
+ ];
1425
+ },
1426
+ invalidate() {},
1427
+ };
1428
+ }
1429
+
1430
+ function formatAheadHeaderField(field: string, width: number, theme: Theme): string {
1431
+ const separator = field.indexOf(":");
1432
+ if (separator < 0) {
1433
+ return truncateToWidth(theme.fg("text", field), width);
1434
+ }
1435
+
1436
+ const label = field.slice(0, separator).toUpperCase().padEnd(8);
1437
+ let value = field.slice(separator + 1).trimStart();
1438
+ let styledValue = theme.fg("text", value);
1439
+ if (label.trim() === "NEXT") {
1440
+ const actor = value.startsWith("You →") ? "You →" : value.startsWith("AI →") ? "AI →" : "";
1441
+ if (actor) {
1442
+ value = value.slice(actor.length);
1443
+ styledValue = `${theme.fg(actor.startsWith("You") ? "success" : "accent", theme.bold(actor))}${theme.fg("text", value)}`;
1444
+ }
1445
+ }
1446
+
1447
+ return truncateToWidth(`${theme.fg("muted", theme.bold(label))} ${styledValue}`, width);
1448
+ }
1449
+
1450
+ async function loadInstructions(workflowId: string, phase: string): Promise<string> {
1451
+ const cacheKey = `${workflowId}/${phase}`;
1452
+ const cached = instructions.get(cacheKey);
1453
+ if (cached) {
1454
+ return cached;
1455
+ }
1456
+ const content = await readFile(`${instructionDirectory}/${cacheKey}.md`, "utf8");
1457
+ instructions.set(cacheKey, content);
825
1458
  return content;
826
1459
  }
827
1460
 
1461
+ function parseStartRequest(
1462
+ request: string,
1463
+ workflowIds: string[],
1464
+ ): { workflowId?: string; title: string } {
1465
+ const trimmed = request.trim();
1466
+ const separator = trimmed.indexOf("::");
1467
+ if (separator >= 0) {
1468
+ return {
1469
+ workflowId: trimmed.slice(0, separator).trim(),
1470
+ title: trimmed.slice(separator + 2).trim(),
1471
+ };
1472
+ }
1473
+ if (workflowIds.includes(trimmed)) {
1474
+ return { workflowId: trimmed, title: "" };
1475
+ }
1476
+ return { title: trimmed };
1477
+ }
1478
+
828
1479
  function formatState(state: RunState): string {
829
1480
  const artifacts = state.artifacts
830
- .map((artifact) => `${artifact.present ? "✓" : artifact.required ? "○" : "·"} ${artifact.kind} (${artifact.actor})`)
1481
+ .map(
1482
+ (artifact) =>
1483
+ `${artifact.present ? "✓" : artifact.required ? "○" : "·"} ${artifact.kind} (${artifact.actor})`,
1484
+ )
831
1485
  .join("\n");
832
1486
  return [
833
1487
  `${state.title} · ${state.workflow_id}@${state.workflow_version}`,
@@ -852,14 +1506,23 @@ async function command(ctx: ExtensionCommandContext, action: () => Promise<void>
852
1506
  async function toolResult(action: () => Promise<unknown>) {
853
1507
  try {
854
1508
  const result = await action();
855
- return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }], details: result };
1509
+ return {
1510
+ content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
1511
+ details: result,
1512
+ };
856
1513
  } catch (error) {
857
1514
  const message = errorMessage(error);
858
- return { content: [{ type: "text" as const, text: `AHEAD error: ${message}` }], details: { error: message }, isError: true };
1515
+ return {
1516
+ content: [{ type: "text" as const, text: `AHEAD error: ${message}` }],
1517
+ details: { error: message },
1518
+ isError: true,
1519
+ };
859
1520
  }
860
1521
  }
861
1522
 
862
1523
  function errorMessage(error: unknown): string {
863
- if (error instanceof AheadEngineError) return `${error.code}: ${error.message}`;
1524
+ if (error instanceof AheadEngineError) {
1525
+ return `${error.code}: ${error.message}`;
1526
+ }
864
1527
  return error instanceof Error ? error.message : String(error);
865
1528
  }