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.
Files changed (86) hide show
  1. package/README.md +29 -1
  2. package/dist/ahead_wasm.wasm +0 -0
  3. package/generated/corrective-debugging/ai-audit.md +4 -2
  4. package/generated/corrective-debugging/ai-review.md +4 -2
  5. package/generated/corrective-debugging/characterize.md +4 -2
  6. package/generated/corrective-debugging/conclude.md +4 -2
  7. package/generated/corrective-debugging/correction.md +4 -2
  8. package/generated/corrective-debugging/deploy.md +4 -2
  9. package/generated/corrective-debugging/human-review.md +4 -2
  10. package/generated/corrective-debugging/implement.md +4 -2
  11. package/generated/corrective-debugging/investigate.md +4 -2
  12. package/generated/corrective-debugging/model.md +4 -2
  13. package/generated/corrective-debugging/outcome.md +4 -2
  14. package/generated/corrective-debugging/plan.md +4 -2
  15. package/generated/corrective-debugging/verify.md +4 -2
  16. package/generated/decision/compare.md +4 -2
  17. package/generated/decision/criteria.md +4 -2
  18. package/generated/decision/decide.md +4 -2
  19. package/generated/decision/frame.md +4 -2
  20. package/generated/decision/options.md +4 -2
  21. package/generated/decision/publish.md +4 -2
  22. package/generated/decision/research.md +4 -2
  23. package/generated/internal-improvement/ai-audit.md +4 -2
  24. package/generated/internal-improvement/ai-review.md +4 -2
  25. package/generated/internal-improvement/baseline.md +4 -2
  26. package/generated/internal-improvement/decision.md +4 -2
  27. package/generated/internal-improvement/deploy.md +4 -2
  28. package/generated/internal-improvement/human-review.md +4 -2
  29. package/generated/internal-improvement/implement.md +4 -2
  30. package/generated/internal-improvement/invariants.md +4 -2
  31. package/generated/internal-improvement/options.md +4 -2
  32. package/generated/internal-improvement/outcome.md +4 -2
  33. package/generated/internal-improvement/plan.md +4 -2
  34. package/generated/internal-improvement/target.md +4 -2
  35. package/generated/internal-improvement/verify.md +4 -2
  36. package/generated/investigation/bound.md +4 -2
  37. package/generated/investigation/conclude.md +4 -2
  38. package/generated/investigation/explore.md +4 -2
  39. package/generated/investigation/frame.md +4 -2
  40. package/generated/investigation/gather.md +4 -2
  41. package/generated/investigation/synthesize.md +4 -2
  42. package/generated/operational-stabilization/assess.md +4 -2
  43. package/generated/operational-stabilization/execute-observe.md +4 -2
  44. package/generated/operational-stabilization/monitor.md +4 -2
  45. package/generated/operational-stabilization/outcome.md +4 -2
  46. package/generated/operational-stabilization/respond.md +4 -2
  47. package/generated/operational-stabilization/verify-recovery.md +4 -2
  48. package/generated/product-change/ai-audit.md +4 -2
  49. package/generated/product-change/ai-review.md +4 -2
  50. package/generated/product-change/decision.md +4 -2
  51. package/generated/product-change/define.md +4 -2
  52. package/generated/product-change/deploy.md +4 -2
  53. package/generated/product-change/human-review.md +4 -2
  54. package/generated/product-change/implement.md +4 -2
  55. package/generated/product-change/options.md +4 -2
  56. package/generated/product-change/outcome.md +4 -2
  57. package/generated/product-change/plan.md +4 -2
  58. package/generated/product-change/questions.md +4 -2
  59. package/generated/product-change/research.md +4 -2
  60. package/generated/product-change/verify.md +4 -2
  61. package/generated/reference/CONSTITUTION.md +4 -0
  62. package/generated/reference/docs/evidence/README.md +1 -0
  63. package/generated/reference/docs/evidence/research-map.md +12 -0
  64. package/generated/reference/docs/evidence/sources/tigerstyle.md +65 -0
  65. package/generated/reference/docs/guide/README.md +2 -1
  66. package/generated/reference/docs/guide/engineering-practice.md +35 -13
  67. package/generated/reference/docs/guide/work-items.md +65 -0
  68. package/generated/reference/docs/guide/workflows/README.md +1 -0
  69. package/generated/reference/index.json +28 -0
  70. package/generated/skills/diagnosing-bugs/LICENSE.ahead +21 -0
  71. package/generated/skills/diagnosing-bugs/LICENSE.mattpocock +21 -0
  72. package/generated/skills/diagnosing-bugs/SKILL.md +126 -0
  73. package/generated/skills/manifest.json +21 -0
  74. package/generated/skills/research/LICENSE.ahead +21 -0
  75. package/generated/skills/research/LICENSE.mattpocock +21 -0
  76. package/generated/skills/research/SKILL.md +116 -0
  77. package/generated/skills/to-tickets/LICENSE.ahead +21 -0
  78. package/generated/skills/to-tickets/LICENSE.mattpocock +21 -0
  79. package/generated/skills/to-tickets/SKILL.md +132 -0
  80. package/package.json +4 -1
  81. package/src/config.ts +171 -0
  82. package/src/engine.ts +2 -0
  83. package/src/guidance.ts +60 -6
  84. package/src/index.ts +540 -9
  85. package/src/storage.ts +153 -2
  86. 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
- "## What to cover",
489
- "",
490
- ...prompts.map((prompt) => `- ${prompt}`),
491
- "",
492
- "## Record",
517
+ "## Required responses",
493
518
  "",
494
- "<!-- Write this human-owned record in your own words. Preserve evidence, uncertainty, and rationale. -->",
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}]`;