@warpgogol/forge 5.1.3 → 5.2.3

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 (116) hide show
  1. package/AGENTS.md +3 -1
  2. package/bin/cli.ts +4 -0
  3. package/dist/bin/cli.js +4 -0
  4. package/dist/bin/cli.js.map +1 -1
  5. package/dist/os/adr/adr.module.js +1 -1
  6. package/dist/os/adr/adr.module.js.map +1 -1
  7. package/dist/os/compass/compass.module.js +2 -2
  8. package/dist/os/compass/compass.module.js.map +1 -1
  9. package/dist/os/compass/handlers/compass-audit-handler.d.ts +3 -0
  10. package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
  11. package/dist/os/compass/handlers/compass-audit-handler.js +87 -8
  12. package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
  13. package/dist/os/core/core.module.js +1 -1
  14. package/dist/os/core/core.module.js.map +1 -1
  15. package/dist/os/mission/handlers/archive.d.ts.map +1 -1
  16. package/dist/os/mission/handlers/archive.js +22 -6
  17. package/dist/os/mission/handlers/archive.js.map +1 -1
  18. package/dist/os/program/lease.js +1 -1
  19. package/dist/os/program/lease.js.map +1 -1
  20. package/dist/os/queue/handlers/queue-validate.d.ts +4 -0
  21. package/dist/os/queue/handlers/queue-validate.d.ts.map +1 -0
  22. package/dist/os/queue/handlers/queue-validate.js +62 -0
  23. package/dist/os/queue/handlers/queue-validate.js.map +1 -0
  24. package/dist/os/queue/index.d.ts +5 -0
  25. package/dist/os/queue/index.d.ts.map +1 -0
  26. package/dist/os/queue/index.js +18 -0
  27. package/dist/os/queue/index.js.map +1 -0
  28. package/dist/os/queue/manifest.d.ts +14 -0
  29. package/dist/os/queue/manifest.d.ts.map +1 -0
  30. package/dist/os/queue/manifest.js +110 -0
  31. package/dist/os/queue/manifest.js.map +1 -0
  32. package/dist/os/queue/queue.module.d.ts +3 -0
  33. package/dist/os/queue/queue.module.d.ts.map +1 -0
  34. package/dist/os/queue/queue.module.js +56 -0
  35. package/dist/os/queue/queue.module.js.map +1 -0
  36. package/dist/os/queue/types.d.ts +25 -0
  37. package/dist/os/queue/types.d.ts.map +1 -0
  38. package/dist/os/queue/types.js +28 -0
  39. package/dist/os/queue/types.js.map +1 -0
  40. package/dist/os/rfc/handlers/list-create.d.ts.map +1 -1
  41. package/dist/os/rfc/handlers/list-create.js +79 -32
  42. package/dist/os/rfc/handlers/list-create.js.map +1 -1
  43. package/dist/os/rfc/handlers/pipeline-status.d.ts +2 -7
  44. package/dist/os/rfc/handlers/pipeline-status.d.ts.map +1 -1
  45. package/dist/os/rfc/handlers/pipeline-status.js +15 -57
  46. package/dist/os/rfc/handlers/pipeline-status.js.map +1 -1
  47. package/dist/os/rfc/rfc.module.js +1 -1
  48. package/dist/os/rfc/rfc.module.js.map +1 -1
  49. package/dist/os/spec/spec.module.d.ts.map +1 -1
  50. package/dist/os/spec/spec.module.js +2 -0
  51. package/dist/os/spec/spec.module.js.map +1 -1
  52. package/dist/src/knowledge/index.d.ts +1 -0
  53. package/dist/src/knowledge/index.d.ts.map +1 -1
  54. package/dist/src/knowledge/index.js +1 -0
  55. package/dist/src/knowledge/index.js.map +1 -1
  56. package/dist/src/knowledge/sync.d.ts +24 -0
  57. package/dist/src/knowledge/sync.d.ts.map +1 -0
  58. package/dist/src/knowledge/sync.js +127 -0
  59. package/dist/src/knowledge/sync.js.map +1 -0
  60. package/dist/src/onboarding/doctor.d.ts.map +1 -1
  61. package/dist/src/onboarding/doctor.js +7 -6
  62. package/dist/src/onboarding/doctor.js.map +1 -1
  63. package/dist/src/onboarding/init.d.ts.map +1 -1
  64. package/dist/src/onboarding/init.js +15 -8
  65. package/dist/src/onboarding/init.js.map +1 -1
  66. package/dist/src/onboarding/upgrade.d.ts.map +1 -1
  67. package/dist/src/onboarding/upgrade.js +42 -13
  68. package/dist/src/onboarding/upgrade.js.map +1 -1
  69. package/dist/src/pipeline-status.d.ts +62 -0
  70. package/dist/src/pipeline-status.d.ts.map +1 -0
  71. package/dist/src/pipeline-status.js +220 -0
  72. package/dist/src/pipeline-status.js.map +1 -0
  73. package/dist/src/types.d.ts +1 -2
  74. package/dist/src/types.d.ts.map +1 -1
  75. package/dist/src/types.js +1 -0
  76. package/dist/src/types.js.map +1 -1
  77. package/dist/src/utils/fs-atomic.js +1 -1
  78. package/dist/src/utils/fs-atomic.js.map +1 -1
  79. package/os/adr/adr.module.ts +1 -1
  80. package/os/compass/compass.module.ts +2 -2
  81. package/os/compass/handlers/compass-audit-handler.ts +116 -8
  82. package/os/compass/handlers/tests/compass-audit-plan.test.ts +98 -0
  83. package/os/compass/handlers/tests/compass-audit-record.test.ts +105 -0
  84. package/os/compass/handlers/tests/compass-audit-validate.test.ts +133 -0
  85. package/os/compass/handlers/tests/compass-ledger-scope.test.ts +124 -0
  86. package/os/core/core.module.ts +1 -1
  87. package/os/mission/handlers/archive.test.ts +49 -0
  88. package/os/mission/handlers/archive.ts +21 -6
  89. package/os/program/lease.ts +1 -1
  90. package/os/queue/handlers/queue-validate.ts +83 -0
  91. package/os/queue/index.ts +19 -0
  92. package/os/queue/manifest.ts +183 -0
  93. package/os/queue/queue-validate.test.ts +177 -0
  94. package/os/queue/queue.module.ts +59 -0
  95. package/os/queue/types.ts +49 -0
  96. package/os/rfc/handlers/list-create.ts +83 -35
  97. package/os/rfc/handlers/pipeline-status.ts +24 -67
  98. package/os/rfc/handlers/validate-rules.test.ts +50 -0
  99. package/os/rfc/rfc-create-concurrent.test.ts +118 -0
  100. package/os/rfc/rfc-create-hint.test.ts +90 -0
  101. package/os/rfc/rfc-read-only-no-side-effects.test.ts +83 -0
  102. package/os/rfc/rfc.module.ts +1 -1
  103. package/os/spec/spec.module.ts +2 -0
  104. package/package.json +17 -1
  105. package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +46 -1
  106. package/src/knowledge/__tests__/sync.test.ts +227 -0
  107. package/src/knowledge/index.ts +7 -0
  108. package/src/knowledge/sync.ts +161 -0
  109. package/src/onboarding/doctor.ts +7 -6
  110. package/src/onboarding/init.ts +15 -8
  111. package/src/onboarding/upgrade.ts +47 -16
  112. package/src/pipeline-status.test.ts +192 -0
  113. package/src/pipeline-status.ts +319 -0
  114. package/src/tests/upgrade.test.ts +53 -0
  115. package/src/types.ts +2 -2
  116. package/src/utils/fs-atomic.ts +1 -1
@@ -0,0 +1,319 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Shared pipeline-status resolver (RFC-1140) — derives per-document
4
+ pipeline progress (audit, enhance, plan, implement) from document frontmatter
5
+ and pipeline artifacts. Single source of truth consumed by rfc.pipeline.status,
6
+ queue.validate, and the orchestrator's resume detection. Portable layer: no
7
+ os/ or kernel imports.</purpose>
8
+ <non-goals>
9
+ <item>Do not validate RFC/ADR content — use rfc.validate / adr.validate.</item>
10
+ <item>Do not mutate any files — derivation is read-only.</item>
11
+ <item>Do not store progress state — status is derived at read time, never persisted.</item>
12
+ </non-goals>
13
+ </MODULE_CONTRACT>
14
+ <CHANGE_SUMMARY>
15
+ <item>RFC-1140: initial shared resolver — artifact conventions (audit-*.md,
16
+ plan-*.md, enhancedAt, implementedAt) extracted from
17
+ os/rfc/handlers/pipeline-status.ts; QueueItemStatus derivation matrix added
18
+ for queue.validate.</item>
19
+ <item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
20
+
21
+ Extract pipeline-status derivation into packages/forge/src/pipeline-status.ts, refactor rfc.pipeline.status onto it, add os/queue module with queue.validate command, register in WORKSHOP_MODULE_MAP.forge + bin/cli.ts + package.json exports.</item>
22
+ <item>RFC-1140: step 6 — cover shared resolver and queue.validate
23
+
24
+ Add pipeline-status.test.ts (derivation matrix, order, next selection, ADR, skipped) and queue-validate.test.ts (schema errors, unknown/dup ids, id-filename mismatch, empty items, dependsOn warning, JSON shape). Fix queue derivation: pipelineStep is the stage after the last completed stage, not first incomplete — handles non-contiguous artifacts.</item>
25
+ </CHANGE_SUMMARY>
26
+ */
27
+
28
+ import fs from "node:fs/promises";
29
+ import path from "node:path";
30
+ import YAML from "yaml";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Pipeline stages (shared with rfc.pipeline.status)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export type PipelineStage = "audit" | "enhance" | "plan" | "implement";
37
+
38
+ export interface RfcPipelineStageInfo {
39
+ stage: PipelineStage;
40
+ done: boolean;
41
+ /** File or marker that confirms completion, when applicable. */
42
+ evidence?: string;
43
+ }
44
+
45
+ export const TERMINAL_STATUSES: ReadonlySet<string> = new Set([
46
+ "implemented",
47
+ "rejected",
48
+ "superseded",
49
+ ]);
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Minimal frontmatter parsing (self-contained — src/ must not import os/)
53
+ // ---------------------------------------------------------------------------
54
+
55
+ export interface ParsedDocument {
56
+ frontmatter: Record<string, unknown>;
57
+ body: string;
58
+ }
59
+
60
+ export function parseFrontmatter(source: string): ParsedDocument {
61
+ const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
62
+ if (!match) {
63
+ return { frontmatter: {}, body: source };
64
+ }
65
+ return {
66
+ frontmatter: (YAML.parse(match[1]!) ?? {}) as Record<string, unknown>,
67
+ body: match[2] ?? "",
68
+ };
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Artifact finders — the canonical conventions
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export async function findAuditFile(
76
+ workspaceRoot: string,
77
+ docId: string,
78
+ ): Promise<string | undefined> {
79
+ const auditsDir = path.join(workspaceRoot, "docs/audits");
80
+ try {
81
+ const entries = await fs.readdir(auditsDir);
82
+ const prefix = `audit-${docId.toLowerCase()}`;
83
+ const match = entries.find((e) => e.toLowerCase().startsWith(prefix) && e.endsWith(".md"));
84
+ return match ? path.join("docs/audits", match) : undefined;
85
+ } catch {
86
+ return undefined;
87
+ }
88
+ }
89
+
90
+ export async function findPlanFile(
91
+ workspaceRoot: string,
92
+ docId: string,
93
+ ): Promise<string | undefined> {
94
+ const plansDir = path.join(workspaceRoot, "docs/plans");
95
+ try {
96
+ const entries = await fs.readdir(plansDir);
97
+ const prefix = `plan-${docId.toLowerCase()}`;
98
+ const match = entries.find((e) => e.toLowerCase().startsWith(prefix) && e.endsWith(".md"));
99
+ return match ? path.join("docs/plans", match) : undefined;
100
+ } catch {
101
+ return undefined;
102
+ }
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Document resolution — active dir + archive (implemented docs may be archived)
107
+ // ---------------------------------------------------------------------------
108
+
109
+ export type QueueDocumentKind = "rfc" | "adr";
110
+
111
+ export interface ResolvedDocument {
112
+ id: string;
113
+ kind: QueueDocumentKind;
114
+ /** Workspace-relative path to the document file. */
115
+ file: string;
116
+ frontmatter: Record<string, unknown>;
117
+ }
118
+
119
+ const DOC_ID_PATTERN = /^(RFC|ADR)-\d{4}$/i;
120
+
121
+ async function scanForDocument(
122
+ dirPath: string,
123
+ relativePrefix: string,
124
+ idLower: string,
125
+ ): Promise<string | undefined> {
126
+ let entries;
127
+ try {
128
+ entries = await fs.readdir(dirPath, { withFileTypes: true });
129
+ } catch {
130
+ return undefined;
131
+ }
132
+ for (const entry of entries) {
133
+ const relativePath = relativePrefix ? `${relativePrefix}/${entry.name}` : entry.name;
134
+ if (entry.isDirectory()) {
135
+ const found = await scanForDocument(path.join(dirPath, entry.name), relativePath, idLower);
136
+ if (found) return found;
137
+ } else if (
138
+ entry.isFile() &&
139
+ entry.name.endsWith(".md") &&
140
+ entry.name.toLowerCase().startsWith(idLower)
141
+ ) {
142
+ return relativePath;
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+
148
+ /**
149
+ * Resolve an RFC-XXXX or ADR-XXXX id to its document file and parsed
150
+ * frontmatter. Scans the active dir and archive subdirectories recursively.
151
+ * Returns undefined when the id is malformed or no file matches.
152
+ */
153
+ export async function resolveDocument(
154
+ workspaceRoot: string,
155
+ docId: string,
156
+ ): Promise<ResolvedDocument | undefined> {
157
+ const idMatch = DOC_ID_PATTERN.exec(docId.trim());
158
+ if (!idMatch) return undefined;
159
+ const kind: QueueDocumentKind = idMatch[1]!.toUpperCase() === "ADR" ? "adr" : "rfc";
160
+ const baseDir = kind === "adr" ? "docs/adrs" : "docs/rfcs";
161
+ const idLower = docId.toLowerCase();
162
+
163
+ const found = await scanForDocument(path.join(workspaceRoot, baseDir), "", idLower);
164
+ if (!found) return undefined;
165
+
166
+ let source: string;
167
+ try {
168
+ source = await fs.readFile(path.join(workspaceRoot, baseDir, found), "utf-8");
169
+ } catch {
170
+ return undefined;
171
+ }
172
+ const parsed = parseFrontmatter(source);
173
+ return {
174
+ id: docId.toUpperCase(),
175
+ kind,
176
+ file: path.join(baseDir, found),
177
+ frontmatter: parsed.frontmatter,
178
+ };
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // RFC stage derivation — shared by rfc.pipeline.status and queue.validate
183
+ // ---------------------------------------------------------------------------
184
+
185
+ export interface RfcStageInput {
186
+ status: string;
187
+ enhancedAt?: string;
188
+ implementedAt?: string;
189
+ auditFile?: string;
190
+ planFile?: string;
191
+ }
192
+
193
+ /** Compute the four pipeline stages for an RFC from frontmatter + artifacts. */
194
+ export function computeRfcPipelineStages(input: RfcStageInput): RfcPipelineStageInfo[] {
195
+ return [
196
+ { stage: "audit", done: !!input.auditFile, evidence: input.auditFile },
197
+ {
198
+ stage: "enhance",
199
+ done: !!input.enhancedAt,
200
+ evidence: input.enhancedAt ? `enhancedAt: ${input.enhancedAt}` : undefined,
201
+ },
202
+ { stage: "plan", done: !!input.planFile, evidence: input.planFile },
203
+ {
204
+ stage: "implement",
205
+ done: input.status === "implemented" || !!input.implementedAt,
206
+ evidence: input.implementedAt ? `implementedAt: ${input.implementedAt}` : undefined,
207
+ },
208
+ ];
209
+ }
210
+
211
+ /** First incomplete stage for a non-terminal RFC, or null when terminal. */
212
+ export function nextPipelineStep(
213
+ status: string,
214
+ stages: RfcPipelineStageInfo[],
215
+ ): PipelineStage | null {
216
+ if (TERMINAL_STATUSES.has(status)) return null;
217
+ for (const s of stages) {
218
+ if (!s.done) return s.stage;
219
+ }
220
+ return null;
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Queue item derivation (RFC-1140)
225
+ // ---------------------------------------------------------------------------
226
+
227
+ export type QueueItemStatus = "pending" | "in-progress" | "implemented" | "skipped";
228
+
229
+ export interface QueueItemReport {
230
+ id: string;
231
+ status: QueueItemStatus;
232
+ /** First incomplete pipeline stage — present only when in-progress. */
233
+ pipelineStep?: PipelineStage;
234
+ }
235
+
236
+ /**
237
+ * Derive the queue status of a single document per the RFC-1140 matrix:
238
+ * implemented → implemented; rejected/superseded → skipped; RFC accepted or
239
+ * plan file → in-progress/implement; enhancedAt → in-progress/plan; audit
240
+ * file → in-progress/enhance; otherwise pending. ADR non-terminal → pending.
241
+ */
242
+ export async function deriveQueueItemStatus(
243
+ workspaceRoot: string,
244
+ docId: string,
245
+ ): Promise<QueueItemReport | undefined> {
246
+ const doc = await resolveDocument(workspaceRoot, docId);
247
+ if (!doc) return undefined;
248
+
249
+ const fm = doc.frontmatter;
250
+ const status = String(fm["status"] ?? "");
251
+ const implementedAt = fm["implementedAt"] ? String(fm["implementedAt"]) : undefined;
252
+
253
+ if (status === "implemented" || implementedAt) {
254
+ return { id: doc.id, status: "implemented" };
255
+ }
256
+ if (status === "rejected" || status === "superseded") {
257
+ return { id: doc.id, status: "skipped" };
258
+ }
259
+
260
+ // ADR pipeline is create → implement — no intermediate artifacts exist.
261
+ if (doc.kind === "adr") {
262
+ return { id: doc.id, status: "pending" };
263
+ }
264
+
265
+ // RFC accepted means the plan step is done by definition.
266
+ if (status === "accepted") {
267
+ return { id: doc.id, status: "in-progress", pipelineStep: "implement" };
268
+ }
269
+
270
+ const enhancedAt = fm["enhancedAt"] ? String(fm["enhancedAt"]) : undefined;
271
+ const auditFile = await findAuditFile(workspaceRoot, doc.id);
272
+ const planFile = await findPlanFile(workspaceRoot, doc.id);
273
+
274
+ const stages = computeRfcPipelineStages({
275
+ status,
276
+ enhancedAt,
277
+ implementedAt,
278
+ auditFile,
279
+ planFile,
280
+ });
281
+
282
+ // Queue semantics: the actionable step is the stage AFTER the last completed
283
+ // one — artifacts may be non-contiguous (e.g. enhancedAt without an audit
284
+ // file), where "first incomplete stage" would wrongly report audit.
285
+ let lastDone = -1;
286
+ for (let i = 0; i < stages.length; i++) {
287
+ if (stages[i]!.done) lastDone = i;
288
+ }
289
+ if (lastDone === -1) {
290
+ return { id: doc.id, status: "pending" };
291
+ }
292
+ const nextStage = stages[lastDone + 1];
293
+ if (!nextStage) {
294
+ return { id: doc.id, status: "pending" };
295
+ }
296
+ return { id: doc.id, status: "in-progress", pipelineStep: nextStage.stage };
297
+ }
298
+
299
+ export interface QueueReport {
300
+ items: QueueItemReport[];
301
+ /** First item whose derived status is pending or in-progress; null when fully terminal. */
302
+ next: string | null;
303
+ }
304
+
305
+ /** Derive the full queue report, preserving manifest order. */
306
+ export async function deriveQueueReport(
307
+ workspaceRoot: string,
308
+ itemIds: string[],
309
+ ): Promise<QueueReport> {
310
+ const items: QueueItemReport[] = [];
311
+ for (const id of itemIds) {
312
+ const report = await deriveQueueItemStatus(workspaceRoot, id);
313
+ // Unresolvable ids are reported by the manifest validator; the resolver
314
+ // skips them here so a partial report stays usable.
315
+ if (report) items.push(report);
316
+ }
317
+ const nextItem = items.find((i) => i.status === "pending" || i.status === "in-progress");
318
+ return { items, next: nextItem?.id ?? null };
319
+ }
@@ -431,3 +431,56 @@ test("forge.upgrade without --update-npm does not attempt npm install", async ()
431
431
  expect(result.data?.npmUpdated).toBe(false);
432
432
  expect(result.data?.npmUpdateSkipped).toBe(null);
433
433
  });
434
+
435
+ function qaLog(entries: { id: string; title: string; body: string }[]): string {
436
+ const blocks = entries.map(
437
+ (e) =>
438
+ `### ${e.id}: ${e.title}\n\n\`\`\`knowledge-entry\nid: ${e.id}\nlayer: L0\ncreated: 2026-08-03\nstatus: active\n\`\`\`\n\n${e.body}\n`,
439
+ );
440
+ return `<!-- knowledge-layer: L0 -->\n\n# Q&A Log (L0)\n\n${blocks.join("\n")}`;
441
+ }
442
+
443
+ test("forge.upgrade preserves project-accumulated knowledge entries (append-only merge)", async () => {
444
+ await setupForgeSource(tempDir, "0.2.0");
445
+ // Declare a knowledge file on the fixture skill
446
+ await writeFile(
447
+ join(tempDir, "packages", "forge", "skills", "fo", "fo-idea", "SKILL.md"),
448
+ "---\nname: fo-idea\nknowledge: [qa-log.md]\n---\n# fo-idea\n",
449
+ "utf8",
450
+ );
451
+ // Package ships K-0001 + new K-0003
452
+ await writeFile(
453
+ join(tempDir, "packages", "forge", "skills", "fo", "fo-idea", "qa-log.md"),
454
+ qaLog([
455
+ { id: "K-0001", title: "First", body: "Package body." },
456
+ { id: "K-0003", title: "Third", body: "New package entry." },
457
+ ]),
458
+ "utf8",
459
+ );
460
+ await setupConsumerForgeYaml(tempDir, null);
461
+
462
+ // Local copy already accumulated: edited K-0001 + project-only K-0002
463
+ await mkdir(join(tempDir, ".agents", "skills", "fo-idea"), { recursive: true });
464
+ await writeFile(
465
+ join(tempDir, ".agents", "skills", "fo-idea", "qa-log.md"),
466
+ qaLog([
467
+ { id: "K-0001", title: "First", body: "Locally edited body." },
468
+ { id: "K-0002", title: "Local record", body: "Project-accumulated entry." },
469
+ ]),
470
+ "utf8",
471
+ );
472
+
473
+ const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
474
+
475
+ expect(result.data?.status).toBe("pass");
476
+ const merged = await readFile(join(tempDir, ".agents", "skills", "fo-idea", "qa-log.md"), "utf8");
477
+ // Local entries preserved — the data-loss bug would have wiped K-0002
478
+ expect(merged).toContain("K-0002");
479
+ expect(merged).toContain("Project-accumulated entry.");
480
+ // Local version of the conflicting entry wins
481
+ expect(merged).toContain("Locally edited body.");
482
+ expect(merged).not.toContain("Package body.");
483
+ // New package entry appended
484
+ expect(merged).toContain("K-0003");
485
+ expect(merged).toContain("New package entry.");
486
+ });
package/src/types.ts CHANGED
@@ -16,6 +16,7 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
16
16
  <item>RFC-1097: sweep — werkstatt-engine clean
17
17
 
18
18
  Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
19
+ <item>RFC-1147: ForgeRegisteredCommandInfo.provider narrowed to "workspace" and siteName field removed — forge never enumerated sites; mirrors the KernelRegisteredCommandInfo cleanup.</item>
19
20
  </CHANGE_SUMMARY>
20
21
  */
21
22
 
@@ -112,8 +113,7 @@ export interface GateMetadata {
112
113
 
113
114
  export interface ForgeRegisteredCommandInfo extends ForgeCommandMetadata {
114
115
  name: string;
115
- provider: "workspace" | "site";
116
- siteName?: string;
116
+ provider: "workspace";
117
117
  module?: string;
118
118
  flags?: Record<string, ForgeFlagSpec>;
119
119
  reads?: string[];
@@ -56,7 +56,7 @@ function sleep(ms: number): Promise<void> {
56
56
  function makeTempPath(filePath: string): string {
57
57
  const dir = dirname(filePath);
58
58
  const name = basename(filePath);
59
- const random = randomBytes(6).toString("hex");
59
+ const random = Array.from(randomBytes(6), (b) => b.toString(16).padStart(2, "0")).join("");
60
60
  return join(dir, `${name}.${random}.tmp`);
61
61
  }
62
62