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/storage.ts
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
copyFile,
|
|
5
|
+
mkdir,
|
|
6
|
+
readFile,
|
|
7
|
+
readdir,
|
|
8
|
+
rename,
|
|
9
|
+
rm,
|
|
10
|
+
unlink,
|
|
11
|
+
writeFile,
|
|
12
|
+
} from "node:fs/promises";
|
|
4
13
|
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
-
import type { Actor, Run } from "./types.js";
|
|
14
|
+
import type { Actor, Run, RunPolicy } from "./types.js";
|
|
6
15
|
|
|
7
16
|
interface CurrentRunPointer {
|
|
8
17
|
api_version: "ahead.current/v0";
|
|
9
18
|
run_id: string;
|
|
10
19
|
}
|
|
11
20
|
|
|
21
|
+
export interface AheadProjectConfig {
|
|
22
|
+
api_version: "ahead.config/v0";
|
|
23
|
+
work_items: {
|
|
24
|
+
required_before_phase: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ProjectConfigInspection =
|
|
29
|
+
| { status: "missing" }
|
|
30
|
+
| { status: "valid"; config: AheadProjectConfig; content: string }
|
|
31
|
+
| { status: "invalid"; content: string; error: string };
|
|
32
|
+
|
|
33
|
+
export interface SavedProjectConfig {
|
|
34
|
+
path: string;
|
|
35
|
+
backup?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
12
38
|
export class RunStore {
|
|
13
39
|
readonly aheadDirectory: string;
|
|
14
40
|
readonly projectRoot: string;
|
|
@@ -41,6 +67,74 @@ export class RunStore {
|
|
|
41
67
|
return parseRun(await readFile(this.runPath(runId), "utf8"));
|
|
42
68
|
}
|
|
43
69
|
|
|
70
|
+
async loadProjectConfig(): Promise<AheadProjectConfig> {
|
|
71
|
+
const inspection = await this.inspectProjectConfig();
|
|
72
|
+
if (inspection.status === "missing") {
|
|
73
|
+
return emptyProjectConfig();
|
|
74
|
+
}
|
|
75
|
+
if (inspection.status === "invalid") {
|
|
76
|
+
throw new Error(inspection.error);
|
|
77
|
+
}
|
|
78
|
+
return inspection.config;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async inspectProjectConfig(): Promise<ProjectConfigInspection> {
|
|
82
|
+
let content: string;
|
|
83
|
+
try {
|
|
84
|
+
content = await readFile(join(this.aheadDirectory, "config.json"), "utf8");
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (isMissing(error)) {
|
|
87
|
+
return { status: "missing" };
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return { status: "valid", config: parseProjectConfig(content), content };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return { status: "invalid", content, error: errorMessage(error) };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async saveProjectConfig(
|
|
99
|
+
config: AheadProjectConfig,
|
|
100
|
+
options: { overwrite: boolean },
|
|
101
|
+
): Promise<SavedProjectConfig> {
|
|
102
|
+
const validated = parseProjectConfig(JSON.stringify(config));
|
|
103
|
+
const configPath = join(this.aheadDirectory, "config.json");
|
|
104
|
+
const inspection = await this.inspectProjectConfig();
|
|
105
|
+
if (inspection.status !== "missing" && !options.overwrite) {
|
|
106
|
+
throw new Error(".ahead/config.json already exists; explicit replacement is required");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let backup: string | undefined;
|
|
110
|
+
if (inspection.status !== "missing") {
|
|
111
|
+
const timestamp = new Date().toISOString().replaceAll(/[-:.]/g, "");
|
|
112
|
+
const backupPath = join(
|
|
113
|
+
this.aheadDirectory,
|
|
114
|
+
"backups",
|
|
115
|
+
`config-${timestamp}-${randomUUID().slice(0, 8)}.json`,
|
|
116
|
+
);
|
|
117
|
+
await mkdir(dirname(backupPath), { recursive: true });
|
|
118
|
+
await copyFile(configPath, backupPath);
|
|
119
|
+
backup = relative(this.projectRoot, backupPath);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await atomicJson(configPath, validated);
|
|
123
|
+
return {
|
|
124
|
+
path: relative(this.projectRoot, configPath),
|
|
125
|
+
...(backup ? { backup } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async policyForWorkflow(workflowId: string): Promise<RunPolicy> {
|
|
130
|
+
const config = await this.loadProjectConfig();
|
|
131
|
+
return {
|
|
132
|
+
work_items: {
|
|
133
|
+
required_before_phase: config.work_items.required_before_phase[workflowId] ?? null,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
44
138
|
async save(run: Run, makeCurrent = true): Promise<void> {
|
|
45
139
|
await atomicJson(this.runPath(run.id), run);
|
|
46
140
|
if (makeCurrent) {
|
|
@@ -218,6 +312,55 @@ function parseRun(content: string): Run {
|
|
|
218
312
|
return value;
|
|
219
313
|
}
|
|
220
314
|
|
|
315
|
+
function emptyProjectConfig(): AheadProjectConfig {
|
|
316
|
+
return {
|
|
317
|
+
api_version: "ahead.config/v0",
|
|
318
|
+
work_items: { required_before_phase: {} },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function parseProjectConfig(content: string): AheadProjectConfig {
|
|
323
|
+
const value: unknown = JSON.parse(content);
|
|
324
|
+
if (!isRecord(value) || value.api_version !== "ahead.config/v0") {
|
|
325
|
+
throw new Error("invalid .ahead/config.json: expected api_version ahead.config/v0");
|
|
326
|
+
}
|
|
327
|
+
if (!hasOnlyKeys(value, ["api_version", "work_items"])) {
|
|
328
|
+
throw new Error("invalid .ahead/config.json: unknown top-level property");
|
|
329
|
+
}
|
|
330
|
+
if (value.work_items === undefined) {
|
|
331
|
+
return emptyProjectConfig();
|
|
332
|
+
}
|
|
333
|
+
if (!isRecord(value.work_items)) {
|
|
334
|
+
throw new Error("invalid .ahead/config.json: work_items must be an object");
|
|
335
|
+
}
|
|
336
|
+
if (!hasOnlyKeys(value.work_items, ["required_before_phase"])) {
|
|
337
|
+
throw new Error("invalid .ahead/config.json: unknown work_items property");
|
|
338
|
+
}
|
|
339
|
+
const required = value.work_items.required_before_phase;
|
|
340
|
+
if (required === undefined) {
|
|
341
|
+
return emptyProjectConfig();
|
|
342
|
+
}
|
|
343
|
+
if (!isRecord(required)) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
"invalid .ahead/config.json: work_items.required_before_phase must be an object",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const requiredBeforePhase = Object.fromEntries(
|
|
349
|
+
Object.entries(required).map(([workflow, phase]) => {
|
|
350
|
+
if (!workflow.trim() || typeof phase !== "string" || !phase.trim()) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
"invalid .ahead/config.json: every work-item boundary needs a workflow and phase",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return [workflow, phase];
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
return {
|
|
359
|
+
api_version: "ahead.config/v0",
|
|
360
|
+
work_items: { required_before_phase: requiredBeforePhase },
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
221
364
|
function isRun(value: unknown): value is Run {
|
|
222
365
|
return (
|
|
223
366
|
isRecord(value) &&
|
|
@@ -234,3 +377,11 @@ function isRun(value: unknown): value is Run {
|
|
|
234
377
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
235
378
|
return typeof value === "object" && value !== null;
|
|
236
379
|
}
|
|
380
|
+
|
|
381
|
+
function hasOnlyKeys(value: Record<string, unknown>, allowed: string[]): boolean {
|
|
382
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function errorMessage(error: unknown): string {
|
|
386
|
+
return error instanceof Error ? error.message : String(error);
|
|
387
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,17 @@ export interface Actor {
|
|
|
7
7
|
identity: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface WorkItem {
|
|
11
|
+
provider: string;
|
|
12
|
+
url: string;
|
|
13
|
+
external_id?: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RunPolicy {
|
|
18
|
+
work_items: { required_before_phase: string | null };
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
export interface ArtifactDefinition {
|
|
11
22
|
kind: string;
|
|
12
23
|
title: string;
|
|
@@ -40,7 +51,13 @@ export interface Event {
|
|
|
40
51
|
sequence: number;
|
|
41
52
|
timestamp: string;
|
|
42
53
|
actor: Actor;
|
|
43
|
-
type:
|
|
54
|
+
type:
|
|
55
|
+
| "run_started"
|
|
56
|
+
| "work_item_linked"
|
|
57
|
+
| "artifact_recorded"
|
|
58
|
+
| "gate_accepted"
|
|
59
|
+
| "phase_transitioned"
|
|
60
|
+
| "run_closed";
|
|
44
61
|
phase?: string;
|
|
45
62
|
kind?: string;
|
|
46
63
|
path?: string;
|
|
@@ -49,6 +66,7 @@ export interface Event {
|
|
|
49
66
|
to?: string;
|
|
50
67
|
direction?: "advance" | "return";
|
|
51
68
|
reason?: string;
|
|
69
|
+
work_item?: WorkItem;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
export interface Run {
|
|
@@ -58,6 +76,7 @@ export interface Run {
|
|
|
58
76
|
workflow_id: string;
|
|
59
77
|
workflow_version: string;
|
|
60
78
|
owner: string;
|
|
79
|
+
policy?: RunPolicy;
|
|
61
80
|
events: Event[];
|
|
62
81
|
}
|
|
63
82
|
|
|
@@ -72,6 +91,9 @@ export interface RunState {
|
|
|
72
91
|
title: string;
|
|
73
92
|
workflow_id: string;
|
|
74
93
|
workflow_version: string;
|
|
94
|
+
policy: RunPolicy;
|
|
95
|
+
work_item: WorkItem | null;
|
|
96
|
+
work_item_required_for_next_phase: boolean;
|
|
75
97
|
phase: { id: string; title: string; visit: number; next: string | null };
|
|
76
98
|
artifacts: ArtifactState[];
|
|
77
99
|
gate: { id: string; title: string; accepted: boolean; accepted_by: Actor | null };
|
|
@@ -88,6 +110,7 @@ export interface EngineErrorShape {
|
|
|
88
110
|
}
|
|
89
111
|
|
|
90
112
|
export type EventAction =
|
|
113
|
+
| { type: "work_item_linked"; work_item: WorkItem }
|
|
91
114
|
| { type: "artifact_recorded"; phase: string; kind: string; path: string }
|
|
92
115
|
| { type: "gate_accepted"; phase: string; gate: string }
|
|
93
116
|
| {
|