ahead-pi 0.5.0 → 0.7.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.
- package/README.md +37 -1
- package/dist/ahead_wasm.wasm +0 -0
- package/generated/decision/compare.md +2 -2
- package/generated/decision/criteria.md +2 -2
- package/generated/decision/decide.md +2 -2
- package/generated/decision/frame.md +2 -2
- package/generated/decision/manifest.json +2 -2
- package/generated/decision/options.md +2 -2
- package/generated/decision/publish.md +5 -5
- package/generated/decision/research.md +2 -2
- package/generated/internal-improvement/ai-audit.md +3 -3
- package/generated/internal-improvement/ai-review.md +4 -3
- package/generated/internal-improvement/baseline.md +2 -2
- package/generated/internal-improvement/decision.md +2 -2
- package/generated/internal-improvement/deploy.md +2 -2
- package/generated/internal-improvement/human-review.md +3 -3
- package/generated/internal-improvement/implement.md +5 -4
- package/generated/internal-improvement/invariants.md +2 -2
- package/generated/internal-improvement/manifest.json +2 -2
- package/generated/internal-improvement/options.md +2 -2
- package/generated/internal-improvement/outcome.md +3 -3
- package/generated/internal-improvement/plan.md +5 -4
- package/generated/internal-improvement/target.md +2 -2
- package/generated/internal-improvement/verify.md +2 -2
- package/generated/operational-stabilization/assess.md +2 -2
- package/generated/operational-stabilization/execute-observe.md +2 -2
- package/generated/operational-stabilization/manifest.json +2 -2
- package/generated/operational-stabilization/monitor.md +2 -2
- package/generated/operational-stabilization/outcome.md +5 -4
- package/generated/operational-stabilization/respond.md +2 -2
- package/generated/operational-stabilization/verify-recovery.md +2 -2
- package/generated/product-change/ai-audit.md +3 -3
- package/generated/product-change/ai-review.md +4 -3
- package/generated/product-change/decision.md +2 -2
- package/generated/product-change/define.md +2 -2
- package/generated/product-change/deploy.md +2 -2
- package/generated/product-change/human-review.md +3 -3
- package/generated/product-change/implement.md +5 -4
- package/generated/product-change/manifest.json +2 -2
- package/generated/product-change/options.md +2 -2
- package/generated/product-change/outcome.md +3 -3
- package/generated/product-change/plan.md +5 -4
- package/generated/product-change/questions.md +2 -2
- package/generated/product-change/research.md +2 -2
- package/generated/product-change/verify.md +2 -2
- package/generated/reference/docs/evidence/research-map.md +2 -1
- package/generated/reference/docs/guide/README.md +7 -2
- package/generated/reference/docs/guide/acceptable-ai-use.md +5 -2
- package/generated/reference/docs/guide/durable-reference.md +262 -0
- package/generated/reference/docs/guide/workflows/decision.md +1 -1
- package/generated/reference/docs/guide/workflows/internal-improvement.md +4 -4
- package/generated/reference/docs/guide/workflows/operational-stabilization.md +1 -0
- package/generated/reference/docs/guide/workflows/product-change.md +6 -4
- package/generated/reference/index.json +14 -0
- package/package.json +6 -1
- package/src/guidance.ts +17 -7
- package/src/index.ts +102 -46
- package/src/storage.ts +81 -24
|
@@ -177,6 +177,7 @@ During incident or emergency mode, maintain at minimum:
|
|
|
177
177
|
- A live intervention requires an accountable human unless existing automation was previously authorized for that action.
|
|
178
178
|
- Any lasting engineering change produced during stabilization still requires a human first-pass plan and independent human review, either in this record or a linked change run; emergency policy may defer but not erase those gates.
|
|
179
179
|
- Kubernetes readiness, controller status, or a successful command is not automatically user-visible recovery.
|
|
180
|
+
- The outcome records the durable references (runbooks, design docs, decision records) this stabilization creates or updates, or explicit none; emergency policy may defer the reference-update record to a linked postmortem, but the deferral itself is recorded. See [Durable references](../durable-reference.md).
|
|
180
181
|
- The conclusion may include mechanism, trigger, enabling conditions, and detection or containment gaps rather than one root cause.
|
|
181
182
|
- Closure does not require every follow-up to remain inside the operational run.
|
|
182
183
|
|
|
@@ -151,10 +151,10 @@ An important unknown is disposed only when it is answered or an accountable huma
|
|
|
151
151
|
| Questions | Understanding and disposition of unknowns | Find missing questions and research answers | Answered, accepted, blocked, or deferred unknowns | Important unknowns are answered or explicitly accepted |
|
|
152
152
|
| Options | At least one initial approach and evaluation criteria | Expand, compare, challenge, add alternatives | Options and tradeoffs | Viable options are understood |
|
|
153
153
|
| Decide | Selection and consequences | Check rationale and surface risks | Decision, rationale, tradeoffs, reversibility | Accountable human approves the decision |
|
|
154
|
-
| Plan | First-pass implementation plan | Identify gaps, dependencies, tests, risks,
|
|
155
|
-
| Implement | Code
|
|
156
|
-
| AI review | Validate and disposition every material AI finding | Review the
|
|
157
|
-
| Human review | Independent final engineering judgment by someone other than the implementer | Answer targeted questions and retrieve evidence | Current independent human review | Independent human reviewer accepts the current change |
|
|
154
|
+
| Plan | First-pass implementation plan and the set of durable references it will touch | Identify gaps, dependencies, tests, risks, rollback needs, and propose reference updates | Sequenced plan, deviations policy, and planned reference updates (decision records, design docs, runbooks, or explicit none) | Human approves the final plan |
|
|
155
|
+
| Implement | Code, engineering changes, and the durable-reference edits that record the change | Bounded generation, explanation, tests, debugging, refactoring assistance, and proposed reference-update drafts | Linked changeset, plan deviations, and actual reference updates | Work is ready for independent review and checks pass |
|
|
156
|
+
| AI review | Validate and disposition every material AI finding | Review against the full open run — decision, plan, plan-deviations, reference-updates, changeset, tests, and evidence — without modifying the change | Review inputs enumeration, snapshot-bound AI findings, and separate human disposition | Every material finding is fixed, invalid, accepted risk, or follow-up with rationale |
|
|
157
|
+
| Human review | Independent final engineering judgment by someone other than the implementer, with the full open run available | Answer targeted questions and retrieve evidence from the live run | Current independent human review | Independent human reviewer accepts the current change |
|
|
158
158
|
| Deploy or release | Authorization and rollout decision | Analyze readiness evidence within policy | Version, environment, actor, time, and result | The intended version reaches the target environment or deployment is explicitly not applicable |
|
|
159
159
|
| Verify and observe | Evaluation against intended behavior | Suggest checks and analyze authorized observations | Test, deployment, and user-visible evidence | Intended outcome is demonstrated or failure is recorded |
|
|
160
160
|
| AI audit | Disposition of findings and any required response | Compare the result with the problem, decision, plan, reviews, and observed behavior; identify divergence, weak evidence, and missed learning | AI audit findings; separate human disposition | The human disposer accepts the audit gate or reopens work |
|
|
@@ -207,6 +207,8 @@ HUMAN OUTCOME / LEARNING
|
|
|
207
207
|
- A human defines or affirms the problem and outcome.
|
|
208
208
|
- A human contributes an option before AI expands the option set.
|
|
209
209
|
- A human authors the first-pass plan.
|
|
210
|
+
- The plan and implementation each record the durable references touched (decision records, design docs, runbooks) or explicit none. See [Durable references](../durable-reference.md).
|
|
211
|
+
- The run stays open from framing through outcome. AI review and independent human review both read the full open run.
|
|
210
212
|
- AI review and implementer self-review do not satisfy independent human review.
|
|
211
213
|
- Implementation completion is not deployment or outcome verification.
|
|
212
214
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"docs/evidence/sources/tigerstyle.md",
|
|
11
11
|
"docs/guide/README.md",
|
|
12
12
|
"docs/guide/acceptable-ai-use.md",
|
|
13
|
+
"docs/guide/durable-reference.md",
|
|
13
14
|
"docs/guide/engineering-practice.md",
|
|
14
15
|
"docs/guide/rationale.md",
|
|
15
16
|
"docs/guide/recommended-skills.md",
|
|
@@ -156,6 +157,19 @@
|
|
|
156
157
|
"*"
|
|
157
158
|
]
|
|
158
159
|
},
|
|
160
|
+
{
|
|
161
|
+
"id": "durable-reference",
|
|
162
|
+
"path": "docs/guide/durable-reference.md",
|
|
163
|
+
"title": "Durable References",
|
|
164
|
+
"summary": "AHEAD's workflows produce two kinds of records. **Run artifacts** are scoped to a single run and describe what happened: framing, research, decisions, plans, changesets, review findings, outcomes. **Durable references** outlive a run and de",
|
|
165
|
+
"audience": "practitioner",
|
|
166
|
+
"authority": "guidance",
|
|
167
|
+
"distribution": "agent-and-human",
|
|
168
|
+
"phases": [],
|
|
169
|
+
"workflows": [
|
|
170
|
+
"*"
|
|
171
|
+
]
|
|
172
|
+
},
|
|
159
173
|
{
|
|
160
174
|
"id": "engineering-practice",
|
|
161
175
|
"path": "docs/guide/engineering-practice.md",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ahead-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AHEAD workflow enforcement and context for Pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ahead",
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=22.19.0"
|
|
65
65
|
},
|
|
66
|
+
"omp": {
|
|
67
|
+
"extensions": [
|
|
68
|
+
"./src/index.ts"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
66
71
|
"pi": {
|
|
67
72
|
"extensions": [
|
|
68
73
|
"./src/index.ts"
|
package/src/guidance.ts
CHANGED
|
@@ -35,6 +35,7 @@ const guides: Record<string, PhaseGuide> = {
|
|
|
35
35
|
"What sources, code, behavior, history, or measurements were examined?",
|
|
36
36
|
"What findings are supported, contradicted, or still uncertain?",
|
|
37
37
|
"How directly does each finding apply to this work?",
|
|
38
|
+
"Does novelty or unfamiliarity require primary documentation, direct inspection, or an experiment?",
|
|
38
39
|
"What important evidence is still missing?",
|
|
39
40
|
],
|
|
40
41
|
},
|
|
@@ -109,7 +110,7 @@ const guides: Record<string, PhaseGuide> = {
|
|
|
109
110
|
],
|
|
110
111
|
plan: [
|
|
111
112
|
"What is the final ordered implementation sequence?",
|
|
112
|
-
"What
|
|
113
|
+
"What human-defined behavior, invariants, test oracles, and observable evidence must the checks evaluate?",
|
|
113
114
|
"How will rollout and recovery work?",
|
|
114
115
|
"Which deviations require returning to decision or plan?",
|
|
115
116
|
],
|
|
@@ -305,11 +306,20 @@ const artifactPromptCatalog: Record<string, string[]> = {
|
|
|
305
306
|
"How does each option perform against the recorded criteria?",
|
|
306
307
|
"What tradeoffs, risks, sensitivity, confidence, and disagreement remain?",
|
|
307
308
|
],
|
|
308
|
-
"
|
|
309
|
-
"Where is the durable decision and
|
|
309
|
+
"decision-record": [
|
|
310
|
+
"Where is the durable decision record written, and does it follow the project's ADR convention?",
|
|
310
311
|
"Who is affected, what downstream work is linked, and how will it be communicated?",
|
|
311
312
|
"What event or date should trigger review?",
|
|
312
313
|
],
|
|
314
|
+
"reference-updates": [
|
|
315
|
+
"Which decision records, design docs, or runbooks does this run create or update?",
|
|
316
|
+
"For each, what section changes and who accepts the change?",
|
|
317
|
+
"If none, is the run genuinely not altering any durable behavior or documented contract?",
|
|
318
|
+
],
|
|
319
|
+
"review-inputs": [
|
|
320
|
+
"Which run inputs did the review read: decision, plan, plan-deviations, reference-updates, changeset, tests, and evidence?",
|
|
321
|
+
"Which inputs were unavailable or intentionally excluded?",
|
|
322
|
+
],
|
|
313
323
|
question: [
|
|
314
324
|
"What exact question is being investigated, and why does it matter?",
|
|
315
325
|
"Who owns it, how will findings be used, and what would count as an answer?",
|
|
@@ -481,7 +491,7 @@ export function buildHeaderLines(
|
|
|
481
491
|
: undefined;
|
|
482
492
|
|
|
483
493
|
return [
|
|
484
|
-
`AHEAD · ${workflow.title} · ${position.current}/${position.total}
|
|
494
|
+
`AHEAD · ${workflow.title} · ${state.phase.title} (${position.current}/${position.total})`,
|
|
485
495
|
...(workItem ? [workItem] : []),
|
|
486
496
|
`Goal: ${guide.objective}`,
|
|
487
497
|
`Required: ${checklist}`,
|
|
@@ -501,14 +511,14 @@ export function buildArtifactTemplate(
|
|
|
501
511
|
return [
|
|
502
512
|
`### ${number}. ${prompt}`,
|
|
503
513
|
`<!-- AHEAD-FIELD:${number}:BEGIN -->`,
|
|
504
|
-
"
|
|
514
|
+
"",
|
|
505
515
|
"",
|
|
506
516
|
`<!-- AHEAD-FIELD:${number}:END -->`,
|
|
507
517
|
"",
|
|
508
518
|
];
|
|
509
519
|
});
|
|
510
520
|
return [
|
|
511
|
-
|
|
521
|
+
"# " + title,
|
|
512
522
|
"",
|
|
513
523
|
`AHEAD run: ${run.id}`,
|
|
514
524
|
`Phase: ${state.phase.id} (visit ${state.phase.visit})`,
|
|
@@ -516,7 +526,7 @@ export function buildArtifactTemplate(
|
|
|
516
526
|
"",
|
|
517
527
|
"## Required responses",
|
|
518
528
|
"",
|
|
519
|
-
"
|
|
529
|
+
"Write directly under each question, between the AHEAD-FIELD markers. Every field is required; use “Not applicable — reason” only when you can justify it.",
|
|
520
530
|
"",
|
|
521
531
|
...fields,
|
|
522
532
|
].join("\n");
|
package/src/index.ts
CHANGED
|
@@ -757,33 +757,32 @@ async function openAheadMode(
|
|
|
757
757
|
}
|
|
758
758
|
|
|
759
759
|
actions.push({
|
|
760
|
-
label: "
|
|
761
|
-
run: async () => manageProjectConfig(ctx),
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
actions.push({
|
|
765
|
-
label: "Read AHEAD framework guidance for this phase",
|
|
766
|
-
run: async () => showAheadGuide(ctx, ""),
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
actions.push({
|
|
770
|
-
label: "Inspect optional skills reviewed for this phase",
|
|
771
|
-
run: async () => showRecommendedSkills(ctx),
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
actions.push({
|
|
775
|
-
label: "Explain this phase and its expectations",
|
|
760
|
+
label: "Help · policy, guidance, skills, phase explanation",
|
|
776
761
|
run: async () => {
|
|
777
|
-
ctx.ui.
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
762
|
+
const helpChoice = await ctx.ui.select("Help · pick one", [
|
|
763
|
+
"Configure project AHEAD policy for future runs",
|
|
764
|
+
"Read AHEAD framework guidance for this phase",
|
|
765
|
+
"Inspect optional skills reviewed for this phase",
|
|
766
|
+
"Explain this phase and its expectations",
|
|
767
|
+
]);
|
|
768
|
+
if (helpChoice === "Configure project AHEAD policy for future runs") {
|
|
769
|
+
await manageProjectConfig(ctx);
|
|
770
|
+
} else if (helpChoice === "Read AHEAD framework guidance for this phase") {
|
|
771
|
+
await showAheadGuide(ctx, "");
|
|
772
|
+
} else if (helpChoice === "Inspect optional skills reviewed for this phase") {
|
|
773
|
+
await showRecommendedSkills(ctx);
|
|
774
|
+
} else if (helpChoice === "Explain this phase and its expectations") {
|
|
775
|
+
ctx.ui.notify(
|
|
776
|
+
[
|
|
777
|
+
state.phase.title,
|
|
778
|
+
`Goal: ${guidance.objective}`,
|
|
779
|
+
`You: ${guidance.human}`,
|
|
780
|
+
`AI: ${guidance.ai}`,
|
|
781
|
+
`Gate: ${state.gate.title}`,
|
|
782
|
+
].join("\n"),
|
|
783
|
+
"info",
|
|
784
|
+
);
|
|
785
|
+
}
|
|
787
786
|
},
|
|
788
787
|
});
|
|
789
788
|
|
|
@@ -1475,7 +1474,10 @@ async function startRun(ctx: ExtensionCommandContext, request: string): Promise<
|
|
|
1475
1474
|
parsed.title ||
|
|
1476
1475
|
linkedTitle ||
|
|
1477
1476
|
(ctx.hasUI
|
|
1478
|
-
? await ctx.ui.input(
|
|
1477
|
+
? await ctx.ui.input(
|
|
1478
|
+
`Enter AHEAD mode · ${workflow.title}`,
|
|
1479
|
+
`Short name for this work — e.g. ${titleExample(workflow.id)}`,
|
|
1480
|
+
)
|
|
1479
1481
|
: parsed.workItemUrl);
|
|
1480
1482
|
if (!title?.trim()) {
|
|
1481
1483
|
return undefined;
|
|
@@ -1499,14 +1501,14 @@ async function startRun(ctx: ExtensionCommandContext, request: string): Promise<
|
|
|
1499
1501
|
await store.save(run);
|
|
1500
1502
|
await refreshUi(ctx, run);
|
|
1501
1503
|
const state = engine.deriveState(run);
|
|
1504
|
+
const requiredPhase = state.policy.work_items.required_before_phase;
|
|
1502
1505
|
ctx.ui.notify(
|
|
1503
1506
|
[
|
|
1504
1507
|
`AHEAD mode started · ${workflow.title} · ${run.title}`,
|
|
1505
|
-
"Human leads · AI assists",
|
|
1506
1508
|
...(state.work_item ? [`Work item: ${state.work_item.url}`] : []),
|
|
1507
|
-
...(state.
|
|
1509
|
+
...(requiredPhase && !state.work_item
|
|
1508
1510
|
? [
|
|
1509
|
-
`
|
|
1511
|
+
`Before the ${requiredPhase} phase, link a work item: /ahead, then choose “Link or create a work item”.`,
|
|
1510
1512
|
]
|
|
1511
1513
|
: []),
|
|
1512
1514
|
"This run remains active until an accountable human closes the outcome or uses /ahead-stop.",
|
|
@@ -1517,6 +1519,25 @@ async function startRun(ctx: ExtensionCommandContext, request: string): Promise<
|
|
|
1517
1519
|
return run;
|
|
1518
1520
|
}
|
|
1519
1521
|
|
|
1522
|
+
function titleExample(workflowId: string): string {
|
|
1523
|
+
switch (workflowId) {
|
|
1524
|
+
case "product-change":
|
|
1525
|
+
return "“Add audit log viewer page”";
|
|
1526
|
+
case "internal-improvement":
|
|
1527
|
+
return "“Reduce cola-api cold-start time”";
|
|
1528
|
+
case "corrective-debugging":
|
|
1529
|
+
return "“Fix race in worker claim loop”";
|
|
1530
|
+
case "operational-stabilization":
|
|
1531
|
+
return "“Restore queue throughput after incident”";
|
|
1532
|
+
case "decision":
|
|
1533
|
+
return "“Choose audit-log retention policy”";
|
|
1534
|
+
case "investigation":
|
|
1535
|
+
return "“Why are runs flaking on CI?”";
|
|
1536
|
+
default:
|
|
1537
|
+
return "“Improve X”";
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1520
1541
|
async function recordHumanArtifact(
|
|
1521
1542
|
ctx: ExtensionCommandContext,
|
|
1522
1543
|
requestedKind: string,
|
|
@@ -1549,7 +1570,7 @@ async function recordHumanArtifact(
|
|
|
1549
1570
|
}
|
|
1550
1571
|
|
|
1551
1572
|
const template = await humanArtifactTemplate(store, state, run, artifact.kind, artifact.title);
|
|
1552
|
-
|
|
1573
|
+
let content = await ctx.ui.editor(
|
|
1553
1574
|
`AHEAD mode · ${artifact.title} · write in your own words`,
|
|
1554
1575
|
template,
|
|
1555
1576
|
);
|
|
@@ -1557,17 +1578,39 @@ async function recordHumanArtifact(
|
|
|
1557
1578
|
return;
|
|
1558
1579
|
}
|
|
1559
1580
|
if (artifact.kind !== "review-disposition") {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1581
|
+
// Keep the form open until validation passes or the human explicitly cancels,
|
|
1582
|
+
// so partial input is never discarded by a validation failure.
|
|
1583
|
+
while (true) {
|
|
1584
|
+
const formErrors = validateArtifactForm(
|
|
1585
|
+
content,
|
|
1586
|
+
promptsForArtifact(state.workflow_id, state.phase.id, artifact.kind),
|
|
1587
|
+
);
|
|
1588
|
+
if (formErrors.length === 0) {
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
const retry = await ctx.ui.confirm(
|
|
1592
|
+
"Some required fields are still empty",
|
|
1593
|
+
[
|
|
1594
|
+
`The following fields in ${artifact.title} need a response:`,
|
|
1595
|
+
"",
|
|
1596
|
+
...formErrors.map((error) => `- ${error}`),
|
|
1597
|
+
"",
|
|
1598
|
+
"Your text is preserved. Reopen to finish, or discard?",
|
|
1599
|
+
].join("\n"),
|
|
1570
1600
|
);
|
|
1601
|
+
if (!retry) {
|
|
1602
|
+
ctx.ui.notify("Draft discarded. Nothing was saved.", "info");
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
const revised = await ctx.ui.editor(
|
|
1606
|
+
`AHEAD mode · ${artifact.title} · finish the required fields`,
|
|
1607
|
+
content,
|
|
1608
|
+
);
|
|
1609
|
+
if (!revised?.trim()) {
|
|
1610
|
+
ctx.ui.notify("Draft kept in this dialog only — nothing was saved.", "info");
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
content = revised;
|
|
1571
1614
|
}
|
|
1572
1615
|
}
|
|
1573
1616
|
await validateHumanReviewArtifact(store, state, artifact.kind, content);
|
|
@@ -1582,8 +1625,21 @@ async function recordHumanArtifact(
|
|
|
1582
1625
|
await store.writeArtifact(path.absolute, content);
|
|
1583
1626
|
await store.save(updated);
|
|
1584
1627
|
await refreshUi(ctx, updated);
|
|
1628
|
+
const nextState = engine.deriveState(updated);
|
|
1629
|
+
const nextArtifact = nextState.artifacts.find(
|
|
1630
|
+
(candidate) => candidate.required && !candidate.present,
|
|
1631
|
+
);
|
|
1632
|
+
const nextStep = nextState.gate.accepted
|
|
1633
|
+
? nextState.gate.title
|
|
1634
|
+
: nextArtifact
|
|
1635
|
+
? `Next artifact: ${nextArtifact.title}`
|
|
1636
|
+
: `Accept the gate when ready: ${nextState.gate.title}`;
|
|
1585
1637
|
ctx.ui.notify(
|
|
1586
|
-
|
|
1638
|
+
[
|
|
1639
|
+
`✓ Saved ${artifact.title}.`,
|
|
1640
|
+
`Next: ${nextStep}`,
|
|
1641
|
+
"/ahead is available for the action menu; or just tell me what to do next.",
|
|
1642
|
+
].join("\n"),
|
|
1587
1643
|
"info",
|
|
1588
1644
|
);
|
|
1589
1645
|
}
|
|
@@ -1887,7 +1943,7 @@ async function refreshUi(ctx: ExtensionContext, supplied?: Run): Promise<void> {
|
|
|
1887
1943
|
"ahead",
|
|
1888
1944
|
state.closed
|
|
1889
1945
|
? `AHEAD · complete · ${state.workflow_id}`
|
|
1890
|
-
: `AHEAD · ${
|
|
1946
|
+
: `AHEAD · ${state.phase.id} · ${action.actor} action (${position.current}/${position.total})`,
|
|
1891
1947
|
);
|
|
1892
1948
|
if (state.closed) {
|
|
1893
1949
|
ctx.ui.setWidget("ahead", undefined);
|
|
@@ -2052,8 +2108,8 @@ async function toolResult(action: () => Promise<unknown>) {
|
|
|
2052
2108
|
}
|
|
2053
2109
|
|
|
2054
2110
|
function errorMessage(error: unknown): string {
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2111
|
+
// User-facing messages should read as prose. The internal error code is
|
|
2112
|
+
// available on AheadEngineError.code for tooling, but does not belong in
|
|
2113
|
+
// the user-visible text.
|
|
2058
2114
|
return error instanceof Error ? error.message : String(error);
|
|
2059
2115
|
}
|
package/src/storage.ts
CHANGED
|
@@ -44,17 +44,54 @@ export class RunStore {
|
|
|
44
44
|
this.aheadDirectory = join(rootPath, ".ahead");
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return `${date}-${randomUUID().slice(0, 8)}`;
|
|
47
|
+
private get localDirectory(): string {
|
|
48
|
+
return join(this.aheadDirectory, "local");
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
private get currentPath(): string {
|
|
52
|
+
return join(this.localDirectory, "current.json");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private get legacyCurrentPath(): string {
|
|
56
|
+
return join(this.aheadDirectory, "current.json");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async ensureLocalDirectory(): Promise<void> {
|
|
60
|
+
await mkdir(this.localDirectory, { recursive: true });
|
|
61
|
+
const gitignorePath = join(this.localDirectory, ".gitignore");
|
|
53
62
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
63
|
+
await writeFile(gitignorePath, "*\n!.gitignore\n", { encoding: "utf8", flag: "wx" });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (!isExists(error)) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async writeCurrentPointer(pointer: CurrentRunPointer): Promise<void> {
|
|
72
|
+
await this.ensureLocalDirectory();
|
|
73
|
+
await atomicJson(this.currentPath, pointer);
|
|
74
|
+
// Cleanup legacy pointer at the old shared path, if present.
|
|
75
|
+
try {
|
|
76
|
+
await unlink(this.legacyCurrentPath);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (!isMissing(error)) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async readCurrentPointer(): Promise<string | undefined> {
|
|
85
|
+
try {
|
|
86
|
+
return await readFile(this.currentPath, "utf8");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (!isMissing(error)) {
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Legacy fallback: read from the pre-0.7.0 shared path if the local path is absent.
|
|
93
|
+
try {
|
|
94
|
+
return await readFile(this.legacyCurrentPath, "utf8");
|
|
58
95
|
} catch (error) {
|
|
59
96
|
if (isMissing(error)) {
|
|
60
97
|
return undefined;
|
|
@@ -63,6 +100,20 @@ export class RunStore {
|
|
|
63
100
|
}
|
|
64
101
|
}
|
|
65
102
|
|
|
103
|
+
newRunId(): string {
|
|
104
|
+
const date = new Date().toISOString().slice(0, 10).replaceAll("-", "");
|
|
105
|
+
return `${date}-${randomUUID().slice(0, 8)}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async loadCurrent(): Promise<Run | undefined> {
|
|
109
|
+
const content = await this.readCurrentPointer();
|
|
110
|
+
if (content === undefined) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const pointer = parseCurrentRunPointer(content);
|
|
114
|
+
return this.load(pointer.run_id);
|
|
115
|
+
}
|
|
116
|
+
|
|
66
117
|
async load(runId: string): Promise<Run> {
|
|
67
118
|
return parseRun(await readFile(this.runPath(runId), "utf8"));
|
|
68
119
|
}
|
|
@@ -139,7 +190,7 @@ export class RunStore {
|
|
|
139
190
|
await atomicJson(this.runPath(run.id), run);
|
|
140
191
|
if (makeCurrent) {
|
|
141
192
|
const pointer: CurrentRunPointer = { api_version: "ahead.current/v0", run_id: run.id };
|
|
142
|
-
await
|
|
193
|
+
await this.writeCurrentPointer(pointer);
|
|
143
194
|
}
|
|
144
195
|
}
|
|
145
196
|
|
|
@@ -166,7 +217,7 @@ export class RunStore {
|
|
|
166
217
|
async resume(runId: string): Promise<Run> {
|
|
167
218
|
const run = await this.load(runId);
|
|
168
219
|
const pointer: CurrentRunPointer = { api_version: "ahead.current/v0", run_id: runId };
|
|
169
|
-
await
|
|
220
|
+
await this.writeCurrentPointer(pointer);
|
|
170
221
|
return run;
|
|
171
222
|
}
|
|
172
223
|
|
|
@@ -219,25 +270,31 @@ export class RunStore {
|
|
|
219
270
|
}
|
|
220
271
|
|
|
221
272
|
private async clearCurrent(expectedRunId: string): Promise<void> {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
273
|
+
// Clear the current pointer wherever it lives (new local path and legacy shared path).
|
|
274
|
+
for (const path of [this.currentPath, this.legacyCurrentPath]) {
|
|
275
|
+
let pointer: CurrentRunPointer;
|
|
276
|
+
try {
|
|
277
|
+
pointer = parseCurrentRunPointer(await readFile(path, "utf8"));
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (isMissing(error)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
throw error;
|
|
229
283
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
);
|
|
284
|
+
if (pointer.run_id !== expectedRunId) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`active AHEAD run changed from ${expectedRunId} to ${pointer.run_id}; stop or resume again`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
await unlink(path);
|
|
236
290
|
}
|
|
237
|
-
await unlink(path);
|
|
238
291
|
}
|
|
239
292
|
}
|
|
240
293
|
|
|
294
|
+
function isExists(error: unknown): boolean {
|
|
295
|
+
return !!error && typeof error === "object" && "code" in error && error.code === "EEXIST";
|
|
296
|
+
}
|
|
297
|
+
|
|
241
298
|
export function humanActor(cwd: string): Actor {
|
|
242
299
|
const explicit = process.env.AHEAD_HUMAN_IDENTITY?.trim();
|
|
243
300
|
if (explicit) {
|