@warpgogol/forge 2.7.0 → 2.8.1

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 (268) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +23 -13
  3. package/README.uk.md +19 -12
  4. package/dist/src/config/forge-config.d.ts +1 -1
  5. package/dist/src/config/forge-config.d.ts.map +1 -1
  6. package/dist/src/config/forge-config.js +2 -2
  7. package/dist/src/config/forge-config.js.map +1 -1
  8. package/dist/src/onboarding/agents-generate.d.ts +2 -1
  9. package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
  10. package/dist/src/onboarding/agents-generate.js +24 -4
  11. package/dist/src/onboarding/agents-generate.js.map +1 -1
  12. package/dist/src/profiles/profile-schema.d.ts +2 -0
  13. package/dist/src/profiles/profile-schema.d.ts.map +1 -1
  14. package/dist/src/profiles/profile-schema.js +1 -0
  15. package/dist/src/profiles/profile-schema.js.map +1 -1
  16. package/dist/src/profiles/stack-profile.d.ts +1 -0
  17. package/dist/src/profiles/stack-profile.d.ts.map +1 -1
  18. package/dist/src/profiles/stack-profile.js +2 -0
  19. package/dist/src/profiles/stack-profile.js.map +1 -1
  20. package/os/adr/adr.module.ts +158 -0
  21. package/os/adr/frontmatter-io.ts +80 -0
  22. package/os/adr/handlers/archive.ts +244 -0
  23. package/os/adr/handlers/implement-stamp.ts +331 -0
  24. package/os/adr/handlers/list-create.ts +207 -0
  25. package/os/adr/handlers/validate.test.ts +232 -0
  26. package/os/adr/handlers/validate.ts +429 -0
  27. package/os/adr/index.ts +33 -0
  28. package/os/adr/types.ts +141 -0
  29. package/os/audit/audit.module.ts +48 -0
  30. package/os/audit/frontmatter-io.ts +84 -0
  31. package/os/audit/handlers/archive.ts +239 -0
  32. package/os/audit/index.ts +23 -0
  33. package/os/audit/types.ts +29 -0
  34. package/os/compass/compass.module.ts +186 -0
  35. package/os/compass/handlers/compass-audit-handler.ts +382 -0
  36. package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
  37. package/os/compass/handlers/compass-inventory-handler.ts +294 -0
  38. package/os/compass/handlers/compass-inventory.ts +521 -0
  39. package/os/compass/handlers/git-revision.ts +137 -0
  40. package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
  41. package/os/compass/handlers/resolve-scan-root.ts +79 -0
  42. package/os/compass/index.ts +33 -0
  43. package/os/core/core.module.ts +948 -0
  44. package/os/core/handlers/assets-check.ts +122 -0
  45. package/os/core/handlers/assets-helpers.ts +174 -0
  46. package/os/core/handlers/assets-list.ts +99 -0
  47. package/os/core/handlers/build.ts +155 -0
  48. package/os/core/handlers/determinism-check.ts +354 -0
  49. package/os/core/handlers/dev.ts +127 -0
  50. package/os/core/handlers/invariant-engine.test.ts +681 -0
  51. package/os/core/handlers/knowledge-compact.ts +248 -0
  52. package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
  53. package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
  54. package/os/core/handlers/note-link-validate.test.ts +117 -0
  55. package/os/core/handlers/note-orphan-detect.test.ts +94 -0
  56. package/os/core/handlers/package-health.test.ts +226 -0
  57. package/os/core/handlers/package-health.ts +231 -0
  58. package/os/core/handlers/pinned-check.ts +200 -0
  59. package/os/core/handlers/pinned-init.ts +333 -0
  60. package/os/core/handlers/pinned-types.ts +50 -0
  61. package/os/core/handlers/pinned-validate.ts +301 -0
  62. package/os/core/handlers/profile-resolve.ts +92 -0
  63. package/os/core/handlers/release-prepare.ts +294 -0
  64. package/os/core/handlers/release-publish.ts +211 -0
  65. package/os/core/handlers/validate.ts +262 -0
  66. package/os/core/handlers/workspace-deps.ts +55 -0
  67. package/os/core/index.ts +13 -0
  68. package/os/exploration/exploration.module.ts +90 -0
  69. package/os/exploration/frontmatter-io.ts +77 -0
  70. package/os/exploration/handlers/archive.ts +159 -0
  71. package/os/exploration/handlers/list.ts +77 -0
  72. package/os/exploration/handlers/show.ts +107 -0
  73. package/os/exploration/index.ts +25 -0
  74. package/os/exploration/types.ts +65 -0
  75. package/os/mission/handlers/archive.test.ts +391 -0
  76. package/os/mission/handlers/archive.ts +453 -0
  77. package/os/mission/index.ts +1 -0
  78. package/os/mission/mission.module.ts +48 -0
  79. package/os/mission/types.ts +41 -0
  80. package/os/naming/index.ts +14 -0
  81. package/os/naming/naming-convention.test.ts +163 -0
  82. package/os/naming/naming-convention.ts +378 -0
  83. package/os/naming/naming.module.ts +36 -0
  84. package/os/notes/index.ts +10 -0
  85. package/os/notes/notes.module.ts +109 -0
  86. package/os/plan/frontmatter-io.ts +82 -0
  87. package/os/plan/handlers/archive.ts +238 -0
  88. package/os/plan/index.ts +23 -0
  89. package/os/plan/plan.module.ts +48 -0
  90. package/os/plan/types.ts +29 -0
  91. package/os/program/discovery.ts +294 -0
  92. package/os/program/handlers/complete.ts +419 -0
  93. package/os/program/handlers/lease.ts +433 -0
  94. package/os/program/handlers/seal.ts +332 -0
  95. package/os/program/handlers/validate.ts +213 -0
  96. package/os/program/lease.ts +156 -0
  97. package/os/program/program.module.ts +212 -0
  98. package/os/program/schemas.ts +218 -0
  99. package/os/program/state.ts +264 -0
  100. package/os/rfc/acceptance.ts +344 -0
  101. package/os/rfc/decision-log.ts +310 -0
  102. package/os/rfc/dna-trace.ts +280 -0
  103. package/os/rfc/frontmatter-io.test.ts +132 -0
  104. package/os/rfc/frontmatter-io.ts +130 -0
  105. package/os/rfc/handlers/archive.ts +300 -0
  106. package/os/rfc/handlers/check.ts +238 -0
  107. package/os/rfc/handlers/implement-stamp.ts +515 -0
  108. package/os/rfc/handlers/index-graph.ts +237 -0
  109. package/os/rfc/handlers/lifecycle.test.ts +127 -0
  110. package/os/rfc/handlers/lifecycle.ts +249 -0
  111. package/os/rfc/handlers/list-create.ts +296 -0
  112. package/os/rfc/handlers/pipeline-status.ts +173 -0
  113. package/os/rfc/handlers/shared.ts +121 -0
  114. package/os/rfc/handlers/supersede-propose.ts +241 -0
  115. package/os/rfc/handlers/validate-rules.test.ts +891 -0
  116. package/os/rfc/handlers/validate-rules.ts +984 -0
  117. package/os/rfc/handlers/validate.ts +166 -0
  118. package/os/rfc/handlers.ts +19 -0
  119. package/os/rfc/index.ts +97 -0
  120. package/os/rfc/rfc.module.ts +416 -0
  121. package/os/rfc/types.ts +596 -0
  122. package/os/rfc/verification-evidence.ts +244 -0
  123. package/os/session/atif-parser.ts +166 -0
  124. package/os/session/frontmatter-io.ts +163 -0
  125. package/os/session/handlers/archive.ts +288 -0
  126. package/os/session/handlers/list.ts +174 -0
  127. package/os/session/handlers/save.ts +348 -0
  128. package/os/session/handlers/validate.ts +250 -0
  129. package/os/session/index.ts +50 -0
  130. package/os/session/session.module.ts +126 -0
  131. package/os/session/types.ts +203 -0
  132. package/os/spec/live-spec-list-show-validate.test.ts +257 -0
  133. package/os/spec/live-spec-list.ts +78 -0
  134. package/os/spec/live-spec-merge.test.ts +261 -0
  135. package/os/spec/live-spec-merge.ts +413 -0
  136. package/os/spec/live-spec-show.ts +117 -0
  137. package/os/spec/live-spec-types.ts +93 -0
  138. package/os/spec/live-spec-validate.ts +170 -0
  139. package/os/spec/spec-materialize.ts +390 -0
  140. package/os/spec/spec-schema.ts +174 -0
  141. package/os/spec/spec-status.ts +284 -0
  142. package/os/spec/spec-validate.ts +513 -0
  143. package/os/spec/spec.module.ts +135 -0
  144. package/os/werkstatt/handlers/lock.ts +168 -0
  145. package/os/werkstatt/handlers/schema.ts +47 -0
  146. package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
  147. package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
  148. package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
  149. package/os/werkstatt/index.ts +39 -0
  150. package/os/werkstatt/werkstatt.module.ts +62 -0
  151. package/os/workflow/handlers.ts +304 -0
  152. package/os/workflow/index.ts +28 -0
  153. package/os/workflow/types.ts +99 -0
  154. package/os/workflow/workflow.module.ts +54 -0
  155. package/package.json +3 -3
  156. package/profiles/godot-csharp.yaml +5 -4
  157. package/profiles/knowledge-typescript-turborepo.yaml +110 -0
  158. package/profiles/phaser-turborepo.yaml +5 -5
  159. package/profiles/root-agents-godot.md +133 -0
  160. package/skills/_shared/fo-session-summary.md +132 -16
  161. package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
  162. package/skills/fo/fo-doc-audit/SKILL.md +6 -6
  163. package/skills/fo/fo-handoff/SKILL.md +5 -0
  164. package/skills/fo/fo-idea/SKILL.md +1 -1
  165. package/skills/fo/fo-review/SKILL.md +1 -1
  166. package/skills/fo/fo-session-retro/SKILL.md +8 -0
  167. package/skills/fo/fo-session-save/SKILL.md +8 -0
  168. package/src/cli-output.ts +59 -0
  169. package/src/config/forge-config.ts +524 -0
  170. package/src/forge-module.ts +39 -0
  171. package/src/index.ts +172 -0
  172. package/src/knowledge/budgets.ts +202 -0
  173. package/src/knowledge/compact.ts +405 -0
  174. package/src/knowledge/index.ts +53 -0
  175. package/src/knowledge/parse.ts +227 -0
  176. package/src/knowledge/promote.ts +156 -0
  177. package/src/knowledge/schema.ts +112 -0
  178. package/src/knowledge/serialize.ts +72 -0
  179. package/src/migration-adapters/git-utils.ts +71 -0
  180. package/src/migration-adapters/ignored-files.ts +221 -0
  181. package/src/migration-adapters/index.ts +21 -0
  182. package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
  183. package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
  184. package/src/migration-adapters/registry.ts +61 -0
  185. package/src/migration-adapters/types.ts +68 -0
  186. package/src/onboarding/agents-generate.ts +443 -0
  187. package/src/onboarding/create.ts +427 -0
  188. package/src/onboarding/doctor.ts +1297 -0
  189. package/src/onboarding/init.ts +337 -0
  190. package/src/onboarding/invariant-engine.ts +448 -0
  191. package/src/onboarding/memory-scaffold.ts +189 -0
  192. package/src/onboarding/nested-agents-generate.ts +93 -0
  193. package/src/onboarding/nested-agents-templates.ts +233 -0
  194. package/src/onboarding/profile-validate.ts +139 -0
  195. package/src/onboarding/scaffold-project.ts +299 -0
  196. package/src/onboarding/scaffold.ts +143 -0
  197. package/src/onboarding/upgrade.ts +558 -0
  198. package/src/onboarding/workspace-discovery.ts +167 -0
  199. package/src/profiles/profile-schema.ts +350 -0
  200. package/src/profiles/stack-profile.ts +188 -0
  201. package/src/profiles/terminology-utils.ts +36 -0
  202. package/src/registry.ts +185 -0
  203. package/src/skill-schema.ts +37 -0
  204. package/src/tests/acceptance-criteria.test.ts +99 -0
  205. package/src/tests/adr-implement-stamp.test.ts +368 -0
  206. package/src/tests/agents-generate-domain.test.ts +317 -0
  207. package/src/tests/agents-generate.test.ts +458 -0
  208. package/src/tests/bindings-schema.test.ts +314 -0
  209. package/src/tests/budgets.test.ts +213 -0
  210. package/src/tests/cli-output.test.ts +196 -0
  211. package/src/tests/compact.test.ts +796 -0
  212. package/src/tests/create.test.ts +289 -0
  213. package/src/tests/doctor-autonomy.test.ts +98 -0
  214. package/src/tests/doctor-bindings.test.ts +286 -0
  215. package/src/tests/doctor-domain.test.ts +113 -0
  216. package/src/tests/exploration-handlers.test.ts +256 -0
  217. package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
  218. package/src/tests/forge-config.test.ts +420 -0
  219. package/src/tests/fs-atomic.test.ts +96 -0
  220. package/src/tests/fs.test.ts +103 -0
  221. package/src/tests/generated-marker.test.ts +161 -0
  222. package/src/tests/hash.test.ts +37 -0
  223. package/src/tests/implement-stamp.test.ts +505 -0
  224. package/src/tests/init-bindings.test.ts +183 -0
  225. package/src/tests/knowledge-parse.test.ts +307 -0
  226. package/src/tests/knowledge-pbt.test.ts +164 -0
  227. package/src/tests/memory-scaffold.test.ts +192 -0
  228. package/src/tests/migration-adapters.test.ts +480 -0
  229. package/src/tests/package-files.test.ts +51 -0
  230. package/src/tests/pinned-check.test.ts +194 -0
  231. package/src/tests/pinned-init.test.ts +126 -0
  232. package/src/tests/profile-schema.test.ts +194 -0
  233. package/src/tests/profile-validate.test.ts +120 -0
  234. package/src/tests/program-lease.test.ts +245 -0
  235. package/src/tests/program-paths-extended.test.ts +117 -0
  236. package/src/tests/program-paths.test.ts +98 -0
  237. package/src/tests/program-property.test.ts +139 -0
  238. package/src/tests/program-schemas.test.ts +445 -0
  239. package/src/tests/program-spec-node.test.ts +250 -0
  240. package/src/tests/promote.test.ts +481 -0
  241. package/src/tests/registry.test.ts +73 -0
  242. package/src/tests/scaffold-project.test.ts +150 -0
  243. package/src/tests/session-handlers.test.ts +503 -0
  244. package/src/tests/session-pbt.test.ts +190 -0
  245. package/src/tests/skill-schema.test.ts +146 -0
  246. package/src/tests/skill-validate-knowledge.test.ts +26 -0
  247. package/src/tests/skill-validate.test.ts +254 -0
  248. package/src/tests/stack-profile.test.ts +217 -0
  249. package/src/tests/string-utils.test.ts +42 -0
  250. package/src/tests/upgrade.test.ts +428 -0
  251. package/src/tests/werkstatt-lock.test.ts +343 -0
  252. package/src/tests/workspace-discovery-domain.test.ts +112 -0
  253. package/src/tests/workspace-discovery.test.ts +135 -0
  254. package/src/types.ts +221 -0
  255. package/src/utils/fs-atomic.ts +93 -0
  256. package/src/utils/fs-idempotent.ts +41 -0
  257. package/src/utils/fs-trash-sync.ts +37 -0
  258. package/src/utils/fs-trash.ts +24 -0
  259. package/src/utils/fs.ts +74 -0
  260. package/src/utils/generated-marker.ts +166 -0
  261. package/src/utils/hash.ts +19 -0
  262. package/src/utils/index.ts +29 -0
  263. package/src/utils/string-utils.ts +19 -0
  264. package/src/validators/note-frontmatter-validate.ts +142 -0
  265. package/src/validators/note-link-validate.ts +145 -0
  266. package/src/validators/note-orphan-detect.ts +146 -0
  267. package/src/validators/port-validate.ts +97 -0
  268. package/src/validators/skill-validate.ts +831 -0
package/src/index.ts ADDED
@@ -0,0 +1,172 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Public entrypoint for @warpgogol/forge — exports ForgeModule types, canonical types, utilities, skill schema, registry, validators, onboarding, and OS modules.</purpose>
4
+ <non-goals>
5
+ <item>Do not export internal implementation details — only public API.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-0374: initial public exports for forge package.</item>
10
+ <item>Forge autonomy refactor: add canonical types and utils exports.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ // Canonical types
15
+ export type {
16
+ ForgeCommandInput,
17
+ ForgeCommandResult,
18
+ ForgeCommandTiming,
19
+ ForgeNextStep,
20
+ ForgeFlagValue,
21
+ ForgeFlagSpec,
22
+ ForgeCommandMetadata,
23
+ ForgeRegisteredCommandInfo,
24
+ ForgeCommandDefinition,
25
+ ForgeCommandScope,
26
+ Diagnostic,
27
+ DiagnosticSeverity,
28
+ DiagnosticEvidence,
29
+ CheckResult,
30
+ ForgeLogger,
31
+ CommandRegistry,
32
+ ForgeRuntimeContext,
33
+ ForgeSiteContext,
34
+ ForgeOutputFormat,
35
+ GateMetadata,
36
+ GateSeverity,
37
+ GatePhase,
38
+ GateConditional,
39
+ } from "./types.ts";
40
+
41
+ // Canonical utilities
42
+ export {
43
+ writeFileAtomic,
44
+ type WriteFileAtomicOptions,
45
+ GENERATED_MARKER,
46
+ hasGeneratedMarker,
47
+ stripGeneratedMarker,
48
+ buildGeneratedHeader,
49
+ isGeneratedMarkerTextCandidate,
50
+ type GeneratedHeaderInput,
51
+ type StripGeneratedMarkerResult,
52
+ toKebabCase,
53
+ collectFiles,
54
+ fileExists,
55
+ type CollectFilesOptions,
56
+ byteHash,
57
+ } from "./utils/index.ts";
58
+
59
+ // ForgeModule types
60
+ export type { ForgeModule, ForgeModuleRegistry, ForgePipelineStep } from "./forge-module.ts";
61
+
62
+ // Skill schema
63
+ export { skillFrontmatterSchema, type SkillFrontmatter } from "./skill-schema.ts";
64
+
65
+ // Skill registry
66
+ export { FORGE_SKILLS, type ForgeSkillEntry } from "./registry.ts";
67
+
68
+ // Forge config (RFC-0391)
69
+ export {
70
+ forgeConfigSchema,
71
+ forgeBindingsSchema,
72
+ defaultForgeConfig,
73
+ loadForgeConfig,
74
+ resolveForgeRoot,
75
+ resolveBinding,
76
+ resolveTerminology,
77
+ FORGE_CLI_BINDING_DEFAULTS,
78
+ PM_RUNNER_MAP,
79
+ resolvePmRunner,
80
+ applyCliBindingDefaults,
81
+ resolvePackageManager,
82
+ type ForgeConfig,
83
+ type ForgeBindings,
84
+ type ForgeCliBindingDefault,
85
+ type ForgePackageManager,
86
+ type ForgeMigrationAdapter,
87
+ } from "./config/forge-config.ts";
88
+
89
+ // Stack profiles (RFC-0392, RFC-0638)
90
+ export {
91
+ stackProfileSchema,
92
+ loadStackProfile,
93
+ listStackProfiles,
94
+ detectStack,
95
+ type StackProfile,
96
+ type ProfileFile,
97
+ } from "./profiles/stack-profile.ts";
98
+
99
+ // Profile domain fields (RFC-0638)
100
+ export {
101
+ stackProfileDomainFieldsSchema,
102
+ profileArtifactSchema,
103
+ profileWorkspaceTypeSchema,
104
+ profileInvariantSchema,
105
+ UNIVERSAL_TERMINOLOGY_KEYS,
106
+ TERMINOLOGY_DEFAULTS,
107
+ type StackProfileDomainFields,
108
+ type ProfileArtifact,
109
+ type ProfileWorkspaceType,
110
+ type ProfileInvariant,
111
+ } from "./profiles/profile-schema.ts";
112
+
113
+ // Knowledge module (RFC-0660)
114
+ export {
115
+ parseKnowledgeFile,
116
+ serializeKnowledgeFile,
117
+ knowledgeEntryMetaSchema,
118
+ type KnowledgeLayer,
119
+ type KnowledgeEntryStatus,
120
+ type KnowledgeEntryMeta,
121
+ type KnowledgeEntry,
122
+ type LegacySection,
123
+ type ParseIssue,
124
+ type ParsedKnowledgeFile,
125
+ } from "./knowledge/index.ts";
126
+
127
+ // Validators
128
+ export { runSkillValidate } from "./validators/skill-validate.ts";
129
+ export { runPortValidate } from "./validators/port-validate.ts";
130
+
131
+ // CLI output rendering (RFC-0542)
132
+ export { renderNextSteps, renderIdeRecommendation, generateHelp } from "./cli-output.ts";
133
+
134
+ // Onboarding
135
+ export { runInit } from "./onboarding/init.ts";
136
+ export { runScaffold } from "./onboarding/scaffold.ts";
137
+ export { runDoctor } from "./onboarding/doctor.ts";
138
+ export { runAgentsGenerate } from "./onboarding/agents-generate.ts";
139
+ export { runScaffoldProject } from "./onboarding/scaffold-project.ts";
140
+ export { scaffoldMemoryLayer, type MemoryScaffoldResult } from "./onboarding/memory-scaffold.ts";
141
+
142
+ // Migration adapters (RFC-0546)
143
+ export type {
144
+ MigrationAdapter,
145
+ AdapterAnalysis,
146
+ MigrationResult,
147
+ Conflict,
148
+ } from "./migration-adapters/types.ts";
149
+ export { FORGE_PROTECTED_PATHS, DEFAULT_EXCLUDE_PATTERNS } from "./migration-adapters/types.ts";
150
+ export {
151
+ nodeTypescriptPnpmAdapter,
152
+ phaserPnpmAdapter,
153
+ getAdapters,
154
+ detectAdapter,
155
+ detectAdapters,
156
+ } from "./migration-adapters/index.ts";
157
+
158
+ // OS modules
159
+ export { forgeCoreModule } from "../os/core/core.module.ts";
160
+ export { forgeRfcModule } from "../os/rfc/rfc.module.ts";
161
+ export { forgeWorkflowModule } from "../os/workflow/workflow.module.ts";
162
+ export { forgeNamingModule } from "../os/naming/naming.module.ts";
163
+ export { forgeCompassModule } from "../os/compass/compass.module.ts";
164
+ export { forgeWerkstattModule } from "../os/werkstatt/werkstatt.module.ts";
165
+ export { forgeSpecModule } from "../os/spec/spec.module.ts";
166
+ export { forgeAdrModule } from "../os/adr/adr.module.ts";
167
+ export { forgePlanModule } from "../os/plan/plan.module.ts";
168
+ export { forgeAuditModule } from "../os/audit/audit.module.ts";
169
+ export { forgeMissionModule } from "../os/mission/mission.module.ts";
170
+ export { forgeExplorationModule } from "../os/exploration/exploration.module.ts";
171
+ export { forgeNotesModule } from "../os/notes/notes.module.ts";
172
+ export { forgeProgramModule } from "../os/program/program.module.ts";
@@ -0,0 +1,202 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Knowledge layer character budget computation and resolution — pure functions for hot/warm budget enforcement (RFC-0661).</purpose>
4
+ <non-goals>
5
+ <item>Do not emit warnings or violations — that is handled by SKILL-21 in skill-validate.ts.</item>
6
+ <item>Do not parse knowledge files — that is handled by parse.ts (RFC-0660).</item>
7
+ <item>Do not validate budget override shape — that is handled by forge.doctor.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0661: initial budget module with KnowledgeBudgets, LayerBudgetReport, computeLayerBudgets, resolveKnowledgeBudgets.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import path from "node:path";
16
+ import { readFileSync } from "node:fs";
17
+ import { parse as parseYaml } from "yaml";
18
+ import type { ParsedKnowledgeFile, KnowledgeLayer } from "./schema.ts";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Types (RFC-0661)
22
+ // ---------------------------------------------------------------------------
23
+
24
+ export interface KnowledgeBudgets {
25
+ hot: number; // default 4096 — L2 (learned-principles.md)
26
+ warm: number; // default 8192 — L1 (fix-patterns.md)
27
+ }
28
+
29
+ export interface LayerBudgetReport {
30
+ skill: string;
31
+ file: string;
32
+ layer: KnowledgeLayer;
33
+ activeChars: number;
34
+ budget: number;
35
+ exceededBy: number; // 0 when within budget
36
+ pack?: string;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Defaults
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export const DEFAULT_KNOWLEDGE_BUDGETS: KnowledgeBudgets = {
44
+ hot: 4096,
45
+ warm: 8192,
46
+ };
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // computeLayerBudgets — pure function
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Compute per-file budget reports for parsed knowledge files.
54
+ *
55
+ * - Counts only `status: active` entries (heading + metadata block + body).
56
+ * - Uses `ParsedKnowledgeFile.layer` to determine which budget applies.
57
+ * - Skips files with undeterminable layer (null) or L0 (cold, no budget).
58
+ * - Skips files with parse issues (schema errors are already reported by SKILL-19).
59
+ *
60
+ * Pure: no filesystem side effects.
61
+ */
62
+ export function computeLayerBudgets(
63
+ files: ParsedKnowledgeFile[],
64
+ budgets: KnowledgeBudgets,
65
+ skillNames: Map<string, string>,
66
+ ): LayerBudgetReport[] {
67
+ const reports: LayerBudgetReport[] = [];
68
+
69
+ for (const file of files) {
70
+ // Skip files with undeterminable layer
71
+ if (file.layer === null) {
72
+ continue;
73
+ }
74
+
75
+ // Skip cold layer (L0) — no budget by design
76
+ if (file.layer === "L0") {
77
+ continue;
78
+ }
79
+
80
+ // Skip files with parse issues — SKILL-19 already reports those
81
+ if (file.parseIssues.length > 0) {
82
+ continue;
83
+ }
84
+
85
+ // Skip knowledge-adjacent files (no structured entries)
86
+ if (file.isKnowledgeAdjacent) {
87
+ continue;
88
+ }
89
+
90
+ const budget = file.layer === "L2" ? budgets.hot : budgets.warm;
91
+ const fileName = path.basename(file.path);
92
+ const skillName = skillNames.get(file.path) ?? path.basename(path.dirname(file.path));
93
+
94
+ // Count only active entries: heading + metadata block + body
95
+ let activeChars = 0;
96
+ for (const entry of file.entries) {
97
+ if (entry.meta.status !== "active") {
98
+ continue;
99
+ }
100
+ // Heading: "### K-XXXX: <title>\n"
101
+ activeChars += `### ${entry.meta.id}: ${entry.title}\n`.length;
102
+ // Metadata block: ```knowledge-entry\n<yaml>\n```\n
103
+ const metaYaml = approximateMetaCharCount(entry.meta);
104
+ activeChars += "```knowledge-entry\n".length;
105
+ activeChars += metaYaml.length;
106
+ activeChars += "```\n".length;
107
+ // Body
108
+ activeChars += entry.body.length;
109
+ }
110
+
111
+ const exceededBy = Math.max(0, activeChars - budget);
112
+
113
+ reports.push({
114
+ skill: skillName,
115
+ file: fileName,
116
+ layer: file.layer,
117
+ activeChars,
118
+ budget,
119
+ exceededBy,
120
+ });
121
+ }
122
+
123
+ return reports;
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // resolveKnowledgeBudgets — reads forge.yaml
128
+ // ---------------------------------------------------------------------------
129
+
130
+ /**
131
+ * Read effective knowledge budgets from forge.yaml bindings.
132
+ *
133
+ * Falls back to defaults (hot: 4096, warm: 8192) when:
134
+ * - forge.yaml is not found
135
+ * - bindings.knowledge.budgets is absent
136
+ * - override values are invalid (non-positive, non-integer)
137
+ *
138
+ * Does NOT validate the override shape — forge.doctor handles that separately.
139
+ */
140
+ export function resolveKnowledgeBudgets(workspaceRoot: string): KnowledgeBudgets {
141
+ try {
142
+ const forgeYamlPath = path.join(workspaceRoot, "forge.yaml");
143
+ const content = readFileSync(forgeYamlPath, "utf8");
144
+ const config = parseYaml(content) as Record<string, unknown>;
145
+ const bindings = config?.bindings as Record<string, unknown> | undefined;
146
+ const knowledge = bindings?.knowledge as Record<string, unknown> | undefined;
147
+ const budgets = knowledge?.budgets as { hot?: unknown; warm?: unknown } | undefined;
148
+
149
+ if (!budgets) {
150
+ return { ...DEFAULT_KNOWLEDGE_BUDGETS };
151
+ }
152
+
153
+ const hot =
154
+ typeof budgets.hot === "number" && budgets.hot > 0 && Number.isInteger(budgets.hot)
155
+ ? budgets.hot
156
+ : DEFAULT_KNOWLEDGE_BUDGETS.hot;
157
+ const warm =
158
+ typeof budgets.warm === "number" && budgets.warm > 0 && Number.isInteger(budgets.warm)
159
+ ? budgets.warm
160
+ : DEFAULT_KNOWLEDGE_BUDGETS.warm;
161
+
162
+ return { hot, warm };
163
+ } catch {
164
+ return { ...DEFAULT_KNOWLEDGE_BUDGETS };
165
+ }
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Helper: approximate metadata character count for budget measurement
170
+ // ---------------------------------------------------------------------------
171
+
172
+ /**
173
+ * Approximate metadata character count for budget purposes.
174
+ *
175
+ * This is NOT exact YAML serialization — it produces a line-per-field
176
+ * approximation that is deterministic and sufficient for character budget
177
+ * measurement. The RFC explicitly rejects tokenizer-accurate measurement;
178
+ * this approximation is the documented proxy.
179
+ */
180
+ function approximateMetaCharCount(meta: ParsedKnowledgeFile["entries"][number]["meta"]): string {
181
+ const lines: string[] = [];
182
+ lines.push(`id: ${meta.id}`);
183
+ lines.push(`layer: ${meta.layer}`);
184
+ lines.push(`created: ${meta.created}`);
185
+ if (meta.lastConfirmedAt !== undefined) {
186
+ lines.push(`lastConfirmedAt: ${meta.lastConfirmedAt ?? "null"}`);
187
+ }
188
+ if (meta.confirmations !== undefined) {
189
+ lines.push(`confirmations: ${meta.confirmations}`);
190
+ }
191
+ if (meta.expiresAt !== undefined) {
192
+ lines.push(`expiresAt: ${meta.expiresAt ?? "null"}`);
193
+ }
194
+ if (meta.supersedes !== undefined) {
195
+ lines.push(`supersedes: [${meta.supersedes.join(", ")}]`);
196
+ }
197
+ if (meta.promotedTo !== undefined) {
198
+ lines.push(`promotedTo: ${meta.promotedTo ?? "null"}`);
199
+ }
200
+ lines.push(`status: ${meta.status}`);
201
+ return lines.join("\n") + "\n";
202
+ }