@warpgogol/forge 0.27.0 → 1.0.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/AGENTS.md +17 -0
- package/README.md +4 -4
- package/README.uk.md +4 -4
- package/dist/bin/cli.js +1 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/os/core/core.module.d.ts.map +1 -1
- package/dist/os/core/core.module.js +34 -0
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/mission/handlers/archive.d.ts.map +1 -1
- package/dist/os/mission/handlers/archive.js +77 -5
- package/dist/os/mission/handlers/archive.js.map +1 -1
- package/dist/os/notes/index.d.ts +2 -0
- package/dist/os/notes/index.d.ts.map +1 -0
- package/dist/os/notes/index.js +10 -0
- package/dist/os/notes/index.js.map +1 -0
- package/dist/os/notes/notes.module.d.ts +3 -0
- package/dist/os/notes/notes.module.d.ts.map +1 -0
- package/dist/os/notes/notes.module.js +83 -0
- package/dist/os/notes/notes.module.js.map +1 -0
- package/dist/os/program/discovery.d.ts +61 -0
- package/dist/os/program/discovery.d.ts.map +1 -0
- package/dist/os/program/discovery.js +236 -0
- package/dist/os/program/discovery.js.map +1 -0
- package/dist/os/program/handlers/complete.d.ts +12 -0
- package/dist/os/program/handlers/complete.d.ts.map +1 -0
- package/dist/os/program/handlers/complete.js +344 -0
- package/dist/os/program/handlers/complete.js.map +1 -0
- package/dist/os/program/handlers/lease.d.ts +13 -0
- package/dist/os/program/handlers/lease.d.ts.map +1 -0
- package/dist/os/program/handlers/lease.js +322 -0
- package/dist/os/program/handlers/lease.js.map +1 -0
- package/dist/os/program/handlers/seal.d.ts +12 -0
- package/dist/os/program/handlers/seal.d.ts.map +1 -0
- package/dist/os/program/handlers/seal.js +265 -0
- package/dist/os/program/handlers/seal.js.map +1 -0
- package/dist/os/program/handlers/validate.d.ts +14 -0
- package/dist/os/program/handlers/validate.d.ts.map +1 -0
- package/dist/os/program/handlers/validate.js +164 -0
- package/dist/os/program/handlers/validate.js.map +1 -0
- package/dist/os/program/lease.d.ts +39 -0
- package/dist/os/program/lease.d.ts.map +1 -0
- package/dist/os/program/lease.js +129 -0
- package/dist/os/program/lease.js.map +1 -0
- package/dist/os/program/program.module.d.ts +3 -0
- package/dist/os/program/program.module.d.ts.map +1 -0
- package/dist/os/program/program.module.js +201 -0
- package/dist/os/program/program.module.js.map +1 -0
- package/dist/os/program/schemas.d.ts +225 -0
- package/dist/os/program/schemas.d.ts.map +1 -0
- package/dist/os/program/schemas.js +174 -0
- package/dist/os/program/schemas.js.map +1 -0
- package/dist/os/program/state.d.ts +38 -0
- package/dist/os/program/state.d.ts.map +1 -0
- package/dist/os/program/state.js +189 -0
- package/dist/os/program/state.js.map +1 -0
- package/dist/os/rfc/handlers/archive.d.ts +1 -0
- package/dist/os/rfc/handlers/archive.d.ts.map +1 -1
- package/dist/os/rfc/handlers/archive.js +54 -0
- package/dist/os/rfc/handlers/archive.js.map +1 -1
- package/dist/os/rfc/handlers/validate.d.ts.map +1 -1
- package/dist/os/rfc/handlers/validate.js +5 -5
- package/dist/os/rfc/handlers/validate.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/onboarding/invariant-engine.d.ts.map +1 -1
- package/dist/src/onboarding/invariant-engine.js +137 -5
- package/dist/src/onboarding/invariant-engine.js.map +1 -1
- package/dist/src/profiles/profile-schema.d.ts +16 -1
- package/dist/src/profiles/profile-schema.d.ts.map +1 -1
- package/dist/src/profiles/profile-schema.js +16 -1
- package/dist/src/profiles/profile-schema.js.map +1 -1
- package/dist/src/profiles/stack-profile.d.ts +5 -0
- package/dist/src/profiles/stack-profile.d.ts.map +1 -1
- package/dist/src/profiles/stack-profile.js +3 -0
- package/dist/src/profiles/stack-profile.js.map +1 -1
- package/dist/src/validators/note-frontmatter-validate.d.ts +20 -0
- package/dist/src/validators/note-frontmatter-validate.d.ts.map +1 -0
- package/dist/src/validators/note-frontmatter-validate.js +117 -0
- package/dist/src/validators/note-frontmatter-validate.js.map +1 -0
- package/dist/src/validators/note-link-validate.d.ts +21 -0
- package/dist/src/validators/note-link-validate.d.ts.map +1 -0
- package/dist/src/validators/note-link-validate.js +121 -0
- package/dist/src/validators/note-link-validate.js.map +1 -0
- package/dist/src/validators/note-orphan-detect.d.ts +19 -0
- package/dist/src/validators/note-orphan-detect.d.ts.map +1 -0
- package/dist/src/validators/note-orphan-detect.js +123 -0
- package/dist/src/validators/note-orphan-detect.js.map +1 -0
- package/package.json +31 -15
- package/profiles/astro-typescript-turborepo.yaml +1 -1
- package/profiles/editframe.yaml +2 -2
- package/profiles/forge-shell.yaml +1 -1
- package/profiles/godot-csharp.yaml +388 -0
- package/profiles/obsidian-vault.yaml +103 -0
- package/profiles/phaser-turborepo.yaml +1 -1
- package/skills/fo/ef-onboard/SKILL.md +1 -1
- package/skills/fo/fo-idea/SKILL.md +2 -2
- package/skills/fo/fo-idea-implement/SKILL.md +1 -1
- package/skills/fo/fo-step-commit/SKILL.md +7 -5
- package/skills/shared/grilling/learned-principles.archive.md +212 -0
- package/skills/shared/grilling/learned-principles.md +0 -13
- package/skills/shared/grilling/qa-log.md +624 -0
- package/skills/shared/knowledge/learned-principles.md +99 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ProgramManifest, type ProgramPacket, type ProgramPacketIndexEntry } from "./schemas.ts";
|
|
2
|
+
export interface ProgramLocation {
|
|
3
|
+
programDir: string;
|
|
4
|
+
manifestPath: string;
|
|
5
|
+
manifest: ProgramManifest;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Discover a program by RFC id. Scans docs/plans/ for a program.yaml whose
|
|
9
|
+
* `program` field matches the given RFC id.
|
|
10
|
+
*/
|
|
11
|
+
export declare function discoverProgram(workspaceRoot: string, programRfc: string): ProgramLocation | null;
|
|
12
|
+
/**
|
|
13
|
+
* Parse a packet .md file: extract YAML frontmatter and validate against
|
|
14
|
+
* the forge/program-packet@1 schema.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parsePacketFile(packetPath: string): ProgramPacket;
|
|
17
|
+
/**
|
|
18
|
+
* Find a packet by packetId within a program.
|
|
19
|
+
*/
|
|
20
|
+
export declare function findPacketEntry(manifest: ProgramManifest, packetId: string): ProgramPacketIndexEntry | null;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the full path to a packet file.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolvePacketPath(programDir: string, entry: ProgramPacketIndexEntry): string;
|
|
25
|
+
/**
|
|
26
|
+
* Compute SHA-256 hex digest of a file's bytes.
|
|
27
|
+
*/
|
|
28
|
+
export declare function fileSha256(filePath: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Verify normative source hashes for a packet. Returns violations.
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyNormativeSources(workspaceRoot: string, packet: ProgramPacket): Array<{
|
|
33
|
+
path: string;
|
|
34
|
+
expected: string;
|
|
35
|
+
actual: string;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function gitHead(workspaceRoot: string): string;
|
|
38
|
+
export declare function gitBranch(workspaceRoot: string): string;
|
|
39
|
+
export declare function gitIsClean(workspaceRoot: string): boolean;
|
|
40
|
+
export declare function gitAncestorOf(workspaceRoot: string, ancestor: string, descendant: string): boolean;
|
|
41
|
+
export declare function gitChangedFilesBetween(workspaceRoot: string, base: string, head: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Normalize a path to forward slashes for cross-platform comparison.
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizePath(p: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Check if a path matches a glob pattern. Supports:
|
|
48
|
+
* - `**` for recursive directory matching
|
|
49
|
+
* - `*` for single-level wildcard
|
|
50
|
+
* - exact prefix matching with trailing `/**`
|
|
51
|
+
*/
|
|
52
|
+
export declare function pathMatchesGlob(filePath: string, pattern: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Check if a file path is within the allowed files list.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isPathAllowed(filePath: string, allowedFiles: string[]): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a file path matches any forbidden pattern.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isPathForbidden(filePath: string, forbiddenFiles: string[]): boolean;
|
|
61
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../os/program/discovery.ts"],"names":[],"mappings":"AAoBA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC7B,MAAM,cAAc,CAAC;AAMtB,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA4BjG;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAgBjE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,GACf,uBAAuB,GAAG,IAAI,CAEhC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAE5F;AAMD;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,aAAa,GACpB,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB3D;AAMD,wBAAgB,OAAO,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAWT;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM,EAAE,CAWV;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/C;AAmBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAqB1E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAG/E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAGnF"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Discovery and parsing for program manifests and packet files.
|
|
4
|
+
Reads YAML frontmatter from program.yaml and packet .md files, validates
|
|
5
|
+
against schemas, and computes normative source hashes.</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not import from @warpgogol/* — forge must remain dependency-free.</item>
|
|
8
|
+
<item>Do not mutate files — discovery is read-only.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0856: initial program/packet discovery and parsing.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import { execSync } from "node:child_process";
|
|
19
|
+
import { parse as parseYaml } from "yaml";
|
|
20
|
+
import { programManifestSchema, programPacketSchema, } from "./schemas.js";
|
|
21
|
+
/**
|
|
22
|
+
* Discover a program by RFC id. Scans docs/plans/ for a program.yaml whose
|
|
23
|
+
* `program` field matches the given RFC id.
|
|
24
|
+
*/
|
|
25
|
+
export function discoverProgram(workspaceRoot, programRfc) {
|
|
26
|
+
const plansDir = path.join(workspaceRoot, "docs", "plans");
|
|
27
|
+
if (!fs.existsSync(plansDir))
|
|
28
|
+
return null;
|
|
29
|
+
for (const entry of fs.readdirSync(plansDir, { withFileTypes: true })) {
|
|
30
|
+
if (!entry.isDirectory())
|
|
31
|
+
continue;
|
|
32
|
+
const manifestPath = path.join(plansDir, entry.name, "program.yaml");
|
|
33
|
+
if (!fs.existsSync(manifestPath))
|
|
34
|
+
continue;
|
|
35
|
+
const raw = fs.readFileSync(manifestPath, "utf8");
|
|
36
|
+
const parsed = parseYaml(raw);
|
|
37
|
+
if (parsed.program !== programRfc)
|
|
38
|
+
continue;
|
|
39
|
+
const result = programManifestSchema.safeParse(parsed);
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
throw new Error(`PROGRAM-PACKET-01: malformed program manifest at ${manifestPath}: ${result.error.message}`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
programDir: path.join(plansDir, entry.name),
|
|
45
|
+
manifestPath,
|
|
46
|
+
manifest: result.data,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Packet parsing
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
/**
|
|
55
|
+
* Parse a packet .md file: extract YAML frontmatter and validate against
|
|
56
|
+
* the forge/program-packet@1 schema.
|
|
57
|
+
*/
|
|
58
|
+
export function parsePacketFile(packetPath) {
|
|
59
|
+
const raw = fs.readFileSync(packetPath, "utf8");
|
|
60
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
61
|
+
if (!match) {
|
|
62
|
+
throw new Error(`PROGRAM-PACKET-01: missing frontmatter in ${packetPath}`);
|
|
63
|
+
}
|
|
64
|
+
const fm = parseYaml(match[1]);
|
|
65
|
+
const result = programPacketSchema.safeParse(fm);
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
throw new Error(`PROGRAM-PACKET-01: malformed packet at ${packetPath}: ${result.error.message}`);
|
|
68
|
+
}
|
|
69
|
+
return result.data;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Find a packet by packetId within a program.
|
|
73
|
+
*/
|
|
74
|
+
export function findPacketEntry(manifest, packetId) {
|
|
75
|
+
return manifest.packets.find((p) => p.packetId === packetId) ?? null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the full path to a packet file.
|
|
79
|
+
*/
|
|
80
|
+
export function resolvePacketPath(programDir, entry) {
|
|
81
|
+
return path.join(programDir, entry.file);
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Normative source hashing
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Compute SHA-256 hex digest of a file's bytes.
|
|
88
|
+
*/
|
|
89
|
+
export function fileSha256(filePath) {
|
|
90
|
+
const bytes = fs.readFileSync(filePath);
|
|
91
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Verify normative source hashes for a packet. Returns violations.
|
|
95
|
+
*/
|
|
96
|
+
export function verifyNormativeSources(workspaceRoot, packet) {
|
|
97
|
+
const violations = [];
|
|
98
|
+
for (const source of packet.normativeSources) {
|
|
99
|
+
const fullPath = path.join(workspaceRoot, source.path);
|
|
100
|
+
if (!fs.existsSync(fullPath)) {
|
|
101
|
+
violations.push({
|
|
102
|
+
path: source.path,
|
|
103
|
+
expected: source.sha256,
|
|
104
|
+
actual: "<missing>",
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const actual = fileSha256(fullPath);
|
|
109
|
+
if (actual !== source.sha256) {
|
|
110
|
+
violations.push({
|
|
111
|
+
path: source.path,
|
|
112
|
+
expected: source.sha256,
|
|
113
|
+
actual,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return violations;
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Git helpers (read-only)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
export function gitHead(workspaceRoot) {
|
|
123
|
+
return execSync("git rev-parse HEAD", {
|
|
124
|
+
cwd: workspaceRoot,
|
|
125
|
+
encoding: "utf8",
|
|
126
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
127
|
+
}).trim();
|
|
128
|
+
}
|
|
129
|
+
export function gitBranch(workspaceRoot) {
|
|
130
|
+
return execSync("git branch --show-current", {
|
|
131
|
+
cwd: workspaceRoot,
|
|
132
|
+
encoding: "utf8",
|
|
133
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
134
|
+
}).trim();
|
|
135
|
+
}
|
|
136
|
+
export function gitIsClean(workspaceRoot) {
|
|
137
|
+
const status = execSync("git status --porcelain", {
|
|
138
|
+
cwd: workspaceRoot,
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
141
|
+
}).trim();
|
|
142
|
+
return status.length === 0;
|
|
143
|
+
}
|
|
144
|
+
export function gitAncestorOf(workspaceRoot, ancestor, descendant) {
|
|
145
|
+
try {
|
|
146
|
+
const result = execSync(`git merge-base --is-ancestor ${ancestor} ${descendant}`, {
|
|
147
|
+
cwd: workspaceRoot,
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
150
|
+
});
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export function gitChangedFilesBetween(workspaceRoot, base, head) {
|
|
158
|
+
try {
|
|
159
|
+
const output = execSync(`git diff --name-only ${base}..${head}`, {
|
|
160
|
+
cwd: workspaceRoot,
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
163
|
+
}).trim();
|
|
164
|
+
return output.length === 0 ? [] : output.split("\n");
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Path matching (cross-platform)
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
/**
|
|
174
|
+
* Normalize a path to forward slashes for cross-platform comparison.
|
|
175
|
+
*/
|
|
176
|
+
export function normalizePath(p) {
|
|
177
|
+
return p.replace(/\\/g, "/");
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Resolve ".." and "." segments in a path without touching the filesystem.
|
|
181
|
+
* Returns the lexical normalization (e.g. "a/b/../c" → "a/c").
|
|
182
|
+
*/
|
|
183
|
+
function resolveLexical(p) {
|
|
184
|
+
const segments = p.split("/");
|
|
185
|
+
const result = [];
|
|
186
|
+
for (const seg of segments) {
|
|
187
|
+
if (seg === "..") {
|
|
188
|
+
result.pop();
|
|
189
|
+
}
|
|
190
|
+
else if (seg !== "." && seg !== "") {
|
|
191
|
+
result.push(seg);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return result.join("/");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if a path matches a glob pattern. Supports:
|
|
198
|
+
* - `**` for recursive directory matching
|
|
199
|
+
* - `*` for single-level wildcard
|
|
200
|
+
* - exact prefix matching with trailing `/**`
|
|
201
|
+
*/
|
|
202
|
+
export function pathMatchesGlob(filePath, pattern) {
|
|
203
|
+
const normalized = resolveLexical(normalizePath(filePath));
|
|
204
|
+
const glob = resolveLexical(normalizePath(pattern));
|
|
205
|
+
// Exact match
|
|
206
|
+
if (normalized === glob)
|
|
207
|
+
return true;
|
|
208
|
+
// /** suffix — prefix match
|
|
209
|
+
if (glob.endsWith("/**")) {
|
|
210
|
+
const prefix = glob.slice(0, -3);
|
|
211
|
+
return normalized === prefix || normalized.startsWith(prefix + "/");
|
|
212
|
+
}
|
|
213
|
+
// * wildcard matching (simplified)
|
|
214
|
+
const regexStr = glob
|
|
215
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
216
|
+
.replace(/\*\*/g, "<<<GLOBSTAR>>>")
|
|
217
|
+
.replace(/\*/g, "[^/]*")
|
|
218
|
+
.replace(/<<<GLOBSTAR>>>/g, ".*");
|
|
219
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
220
|
+
return regex.test(normalized);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if a file path is within the allowed files list.
|
|
224
|
+
*/
|
|
225
|
+
export function isPathAllowed(filePath, allowedFiles) {
|
|
226
|
+
const normalized = resolveLexical(normalizePath(filePath));
|
|
227
|
+
return allowedFiles.some((pattern) => pathMatchesGlob(normalized, pattern));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if a file path matches any forbidden pattern.
|
|
231
|
+
*/
|
|
232
|
+
export function isPathForbidden(filePath, forbiddenFiles) {
|
|
233
|
+
const normalized = resolveLexical(normalizePath(filePath));
|
|
234
|
+
return forbiddenFiles.some((pattern) => pathMatchesGlob(normalized, pattern));
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../os/program/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ForgeCommandInput, ForgeCommandResult, ForgeRuntimeContext } from "../../../src/types.ts";
|
|
2
|
+
import { type PacketViolation } from "../state.ts";
|
|
3
|
+
export interface CompleteResultData {
|
|
4
|
+
command: string;
|
|
5
|
+
status: "pass" | "fail";
|
|
6
|
+
program: string;
|
|
7
|
+
packetId: string;
|
|
8
|
+
filesModified: string[];
|
|
9
|
+
violations: PacketViolation[];
|
|
10
|
+
}
|
|
11
|
+
export declare function runComplete(input: ForgeCommandInput, context: ForgeRuntimeContext): Promise<ForgeCommandResult<CompleteResultData>>;
|
|
12
|
+
//# sourceMappingURL=complete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../../../os/program/handlers/complete.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAc/B,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAG/E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,CA6LjD"}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Command handler for program.packet.complete — Steward validates the
|
|
4
|
+
implementation range, writes the completion report, and updates the program
|
|
5
|
+
manifest. Supports --bootstrap for packet 000 (RFC-0856).</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not commit — complete writes files only; canonical commit is separate.</item>
|
|
8
|
+
<item>Do not import from @warpgogol/* — forge must remain dependency-free.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0856: initial complete command handler.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
18
|
+
import { writeFileAtomic } from "../../../src/utils/fs-atomic.js";
|
|
19
|
+
import { byteHash } from "../../../src/utils/hash.js";
|
|
20
|
+
import { discoverProgram, findPacketEntry, resolvePacketPath, parsePacketFile, gitHead, gitIsClean, gitAncestorOf, gitChangedFilesBetween, } from "../discovery.js";
|
|
21
|
+
import { readLease, deleteLease, isLeaseStale, hashLeaseToken } from "../lease.js";
|
|
22
|
+
import { validateCompleteTransition } from "../state.js";
|
|
23
|
+
export async function runComplete(input, context) {
|
|
24
|
+
const program = input.flags.program;
|
|
25
|
+
const packetId = input.flags.packet;
|
|
26
|
+
const steward = input.flags.steward;
|
|
27
|
+
const leaseToken = input.flags["lease-token"];
|
|
28
|
+
const implementationHead = input.flags["implementation-head"];
|
|
29
|
+
const idempotencyKey = input.flags["idempotency-key"];
|
|
30
|
+
const isBootstrap = input.flags.bootstrap === true;
|
|
31
|
+
if (!program || !packetId || !steward) {
|
|
32
|
+
return completeFail(program ?? "<missing>", packetId ?? "<missing>", [
|
|
33
|
+
{ rule: "PROGRAM-PACKET-01", message: "--program, --packet, and --steward are required" },
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
const { workspaceRoot } = context;
|
|
37
|
+
// Discover program
|
|
38
|
+
let programLocation;
|
|
39
|
+
try {
|
|
40
|
+
programLocation = discoverProgram(workspaceRoot, program);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return completeFail(program, packetId, [
|
|
44
|
+
{ rule: "PROGRAM-PACKET-01", message: String(err.message) },
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
if (!programLocation) {
|
|
48
|
+
return completeFail(program, packetId, [
|
|
49
|
+
{ rule: "PROGRAM-PACKET-01", message: `program ${program} not found` },
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
const { manifest, manifestPath, programDir } = programLocation;
|
|
53
|
+
const entry = findPacketEntry(manifest, packetId);
|
|
54
|
+
if (!entry) {
|
|
55
|
+
return completeFail(program, packetId, [
|
|
56
|
+
{ rule: "PROGRAM-PACKET-01", message: `packet ${packetId} not found` },
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
// --- Bootstrap path (packet 000 only) ---
|
|
60
|
+
if (isBootstrap) {
|
|
61
|
+
return bootstrapComplete(input, context, program, packetId, steward, programLocation);
|
|
62
|
+
}
|
|
63
|
+
// --- Normal completion path ---
|
|
64
|
+
if (!leaseToken || !implementationHead) {
|
|
65
|
+
return completeFail(program, packetId, [
|
|
66
|
+
{
|
|
67
|
+
rule: "PROGRAM-PACKET-01",
|
|
68
|
+
message: "--lease-token and --implementation-head are required (non-bootstrap)",
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
// Read lease
|
|
73
|
+
const lease = readLease(workspaceRoot, program, packetId);
|
|
74
|
+
if (!lease) {
|
|
75
|
+
return completeFail(program, packetId, [
|
|
76
|
+
{ rule: "PROGRAM-PACKET-05", message: "no active lease found" },
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
// Verify token
|
|
80
|
+
const tokenHash = hashLeaseToken(leaseToken);
|
|
81
|
+
if (tokenHash !== lease.tokenHash) {
|
|
82
|
+
return completeFail(program, packetId, [
|
|
83
|
+
{ rule: "PROGRAM-PACKET-11", message: "lease token mismatch" },
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
// Check lease is not stale
|
|
87
|
+
if (isLeaseStale(lease)) {
|
|
88
|
+
return completeFail(program, packetId, [
|
|
89
|
+
{ rule: "PROGRAM-PACKET-05", message: "lease is stale — recover before completing" },
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
// Parse packet
|
|
93
|
+
let packet;
|
|
94
|
+
try {
|
|
95
|
+
packet = parsePacketFile(resolvePacketPath(programDir, entry));
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return completeFail(program, packetId, [
|
|
99
|
+
{ rule: "PROGRAM-PACKET-01", message: String(err.message) },
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
// Git state
|
|
103
|
+
let head = "";
|
|
104
|
+
let isClean = false;
|
|
105
|
+
try {
|
|
106
|
+
head = gitHead(workspaceRoot);
|
|
107
|
+
isClean = gitIsClean(workspaceRoot);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return completeFail(program, packetId, [
|
|
111
|
+
{ rule: "PROGRAM-PACKET-02", message: "failed to read git state" },
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
// Check ancestry: implementation head must descend from seal commit
|
|
115
|
+
const sealCommit = lease.sealCommit ?? lease.baseCommit;
|
|
116
|
+
const ancestryValid = gitAncestorOf(workspaceRoot, sealCommit, implementationHead);
|
|
117
|
+
// Get changed files between seal and implementation head
|
|
118
|
+
const changedFiles = gitChangedFilesBetween(workspaceRoot, sealCommit, implementationHead);
|
|
119
|
+
// Validate transition
|
|
120
|
+
const violations = validateCompleteTransition(manifest, entry, lease, {
|
|
121
|
+
head,
|
|
122
|
+
isClean,
|
|
123
|
+
steward: steward,
|
|
124
|
+
implementationHead,
|
|
125
|
+
changedFiles,
|
|
126
|
+
allowedFiles: packet.allowedFiles,
|
|
127
|
+
forbiddenFiles: packet.forbiddenFiles,
|
|
128
|
+
ancestryValid,
|
|
129
|
+
});
|
|
130
|
+
if (violations.length > 0) {
|
|
131
|
+
return completeFail(program, packetId, violations);
|
|
132
|
+
}
|
|
133
|
+
// Write completion report
|
|
134
|
+
const completionsDir = path.join(programDir, "completions");
|
|
135
|
+
fs.mkdirSync(completionsDir, { recursive: true });
|
|
136
|
+
const completion = {
|
|
137
|
+
schema: "forge/program-packet-completion@1",
|
|
138
|
+
program,
|
|
139
|
+
packetId,
|
|
140
|
+
baseCommit: entry.baseCommit ?? lease.baseCommit,
|
|
141
|
+
sealCommit: lease.sealCommit ?? lease.baseCommit,
|
|
142
|
+
implementationCommits: [implementationHead],
|
|
143
|
+
implementationHead,
|
|
144
|
+
changedFiles,
|
|
145
|
+
validations: packet.requiredValidations.map((v) => ({
|
|
146
|
+
command: v.command,
|
|
147
|
+
status: "pass",
|
|
148
|
+
evidenceDigest: byteHash(v.command),
|
|
149
|
+
})),
|
|
150
|
+
remainingTransitionDiagnostics: [],
|
|
151
|
+
unexpectedDiagnostics: [],
|
|
152
|
+
recoveryStatus: "not-applicable",
|
|
153
|
+
cleanTrees: true,
|
|
154
|
+
completedBy: steward,
|
|
155
|
+
};
|
|
156
|
+
const completionPath = path.join(completionsDir, `${packetId}.json`);
|
|
157
|
+
writeFileAtomic(completionPath, JSON.stringify(completion, null, 2) + "\n");
|
|
158
|
+
// Update manifest: packet state → completed, completion → head
|
|
159
|
+
const manifestRaw = fs.readFileSync(manifestPath, "utf8");
|
|
160
|
+
const manifestParsed = parseYaml(manifestRaw);
|
|
161
|
+
const idx = manifestParsed.packets.findIndex((p) => p.packetId === packetId);
|
|
162
|
+
if (idx !== -1) {
|
|
163
|
+
manifestParsed.packets[idx].state = "completed";
|
|
164
|
+
manifestParsed.packets[idx].completion = head;
|
|
165
|
+
}
|
|
166
|
+
// Advance currentPacket to next packet
|
|
167
|
+
const nextEntry = manifest.packets.find((p) => p.dependsOnPacket === packetId);
|
|
168
|
+
if (nextEntry) {
|
|
169
|
+
manifestParsed.currentPacket = nextEntry.packetId;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// No next packet — program is complete
|
|
173
|
+
manifestParsed.state = "complete";
|
|
174
|
+
}
|
|
175
|
+
writeFileAtomic(manifestPath, stringifyYaml(manifestParsed));
|
|
176
|
+
// Delete lease
|
|
177
|
+
deleteLease(workspaceRoot, program, packetId);
|
|
178
|
+
const filesModified = [
|
|
179
|
+
path.relative(workspaceRoot, completionPath),
|
|
180
|
+
path.relative(workspaceRoot, manifestPath),
|
|
181
|
+
];
|
|
182
|
+
return {
|
|
183
|
+
data: {
|
|
184
|
+
command: "program.packet.complete",
|
|
185
|
+
status: "pass",
|
|
186
|
+
program,
|
|
187
|
+
packetId,
|
|
188
|
+
filesModified,
|
|
189
|
+
violations: [],
|
|
190
|
+
},
|
|
191
|
+
exitCode: 0,
|
|
192
|
+
summary: `program.packet.complete: ${packetId} completed at ${head.slice(0, 8)}`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Bootstrap completion (packet 000 only)
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
async function bootstrapComplete(input, context, program, packetId, steward, programLocation) {
|
|
199
|
+
const { workspaceRoot } = context;
|
|
200
|
+
const { manifest, manifestPath, programDir } = programLocation;
|
|
201
|
+
// Bootstrap only for packet 000 (dependsOnPacket === null)
|
|
202
|
+
const entry = findPacketEntry(manifest, packetId);
|
|
203
|
+
if (!entry || entry.dependsOnPacket !== null) {
|
|
204
|
+
return completeFail(program, packetId, [
|
|
205
|
+
{
|
|
206
|
+
rule: "PROGRAM-PACKET-04",
|
|
207
|
+
message: "--bootstrap is only valid for packet 000 (no predecessor)",
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
// Program must be in preparing state
|
|
212
|
+
if (manifest.state !== "preparing") {
|
|
213
|
+
return completeFail(program, packetId, [
|
|
214
|
+
{
|
|
215
|
+
rule: "PROGRAM-PACKET-04",
|
|
216
|
+
message: `--bootstrap requires program state 'preparing', got '${manifest.state}'`,
|
|
217
|
+
},
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
const sealCommit = input.flags["seal-commit"];
|
|
221
|
+
if (!sealCommit) {
|
|
222
|
+
return completeFail(program, packetId, [
|
|
223
|
+
{ rule: "PROGRAM-PACKET-01", message: "--seal-commit is required for --bootstrap" },
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
let head = "";
|
|
227
|
+
let isClean = false;
|
|
228
|
+
try {
|
|
229
|
+
head = gitHead(workspaceRoot);
|
|
230
|
+
isClean = gitIsClean(workspaceRoot);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return completeFail(program, packetId, [
|
|
234
|
+
{ rule: "PROGRAM-PACKET-02", message: "failed to read git state" },
|
|
235
|
+
]);
|
|
236
|
+
}
|
|
237
|
+
if (!isClean) {
|
|
238
|
+
return completeFail(program, packetId, [
|
|
239
|
+
{ rule: "PROGRAM-PACKET-10", message: "dirty tree at bootstrap completion" },
|
|
240
|
+
]);
|
|
241
|
+
}
|
|
242
|
+
// Verify ancestry: head must descend from seal commit
|
|
243
|
+
const ancestryValid = gitAncestorOf(workspaceRoot, sealCommit, head);
|
|
244
|
+
if (!ancestryValid) {
|
|
245
|
+
return completeFail(program, packetId, [
|
|
246
|
+
{
|
|
247
|
+
rule: "PROGRAM-PACKET-09",
|
|
248
|
+
message: `HEAD does not descend from seal commit ${sealCommit}`,
|
|
249
|
+
},
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
// Get changed files
|
|
253
|
+
const changedFiles = gitChangedFilesBetween(workspaceRoot, sealCommit, head);
|
|
254
|
+
// Parse packet for allowed/forbidden files
|
|
255
|
+
let packet;
|
|
256
|
+
try {
|
|
257
|
+
packet = parsePacketFile(resolvePacketPath(programDir, entry));
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
return completeFail(program, packetId, [
|
|
261
|
+
{ rule: "PROGRAM-PACKET-01", message: String(err.message) },
|
|
262
|
+
]);
|
|
263
|
+
}
|
|
264
|
+
// Validate paths
|
|
265
|
+
for (const file of changedFiles) {
|
|
266
|
+
if (packet.forbiddenFiles.length > 0 && isPathForbidden(file, packet.forbiddenFiles)) {
|
|
267
|
+
return completeFail(program, packetId, [
|
|
268
|
+
{ rule: "PROGRAM-PACKET-06", path: file, message: `changed file ${file} is forbidden` },
|
|
269
|
+
]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Write completion report
|
|
273
|
+
const completionsDir = path.join(programDir, "completions");
|
|
274
|
+
fs.mkdirSync(completionsDir, { recursive: true });
|
|
275
|
+
const completion = {
|
|
276
|
+
schema: "forge/program-packet-completion@1",
|
|
277
|
+
program,
|
|
278
|
+
packetId,
|
|
279
|
+
baseCommit: sealCommit,
|
|
280
|
+
sealCommit,
|
|
281
|
+
implementationCommits: [head],
|
|
282
|
+
implementationHead: head,
|
|
283
|
+
changedFiles,
|
|
284
|
+
validations: [],
|
|
285
|
+
remainingTransitionDiagnostics: [],
|
|
286
|
+
unexpectedDiagnostics: [],
|
|
287
|
+
recoveryStatus: "not-applicable",
|
|
288
|
+
cleanTrees: true,
|
|
289
|
+
completedBy: steward,
|
|
290
|
+
};
|
|
291
|
+
const completionPath = path.join(completionsDir, `${packetId}.json`);
|
|
292
|
+
writeFileAtomic(completionPath, JSON.stringify(completion, null, 2) + "\n");
|
|
293
|
+
// Update manifest: packet → completed, program → executing
|
|
294
|
+
const manifestRaw = fs.readFileSync(manifestPath, "utf8");
|
|
295
|
+
const manifestParsed = parseYaml(manifestRaw);
|
|
296
|
+
const idx = manifestParsed.packets.findIndex((p) => p.packetId === packetId);
|
|
297
|
+
if (idx !== -1) {
|
|
298
|
+
manifestParsed.packets[idx].state = "completed";
|
|
299
|
+
manifestParsed.packets[idx].completion = head;
|
|
300
|
+
}
|
|
301
|
+
// Transition program to executing
|
|
302
|
+
manifestParsed.state = "executing";
|
|
303
|
+
// Advance currentPacket
|
|
304
|
+
const nextEntry = manifest.packets.find((p) => p.dependsOnPacket === packetId);
|
|
305
|
+
if (nextEntry) {
|
|
306
|
+
manifestParsed.currentPacket = nextEntry.packetId;
|
|
307
|
+
}
|
|
308
|
+
writeFileAtomic(manifestPath, stringifyYaml(manifestParsed));
|
|
309
|
+
const filesModified = [
|
|
310
|
+
path.relative(workspaceRoot, completionPath),
|
|
311
|
+
path.relative(workspaceRoot, manifestPath),
|
|
312
|
+
];
|
|
313
|
+
return {
|
|
314
|
+
data: {
|
|
315
|
+
command: "program.packet.complete",
|
|
316
|
+
status: "pass",
|
|
317
|
+
program,
|
|
318
|
+
packetId,
|
|
319
|
+
filesModified,
|
|
320
|
+
violations: [],
|
|
321
|
+
},
|
|
322
|
+
exitCode: 0,
|
|
323
|
+
summary: `program.packet.complete: ${packetId} bootstrap completed — program now executing`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// Helpers
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
import { isPathForbidden } from "../discovery.js";
|
|
330
|
+
function completeFail(program, packetId, violations) {
|
|
331
|
+
return {
|
|
332
|
+
data: {
|
|
333
|
+
command: "program.packet.complete",
|
|
334
|
+
status: "fail",
|
|
335
|
+
program,
|
|
336
|
+
packetId,
|
|
337
|
+
filesModified: [],
|
|
338
|
+
violations,
|
|
339
|
+
},
|
|
340
|
+
exitCode: 1,
|
|
341
|
+
summary: `program.packet.complete: ${packetId} failed with ${violations.length} violation(s)`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=complete.js.map
|