@warpgogol/forge 5.3.4 → 6.2.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 (253) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/cli.ts +7 -2
  3. package/dist/bin/cli.js +7 -2
  4. package/dist/bin/cli.js.map +1 -1
  5. package/dist/os/adr/adr.module.d.ts.map +1 -1
  6. package/dist/os/adr/adr.module.js +2 -0
  7. package/dist/os/adr/adr.module.js.map +1 -1
  8. package/dist/os/audit/audit.module.d.ts.map +1 -1
  9. package/dist/os/audit/audit.module.js +2 -0
  10. package/dist/os/audit/audit.module.js.map +1 -1
  11. package/dist/os/compass/compass.module.d.ts.map +1 -1
  12. package/dist/os/compass/compass.module.js +47 -15
  13. package/dist/os/compass/compass.module.js.map +1 -1
  14. package/dist/os/compass/handlers/compass-audit-handler.d.ts +2 -0
  15. package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
  16. package/dist/os/compass/handlers/compass-audit-handler.js +40 -10
  17. package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
  18. package/dist/os/compass/handlers/compass-docs-validate.d.ts +13 -0
  19. package/dist/os/compass/handlers/compass-docs-validate.d.ts.map +1 -0
  20. package/dist/os/compass/handlers/compass-docs-validate.js +328 -0
  21. package/dist/os/compass/handlers/compass-docs-validate.js.map +1 -0
  22. package/dist/os/compass/handlers/summary-record.d.ts +11 -2
  23. package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
  24. package/dist/os/compass/handlers/summary-record.js +95 -15
  25. package/dist/os/compass/handlers/summary-record.js.map +1 -1
  26. package/dist/os/core/core.module.d.ts.map +1 -1
  27. package/dist/os/core/core.module.js +124 -73
  28. package/dist/os/core/core.module.js.map +1 -1
  29. package/dist/os/core/handlers/file-size-lint.d.ts.map +1 -1
  30. package/dist/os/core/handlers/file-size-lint.js +2 -0
  31. package/dist/os/core/handlers/file-size-lint.js.map +1 -1
  32. package/dist/os/exploration/exploration.module.d.ts.map +1 -1
  33. package/dist/os/exploration/exploration.module.js +3 -0
  34. package/dist/os/exploration/exploration.module.js.map +1 -1
  35. package/dist/os/naming/naming-convention.d.ts.map +1 -1
  36. package/dist/os/naming/naming-convention.js +11 -1
  37. package/dist/os/naming/naming-convention.js.map +1 -1
  38. package/dist/os/naming/naming.module.d.ts.map +1 -1
  39. package/dist/os/naming/naming.module.js +9 -2
  40. package/dist/os/naming/naming.module.js.map +1 -1
  41. package/dist/os/notes/notes.module.d.ts.map +1 -1
  42. package/dist/os/notes/notes.module.js +17 -6
  43. package/dist/os/notes/notes.module.js.map +1 -1
  44. package/dist/os/plan/plan.module.d.ts.map +1 -1
  45. package/dist/os/plan/plan.module.js +2 -0
  46. package/dist/os/plan/plan.module.js.map +1 -1
  47. package/dist/os/plugin/plugin.module.d.ts.map +1 -1
  48. package/dist/os/plugin/plugin.module.js +9 -2
  49. package/dist/os/plugin/plugin.module.js.map +1 -1
  50. package/dist/os/program/program.module.d.ts.map +1 -1
  51. package/dist/os/program/program.module.js +19 -4
  52. package/dist/os/program/program.module.js.map +1 -1
  53. package/dist/os/queue/handlers/queue-validate.d.ts.map +1 -1
  54. package/dist/os/queue/handlers/queue-validate.js +154 -7
  55. package/dist/os/queue/handlers/queue-validate.js.map +1 -1
  56. package/dist/os/queue/manifest.d.ts +17 -1
  57. package/dist/os/queue/manifest.d.ts.map +1 -1
  58. package/dist/os/queue/manifest.js +127 -6
  59. package/dist/os/queue/manifest.js.map +1 -1
  60. package/dist/os/queue/queue.module.d.ts.map +1 -1
  61. package/dist/os/queue/queue.module.js +32 -4
  62. package/dist/os/queue/queue.module.js.map +1 -1
  63. package/dist/os/queue/types.d.ts +100 -1
  64. package/dist/os/queue/types.d.ts.map +1 -1
  65. package/dist/os/queue/types.js +44 -0
  66. package/dist/os/queue/types.js.map +1 -1
  67. package/dist/os/rfc/handlers/implement-stamp.d.ts.map +1 -1
  68. package/dist/os/rfc/handlers/implement-stamp.js +30 -8
  69. package/dist/os/rfc/handlers/implement-stamp.js.map +1 -1
  70. package/dist/os/rfc/rfc.module.d.ts.map +1 -1
  71. package/dist/os/rfc/rfc.module.js +25 -6
  72. package/dist/os/rfc/rfc.module.js.map +1 -1
  73. package/dist/os/rfc/types.d.ts +12 -2
  74. package/dist/os/rfc/types.d.ts.map +1 -1
  75. package/dist/os/rfc/types.js +2 -2
  76. package/dist/os/rfc/types.js.map +1 -1
  77. package/dist/os/rfc/verification-evidence.d.ts +21 -0
  78. package/dist/os/rfc/verification-evidence.d.ts.map +1 -1
  79. package/dist/os/rfc/verification-evidence.js +134 -23
  80. package/dist/os/rfc/verification-evidence.js.map +1 -1
  81. package/dist/os/rfc/verification-refresh.d.ts.map +1 -1
  82. package/dist/os/rfc/verification-refresh.js +66 -37
  83. package/dist/os/rfc/verification-refresh.js.map +1 -1
  84. package/dist/os/session/handlers/save.d.ts.map +1 -1
  85. package/dist/os/session/handlers/save.js +42 -5
  86. package/dist/os/session/handlers/save.js.map +1 -1
  87. package/dist/os/session/session.module.d.ts.map +1 -1
  88. package/dist/os/session/session.module.js +5 -0
  89. package/dist/os/session/session.module.js.map +1 -1
  90. package/dist/os/spec/live-spec-merge.d.ts.map +1 -1
  91. package/dist/os/spec/live-spec-merge.js +55 -134
  92. package/dist/os/spec/live-spec-merge.js.map +1 -1
  93. package/dist/os/spec/live-spec-rebuild.d.ts +32 -0
  94. package/dist/os/spec/live-spec-rebuild.d.ts.map +1 -0
  95. package/dist/os/spec/live-spec-rebuild.js +272 -0
  96. package/dist/os/spec/live-spec-rebuild.js.map +1 -0
  97. package/dist/os/spec/live-spec-shared.d.ts +62 -0
  98. package/dist/os/spec/live-spec-shared.d.ts.map +1 -0
  99. package/dist/os/spec/live-spec-shared.js +315 -0
  100. package/dist/os/spec/live-spec-shared.js.map +1 -0
  101. package/dist/os/spec/live-spec-types.d.ts +17 -1
  102. package/dist/os/spec/live-spec-types.d.ts.map +1 -1
  103. package/dist/os/spec/live-spec-types.js +3 -0
  104. package/dist/os/spec/live-spec-types.js.map +1 -1
  105. package/dist/os/spec/live-spec-validate.d.ts.map +1 -1
  106. package/dist/os/spec/live-spec-validate.js +105 -2
  107. package/dist/os/spec/live-spec-validate.js.map +1 -1
  108. package/dist/os/spec/spec-materialize.d.ts +2 -0
  109. package/dist/os/spec/spec-materialize.d.ts.map +1 -1
  110. package/dist/os/spec/spec-materialize.js +45 -5
  111. package/dist/os/spec/spec-materialize.js.map +1 -1
  112. package/dist/os/spec/spec-schema.d.ts +19 -0
  113. package/dist/os/spec/spec-schema.d.ts.map +1 -1
  114. package/dist/os/spec/spec-schema.js +30 -0
  115. package/dist/os/spec/spec-schema.js.map +1 -1
  116. package/dist/os/spec/spec-status.d.ts +4 -0
  117. package/dist/os/spec/spec-status.d.ts.map +1 -1
  118. package/dist/os/spec/spec-status.js +19 -1
  119. package/dist/os/spec/spec-status.js.map +1 -1
  120. package/dist/os/spec/spec-validate.d.ts.map +1 -1
  121. package/dist/os/spec/spec-validate.js +55 -8
  122. package/dist/os/spec/spec-validate.js.map +1 -1
  123. package/dist/os/spec/spec.module.d.ts.map +1 -1
  124. package/dist/os/spec/spec.module.js +84 -8
  125. package/dist/os/spec/spec.module.js.map +1 -1
  126. package/dist/os/werkstatt/handlers/werkstatt-operation-validate.d.ts.map +1 -1
  127. package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js +10 -4
  128. package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js.map +1 -1
  129. package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
  130. package/dist/os/werkstatt/werkstatt.module.js +13 -4
  131. package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
  132. package/dist/os/workflow/workflow.module.d.ts.map +1 -1
  133. package/dist/os/workflow/workflow.module.js +13 -5
  134. package/dist/os/workflow/workflow.module.js.map +1 -1
  135. package/dist/src/cli-flags.d.ts.map +1 -1
  136. package/dist/src/cli-flags.js +25 -5
  137. package/dist/src/cli-flags.js.map +1 -1
  138. package/dist/src/config/forge-config.d.ts +3 -0
  139. package/dist/src/config/forge-config.d.ts.map +1 -1
  140. package/dist/src/config/forge-config.js +2 -0
  141. package/dist/src/config/forge-config.js.map +1 -1
  142. package/dist/src/onboarding/doctor.d.ts.map +1 -1
  143. package/dist/src/onboarding/doctor.js +2 -3
  144. package/dist/src/onboarding/doctor.js.map +1 -1
  145. package/dist/src/onboarding/init.d.ts.map +1 -1
  146. package/dist/src/onboarding/init.js +8 -3
  147. package/dist/src/onboarding/init.js.map +1 -1
  148. package/dist/src/pipeline-status.d.ts +14 -1
  149. package/dist/src/pipeline-status.d.ts.map +1 -1
  150. package/dist/src/pipeline-status.js +28 -8
  151. package/dist/src/pipeline-status.js.map +1 -1
  152. package/dist/src/types.d.ts +7 -1
  153. package/dist/src/types.d.ts.map +1 -1
  154. package/dist/src/types.js +6 -0
  155. package/dist/src/types.js.map +1 -1
  156. package/dist/src/utils/generated-marker.d.ts +8 -0
  157. package/dist/src/utils/generated-marker.d.ts.map +1 -1
  158. package/dist/src/utils/generated-marker.js +25 -0
  159. package/dist/src/utils/generated-marker.js.map +1 -1
  160. package/dist/src/validators/skill-validate.d.ts.map +1 -1
  161. package/dist/src/validators/skill-validate.js +2 -4
  162. package/dist/src/validators/skill-validate.js.map +1 -1
  163. package/os/adr/adr.module.ts +2 -0
  164. package/os/audit/audit.module.ts +2 -0
  165. package/os/compass/compass.module.ts +49 -15
  166. package/os/compass/handlers/compass-audit-handler.ts +51 -10
  167. package/os/compass/handlers/compass-docs-validate.ts +361 -0
  168. package/os/compass/handlers/summary-record.ts +106 -15
  169. package/os/compass/handlers/tests/compass-audit-record.test.ts +2 -1
  170. package/os/compass/handlers/tests/compass-audit-validate.test.ts +92 -3
  171. package/os/compass/handlers/tests/compass-docs-validate.test.ts +203 -0
  172. package/os/compass/handlers/tests/compass-ledger-scope.test.ts +2 -1
  173. package/os/compass/handlers/tests/compass-policy.test.ts +7 -1
  174. package/os/compass/handlers/tests/summary-record.test.ts +127 -0
  175. package/os/core/core.module.ts +147 -75
  176. package/os/core/docs-archive.test.ts +136 -0
  177. package/os/core/handlers/file-size-lint.ts +2 -0
  178. package/os/exploration/exploration.module.ts +3 -0
  179. package/os/naming/naming-convention.ts +11 -1
  180. package/os/naming/naming.module.ts +9 -2
  181. package/os/notes/notes.module.ts +17 -6
  182. package/os/plan/plan-0000-template.md +150 -0
  183. package/os/plan/plan.module.ts +2 -0
  184. package/os/plugin/plugin.module.ts +9 -2
  185. package/os/program/program.module.ts +19 -4
  186. package/os/queue/decision-ledger.test.ts +163 -0
  187. package/os/queue/handlers/queue-validate.ts +190 -8
  188. package/os/queue/manifest.ts +201 -7
  189. package/os/queue/queue-validate.test.ts +303 -0
  190. package/os/queue/queue.module.ts +32 -4
  191. package/os/queue/types.ts +56 -1
  192. package/os/rfc/handlers/implement-stamp.ts +37 -8
  193. package/os/rfc/rfc-read-only-no-side-effects.test.ts +1 -0
  194. package/os/rfc/rfc.module.ts +27 -6
  195. package/os/rfc/types.ts +18 -3
  196. package/os/rfc/verification-emit.test.ts +305 -0
  197. package/os/rfc/verification-evidence.ts +149 -24
  198. package/os/rfc/verification-refresh.test.ts +222 -1
  199. package/os/rfc/verification-refresh.ts +74 -38
  200. package/os/session/handlers/save.ts +37 -5
  201. package/os/session/session.module.ts +5 -0
  202. package/os/spec/live-spec-idempotency.pbt.test.ts +222 -0
  203. package/os/spec/live-spec-list-show-validate.test.ts +10 -6
  204. package/os/spec/live-spec-merge.test.ts +187 -7
  205. package/os/spec/live-spec-merge.ts +71 -155
  206. package/os/spec/live-spec-rebuild.test.ts +317 -0
  207. package/os/spec/live-spec-rebuild.ts +356 -0
  208. package/os/spec/live-spec-shared.ts +378 -0
  209. package/os/spec/live-spec-types.ts +21 -1
  210. package/os/spec/live-spec-validate.test.ts +317 -0
  211. package/os/spec/live-spec-validate.ts +106 -2
  212. package/os/spec/spec-materialize.test.ts +192 -0
  213. package/os/spec/spec-materialize.ts +48 -4
  214. package/os/spec/spec-schema.ts +49 -0
  215. package/os/spec/spec-status.ts +24 -0
  216. package/os/spec/spec-validate.test.ts +66 -0
  217. package/os/spec/spec-validate.ts +62 -8
  218. package/os/spec/spec.module.ts +85 -8
  219. package/os/werkstatt/handlers/werkstatt-operation-validate.ts +10 -4
  220. package/os/werkstatt/werkstatt.module.ts +13 -4
  221. package/os/workflow/workflow.module.ts +13 -5
  222. package/package.json +1 -1
  223. package/skills/_shared/fo-pipeline-conventions.md +126 -1
  224. package/skills/fo/fo-doc-audit/SKILL.md +4 -4
  225. package/skills/fo/fo-explore/SKILL.md +2 -1
  226. package/skills/fo/fo-fix/SKILL.md +11 -3
  227. package/skills/fo/fo-handoff/SKILL.md +4 -6
  228. package/skills/fo/fo-idea-create-rfc/SKILL.md +2 -1
  229. package/skills/fo/fo-idea-enhance/SKILL.md +23 -4
  230. package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +86 -22
  231. package/skills/fo/fo-idea-implement/SKILL.md +53 -10
  232. package/skills/fo/fo-idea-plan/SKILL.md +28 -6
  233. package/skills/fo/fo-review/SKILL.md +69 -8
  234. package/skills/fo/fo-session-retro/SKILL.md +3 -2
  235. package/skills/fo/fo-step-commit/SKILL.md +2 -0
  236. package/skills/shared/grilling/SKILL.md +12 -0
  237. package/skills/shared/writing-great-skills/GLOSSARY.md +2 -0
  238. package/skills/shared/writing-great-skills/SKILL.md +2 -1
  239. package/src/cli-flags.ts +23 -5
  240. package/src/config/forge-config.ts +4 -0
  241. package/src/onboarding/doctor.ts +2 -3
  242. package/src/onboarding/init.ts +8 -3
  243. package/src/pipeline-status.ts +32 -8
  244. package/src/tests/cli-flags.test.ts +20 -0
  245. package/src/tests/cli-output.test.ts +13 -7
  246. package/src/tests/fixtures/agents-generate-business-before.txt +7 -7
  247. package/src/tests/implement-stamp.test.ts +38 -0
  248. package/src/tests/session-handlers.test.ts +33 -0
  249. package/src/types.ts +13 -1
  250. package/src/utils/generated-marker.ts +28 -0
  251. package/src/validators/skill-validate.ts +2 -4
  252. package/skills/fo/fo-idea-implement/ADR-FLOW.md +0 -179
  253. package/skills/fo/fo-review/AXES.md +0 -70
@@ -0,0 +1,272 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>spec.live.rebuild handler — regenerates a living spec by deterministic
4
+ replay of its deduplicated history[] (RFC-1230). The repair path for duplicated
5
+ namespaced sections and duplicate history entries produced before spec.live.merge
6
+ became idempotent. Replaying each unique RFC's current ## Design in first-occurrence
7
+ order yields the canonical spec; hand-applied edits are dropped by design.</purpose>
8
+ <non-goals>
9
+ <item>Do not merge new RFCs — that is spec.live.merge.</item>
10
+ <item>Do not validate — spec.live.validate reports what rebuild must repair.</item>
11
+ <item>Do not delete spec files — empty-history or missing specs are skipped, never removed.</item>
12
+ </non-goals>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-1230: initial spec.live.rebuild handler — dedupe history, single RFC scan, replay via shared section mechanics, unchanged-detection modulo updatedAt.</item>
15
+ <item>RFC-1230: review findings — scoped droppedSections to namespaced headings, warn on unreadable spec, fail-fast merge on corrupt frontmatter, CHANGE_SUMMARY dedupe</item>
16
+ <item>RFC-1231: step 1 — rename supportsAllSites to acceptsAllFlag
17
+
18
+ Mechanical sweep: the field only ever gated --all argv acceptance; fan-out
19
+ follows the parsed selector. Guard renamed assertAllSitesAllowed ->
20
+ assertAllFlagAccepted, message updated. 417 declaration sites + type
21
+ surfaces (KernelCommandMetadata, ForgeCommandMetadata) in one atomic pass.</item>
22
+ <item>RFC-1234: extracted projectLiveSpec — the pure projection core (dedupe history, replay Design sections, serialize modulo updatedAt) — consumable without writes or logging; spec.live.validate V-LS-09 reuses it so the validator sees exactly what rebuild would write.</item>
23
+ </CHANGE_SUMMARY>
24
+ */
25
+ import { ambientIo as fs } from "../../src/utils/io.js";
26
+ import { existsSync } from "../../src/utils/sync-fs.js";
27
+ import path from "node:path";
28
+ import { writeFileIfChanged, buildGeneratedHeader } from "../../src/utils/index.js";
29
+ import { listRfcFiles, readAndParseRfc, rfcFileMatchesId } from "../rfc/frontmatter-io.js";
30
+ import { RFC_DIR } from "../rfc/types.js";
31
+ import { LIVE_SPECS_DIR, extractDesignSection, parseHeadings, parseLivingSpec, serializeLivingSpec, applyDeltasToSpecBody, namespaceHeadings, seedSpecPrefix, } from "./live-spec-shared.js";
32
+ export async function projectLiveSpec(specFilePath, domain, rfcFiles, rfcDirPath) {
33
+ const content = await fs.readFile(specFilePath).catch(() => null);
34
+ if (content === null)
35
+ return { kind: "unreadable" };
36
+ const spec = parseLivingSpec(content);
37
+ if (!spec)
38
+ return { kind: "invalid-frontmatter" };
39
+ if (spec.history.length === 0)
40
+ return { kind: "empty-history" };
41
+ const uniqueIds = [];
42
+ const normalizedHistory = [];
43
+ for (const entry of spec.history) {
44
+ if (!entry?.rfc || uniqueIds.includes(entry.rfc))
45
+ continue;
46
+ uniqueIds.push(entry.rfc);
47
+ normalizedHistory.push(entry);
48
+ }
49
+ // Count only namespaced duplicates — the metric is "duplicate (RFC-XXXX)
50
+ // sections removed"; non-namespaced heading repeats are out of contract.
51
+ const existingHeadings = parseHeadings(spec.body);
52
+ const seenTexts = new Set();
53
+ let droppedSections = 0;
54
+ for (const h of existingHeadings) {
55
+ if (!/\(RFC-\d{4}\)$/.test(h.text))
56
+ continue;
57
+ if (seenTexts.has(h.text))
58
+ droppedSections++;
59
+ else
60
+ seenTexts.add(h.text);
61
+ }
62
+ const header = buildGeneratedHeader({
63
+ filePath: specFilePath,
64
+ ownerCommand: "spec.live.merge",
65
+ commandPrefix: "pnpm exec werkstatt run",
66
+ });
67
+ // trimEnd so applyDeltas' own "\n\n" separator reproduces the creation path's
68
+ // `seed + designSection` layout exactly — otherwise rebuild inserts an extra
69
+ // blank line after "## Overview" and unchanged-detection never fires.
70
+ let body = seedSpecPrefix(domain, header).trimEnd();
71
+ const unreadableRfcs = [];
72
+ for (const rfcId of uniqueIds) {
73
+ const file = rfcFiles.find((f) => rfcFileMatchesId(f, rfcId));
74
+ const parsedResult = file
75
+ ? await readAndParseRfc(rfcDirPath, file)
76
+ : undefined;
77
+ const fm = parsedResult && "parsed" in parsedResult ? parsedResult.parsed.frontmatter : undefined;
78
+ const status = fm ? String(fm["status"] ?? "").trim() : "";
79
+ const design = parsedResult && "parsed" in parsedResult
80
+ ? extractDesignSection(parsedResult.parsed.body)
81
+ : "";
82
+ if (!fm || status !== "implemented" || !design) {
83
+ unreadableRfcs.push(rfcId);
84
+ continue;
85
+ }
86
+ const namespaced = namespaceHeadings(design, rfcId);
87
+ const headings = parseHeadings(namespaced);
88
+ const ops = headings.map((h) => ({
89
+ type: "added",
90
+ heading: h.text,
91
+ rfc: rfcId,
92
+ }));
93
+ body = applyDeltasToSpecBody(body, headings, ops);
94
+ }
95
+ const regenerated = {
96
+ ...spec,
97
+ lastMergedRfc: uniqueIds[uniqueIds.length - 1],
98
+ history: normalizedHistory,
99
+ body: body.trim(),
100
+ };
101
+ // Compare modulo updatedAt — a clean spec rebuilt on another day must still
102
+ // report unchanged, otherwise every rebuild produces a one-line date diff.
103
+ const projection = serializeLivingSpec({ ...regenerated, updatedAt: spec.updatedAt });
104
+ return {
105
+ kind: "projected",
106
+ spec,
107
+ content,
108
+ projection,
109
+ regenerated,
110
+ uniqueIds,
111
+ unreadableRfcs,
112
+ droppedSections,
113
+ };
114
+ }
115
+ async function rebuildOneSpec(specFilePath, domain, rfcFiles, rfcDirPath, today, dryRun, context) {
116
+ const { logger } = context;
117
+ const base = {
118
+ command: "spec.live.rebuild",
119
+ domain,
120
+ operation: "skipped",
121
+ uniqueRfcs: 0,
122
+ droppedHistoryEntries: 0,
123
+ droppedSections: 0,
124
+ unreadableRfcs: [],
125
+ dryRun,
126
+ };
127
+ const proj = await projectLiveSpec(specFilePath, domain, rfcFiles, rfcDirPath);
128
+ if (proj.kind === "unreadable") {
129
+ logger.warn(`spec.live.rebuild: ${domain} unreadable at ${specFilePath} — skipping`);
130
+ return { result: base, exitCode: 1 };
131
+ }
132
+ if (proj.kind === "invalid-frontmatter") {
133
+ logger.warn(`spec.live.rebuild: ${domain} has no valid frontmatter — skipping`);
134
+ return { result: base, exitCode: 1 };
135
+ }
136
+ if (proj.kind === "empty-history") {
137
+ return { result: base, exitCode: 0 };
138
+ }
139
+ for (const rfcId of proj.unreadableRfcs) {
140
+ logger.warn(`spec.live.rebuild: ${rfcId} unreadable, not implemented, or lacks ## Design — skipping`);
141
+ }
142
+ const replayed = proj.uniqueIds.length - proj.unreadableRfcs.length;
143
+ if (replayed === 0) {
144
+ return {
145
+ result: {
146
+ ...base,
147
+ operation: "skipped",
148
+ uniqueRfcs: proj.uniqueIds.length,
149
+ unreadableRfcs: proj.unreadableRfcs,
150
+ },
151
+ exitCode: 2,
152
+ };
153
+ }
154
+ const droppedHistoryEntries = proj.spec.history.length - proj.regenerated.history.length;
155
+ if (proj.projection === proj.content) {
156
+ return {
157
+ result: {
158
+ ...base,
159
+ operation: "unchanged",
160
+ uniqueRfcs: proj.uniqueIds.length,
161
+ droppedHistoryEntries,
162
+ droppedSections: proj.droppedSections,
163
+ unreadableRfcs: proj.unreadableRfcs,
164
+ },
165
+ exitCode: 0,
166
+ };
167
+ }
168
+ const finalSpec = serializeLivingSpec({ ...proj.regenerated, updatedAt: today });
169
+ if (!dryRun) {
170
+ await writeFileIfChanged(specFilePath, finalSpec);
171
+ }
172
+ return {
173
+ result: {
174
+ ...base,
175
+ operation: "rebuilt",
176
+ uniqueRfcs: proj.uniqueIds.length,
177
+ droppedHistoryEntries,
178
+ droppedSections: proj.droppedSections,
179
+ unreadableRfcs: proj.unreadableRfcs,
180
+ },
181
+ exitCode: 0,
182
+ };
183
+ }
184
+ export async function runSpecLiveRebuild(input, context) {
185
+ const { workspaceRoot, logger, outputFormat } = context;
186
+ const domain = String(input.flags["domain"] ?? "");
187
+ const dryRun = context.dryRun || input.flags["dry-run"] === true;
188
+ // The kernel consumes `--all` as its own site-selector before argv reaches
189
+ // the command (acceptsAllFlag: true lets it through). Under `werkstatt run`
190
+ // the flag never lands in input.flags — so "no --domain" IS the kernel-level
191
+ // --all signal. The explicit flag still works via the standalone forge CLI.
192
+ const explicitAll = input.flags["all"] === true;
193
+ if (domain && explicitAll) {
194
+ return {
195
+ data: {
196
+ command: "spec.live.rebuild",
197
+ domains: [],
198
+ dryRun,
199
+ },
200
+ exitCode: 1,
201
+ summary: "spec.live.rebuild: --domain and --all are mutually exclusive",
202
+ };
203
+ }
204
+ const all = explicitAll || !domain;
205
+ const liveSpecsDir = path.join(workspaceRoot, LIVE_SPECS_DIR);
206
+ const rfcDirPath = path.join(workspaceRoot, RFC_DIR);
207
+ const today = new Date().toISOString().slice(0, 10);
208
+ const specPaths = [];
209
+ if (all) {
210
+ if (existsSync(liveSpecsDir)) {
211
+ const files = (await fs.readdir(liveSpecsDir)).map((e) => e.name);
212
+ for (const f of files.filter((f) => f.endsWith(".md") && f !== "README.md").sort()) {
213
+ specPaths.push({ domain: f.slice(0, -3), filePath: path.join(liveSpecsDir, f) });
214
+ }
215
+ }
216
+ }
217
+ else {
218
+ const filePath = path.join(liveSpecsDir, `${domain}.md`);
219
+ if (!existsSync(filePath)) {
220
+ return {
221
+ data: {
222
+ command: "spec.live.rebuild",
223
+ domains: [],
224
+ dryRun,
225
+ },
226
+ exitCode: 1,
227
+ summary: `spec.live.rebuild: living spec not found for domain "${domain}" (${LIVE_SPECS_DIR}/${domain}.md)`,
228
+ };
229
+ }
230
+ specPaths.push({ domain, filePath });
231
+ }
232
+ if (specPaths.length === 0) {
233
+ return {
234
+ data: { command: "spec.live.rebuild", domains: [], dryRun },
235
+ exitCode: 0,
236
+ summary: "spec.live.rebuild: no living specs found",
237
+ };
238
+ }
239
+ // Single recursive RFC scan reused for every spec — a corrupted spec can carry
240
+ // ~170 history entries, and a per-id listRfcFiles rescan is O(n²).
241
+ const rfcFiles = await listRfcFiles(rfcDirPath);
242
+ const results = [];
243
+ let worstExit = 0;
244
+ for (const { domain: d, filePath } of specPaths) {
245
+ const { result, exitCode } = await rebuildOneSpec(filePath, d, rfcFiles, rfcDirPath, today, dryRun, context);
246
+ results.push(result);
247
+ if (exitCode > worstExit)
248
+ worstExit = exitCode;
249
+ if (outputFormat === "pretty") {
250
+ const suffix = dryRun ? " [dry-run]" : "";
251
+ if (result.operation === "rebuilt") {
252
+ logger.success(`spec.live.rebuild: ${d} rebuilt — ${result.uniqueRfcs} unique RFC(s), dropped ${result.droppedHistoryEntries} history entr(ies), ${result.droppedSections} duplicate section(s)${suffix}`);
253
+ }
254
+ else {
255
+ logger.info(`spec.live.rebuild: ${d} — ${result.operation}${suffix}`);
256
+ }
257
+ }
258
+ }
259
+ if (!all) {
260
+ return {
261
+ data: results[0],
262
+ exitCode: worstExit,
263
+ summary: `spec.live.rebuild: ${domain} — ${results[0].operation}`,
264
+ };
265
+ }
266
+ return {
267
+ data: { command: "spec.live.rebuild", domains: results, dryRun },
268
+ exitCode: worstExit,
269
+ summary: `spec.live.rebuild: ${results.length} spec(s) processed${dryRun ? " [dry-run]" : ""}`,
270
+ };
271
+ }
272
+ //# sourceMappingURL=live-spec-rebuild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-spec-rebuild.js","sourceRoot":"","sources":["../../../os/spec/live-spec-rebuild.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;EAuBE;AAEF,OAAO,EAAE,SAAS,IAAI,EAAE,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAO1C,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,uBAAuB,CAAC;AA4B/B,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,YAAoB,EACpB,MAAc,EACd,QAA2B,EAC3B,UAAkB;IAElB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAClE,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACpD,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;IAClD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IAEhE,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,iBAAiB,GAA6B,EAAE,CAAC;IACvD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;YAAE,SAAS;QAC3D,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,gBAAgB,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,KAAK,MAAM,CAAC,IAAI,gBAAgB,EAAE,CAAC;QACjC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7C,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,eAAe,EAAE,CAAC;;YACxC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC;QAClC,QAAQ,EAAE,YAAY;QACtB,YAAY,EAAE,iBAAiB;QAC/B,aAAa,EAAE,yBAAyB;KACzC,CAAC,CAAC;IACH,8EAA8E;IAC9E,6EAA6E;IAC7E,sEAAsE;IACtE,IAAI,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IACpD,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,IAAI;YACvB,CAAC,CAAC,MAAM,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC;YACzC,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,EAAE,GACN,YAAY,IAAI,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,YAAY,IAAI,QAAQ,IAAI,YAAY;YACrD,CAAC,CAAC,oBAAoB,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;YAChD,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,CAAC,EAAE,IAAI,MAAM,KAAK,aAAa,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/C,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAqB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjD,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,CAAC,CAAC,IAAI;YACf,GAAG,EAAE,KAAK;SACX,CAAC,CAAC,CAAC;QACJ,IAAI,GAAG,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,WAAW,GAAe;QAC9B,GAAG,IAAI;QACP,aAAa,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAE;QAC/C,OAAO,EAAE,iBAAiB;QAC1B,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;KAClB,CAAC;IAEF,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,UAAU,GAAG,mBAAmB,CAAC,EAAE,GAAG,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAEtF,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,IAAI;QACJ,OAAO;QACP,UAAU;QACV,WAAW;QACX,SAAS;QACT,cAAc;QACd,eAAe;KAChB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,YAAoB,EACpB,MAAc,EACd,QAA2B,EAC3B,UAAkB,EAClB,KAAa,EACb,MAAe,EACf,OAA4B;IAE5B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE3B,MAAM,IAAI,GAA0B;QAClC,OAAO,EAAE,mBAAmB;QAC5B,MAAM;QACN,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,CAAC;QACb,qBAAqB,EAAE,CAAC;QACxB,eAAe,EAAE,CAAC;QAClB,cAAc,EAAE,EAAE;QAClB,MAAM;KACP,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/E,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,sBAAsB,MAAM,kBAAkB,YAAY,aAAa,CAAC,CAAC;QACrF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACvC,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,sBAAsB,MAAM,sCAAsC,CAAC,CAAC;QAChF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACvC,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QAClC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CACT,sBAAsB,KAAK,6DAA6D,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;IACpE,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO;YACL,MAAM,EAAE;gBACN,GAAG,IAAI;gBACP,SAAS,EAAE,SAAS;gBACpB,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM;gBACjC,cAAc,EAAE,IAAI,CAAC,cAAc;aACpC;YACD,QAAQ,EAAE,CAAC;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,qBAAqB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;IACzF,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,OAAO;YACL,MAAM,EAAE;gBACN,GAAG,IAAI;gBACP,SAAS,EAAE,WAAW;gBACtB,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM;gBACjC,qBAAqB;gBACrB,eAAe,EAAE,IAAI,CAAC,eAAe;gBACrC,cAAc,EAAE,IAAI,CAAC,cAAc;aACpC;YACD,QAAQ,EAAE,CAAC;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,mBAAmB,CAAC,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACjF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,kBAAkB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACpD,CAAC;IAED,OAAO;QACL,MAAM,EAAE;YACN,GAAG,IAAI;YACP,SAAS,EAAE,SAAS;YACpB,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM;YACjC,qBAAqB;YACrB,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,cAAc,EAAE,IAAI,CAAC,cAAc;SACpC;QACD,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAwB,EACxB,OAA4B;IAE5B,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACxD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAEjE,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAChD,IAAI,MAAM,IAAI,WAAW,EAAE,CAAC;QAC1B,OAAO;YACL,IAAI,EAAE;gBACJ,OAAO,EAAE,mBAAmB;gBAC5B,OAAO,EAAE,EAAE;gBACX,MAAM;aACP;YACD,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,8DAA8D;SACxE,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,WAAW,IAAI,CAAC,MAAM,CAAC;IAEnC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEpD,MAAM,SAAS,GAAgD,EAAE,CAAC;IAClE,IAAI,GAAG,EAAE,CAAC;QACR,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAClE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnF,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC;QACzD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,IAAI,EAAE;oBACJ,OAAO,EAAE,mBAAmB;oBAC5B,OAAO,EAAE,EAAE;oBACX,MAAM;iBACP;gBACD,QAAQ,EAAE,CAAC;gBACX,OAAO,EAAE,wDAAwD,MAAM,MAAM,cAAc,IAAI,MAAM,MAAM;aAC5G,CAAC;QACJ,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,IAAI,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE;YAC3D,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,0CAA0C;SACpD,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,mEAAmE;IACnE,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC;IAEhD,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,SAAS,EAAE,CAAC;QAChD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,cAAc,CAC/C,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAC1D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,QAAQ,GAAG,SAAS;YAAE,SAAS,GAAG,QAAQ,CAAC;QAC/C,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACnC,MAAM,CAAC,OAAO,CACZ,sBAAsB,CAAC,cAAc,MAAM,CAAC,UAAU,2BAA2B,MAAM,CAAC,qBAAqB,uBAAuB,MAAM,CAAC,eAAe,wBAAwB,MAAM,EAAE,CAC3L,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,MAAM,MAAM,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO;YACL,IAAI,EAAE,OAAO,CAAC,CAAC,CAAE;YACjB,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,sBAAsB,MAAM,MAAM,OAAO,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE;SACnE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE;QAChE,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,sBAAsB,OAAO,CAAC,MAAM,qBAAqB,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE;KAC/F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { LivingSpec, DeltaOperation } from "./live-spec-types.ts";
2
+ export declare const LIVE_SPECS_DIR = "docs/specs/live";
3
+ export interface ParsedHeading {
4
+ level: number;
5
+ text: string;
6
+ body: string;
7
+ }
8
+ export declare function extractDesignSection(rfcBody: string): string;
9
+ export declare function parseHeadings(content: string): ParsedHeading[];
10
+ export declare function deriveDomain(frontmatter: Record<string, unknown>): string | null;
11
+ export declare function parseLivingSpec(content: string): LivingSpec | null;
12
+ export declare function serializeLivingSpec(spec: LivingSpec): string;
13
+ export declare function findHeadingInSpec(spec: LivingSpec, headingText: string): ParsedHeading | null;
14
+ export declare function applyDeltasToSpecBody(specBody: string, deltas: ParsedHeading[], operations: DeltaOperation[]): string;
15
+ export declare function escapeRegex(str: string): string;
16
+ export declare function namespaceHeadings(content: string, rfcId: string): string;
17
+ export declare function removeNamespacedSections(body: string, rfcId: string): string;
18
+ export declare function findRfcFile(rfcDir: string, rfcId: string): Promise<string | null>;
19
+ export declare function seedSpecPrefix(domain: string, generatedHeader: string): string;
20
+ export interface LiveMergeTargetSelection {
21
+ candidates: string[];
22
+ rejected: string[];
23
+ }
24
+ export declare function collectLiveMergeTargets(moved: ReadonlyArray<{
25
+ id: string;
26
+ status: string;
27
+ direction: string;
28
+ }>): LiveMergeTargetSelection;
29
+ export interface LiveMergeEntry {
30
+ id: string;
31
+ domain: string;
32
+ operation: string;
33
+ conflicts: number;
34
+ }
35
+ export interface LiveMergeFailure {
36
+ id: string;
37
+ exitCode?: number;
38
+ error: string;
39
+ }
40
+ export type LiveMergeOutcome = {
41
+ kind: "merged";
42
+ entry: LiveMergeEntry;
43
+ } | {
44
+ kind: "failed";
45
+ entry: LiveMergeFailure;
46
+ };
47
+ export declare function classifyLiveMergeOutcome(rfcId: string, result: {
48
+ exitCode?: number;
49
+ data?: unknown;
50
+ summary?: string;
51
+ }): LiveMergeOutcome;
52
+ export declare function liveMergeFailureEntry(rfcId: string, error: unknown): LiveMergeFailure;
53
+ export declare function formatLiveMergeFailure(entry: LiveMergeFailure): string;
54
+ /** Emission gate + block assembly for results["spec.live.merge"] — undefined
55
+ * when nothing was attempted, so an all-failed run still surfaces. */
56
+ export declare function buildLiveMergeBlock(merged: LiveMergeEntry[], failed: LiveMergeFailure[], skipped: number, dryRun: boolean): {
57
+ merged: LiveMergeEntry[];
58
+ failed: LiveMergeFailure[];
59
+ skipped: number;
60
+ dryRun: boolean;
61
+ } | undefined;
62
+ //# sourceMappingURL=live-spec-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-spec-shared.d.ts","sourceRoot":"","sources":["../../../os/spec/live-spec-shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,315 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Shared pure helpers for living feature specs — heading parsing, RFC-namespaced
4
+ section mechanics, spec (de)serialization, domain derivation, and the docs.archive
5
+ this-run merge-target selection (RFC-0711, RFC-0957, RFC-1230).</purpose>
6
+ <non-goals>
7
+ <item>Do not implement command handlers — spec.live.merge/validate/rebuild handlers live in their own files.</item>
8
+ <item>Do not touch the filesystem — these helpers are pure; IO stays in handlers.</item>
9
+ </non-goals>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-1230: extracted shared helpers from live-spec-merge.ts; added removeNamespacedSections, seedSpecPrefix, and collectLiveMergeTargets for the idempotent-merge + rebuild design.</item>
12
+ <item>RFC-1230: review findings — scoped droppedSections to namespaced headings, warn on unreadable spec, fail-fast merge on corrupt frontmatter, CHANGE_SUMMARY dedupe</item>
13
+ <item>RFC-1232: fence-aware heading mechanics — private fencedLineIndexes (CommonMark-subset ``` / ~~~ map) consumed by parseHeadings, extractDesignSection, namespaceHeadings, removeNamespacedSections, and applyDeltasToSpecBody (regex lookaheads replaced with line-slice boundaries).</item>
14
+ <item>RFC-1235: extracted docs.archive merge-outcome accounting — classifyLiveMergeOutcome, liveMergeFailureEntry, formatLiveMergeFailure, buildLiveMergeBlock — so exit-nonzero merges record to failed[] instead of merged[].</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+ import path from "node:path";
18
+ import YAML from "yaml";
19
+ import { listRfcFiles } from "../rfc/frontmatter-io.js";
20
+ export const LIVE_SPECS_DIR = "docs/specs/live";
21
+ // RFC-1232: CommonMark-subset fence map — line indexes inside fenced code
22
+ // blocks. A run of ` or ~ of length >=3 with <=3 leading spaces opens a fence;
23
+ // the same-or-longer run of the same character closes it. Opener and closer
24
+ // lines count as interior. An unterminated fence makes the rest of the input
25
+ // interior (CommonMark behavior). Indented code blocks are out of scope.
26
+ const FENCE_OPEN_REGEX = /^ {0,3}(`{3,}|~{3,})/;
27
+ // CommonMark: a closing fence carries no info string — only trailing
28
+ // whitespace. A same-char run followed by text is interior content, so the
29
+ // map over-masks rather than exposing a would-be heading (safe direction).
30
+ // CRLF note: `[ \t]*$` deliberately excludes \r, so a CRLF closer never
31
+ // matches and the tail stays interior — over-masking is the safe direction
32
+ // and repo content is LF-normalized.
33
+ const FENCE_CLOSE_REGEX = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
34
+ function fencedLineIndexes(lines) {
35
+ const inside = new Set();
36
+ let fenceChar = null;
37
+ let fenceLength = 0;
38
+ for (let i = 0; i < lines.length; i++) {
39
+ const match = lines[i].match(FENCE_OPEN_REGEX);
40
+ if (fenceChar === null) {
41
+ if (match) {
42
+ fenceChar = match[1][0];
43
+ fenceLength = match[1].length;
44
+ inside.add(i);
45
+ }
46
+ continue;
47
+ }
48
+ inside.add(i);
49
+ const closeMatch = lines[i].match(FENCE_CLOSE_REGEX);
50
+ if (closeMatch && closeMatch[1][0] === fenceChar && closeMatch[1].length >= fenceLength) {
51
+ fenceChar = null;
52
+ fenceLength = 0;
53
+ }
54
+ }
55
+ return inside;
56
+ }
57
+ export function extractDesignSection(rfcBody) {
58
+ const lines = rfcBody.split("\n");
59
+ const fenced = fencedLineIndexes(lines);
60
+ let startLine = -1;
61
+ for (let i = 0; i < lines.length; i++) {
62
+ if (!fenced.has(i) && /^##\s+Design\s*$/.test(lines[i])) {
63
+ startLine = i;
64
+ break;
65
+ }
66
+ }
67
+ if (startLine === -1)
68
+ return "";
69
+ let endLine = lines.length;
70
+ for (let i = startLine + 1; i < lines.length; i++) {
71
+ if (!fenced.has(i) && /^##\s/.test(lines[i])) {
72
+ endLine = i;
73
+ break;
74
+ }
75
+ }
76
+ return lines.slice(startLine + 1, endLine).join("\n").trim();
77
+ }
78
+ export function parseHeadings(content) {
79
+ const lines = content.split("\n");
80
+ const fenced = fencedLineIndexes(lines);
81
+ const headings = [];
82
+ let currentHeading = null;
83
+ let currentBody = [];
84
+ for (let i = 0; i < lines.length; i++) {
85
+ const line = lines[i];
86
+ const headingMatch = fenced.has(i) ? null : line.match(/^(#{3,})\s+(.+)$/);
87
+ if (headingMatch) {
88
+ if (currentHeading) {
89
+ currentHeading.body = currentBody.join("\n").trim();
90
+ headings.push(currentHeading);
91
+ }
92
+ currentHeading = {
93
+ level: headingMatch[1].length,
94
+ text: headingMatch[2].trim(),
95
+ body: "",
96
+ };
97
+ currentBody = [];
98
+ }
99
+ else if (currentHeading) {
100
+ currentBody.push(line);
101
+ }
102
+ }
103
+ if (currentHeading) {
104
+ currentHeading.body = currentBody.join("\n").trim();
105
+ headings.push(currentHeading);
106
+ }
107
+ return headings;
108
+ }
109
+ export function deriveDomain(frontmatter) {
110
+ const liveSpec = frontmatter["liveSpec"];
111
+ if (typeof liveSpec === "string" && liveSpec.length > 0) {
112
+ return liveSpec;
113
+ }
114
+ if (liveSpec === true) {
115
+ const packagesImpacted = frontmatter["packagesImpacted"];
116
+ if (Array.isArray(packagesImpacted) && packagesImpacted.length > 0) {
117
+ const firstPkg = String(packagesImpacted[0]);
118
+ return firstPkg.replace(/^packages\//, "").replace(/^@[^/]+\//, "");
119
+ }
120
+ return null;
121
+ }
122
+ return null;
123
+ }
124
+ export function parseLivingSpec(content) {
125
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
126
+ if (!match)
127
+ return null;
128
+ const fm = (YAML.parse(match[1]) ?? {});
129
+ return {
130
+ domain: String(fm["domain"] ?? ""),
131
+ title: String(fm["title"] ?? ""),
132
+ lastMergedRfc: String(fm["lastMergedRfc"] ?? ""),
133
+ updatedAt: String(fm["updatedAt"] ?? ""),
134
+ createdAt: String(fm["createdAt"] ?? ""),
135
+ history: (Array.isArray(fm["history"]) ? fm["history"] : []),
136
+ body: match[2] ?? "",
137
+ };
138
+ }
139
+ export function serializeLivingSpec(spec) {
140
+ const fm = {
141
+ domain: spec.domain,
142
+ title: spec.title,
143
+ lastMergedRfc: spec.lastMergedRfc,
144
+ updatedAt: spec.updatedAt,
145
+ createdAt: spec.createdAt,
146
+ history: spec.history,
147
+ };
148
+ const fmStr = YAML.stringify(fm).trimEnd();
149
+ return `---\n${fmStr}\n---\n\n${spec.body}`;
150
+ }
151
+ export function findHeadingInSpec(spec, headingText) {
152
+ const headings = parseHeadings(spec.body);
153
+ return headings.find((h) => h.text === headingText) ?? null;
154
+ }
155
+ export function applyDeltasToSpecBody(specBody, deltas, operations) {
156
+ const lines = specBody.split("\n");
157
+ for (const delta of deltas) {
158
+ const op = operations.find((o) => o.heading === delta.text);
159
+ if (!op)
160
+ continue;
161
+ if (op.type === "added") {
162
+ lines.push("", `${"#".repeat(delta.level)} ${delta.text}`, "", ...delta.body.split("\n"));
163
+ continue;
164
+ }
165
+ // RFC-1232: line-slice boundaries instead of `(?=#{3,}|$)` regex lookaheads —
166
+ // those were unanchored and matched `###` inside fenced code blocks.
167
+ const fenced = fencedLineIndexes(lines);
168
+ let start = -1;
169
+ let next = lines.length;
170
+ for (let i = 0; i < lines.length; i++) {
171
+ if (fenced.has(i))
172
+ continue;
173
+ const m = lines[i].match(/^(#{3,})\s+(.+)$/);
174
+ if (!m)
175
+ continue;
176
+ if (start === -1) {
177
+ if (m[1].length === delta.level && m[2].trim() === delta.text)
178
+ start = i;
179
+ }
180
+ else {
181
+ next = i;
182
+ break;
183
+ }
184
+ }
185
+ if (start === -1)
186
+ continue;
187
+ if (op.type === "modified") {
188
+ lines.splice(start + 1, next - start - 1, "", ...delta.body.split("\n"));
189
+ }
190
+ else {
191
+ // removed — absorb the blank lines separating the heading from the
192
+ // preceding content (old `\n*` regex prefix), then collapse the section
193
+ // to a single empty line.
194
+ let removeFrom = start;
195
+ while (removeFrom > 0 && lines[removeFrom - 1].trim() === "")
196
+ removeFrom--;
197
+ lines.splice(removeFrom, next - removeFrom, "");
198
+ }
199
+ }
200
+ return lines.join("\n").trim();
201
+ }
202
+ export function escapeRegex(str) {
203
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
204
+ }
205
+ export function namespaceHeadings(content, rfcId) {
206
+ const lines = content.split("\n");
207
+ const fenced = fencedLineIndexes(lines);
208
+ return lines
209
+ .map((line, i) => {
210
+ if (fenced.has(i))
211
+ return line;
212
+ const m = line.match(/^(#{3,})\s+(.+)$/);
213
+ if (!m)
214
+ return line;
215
+ // parseHeadings trims heading text — normalize here too so a heading like
216
+ // "### Foo " round-trips to "### Foo (RFC-XXXX)", not "### Foo (RFC-XXXX)".
217
+ const normalized = m[2].trim();
218
+ if (/\(RFC-\d{4}\)$/.test(normalized))
219
+ return `${m[1]} ${normalized}`;
220
+ return `${m[1]} ${normalized} (${rfcId})`;
221
+ })
222
+ .join("\n");
223
+ }
224
+ // RFC-1230: strip every ###+ section whose heading carries the ` (RFC-XXXX)`
225
+ // marker. Line-based and byte-preserving for kept sections — parseHeadings is
226
+ // lossy on whitespace, so surgical replacement filters lines instead.
227
+ export function removeNamespacedSections(body, rfcId) {
228
+ const suffix = ` (${rfcId})`;
229
+ const lines = body.split("\n");
230
+ const fenced = fencedLineIndexes(lines);
231
+ const out = [];
232
+ let skipping = false;
233
+ for (let i = 0; i < lines.length; i++) {
234
+ const line = lines[i];
235
+ const headingMatch = fenced.has(i) ? null : line.match(/^(#{3,})\s+(.+)$/);
236
+ if (headingMatch) {
237
+ skipping = headingMatch[2].trim().endsWith(suffix);
238
+ }
239
+ if (!skipping)
240
+ out.push(line);
241
+ }
242
+ return out.join("\n").replace(/\n{3,}/g, "\n\n").trim();
243
+ }
244
+ export function findRfcFile(rfcDir, rfcId) {
245
+ return (async () => {
246
+ const files = await listRfcFiles(rfcDir);
247
+ for (const file of files) {
248
+ const basename = path.basename(file);
249
+ if (basename.toLowerCase().startsWith(rfcId.toLowerCase())) {
250
+ return file;
251
+ }
252
+ }
253
+ return null;
254
+ })();
255
+ }
256
+ // Canonical spec seed — identical to what the spec.live.merge creation path
257
+ // emits, so rebuild and first-merge produce the same skeleton.
258
+ export function seedSpecPrefix(domain, generatedHeader) {
259
+ return `${generatedHeader}# Living Spec: ${domain}\n\n## Overview\n\n`;
260
+ }
261
+ // RFC-1230: the docs.archive post-loop merges only RFCs moved this run — the
262
+ // selection derives from rfc.archive's `moved` result, not a rescan of the
263
+ // whole docs/rfcs tree (which is what caused the duplicate-merge corruption).
264
+ export function collectLiveMergeTargets(moved) {
265
+ const candidates = [];
266
+ const rejected = [];
267
+ for (const m of moved) {
268
+ if (m.direction !== "into-archive")
269
+ continue;
270
+ if (m.status === "implemented")
271
+ candidates.push(m.id);
272
+ else if (m.status === "rejected")
273
+ rejected.push(m.id);
274
+ }
275
+ return { candidates, rejected };
276
+ }
277
+ export function classifyLiveMergeOutcome(rfcId, result) {
278
+ const data = result.data;
279
+ if (result.exitCode === 0 && data) {
280
+ return {
281
+ kind: "merged",
282
+ entry: {
283
+ id: rfcId,
284
+ domain: String(data.domain ?? ""),
285
+ operation: String(data.operation ?? ""),
286
+ conflicts: Array.isArray(data.conflicts) ? data.conflicts.length : 0,
287
+ },
288
+ };
289
+ }
290
+ return {
291
+ kind: "failed",
292
+ entry: {
293
+ id: rfcId,
294
+ exitCode: result.exitCode,
295
+ error: result.summary ??
296
+ (result.exitCode === undefined
297
+ ? "spec.live.merge returned no exit code"
298
+ : `spec.live.merge exited ${result.exitCode}`),
299
+ },
300
+ };
301
+ }
302
+ export function liveMergeFailureEntry(rfcId, error) {
303
+ return { id: rfcId, error: String(error instanceof Error ? error.message : error) };
304
+ }
305
+ export function formatLiveMergeFailure(entry) {
306
+ return `spec.live.merge: failed for ${entry.id}: ${entry.error}`;
307
+ }
308
+ /** Emission gate + block assembly for results["spec.live.merge"] — undefined
309
+ * when nothing was attempted, so an all-failed run still surfaces. */
310
+ export function buildLiveMergeBlock(merged, failed, skipped, dryRun) {
311
+ if (merged.length === 0 && failed.length === 0 && skipped === 0)
312
+ return undefined;
313
+ return { merged, failed, skipped, dryRun };
314
+ }
315
+ //# sourceMappingURL=live-spec-shared.js.map