ahead-pi 0.3.1 → 0.5.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 +29 -1
- package/dist/ahead_wasm.wasm +0 -0
- package/generated/corrective-debugging/ai-audit.md +4 -2
- package/generated/corrective-debugging/ai-review.md +4 -2
- package/generated/corrective-debugging/characterize.md +4 -2
- package/generated/corrective-debugging/conclude.md +4 -2
- package/generated/corrective-debugging/correction.md +4 -2
- package/generated/corrective-debugging/deploy.md +4 -2
- package/generated/corrective-debugging/human-review.md +4 -2
- package/generated/corrective-debugging/implement.md +4 -2
- package/generated/corrective-debugging/investigate.md +4 -2
- package/generated/corrective-debugging/model.md +4 -2
- package/generated/corrective-debugging/outcome.md +4 -2
- package/generated/corrective-debugging/plan.md +4 -2
- package/generated/corrective-debugging/verify.md +4 -2
- package/generated/decision/compare.md +4 -2
- package/generated/decision/criteria.md +4 -2
- package/generated/decision/decide.md +4 -2
- package/generated/decision/frame.md +4 -2
- package/generated/decision/options.md +4 -2
- package/generated/decision/publish.md +4 -2
- package/generated/decision/research.md +4 -2
- package/generated/internal-improvement/ai-audit.md +4 -2
- package/generated/internal-improvement/ai-review.md +4 -2
- package/generated/internal-improvement/baseline.md +4 -2
- package/generated/internal-improvement/decision.md +4 -2
- package/generated/internal-improvement/deploy.md +4 -2
- package/generated/internal-improvement/human-review.md +4 -2
- package/generated/internal-improvement/implement.md +4 -2
- package/generated/internal-improvement/invariants.md +4 -2
- package/generated/internal-improvement/options.md +4 -2
- package/generated/internal-improvement/outcome.md +4 -2
- package/generated/internal-improvement/plan.md +4 -2
- package/generated/internal-improvement/target.md +4 -2
- package/generated/internal-improvement/verify.md +4 -2
- package/generated/investigation/bound.md +4 -2
- package/generated/investigation/conclude.md +4 -2
- package/generated/investigation/explore.md +4 -2
- package/generated/investigation/frame.md +4 -2
- package/generated/investigation/gather.md +4 -2
- package/generated/investigation/synthesize.md +4 -2
- package/generated/operational-stabilization/assess.md +4 -2
- package/generated/operational-stabilization/execute-observe.md +4 -2
- package/generated/operational-stabilization/monitor.md +4 -2
- package/generated/operational-stabilization/outcome.md +4 -2
- package/generated/operational-stabilization/respond.md +4 -2
- package/generated/operational-stabilization/verify-recovery.md +4 -2
- package/generated/product-change/ai-audit.md +4 -2
- package/generated/product-change/ai-review.md +4 -2
- package/generated/product-change/decision.md +4 -2
- package/generated/product-change/define.md +4 -2
- package/generated/product-change/deploy.md +4 -2
- package/generated/product-change/human-review.md +4 -2
- package/generated/product-change/implement.md +4 -2
- package/generated/product-change/options.md +4 -2
- package/generated/product-change/outcome.md +4 -2
- package/generated/product-change/plan.md +4 -2
- package/generated/product-change/questions.md +4 -2
- package/generated/product-change/research.md +4 -2
- package/generated/product-change/verify.md +4 -2
- package/generated/reference/CONSTITUTION.md +4 -0
- package/generated/reference/docs/evidence/README.md +1 -0
- package/generated/reference/docs/evidence/research-map.md +12 -0
- package/generated/reference/docs/evidence/sources/tigerstyle.md +65 -0
- package/generated/reference/docs/guide/README.md +2 -1
- package/generated/reference/docs/guide/engineering-practice.md +35 -13
- package/generated/reference/docs/guide/work-items.md +65 -0
- package/generated/reference/docs/guide/workflows/README.md +1 -0
- package/generated/reference/index.json +28 -0
- package/generated/skills/diagnosing-bugs/LICENSE.ahead +21 -0
- package/generated/skills/diagnosing-bugs/LICENSE.mattpocock +21 -0
- package/generated/skills/diagnosing-bugs/SKILL.md +126 -0
- package/generated/skills/manifest.json +21 -0
- package/generated/skills/research/LICENSE.ahead +21 -0
- package/generated/skills/research/LICENSE.mattpocock +21 -0
- package/generated/skills/research/SKILL.md +116 -0
- package/generated/skills/to-tickets/LICENSE.ahead +21 -0
- package/generated/skills/to-tickets/LICENSE.mattpocock +21 -0
- package/generated/skills/to-tickets/SKILL.md +132 -0
- package/package.json +4 -1
- package/src/config.ts +171 -0
- package/src/engine.ts +2 -0
- package/src/guidance.ts +60 -6
- package/src/index.ts +540 -9
- package/src/storage.ts +153 -2
- package/src/types.ts +24 -1
package/src/config.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { AheadProjectConfig, RunStore } from "./storage.js";
|
|
2
|
+
import type { PhaseDefinition, WorkflowDefinition } from "./types.js";
|
|
3
|
+
|
|
4
|
+
interface ProjectConfigCommandContext {
|
|
5
|
+
readonly hasUI: boolean;
|
|
6
|
+
readonly ui: {
|
|
7
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
8
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
9
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const recommendedWorkItemBoundaries: Readonly<Record<string, string>> = {
|
|
14
|
+
"product-change": "implement",
|
|
15
|
+
"corrective-debugging": "implement",
|
|
16
|
+
"internal-improvement": "implement",
|
|
17
|
+
decision: "publish",
|
|
18
|
+
investigation: "conclude",
|
|
19
|
+
"operational-stabilization": "outcome",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function runProjectConfigWizard(
|
|
23
|
+
ctx: ProjectConfigCommandContext,
|
|
24
|
+
store: RunStore,
|
|
25
|
+
workflows: WorkflowDefinition[],
|
|
26
|
+
overwrite: boolean,
|
|
27
|
+
): Promise<boolean> {
|
|
28
|
+
if (!ctx.hasUI) {
|
|
29
|
+
throw new Error("/ahead-config requires interactive or RPC UI support");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const recommended = "Recommended planning boundaries";
|
|
33
|
+
const custom = "Choose a boundary for each workflow";
|
|
34
|
+
const optional = "Do not require work items";
|
|
35
|
+
const choice = await ctx.ui.select("Configure AHEAD for this project", [
|
|
36
|
+
recommended,
|
|
37
|
+
custom,
|
|
38
|
+
optional,
|
|
39
|
+
]);
|
|
40
|
+
if (!choice) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let config: AheadProjectConfig;
|
|
45
|
+
if (choice === recommended) {
|
|
46
|
+
config = recommendedProjectConfig(workflows);
|
|
47
|
+
} else if (choice === custom) {
|
|
48
|
+
const selected = await chooseWorkItemBoundaries(ctx, workflows);
|
|
49
|
+
if (!selected) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
config = projectConfig(selected);
|
|
53
|
+
} else if (choice === optional) {
|
|
54
|
+
config = projectConfig({});
|
|
55
|
+
} else {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const confirmed = await ctx.ui.confirm(
|
|
60
|
+
overwrite ? "Replace the AHEAD project configuration?" : "Create this AHEAD configuration?",
|
|
61
|
+
[
|
|
62
|
+
`Project: ${store.projectRoot}`,
|
|
63
|
+
"",
|
|
64
|
+
JSON.stringify(config, null, 2),
|
|
65
|
+
"",
|
|
66
|
+
...(overwrite
|
|
67
|
+
? ["The existing file will be preserved under .ahead/backups/ before replacement."]
|
|
68
|
+
: []),
|
|
69
|
+
"Existing runs keep the policy snapshot with which they started.",
|
|
70
|
+
].join("\n"),
|
|
71
|
+
);
|
|
72
|
+
if (!confirmed) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const saved = await store.saveProjectConfig(config, { overwrite });
|
|
77
|
+
ctx.ui.notify(
|
|
78
|
+
[
|
|
79
|
+
`Saved AHEAD project configuration: ${saved.path}`,
|
|
80
|
+
...(saved.backup ? [`Previous file preserved at: ${saved.backup}`] : []),
|
|
81
|
+
"The policy applies to new AHEAD runs; active and saved runs are unchanged.",
|
|
82
|
+
].join("\n"),
|
|
83
|
+
"info",
|
|
84
|
+
);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function recommendedProjectConfig(workflows: WorkflowDefinition[]): AheadProjectConfig {
|
|
89
|
+
const boundaries: Record<string, string> = {};
|
|
90
|
+
for (const workflow of workflows) {
|
|
91
|
+
const phaseId = recommendedWorkItemBoundaries[workflow.id];
|
|
92
|
+
if (!phaseId) {
|
|
93
|
+
throw new Error(`no recommended work-item boundary exists for workflow ${workflow.id}`);
|
|
94
|
+
}
|
|
95
|
+
if (!workflow.phases.some((phase) => phase.id === phaseId)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`recommended work-item boundary ${phaseId} does not exist in workflow ${workflow.id}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
boundaries[workflow.id] = phaseId;
|
|
101
|
+
}
|
|
102
|
+
return projectConfig(boundaries);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function projectConfigMarkdown(config: AheadProjectConfig): string {
|
|
106
|
+
return [
|
|
107
|
+
"# AHEAD project configuration",
|
|
108
|
+
"",
|
|
109
|
+
"This policy is read when a new run starts and is then snapshotted into that run.",
|
|
110
|
+
"",
|
|
111
|
+
"```json",
|
|
112
|
+
JSON.stringify(config, null, 2),
|
|
113
|
+
"```",
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function projectConfigIssues(
|
|
118
|
+
config: AheadProjectConfig,
|
|
119
|
+
workflows: WorkflowDefinition[],
|
|
120
|
+
): string[] {
|
|
121
|
+
const issues: string[] = [];
|
|
122
|
+
for (const [workflowId, phaseId] of Object.entries(config.work_items.required_before_phase)) {
|
|
123
|
+
const workflow = workflows.find((candidate) => candidate.id === workflowId);
|
|
124
|
+
if (!workflow) {
|
|
125
|
+
issues.push(`work-item policy names unknown workflow ${workflowId}`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (phaseId === workflow.initial_phase) {
|
|
129
|
+
issues.push(`work-item boundary for ${workflowId} cannot be its initial phase ${phaseId}`);
|
|
130
|
+
} else if (!workflow.phases.some((phase) => phase.id === phaseId)) {
|
|
131
|
+
issues.push(`work-item policy for ${workflowId} names unknown phase ${phaseId}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return issues;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function chooseWorkItemBoundaries(
|
|
138
|
+
ctx: ProjectConfigCommandContext,
|
|
139
|
+
workflows: WorkflowDefinition[],
|
|
140
|
+
): Promise<Record<string, string> | undefined> {
|
|
141
|
+
const boundaries: Record<string, string> = {};
|
|
142
|
+
for (const workflow of workflows) {
|
|
143
|
+
const phases = workflow.phases.filter((phase) => phase.id !== workflow.initial_phase);
|
|
144
|
+
const notRequired = "Do not require a work item";
|
|
145
|
+
const options = [notRequired, ...phases.map(phaseOption)];
|
|
146
|
+
const selected = await ctx.ui.select(`Work-item policy · ${workflow.title}`, options);
|
|
147
|
+
if (!selected) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
if (selected === notRequired) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const phase = phases.find((candidate) => phaseOption(candidate) === selected);
|
|
154
|
+
if (!phase) {
|
|
155
|
+
throw new Error(`unknown work-item boundary selected for ${workflow.id}`);
|
|
156
|
+
}
|
|
157
|
+
boundaries[workflow.id] = phase.id;
|
|
158
|
+
}
|
|
159
|
+
return boundaries;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function phaseOption(phase: PhaseDefinition): string {
|
|
163
|
+
return `Require before ${phase.title} (${phase.id})`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function projectConfig(requiredBeforePhase: Record<string, string>): AheadProjectConfig {
|
|
167
|
+
return {
|
|
168
|
+
api_version: "ahead.config/v0",
|
|
169
|
+
work_items: { required_before_phase: requiredBeforePhase },
|
|
170
|
+
};
|
|
171
|
+
}
|
package/src/engine.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
EngineErrorShape,
|
|
6
6
|
EventAction,
|
|
7
7
|
Run,
|
|
8
|
+
RunPolicy,
|
|
8
9
|
RunState,
|
|
9
10
|
WorkflowDefinition,
|
|
10
11
|
} from "./types.js";
|
|
@@ -61,6 +62,7 @@ export class AheadEngine {
|
|
|
61
62
|
owner: Actor;
|
|
62
63
|
timestamp: string;
|
|
63
64
|
workflow_id: string;
|
|
65
|
+
policy?: RunPolicy;
|
|
64
66
|
}): Run {
|
|
65
67
|
return this.call("create_run", input);
|
|
66
68
|
}
|
package/src/guidance.ts
CHANGED
|
@@ -424,6 +424,13 @@ export function nextAction(state: RunState, workflow: WorkflowDefinition): Guide
|
|
|
424
424
|
return { actor: "human", label: `Review evidence and accept: ${state.gate.title}` };
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
if (state.work_item_required_for_next_phase) {
|
|
428
|
+
return {
|
|
429
|
+
actor: "human",
|
|
430
|
+
label: `Link or create the required work item before ${state.phase.next ?? "continuing"}`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
427
434
|
const nextPhase = workflow.phases.find((phase) => phase.id === state.phase.next);
|
|
428
435
|
return {
|
|
429
436
|
actor: "human",
|
|
@@ -463,8 +470,19 @@ export function buildHeaderLines(
|
|
|
463
470
|
: "No required artifact";
|
|
464
471
|
const action = nextAction(state, workflow);
|
|
465
472
|
|
|
473
|
+
const workItem = state.work_item
|
|
474
|
+
? `Work item: ${state.work_item.url}`
|
|
475
|
+
: state.policy.work_items.required_before_phase
|
|
476
|
+
? `Work item: Required before ${
|
|
477
|
+
workflow.phases.find(
|
|
478
|
+
(phase) => phase.id === state.policy.work_items.required_before_phase,
|
|
479
|
+
)?.title ?? state.policy.work_items.required_before_phase
|
|
480
|
+
}`
|
|
481
|
+
: undefined;
|
|
482
|
+
|
|
466
483
|
return [
|
|
467
484
|
`AHEAD · ${workflow.title} · ${position.current}/${position.total} · ${state.phase.title}`,
|
|
485
|
+
...(workItem ? [workItem] : []),
|
|
468
486
|
`Goal: ${guide.objective}`,
|
|
469
487
|
`Required: ${checklist}`,
|
|
470
488
|
`Next: ${action.actor === "human" ? "You" : "AI"} → ${action.label}`,
|
|
@@ -478,6 +496,17 @@ export function buildArtifactTemplate(
|
|
|
478
496
|
title: string,
|
|
479
497
|
): string {
|
|
480
498
|
const prompts = promptsForArtifact(state.workflow_id, state.phase.id, kind);
|
|
499
|
+
const fields = prompts.flatMap((prompt, index) => {
|
|
500
|
+
const number = index + 1;
|
|
501
|
+
return [
|
|
502
|
+
`### ${number}. ${prompt}`,
|
|
503
|
+
`<!-- AHEAD-FIELD:${number}:BEGIN -->`,
|
|
504
|
+
"<!-- Required. Replace this comment with your response. Use “Not applicable — reason” only when justified. -->",
|
|
505
|
+
"",
|
|
506
|
+
`<!-- AHEAD-FIELD:${number}:END -->`,
|
|
507
|
+
"",
|
|
508
|
+
];
|
|
509
|
+
});
|
|
481
510
|
return [
|
|
482
511
|
`# ${title}`,
|
|
483
512
|
"",
|
|
@@ -485,17 +514,42 @@ export function buildArtifactTemplate(
|
|
|
485
514
|
`Phase: ${state.phase.id} (visit ${state.phase.visit})`,
|
|
486
515
|
`Artifact: ${kind}`,
|
|
487
516
|
"",
|
|
488
|
-
"##
|
|
489
|
-
"",
|
|
490
|
-
...prompts.map((prompt) => `- ${prompt}`),
|
|
491
|
-
"",
|
|
492
|
-
"## Record",
|
|
517
|
+
"## Required responses",
|
|
493
518
|
"",
|
|
494
|
-
"
|
|
519
|
+
"Complete every field in your own words. Preserve evidence, uncertainty, and rationale; form completion does not replace accountable judgment.",
|
|
495
520
|
"",
|
|
521
|
+
...fields,
|
|
496
522
|
].join("\n");
|
|
497
523
|
}
|
|
498
524
|
|
|
525
|
+
export function validateArtifactForm(content: string, prompts: string[]): string[] {
|
|
526
|
+
const errors: string[] = [];
|
|
527
|
+
for (const [index, prompt] of prompts.entries()) {
|
|
528
|
+
const number = index + 1;
|
|
529
|
+
const begin = `<!-- AHEAD-FIELD:${number}:BEGIN -->`;
|
|
530
|
+
const end = `<!-- AHEAD-FIELD:${number}:END -->`;
|
|
531
|
+
const beginIndex = content.indexOf(begin);
|
|
532
|
+
const endIndex = beginIndex < 0 ? -1 : content.indexOf(end, beginIndex + begin.length);
|
|
533
|
+
if (beginIndex < 0 || endIndex < 0) {
|
|
534
|
+
errors.push(`${prompt} (the required form field is missing)`);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const response = content
|
|
539
|
+
.slice(beginIndex + begin.length, endIndex)
|
|
540
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
541
|
+
.trim();
|
|
542
|
+
if (!response) {
|
|
543
|
+
errors.push(prompt);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (/^(?:n\/?a|not applicable)\s*[.!]?$/i.test(response)) {
|
|
547
|
+
errors.push(`${prompt} (explain why it is not applicable)`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return errors;
|
|
551
|
+
}
|
|
552
|
+
|
|
499
553
|
function formatArtifactStatus(artifact: ArtifactState): string {
|
|
500
554
|
const owner = artifact.actor === "ai" ? "AI" : artifact.actor === "human" ? "you" : "you/AI";
|
|
501
555
|
return `${artifact.present ? "✓" : "○"} ${artifact.title} [${owner}]`;
|