ahead-pi 0.2.0 → 0.2.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.
- package/README.md +13 -3
- package/generated/product-change/ai-audit.md +6 -3
- package/generated/product-change/ai-review.md +6 -3
- package/generated/product-change/decision.md +6 -3
- package/generated/product-change/define.md +6 -3
- package/generated/product-change/deploy.md +6 -3
- package/generated/product-change/human-review.md +6 -3
- package/generated/product-change/implement.md +11 -4
- package/generated/product-change/manifest.json +2 -2
- package/generated/product-change/options.md +6 -3
- package/generated/product-change/outcome.md +6 -3
- package/generated/product-change/plan.md +6 -3
- package/generated/product-change/questions.md +6 -3
- package/generated/product-change/research.md +6 -3
- package/generated/product-change/verify.md +6 -3
- package/generated/reference/CONSTITUTION.md +43 -0
- package/generated/reference/docs/acceptable-ai-use.md +217 -0
- package/generated/reference/docs/design/debugging-and-operations.md +119 -0
- package/generated/reference/docs/design/executable-workflows.md +110 -0
- package/generated/reference/docs/design/process-taxonomy.md +144 -0
- package/generated/reference/docs/engineering-practice.md +163 -0
- package/generated/reference/docs/evidence/evidence-standard.md +123 -0
- package/generated/reference/docs/evidence/research-map.md +98 -0
- package/generated/reference/docs/rationale.md +210 -0
- package/generated/reference/docs/references/pragmatic-programmer-page-index.md +113 -0
- package/generated/reference/docs/references/submitted-engineering-notes.md +306 -0
- package/generated/reference/docs/releasing-pi.md +89 -0
- package/generated/reference/docs/workflows/README.md +142 -0
- package/generated/reference/docs/workflows/corrective-debugging.md +203 -0
- package/generated/reference/docs/workflows/decision.md +149 -0
- package/generated/reference/docs/workflows/internal-improvement.md +207 -0
- package/generated/reference/docs/workflows/investigation.md +159 -0
- package/generated/reference/docs/workflows/operational-stabilization.md +185 -0
- package/generated/reference/docs/workflows/product-change.md +205 -0
- package/generated/reference/index.json +180 -0
- package/package.json +4 -2
- package/src/guidance.ts +6 -4
- package/src/index.ts +128 -0
- package/src/reference-viewer.ts +71 -0
- package/src/reference.ts +56 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generated_from": [
|
|
3
|
+
"CONSTITUTION.md",
|
|
4
|
+
"docs/acceptable-ai-use.md",
|
|
5
|
+
"docs/design/debugging-and-operations.md",
|
|
6
|
+
"docs/design/executable-workflows.md",
|
|
7
|
+
"docs/design/process-taxonomy.md",
|
|
8
|
+
"docs/engineering-practice.md",
|
|
9
|
+
"docs/evidence/evidence-standard.md",
|
|
10
|
+
"docs/evidence/research-map.md",
|
|
11
|
+
"docs/rationale.md",
|
|
12
|
+
"docs/references/pragmatic-programmer-page-index.md",
|
|
13
|
+
"docs/references/submitted-engineering-notes.md",
|
|
14
|
+
"docs/releasing-pi.md",
|
|
15
|
+
"docs/workflows/README.md",
|
|
16
|
+
"docs/workflows/corrective-debugging.md",
|
|
17
|
+
"docs/workflows/decision.md",
|
|
18
|
+
"docs/workflows/internal-improvement.md",
|
|
19
|
+
"docs/workflows/investigation.md",
|
|
20
|
+
"docs/workflows/operational-stabilization.md",
|
|
21
|
+
"docs/workflows/product-change.md"
|
|
22
|
+
],
|
|
23
|
+
"references": [
|
|
24
|
+
{
|
|
25
|
+
"id": "constitution",
|
|
26
|
+
"path": "CONSTITUTION.md",
|
|
27
|
+
"title": "AHEAD Constitution",
|
|
28
|
+
"summary": "This constitution records the durable principles of **Assisted Human Engineering and Development**. Workflows and tools may evolve. These principles define what makes them AHEAD.",
|
|
29
|
+
"phases": [
|
|
30
|
+
"*"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "acceptable-ai-use",
|
|
35
|
+
"path": "docs/acceptable-ai-use.md",
|
|
36
|
+
"title": "Acceptable AI Use in AHEAD",
|
|
37
|
+
"summary": "This policy is binding for AHEAD pilot workflows. The [AHEAD Constitution](../CONSTITUTION.md) is the highest authority; this policy interprets its human-ownership boundaries; workflow profiles apply both and may narrow AI permissions for a",
|
|
38
|
+
"phases": [
|
|
39
|
+
"*"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "design:debugging-and-operations",
|
|
44
|
+
"path": "docs/design/debugging-and-operations.md",
|
|
45
|
+
"title": "Debugging and Operational Investigation",
|
|
46
|
+
"summary": "The minimal [corrective-debugging](../workflows/corrective-debugging.md) and [operational-stabilization](../workflows/operational-stabilization.md) profiles translate this discussion into pilotable flows. This document retains the reasoning",
|
|
47
|
+
"phases": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "design:executable-workflows",
|
|
51
|
+
"path": "docs/design/executable-workflows.md",
|
|
52
|
+
"title": "Executable AHEAD Workflows",
|
|
53
|
+
"summary": "The executable layer makes AHEAD workflow state durable and makes selected human/AI boundaries enforceable across integrations. It does not turn judgment into a checklist or make workflow artifacts proof of understanding.",
|
|
54
|
+
"phases": [
|
|
55
|
+
"*"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "design:process-taxonomy",
|
|
60
|
+
"path": "docs/design/process-taxonomy.md",
|
|
61
|
+
"title": "AHEAD Process Taxonomy",
|
|
62
|
+
"summary": "AHEAD should not create a workflow for every issue label. “Security,” “performance,” “data,” “incident,” and “technical debt” often describe risk, domain, urgency, or cause—not the kind of reasoning needed to complete the work.",
|
|
63
|
+
"phases": []
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "engineering-practice",
|
|
67
|
+
"path": "docs/engineering-practice.md",
|
|
68
|
+
"title": "AHEAD Engineering Practice",
|
|
69
|
+
"summary": "AHEAD is not only a sequence of AI gates. It is a way of practicing engineering. This guide distills general habits from the submitted starting list into a smaller set of principles that can be remembered, applied, and evaluated.",
|
|
70
|
+
"phases": [
|
|
71
|
+
"*"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "evidence:evidence-standard",
|
|
76
|
+
"path": "docs/evidence/evidence-standard.md",
|
|
77
|
+
"title": "AHEAD Evidence Standard",
|
|
78
|
+
"summary": "AHEAD should be supported by the best available evidence without claiming more certainty than software-engineering research can provide. A credible methodology must distinguish evidence for a descriptive claim from evidence that a particula",
|
|
79
|
+
"phases": [
|
|
80
|
+
"research",
|
|
81
|
+
"questions",
|
|
82
|
+
"decision",
|
|
83
|
+
"plan",
|
|
84
|
+
"ai-review",
|
|
85
|
+
"human-review",
|
|
86
|
+
"verify",
|
|
87
|
+
"ai-audit",
|
|
88
|
+
"outcome"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "evidence:research-map",
|
|
93
|
+
"path": "docs/evidence/research-map.md",
|
|
94
|
+
"title": "AHEAD Research Map",
|
|
95
|
+
"summary": "This document maps current AHEAD design claims to available evidence. It is not a bibliography of everything related to software engineering. It focuses on claims that materially shape the methodology.",
|
|
96
|
+
"phases": []
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "rationale",
|
|
100
|
+
"path": "docs/rationale.md",
|
|
101
|
+
"title": "Why AHEAD",
|
|
102
|
+
"summary": "AI can make software engineering faster. It can search broadly, synthesize information, identify omissions, generate alternatives, produce routine code, explain unfamiliar systems, and review more material than a person can inspect unaided.",
|
|
103
|
+
"phases": [
|
|
104
|
+
"*"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "references:pragmatic-programmer-page-index",
|
|
109
|
+
"path": "docs/references/pragmatic-programmer-page-index.md",
|
|
110
|
+
"title": "*The Pragmatic Programmer* Page Index",
|
|
111
|
+
"summary": "This index preserves the page-level provenance from the original AHEAD engineering-practice notes for:",
|
|
112
|
+
"phases": []
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "references:submitted-engineering-notes",
|
|
116
|
+
"path": "docs/references/submitted-engineering-notes.md",
|
|
117
|
+
"title": "Submitted Engineering Notes",
|
|
118
|
+
"summary": "This document preserves submitted engineering tips, checklists, and practices that are intentionally condensed in the [AHEAD Engineering Practice](../engineering-practice.md) guide. It is a source record, not a claim that every item is an e",
|
|
119
|
+
"phases": []
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "releasing-pi",
|
|
123
|
+
"path": "docs/releasing-pi.md",
|
|
124
|
+
"title": "Releasing the Pi Extension",
|
|
125
|
+
"summary": "- npm package: `ahead-pi` - package source: `integrations/pi` - release workflow: `.github/workflows/publish-pi.yml` - release tag: `pi-v<package-version>` - stable npm dist-tag: `latest` - prerelease npm dist-tag: `next` - registry: `https",
|
|
126
|
+
"phases": []
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"id": "workflows:readme",
|
|
130
|
+
"path": "docs/workflows/README.md",
|
|
131
|
+
"title": "AHEAD Pilot Workflows",
|
|
132
|
+
"summary": "These six minimal workflow profiles are meant to be used on real engineering work before AHEAD builds a workflow engine, editor extension, or CI enforcement. They are deliberately small. The pilot should reveal which phases, gates, and reco",
|
|
133
|
+
"phases": []
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "workflows:corrective-debugging",
|
|
137
|
+
"path": "docs/workflows/corrective-debugging.md",
|
|
138
|
+
"title": "Corrective Debugging Workflow",
|
|
139
|
+
"summary": "Use this flow when observed behavior conflicts with intended behavior and the dominant work is causal diagnosis plus correction. It ends with a verified correction or an explicit human acceptance of remaining uncertainty.",
|
|
140
|
+
"phases": []
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "workflows:decision",
|
|
144
|
+
"path": "docs/workflows/decision.md",
|
|
145
|
+
"title": "Decision Workflow",
|
|
146
|
+
"summary": "Use this flow when the deliverable is an accountable human choice among alternatives: architecture, buy versus build, technology selection, platform direction, policy, or another consequential course.",
|
|
147
|
+
"phases": []
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"id": "workflows:internal-improvement",
|
|
151
|
+
"path": "docs/workflows/internal-improvement.md",
|
|
152
|
+
"title": "Internal Improvement Workflow",
|
|
153
|
+
"summary": "Use this flow when externally required behavior should remain invariant while an internal quality improves: maintainability, comprehensibility, performance, reliability margin, operability, testability, cost, or preventive risk reduction.",
|
|
154
|
+
"phases": []
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "workflows:investigation",
|
|
158
|
+
"path": "docs/workflows/investigation.md",
|
|
159
|
+
"title": "Investigation Workflow",
|
|
160
|
+
"summary": "Use this flow when the primary deliverable is knowledge or reduced uncertainty and no intervention has yet been selected. It may answer feasibility, causal, capacity, vendor, architectural, or technical questions.",
|
|
161
|
+
"phases": []
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": "workflows:operational-stabilization",
|
|
165
|
+
"path": "docs/workflows/operational-stabilization.md",
|
|
166
|
+
"title": "Operational Stabilization Workflow",
|
|
167
|
+
"summary": "Use this flow when a live system is outside an acceptable operating state and the immediate outcome is restoration, stabilization, or demonstrated convergence. It covers code and non-code causes such as reconciliation storms, configuration ",
|
|
168
|
+
"phases": []
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "workflows:product-change",
|
|
172
|
+
"path": "docs/workflows/product-change.md",
|
|
173
|
+
"title": "Product Change Workflow",
|
|
174
|
+
"summary": "Use this flow when the dominant outcome is new, changed, adapted, migrated, or retired externally meaningful behavior. It ends with verified intended behavior and an accountable human outcome decision.",
|
|
175
|
+
"phases": [
|
|
176
|
+
"*"
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ahead-pi",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AHEAD workflow enforcement and context for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,10 +49,12 @@
|
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@earendil-works/pi-coding-agent": "*",
|
|
52
|
+
"@earendil-works/pi-tui": "*",
|
|
52
53
|
"typebox": "*"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
57
|
+
"@earendil-works/pi-tui": "^0.84.1",
|
|
56
58
|
"@types/node": "24.12.4",
|
|
57
59
|
"typebox": "1.1.38",
|
|
58
60
|
"typescript": "5.9.3"
|
package/src/guidance.ts
CHANGED
|
@@ -110,8 +110,8 @@ const guides: Record<string, PhaseGuide> = {
|
|
|
110
110
|
},
|
|
111
111
|
implement: {
|
|
112
112
|
objective: "Produce a change the engineer understands and can defend.",
|
|
113
|
-
human: "
|
|
114
|
-
ai: "
|
|
113
|
+
human: "Make the first attempt, ask questions freely, own the implementation, understand every lasting change, run the planned checks, and record deviations.",
|
|
114
|
+
ai: "Coach, explain, help diagnose, and suggest bounded next steps within the approved plan. Do not turn a question into taking over the implementation.",
|
|
115
115
|
artifactPrompts: {
|
|
116
116
|
changeset: [
|
|
117
117
|
"What exact commit, branch, pull request, or diff identifies the current change?",
|
|
@@ -252,7 +252,9 @@ export function nextAction(state: RunState, workflow: WorkflowDefinition): Guide
|
|
|
252
252
|
? "ai"
|
|
253
253
|
: "human";
|
|
254
254
|
if (actor === "human") {
|
|
255
|
-
const label = state.phase.id === "
|
|
255
|
+
const label = state.phase.id === "implement" && artifact.kind === "changeset"
|
|
256
|
+
? "Implement first, then record the exact changeset"
|
|
257
|
+
: state.phase.id === "human-review"
|
|
256
258
|
? "Independent reviewer records the current human review"
|
|
257
259
|
: `Write ${artifact.title}`;
|
|
258
260
|
return { actor, label, artifactKind: artifact.kind };
|
|
@@ -323,7 +325,7 @@ export function buildWidgetLines(run: Run, state: RunState, workflow: WorkflowDe
|
|
|
323
325
|
`AI: ${guide.ai}`,
|
|
324
326
|
`Required: ${checklist}`,
|
|
325
327
|
`Next (${action.actor === "human" ? "you" : "AI"}): ${action.label}`,
|
|
326
|
-
"Run /ahead for the guided action.",
|
|
328
|
+
"Run /ahead for the guided action · /ahead-guide for framework docs.",
|
|
327
329
|
];
|
|
328
330
|
}
|
|
329
331
|
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,13 @@ import {
|
|
|
14
14
|
phaseGuide,
|
|
15
15
|
phasePosition,
|
|
16
16
|
} from "./guidance.js";
|
|
17
|
+
import {
|
|
18
|
+
findReference,
|
|
19
|
+
loadReferenceIndex,
|
|
20
|
+
readReference,
|
|
21
|
+
relevantReferences,
|
|
22
|
+
} from "./reference.js";
|
|
23
|
+
import { showReferenceViewer } from "./reference-viewer.js";
|
|
17
24
|
import { humanActor, projectRoot, RunStore } from "./storage.js";
|
|
18
25
|
import type { Actor, Capability, EventAction, Run, RunState } from "./types.js";
|
|
19
26
|
|
|
@@ -38,6 +45,9 @@ const RecordArtifactParams = Type.Object({
|
|
|
38
45
|
kind: Type.String({ description: "Artifact kind permitted for AI in the active phase" }),
|
|
39
46
|
content: Type.String({ description: "Complete Markdown artifact content", maxLength: 100_000 }),
|
|
40
47
|
});
|
|
48
|
+
const ReferenceParams = Type.Object({
|
|
49
|
+
topic: Type.Optional(Type.String({ description: "Reference id, path, or title; omit to list phase-relevant references" })),
|
|
50
|
+
});
|
|
41
51
|
|
|
42
52
|
export default function aheadExtension(pi: ExtensionAPI): void {
|
|
43
53
|
pi.registerCommand("ahead", {
|
|
@@ -47,6 +57,13 @@ export default function aheadExtension(pi: ExtensionAPI): void {
|
|
|
47
57
|
}),
|
|
48
58
|
});
|
|
49
59
|
|
|
60
|
+
pi.registerCommand("ahead-guide", {
|
|
61
|
+
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
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
50
67
|
pi.registerCommand("ahead-start", {
|
|
51
68
|
description: "Advanced: start a Product Change run directly",
|
|
52
69
|
handler: async (args, ctx) => command(ctx, async () => {
|
|
@@ -137,6 +154,7 @@ export default function aheadExtension(pi: ExtensionAPI): void {
|
|
|
137
154
|
ctx.ui.notify(
|
|
138
155
|
[
|
|
139
156
|
"/ahead [title] — enter, resume, or act in guided AHEAD mode",
|
|
157
|
+
"/ahead-guide [topic] — read the applicable AHEAD framework Markdown",
|
|
140
158
|
"",
|
|
141
159
|
"Once started, the repository run remains in AHEAD mode until an accountable human closes the outcome.",
|
|
142
160
|
"Use normal conversation to think and work with AI. Run /ahead whenever you want the next valid action.",
|
|
@@ -165,6 +183,34 @@ export default function aheadExtension(pi: ExtensionAPI): void {
|
|
|
165
183
|
},
|
|
166
184
|
});
|
|
167
185
|
|
|
186
|
+
pi.registerTool({
|
|
187
|
+
name: "ahead_get_reference",
|
|
188
|
+
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.",
|
|
191
|
+
parameters: ReferenceParams,
|
|
192
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
193
|
+
return toolResult(async () => {
|
|
194
|
+
const run = await storeFor(ctx).loadCurrent();
|
|
195
|
+
const phase = run && !(await enginePromise).deriveState(run).closed
|
|
196
|
+
? (await enginePromise).deriveState(run).phase.id
|
|
197
|
+
: undefined;
|
|
198
|
+
if (!params.topic?.trim()) {
|
|
199
|
+
const index = await loadReferenceIndex();
|
|
200
|
+
return {
|
|
201
|
+
phase: phase ?? null,
|
|
202
|
+
recommended: await relevantReferences(phase),
|
|
203
|
+
available: index.references.map(({ id, title, path }) => ({ id, title, path })),
|
|
204
|
+
instruction: "Request one reference by id, path, or title. Load only what is relevant.",
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const entry = await findReference(params.topic);
|
|
208
|
+
if (!entry) throw new Error(`No packaged AHEAD reference matches ${params.topic}`);
|
|
209
|
+
return { reference: entry, content: await readReference(entry) };
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
168
214
|
pi.registerTool({
|
|
169
215
|
name: "ahead_record_artifact",
|
|
170
216
|
label: "Record AHEAD artifact",
|
|
@@ -277,6 +323,8 @@ export default function aheadExtension(pi: ExtensionAPI): void {
|
|
|
277
323
|
"- Never author a human-owned artifact, make a human decision, accept a gate, transition the run, approve a change, or claim accountability.",
|
|
278
324
|
"- When required AI-owned work is ready, record it with ahead_record_artifact and explain what the human must validate or decide.",
|
|
279
325
|
"- Treat AI review findings as hypotheses. Independent human review remains required for lasting engineering changes.",
|
|
326
|
+
"- 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
|
+
"- When AHEAD policy or rationale is unclear, use ahead_get_reference to retrieve only the applicable packaged Markdown.",
|
|
280
328
|
].join("\n");
|
|
281
329
|
return { systemPrompt: `${event.systemPrompt}\n\n${phaseInstructions}\n\n${liveContext}\n` };
|
|
282
330
|
});
|
|
@@ -378,6 +426,7 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
|
|
|
378
426
|
state.allowed_ai_capabilities.length > 0
|
|
379
427
|
&& action.actor !== "ai"
|
|
380
428
|
&& !missingRequired.some((artifact) => artifact.actor === "ai")
|
|
429
|
+
&& state.phase.id !== "implement"
|
|
381
430
|
) {
|
|
382
431
|
actions.push({
|
|
383
432
|
label: `Ask AI to assist · ${state.phase.title}`,
|
|
@@ -385,6 +434,13 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
|
|
|
385
434
|
});
|
|
386
435
|
}
|
|
387
436
|
|
|
437
|
+
if (state.phase.id === "implement") {
|
|
438
|
+
actions.push({
|
|
439
|
+
label: "Ask AI for help understanding or solving a problem",
|
|
440
|
+
run: async () => askImplementationQuestion(pi, ctx, state),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
388
444
|
if (state.return_targets.length > 0) {
|
|
389
445
|
actions.push({
|
|
390
446
|
label: "Return to an earlier phase",
|
|
@@ -392,6 +448,11 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
|
|
|
392
448
|
});
|
|
393
449
|
}
|
|
394
450
|
|
|
451
|
+
actions.push({
|
|
452
|
+
label: "Read AHEAD framework guidance for this phase",
|
|
453
|
+
run: async () => showAheadGuide(ctx, ""),
|
|
454
|
+
});
|
|
455
|
+
|
|
395
456
|
actions.push({
|
|
396
457
|
label: "Explain this phase and its expectations",
|
|
397
458
|
run: async () => {
|
|
@@ -416,6 +477,73 @@ async function openAheadMode(pi: ExtensionAPI, args: string, ctx: ExtensionComma
|
|
|
416
477
|
if (chosen) await chosen.run();
|
|
417
478
|
}
|
|
418
479
|
|
|
480
|
+
async function askImplementationQuestion(
|
|
481
|
+
pi: ExtensionAPI,
|
|
482
|
+
ctx: ExtensionCommandContext,
|
|
483
|
+
state: RunState,
|
|
484
|
+
): Promise<void> {
|
|
485
|
+
if (!ctx.hasUI) throw new Error("Implementation coaching requires interactive or RPC UI support");
|
|
486
|
+
const question = await ctx.ui.editor(
|
|
487
|
+
"AHEAD implementation help · human first",
|
|
488
|
+
[
|
|
489
|
+
"## What are you trying to understand or solve?",
|
|
490
|
+
"",
|
|
491
|
+
"",
|
|
492
|
+
"## What do you currently think is happening or should happen?",
|
|
493
|
+
"",
|
|
494
|
+
"",
|
|
495
|
+
"## What have you tried or inspected so far?",
|
|
496
|
+
"",
|
|
497
|
+
"",
|
|
498
|
+
"## What kind of help would be useful?",
|
|
499
|
+
"",
|
|
500
|
+
"<!-- Ask for explanation, a hint, competing approaches, debugging help, or a bounded suggestion. -->",
|
|
501
|
+
"",
|
|
502
|
+
].join("\n"),
|
|
503
|
+
);
|
|
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"));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
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");
|
|
516
|
+
const run = await storeFor(ctx).loadCurrent();
|
|
517
|
+
const phase = run && !(await enginePromise).deriveState(run).closed
|
|
518
|
+
? (await enginePromise).deriveState(run).phase.id
|
|
519
|
+
: undefined;
|
|
520
|
+
const index = await loadReferenceIndex();
|
|
521
|
+
let entry = requestedTopic.trim() && requestedTopic.trim().toLowerCase() !== "all"
|
|
522
|
+
? await findReference(requestedTopic)
|
|
523
|
+
: undefined;
|
|
524
|
+
|
|
525
|
+
if (requestedTopic.trim() && requestedTopic.trim().toLowerCase() !== "all" && !entry) {
|
|
526
|
+
throw new Error(`No packaged AHEAD reference matches ${requestedTopic.trim()}`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (!entry) {
|
|
530
|
+
const recommended = requestedTopic.trim().toLowerCase() === "all"
|
|
531
|
+
? index.references
|
|
532
|
+
: await relevantReferences(phase);
|
|
533
|
+
const browseAll = "Browse all packaged AHEAD Markdown";
|
|
534
|
+
const selected = await ctx.ui.select(
|
|
535
|
+
phase ? `AHEAD guidance · ${phase}` : "AHEAD framework guidance",
|
|
536
|
+
[...recommended.map((candidate) => candidate.title), ...(recommended.length < index.references.length ? [browseAll] : [])],
|
|
537
|
+
);
|
|
538
|
+
if (!selected) return;
|
|
539
|
+
if (selected === browseAll) return showAheadGuide(ctx, "all");
|
|
540
|
+
entry = recommended.find((candidate) => candidate.title === selected);
|
|
541
|
+
}
|
|
542
|
+
if (!entry) return;
|
|
543
|
+
|
|
544
|
+
await showReferenceViewer(ctx, `AHEAD reference · ${entry.title}`, await readReference(entry));
|
|
545
|
+
}
|
|
546
|
+
|
|
419
547
|
async function startRun(ctx: ExtensionCommandContext, requestedTitle: string): Promise<Run | undefined> {
|
|
420
548
|
const engine = await enginePromise;
|
|
421
549
|
const store = storeFor(ctx);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMarkdownTheme,
|
|
3
|
+
type ExtensionCommandContext,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import {
|
|
6
|
+
Markdown,
|
|
7
|
+
matchesKey,
|
|
8
|
+
truncateToWidth,
|
|
9
|
+
visibleWidth,
|
|
10
|
+
} from "@earendil-works/pi-tui";
|
|
11
|
+
|
|
12
|
+
export async function showReferenceViewer(
|
|
13
|
+
ctx: ExtensionCommandContext,
|
|
14
|
+
title: string,
|
|
15
|
+
content: string,
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
18
|
+
const markdown = new Markdown(content.trim(), 0, 0, getMarkdownTheme());
|
|
19
|
+
let scrollOffset = 0;
|
|
20
|
+
let pageSize = 10;
|
|
21
|
+
let totalLines = 0;
|
|
22
|
+
|
|
23
|
+
const component = {
|
|
24
|
+
render(width: number): string[] {
|
|
25
|
+
const innerWidth = Math.max(20, width - 2);
|
|
26
|
+
const contentWidth = Math.max(18, innerWidth - 2);
|
|
27
|
+
const rendered = markdown.render(contentWidth);
|
|
28
|
+
totalLines = rendered.length;
|
|
29
|
+
pageSize = Math.max(6, Math.min(30, tui.terminal.rows - 7));
|
|
30
|
+
const maxOffset = Math.max(0, totalLines - pageSize);
|
|
31
|
+
scrollOffset = Math.min(scrollOffset, maxOffset);
|
|
32
|
+
const visible = rendered.slice(scrollOffset, scrollOffset + pageSize);
|
|
33
|
+
const remaining = Math.max(0, totalLines - pageSize - scrollOffset);
|
|
34
|
+
const border = (value: string) => theme.fg("border", value);
|
|
35
|
+
const pad = (value: string): string => {
|
|
36
|
+
const truncated = truncateToWidth(value, innerWidth, "…", true);
|
|
37
|
+
return `${truncated}${" ".repeat(Math.max(0, innerWidth - visibleWidth(truncated)))}`;
|
|
38
|
+
};
|
|
39
|
+
const lines = [
|
|
40
|
+
border(`╭${"─".repeat(innerWidth)}╮`),
|
|
41
|
+
`${border("│")}${pad(` ${theme.fg("accent", theme.bold(title))}`)}${border("│")}`,
|
|
42
|
+
`${border("│")}${pad(theme.fg("dim", ` ↑ ${scrollOffset} lines · ↓ ${remaining} lines`))}${border("│")}`,
|
|
43
|
+
];
|
|
44
|
+
for (const line of visible) lines.push(`${border("│")}${pad(` ${line}`)}${border("│")}`);
|
|
45
|
+
for (let index = visible.length; index < pageSize; index += 1) {
|
|
46
|
+
lines.push(`${border("│")}${pad("")}${border("│")}`);
|
|
47
|
+
}
|
|
48
|
+
lines.push(
|
|
49
|
+
`${border("│")}${pad(theme.fg("dim", " ↑↓ scroll · PgUp/PgDn page · Home/End jump · Esc close"))}${border("│")}`,
|
|
50
|
+
border(`╰${"─".repeat(innerWidth)}╯`),
|
|
51
|
+
);
|
|
52
|
+
return lines;
|
|
53
|
+
},
|
|
54
|
+
invalidate(): void {
|
|
55
|
+
markdown.invalidate();
|
|
56
|
+
},
|
|
57
|
+
handleInput(data: string): void {
|
|
58
|
+
const maxOffset = Math.max(0, totalLines - pageSize);
|
|
59
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) done();
|
|
60
|
+
else if (matchesKey(data, "up")) scrollOffset = Math.max(0, scrollOffset - 1);
|
|
61
|
+
else if (matchesKey(data, "down")) scrollOffset = Math.min(maxOffset, scrollOffset + 1);
|
|
62
|
+
else if (matchesKey(data, "pageUp")) scrollOffset = Math.max(0, scrollOffset - pageSize);
|
|
63
|
+
else if (matchesKey(data, "pageDown")) scrollOffset = Math.min(maxOffset, scrollOffset + pageSize);
|
|
64
|
+
else if (matchesKey(data, "home")) scrollOffset = 0;
|
|
65
|
+
else if (matchesKey(data, "end")) scrollOffset = maxOffset;
|
|
66
|
+
tui.requestRender();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return component;
|
|
70
|
+
});
|
|
71
|
+
}
|
package/src/reference.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
const referenceDirectory = fileURLToPath(new URL("../generated/reference/", import.meta.url));
|
|
5
|
+
|
|
6
|
+
export interface ReferenceEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
path: string;
|
|
9
|
+
title: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
phases: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ReferenceIndex {
|
|
15
|
+
generated_from: string[];
|
|
16
|
+
references: ReferenceEntry[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let indexPromise: Promise<ReferenceIndex> | undefined;
|
|
20
|
+
|
|
21
|
+
export async function loadReferenceIndex(): Promise<ReferenceIndex> {
|
|
22
|
+
indexPromise ??= readFile(`${referenceDirectory}index.json`, "utf8")
|
|
23
|
+
.then((content) => JSON.parse(content) as ReferenceIndex);
|
|
24
|
+
return indexPromise;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function relevantReferences(phaseId?: string): Promise<ReferenceEntry[]> {
|
|
28
|
+
const { references } = await loadReferenceIndex();
|
|
29
|
+
if (!phaseId) return references.filter((entry) => entry.phases.includes("*"));
|
|
30
|
+
return references.filter((entry) => entry.phases.includes("*") || entry.phases.includes(phaseId));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function findReference(topic: string): Promise<ReferenceEntry | undefined> {
|
|
34
|
+
const normalized = normalize(topic);
|
|
35
|
+
const { references } = await loadReferenceIndex();
|
|
36
|
+
return references.find((entry) =>
|
|
37
|
+
normalize(entry.id) === normalized
|
|
38
|
+
|| normalize(entry.path) === normalized
|
|
39
|
+
|| normalize(entry.title) === normalized
|
|
40
|
+
) ?? references.find((entry) =>
|
|
41
|
+
normalize(entry.id).includes(normalized)
|
|
42
|
+
|| normalize(entry.path).includes(normalized)
|
|
43
|
+
|| normalize(entry.title).includes(normalized)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function readReference(entry: ReferenceEntry): Promise<string> {
|
|
48
|
+
if (entry.path.includes("..") || entry.path.startsWith("/")) {
|
|
49
|
+
throw new Error(`Invalid packaged AHEAD reference path: ${entry.path}`);
|
|
50
|
+
}
|
|
51
|
+
return readFile(`${referenceDirectory}${entry.path}`, "utf8");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalize(value: string): string {
|
|
55
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
56
|
+
}
|