@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.
Files changed (104) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +4 -4
  3. package/README.uk.md +4 -4
  4. package/dist/bin/cli.js +1 -0
  5. package/dist/bin/cli.js.map +1 -1
  6. package/dist/os/core/core.module.d.ts.map +1 -1
  7. package/dist/os/core/core.module.js +34 -0
  8. package/dist/os/core/core.module.js.map +1 -1
  9. package/dist/os/mission/handlers/archive.d.ts.map +1 -1
  10. package/dist/os/mission/handlers/archive.js +77 -5
  11. package/dist/os/mission/handlers/archive.js.map +1 -1
  12. package/dist/os/notes/index.d.ts +2 -0
  13. package/dist/os/notes/index.d.ts.map +1 -0
  14. package/dist/os/notes/index.js +10 -0
  15. package/dist/os/notes/index.js.map +1 -0
  16. package/dist/os/notes/notes.module.d.ts +3 -0
  17. package/dist/os/notes/notes.module.d.ts.map +1 -0
  18. package/dist/os/notes/notes.module.js +83 -0
  19. package/dist/os/notes/notes.module.js.map +1 -0
  20. package/dist/os/program/discovery.d.ts +61 -0
  21. package/dist/os/program/discovery.d.ts.map +1 -0
  22. package/dist/os/program/discovery.js +236 -0
  23. package/dist/os/program/discovery.js.map +1 -0
  24. package/dist/os/program/handlers/complete.d.ts +12 -0
  25. package/dist/os/program/handlers/complete.d.ts.map +1 -0
  26. package/dist/os/program/handlers/complete.js +344 -0
  27. package/dist/os/program/handlers/complete.js.map +1 -0
  28. package/dist/os/program/handlers/lease.d.ts +13 -0
  29. package/dist/os/program/handlers/lease.d.ts.map +1 -0
  30. package/dist/os/program/handlers/lease.js +322 -0
  31. package/dist/os/program/handlers/lease.js.map +1 -0
  32. package/dist/os/program/handlers/seal.d.ts +12 -0
  33. package/dist/os/program/handlers/seal.d.ts.map +1 -0
  34. package/dist/os/program/handlers/seal.js +265 -0
  35. package/dist/os/program/handlers/seal.js.map +1 -0
  36. package/dist/os/program/handlers/validate.d.ts +14 -0
  37. package/dist/os/program/handlers/validate.d.ts.map +1 -0
  38. package/dist/os/program/handlers/validate.js +164 -0
  39. package/dist/os/program/handlers/validate.js.map +1 -0
  40. package/dist/os/program/lease.d.ts +39 -0
  41. package/dist/os/program/lease.d.ts.map +1 -0
  42. package/dist/os/program/lease.js +129 -0
  43. package/dist/os/program/lease.js.map +1 -0
  44. package/dist/os/program/program.module.d.ts +3 -0
  45. package/dist/os/program/program.module.d.ts.map +1 -0
  46. package/dist/os/program/program.module.js +201 -0
  47. package/dist/os/program/program.module.js.map +1 -0
  48. package/dist/os/program/schemas.d.ts +225 -0
  49. package/dist/os/program/schemas.d.ts.map +1 -0
  50. package/dist/os/program/schemas.js +174 -0
  51. package/dist/os/program/schemas.js.map +1 -0
  52. package/dist/os/program/state.d.ts +38 -0
  53. package/dist/os/program/state.d.ts.map +1 -0
  54. package/dist/os/program/state.js +189 -0
  55. package/dist/os/program/state.js.map +1 -0
  56. package/dist/os/rfc/handlers/archive.d.ts +1 -0
  57. package/dist/os/rfc/handlers/archive.d.ts.map +1 -1
  58. package/dist/os/rfc/handlers/archive.js +54 -0
  59. package/dist/os/rfc/handlers/archive.js.map +1 -1
  60. package/dist/os/rfc/handlers/validate.d.ts.map +1 -1
  61. package/dist/os/rfc/handlers/validate.js +5 -5
  62. package/dist/os/rfc/handlers/validate.js.map +1 -1
  63. package/dist/src/index.d.ts +2 -0
  64. package/dist/src/index.d.ts.map +1 -1
  65. package/dist/src/index.js +2 -0
  66. package/dist/src/index.js.map +1 -1
  67. package/dist/src/onboarding/invariant-engine.d.ts.map +1 -1
  68. package/dist/src/onboarding/invariant-engine.js +137 -5
  69. package/dist/src/onboarding/invariant-engine.js.map +1 -1
  70. package/dist/src/profiles/profile-schema.d.ts +16 -1
  71. package/dist/src/profiles/profile-schema.d.ts.map +1 -1
  72. package/dist/src/profiles/profile-schema.js +16 -1
  73. package/dist/src/profiles/profile-schema.js.map +1 -1
  74. package/dist/src/profiles/stack-profile.d.ts +5 -0
  75. package/dist/src/profiles/stack-profile.d.ts.map +1 -1
  76. package/dist/src/profiles/stack-profile.js +3 -0
  77. package/dist/src/profiles/stack-profile.js.map +1 -1
  78. package/dist/src/validators/note-frontmatter-validate.d.ts +20 -0
  79. package/dist/src/validators/note-frontmatter-validate.d.ts.map +1 -0
  80. package/dist/src/validators/note-frontmatter-validate.js +117 -0
  81. package/dist/src/validators/note-frontmatter-validate.js.map +1 -0
  82. package/dist/src/validators/note-link-validate.d.ts +21 -0
  83. package/dist/src/validators/note-link-validate.d.ts.map +1 -0
  84. package/dist/src/validators/note-link-validate.js +121 -0
  85. package/dist/src/validators/note-link-validate.js.map +1 -0
  86. package/dist/src/validators/note-orphan-detect.d.ts +19 -0
  87. package/dist/src/validators/note-orphan-detect.d.ts.map +1 -0
  88. package/dist/src/validators/note-orphan-detect.js +123 -0
  89. package/dist/src/validators/note-orphan-detect.js.map +1 -0
  90. package/package.json +31 -15
  91. package/profiles/astro-typescript-turborepo.yaml +1 -1
  92. package/profiles/editframe.yaml +2 -2
  93. package/profiles/forge-shell.yaml +1 -1
  94. package/profiles/godot-csharp.yaml +388 -0
  95. package/profiles/obsidian-vault.yaml +103 -0
  96. package/profiles/phaser-turborepo.yaml +1 -1
  97. package/skills/fo/ef-onboard/SKILL.md +1 -1
  98. package/skills/fo/fo-idea/SKILL.md +2 -2
  99. package/skills/fo/fo-idea-implement/SKILL.md +1 -1
  100. package/skills/fo/fo-step-commit/SKILL.md +7 -5
  101. package/skills/shared/grilling/learned-principles.archive.md +212 -0
  102. package/skills/shared/grilling/learned-principles.md +0 -13
  103. package/skills/shared/grilling/qa-log.md +624 -0
  104. 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