@warpgogol/forge 2.8.0 → 2.8.2

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 (271) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +23 -13
  3. package/README.uk.md +19 -12
  4. package/dist/os/session/handlers/validate.d.ts.map +1 -1
  5. package/dist/os/session/handlers/validate.js +23 -1
  6. package/dist/os/session/handlers/validate.js.map +1 -1
  7. package/dist/os/session/types.d.ts +31 -1
  8. package/dist/os/session/types.d.ts.map +1 -1
  9. package/dist/os/session/types.js +7 -0
  10. package/dist/os/session/types.js.map +1 -1
  11. package/os/adr/adr.module.ts +158 -0
  12. package/os/adr/frontmatter-io.ts +80 -0
  13. package/os/adr/handlers/archive.ts +244 -0
  14. package/os/adr/handlers/implement-stamp.ts +331 -0
  15. package/os/adr/handlers/list-create.ts +207 -0
  16. package/os/adr/handlers/validate.test.ts +232 -0
  17. package/os/adr/handlers/validate.ts +429 -0
  18. package/os/adr/index.ts +33 -0
  19. package/os/adr/types.ts +141 -0
  20. package/os/audit/audit.module.ts +48 -0
  21. package/os/audit/frontmatter-io.ts +84 -0
  22. package/os/audit/handlers/archive.ts +239 -0
  23. package/os/audit/index.ts +23 -0
  24. package/os/audit/types.ts +29 -0
  25. package/os/compass/compass.module.ts +186 -0
  26. package/os/compass/handlers/compass-audit-handler.ts +382 -0
  27. package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
  28. package/os/compass/handlers/compass-inventory-handler.ts +294 -0
  29. package/os/compass/handlers/compass-inventory.ts +521 -0
  30. package/os/compass/handlers/git-revision.ts +137 -0
  31. package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
  32. package/os/compass/handlers/resolve-scan-root.ts +79 -0
  33. package/os/compass/index.ts +33 -0
  34. package/os/core/core.module.ts +948 -0
  35. package/os/core/handlers/assets-check.ts +122 -0
  36. package/os/core/handlers/assets-helpers.ts +174 -0
  37. package/os/core/handlers/assets-list.ts +99 -0
  38. package/os/core/handlers/build.ts +155 -0
  39. package/os/core/handlers/determinism-check.ts +354 -0
  40. package/os/core/handlers/dev.ts +127 -0
  41. package/os/core/handlers/invariant-engine.test.ts +681 -0
  42. package/os/core/handlers/knowledge-compact.ts +248 -0
  43. package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
  44. package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
  45. package/os/core/handlers/note-link-validate.test.ts +117 -0
  46. package/os/core/handlers/note-orphan-detect.test.ts +94 -0
  47. package/os/core/handlers/package-health.test.ts +226 -0
  48. package/os/core/handlers/package-health.ts +231 -0
  49. package/os/core/handlers/pinned-check.ts +200 -0
  50. package/os/core/handlers/pinned-init.ts +333 -0
  51. package/os/core/handlers/pinned-types.ts +50 -0
  52. package/os/core/handlers/pinned-validate.ts +301 -0
  53. package/os/core/handlers/profile-resolve.ts +92 -0
  54. package/os/core/handlers/release-prepare.ts +294 -0
  55. package/os/core/handlers/release-publish.ts +211 -0
  56. package/os/core/handlers/validate.ts +262 -0
  57. package/os/core/handlers/workspace-deps.ts +55 -0
  58. package/os/core/index.ts +13 -0
  59. package/os/exploration/exploration.module.ts +90 -0
  60. package/os/exploration/frontmatter-io.ts +77 -0
  61. package/os/exploration/handlers/archive.ts +159 -0
  62. package/os/exploration/handlers/list.ts +77 -0
  63. package/os/exploration/handlers/show.ts +107 -0
  64. package/os/exploration/index.ts +25 -0
  65. package/os/exploration/types.ts +65 -0
  66. package/os/mission/handlers/archive.test.ts +391 -0
  67. package/os/mission/handlers/archive.ts +453 -0
  68. package/os/mission/index.ts +1 -0
  69. package/os/mission/mission.module.ts +48 -0
  70. package/os/mission/types.ts +41 -0
  71. package/os/naming/index.ts +14 -0
  72. package/os/naming/naming-convention.test.ts +163 -0
  73. package/os/naming/naming-convention.ts +378 -0
  74. package/os/naming/naming.module.ts +36 -0
  75. package/os/notes/index.ts +10 -0
  76. package/os/notes/notes.module.ts +109 -0
  77. package/os/plan/frontmatter-io.ts +82 -0
  78. package/os/plan/handlers/archive.ts +238 -0
  79. package/os/plan/index.ts +23 -0
  80. package/os/plan/plan.module.ts +48 -0
  81. package/os/plan/types.ts +29 -0
  82. package/os/program/discovery.ts +294 -0
  83. package/os/program/handlers/complete.ts +419 -0
  84. package/os/program/handlers/lease.ts +433 -0
  85. package/os/program/handlers/seal.ts +332 -0
  86. package/os/program/handlers/validate.ts +213 -0
  87. package/os/program/lease.ts +156 -0
  88. package/os/program/program.module.ts +212 -0
  89. package/os/program/schemas.ts +218 -0
  90. package/os/program/state.ts +264 -0
  91. package/os/rfc/acceptance.ts +344 -0
  92. package/os/rfc/decision-log.ts +310 -0
  93. package/os/rfc/dna-trace.ts +280 -0
  94. package/os/rfc/frontmatter-io.test.ts +132 -0
  95. package/os/rfc/frontmatter-io.ts +130 -0
  96. package/os/rfc/handlers/archive.ts +300 -0
  97. package/os/rfc/handlers/check.ts +238 -0
  98. package/os/rfc/handlers/implement-stamp.ts +515 -0
  99. package/os/rfc/handlers/index-graph.ts +237 -0
  100. package/os/rfc/handlers/lifecycle.test.ts +127 -0
  101. package/os/rfc/handlers/lifecycle.ts +249 -0
  102. package/os/rfc/handlers/list-create.ts +296 -0
  103. package/os/rfc/handlers/pipeline-status.ts +173 -0
  104. package/os/rfc/handlers/shared.ts +121 -0
  105. package/os/rfc/handlers/supersede-propose.ts +241 -0
  106. package/os/rfc/handlers/validate-rules.test.ts +891 -0
  107. package/os/rfc/handlers/validate-rules.ts +984 -0
  108. package/os/rfc/handlers/validate.ts +166 -0
  109. package/os/rfc/handlers.ts +19 -0
  110. package/os/rfc/index.ts +97 -0
  111. package/os/rfc/rfc.module.ts +416 -0
  112. package/os/rfc/types.ts +596 -0
  113. package/os/rfc/verification-evidence.ts +244 -0
  114. package/os/session/atif-parser.ts +166 -0
  115. package/os/session/frontmatter-io.ts +163 -0
  116. package/os/session/handlers/archive.ts +288 -0
  117. package/os/session/handlers/list.ts +174 -0
  118. package/os/session/handlers/save.ts +348 -0
  119. package/os/session/handlers/validate.ts +250 -0
  120. package/os/session/index.ts +50 -0
  121. package/os/session/session.module.ts +126 -0
  122. package/os/session/types.ts +203 -0
  123. package/os/spec/live-spec-list-show-validate.test.ts +257 -0
  124. package/os/spec/live-spec-list.ts +78 -0
  125. package/os/spec/live-spec-merge.test.ts +261 -0
  126. package/os/spec/live-spec-merge.ts +413 -0
  127. package/os/spec/live-spec-show.ts +117 -0
  128. package/os/spec/live-spec-types.ts +93 -0
  129. package/os/spec/live-spec-validate.ts +170 -0
  130. package/os/spec/spec-materialize.ts +390 -0
  131. package/os/spec/spec-schema.ts +174 -0
  132. package/os/spec/spec-status.ts +284 -0
  133. package/os/spec/spec-validate.ts +513 -0
  134. package/os/spec/spec.module.ts +135 -0
  135. package/os/werkstatt/handlers/lock.ts +168 -0
  136. package/os/werkstatt/handlers/schema.ts +47 -0
  137. package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
  138. package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
  139. package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
  140. package/os/werkstatt/index.ts +39 -0
  141. package/os/werkstatt/werkstatt.module.ts +62 -0
  142. package/os/workflow/handlers.ts +304 -0
  143. package/os/workflow/index.ts +28 -0
  144. package/os/workflow/types.ts +99 -0
  145. package/os/workflow/workflow.module.ts +54 -0
  146. package/package.json +3 -3
  147. package/profiles/godot-csharp.yaml +4 -4
  148. package/profiles/knowledge-typescript-turborepo.yaml +110 -0
  149. package/profiles/phaser-turborepo.yaml +5 -5
  150. package/skills/_shared/fo-session-summary.md +132 -16
  151. package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
  152. package/skills/fo/fo-doc-audit/SKILL.md +6 -6
  153. package/skills/fo/fo-handoff/SKILL.md +5 -0
  154. package/skills/fo/fo-idea/SKILL.md +1 -1
  155. package/skills/fo/fo-review/SKILL.md +1 -1
  156. package/skills/fo/fo-session-retro/SKILL.md +8 -0
  157. package/skills/fo/fo-session-save/SKILL.md +8 -0
  158. package/src/cli-output.ts +59 -0
  159. package/src/config/__tests__/resolve-terminology.test.ts +45 -0
  160. package/src/config/forge-config.ts +524 -0
  161. package/src/forge-module.ts +39 -0
  162. package/src/index.ts +172 -0
  163. package/src/knowledge/__tests__/promote.test.ts +219 -0
  164. package/src/knowledge/__tests__/serialize.test.ts +192 -0
  165. package/src/knowledge/budgets.ts +202 -0
  166. package/src/knowledge/compact.ts +405 -0
  167. package/src/knowledge/index.ts +53 -0
  168. package/src/knowledge/parse.ts +227 -0
  169. package/src/knowledge/promote.ts +156 -0
  170. package/src/knowledge/schema.ts +112 -0
  171. package/src/knowledge/serialize.ts +72 -0
  172. package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
  173. package/src/migration-adapters/__tests__/registry.test.ts +131 -0
  174. package/src/migration-adapters/__tests__/types.test.ts +86 -0
  175. package/src/migration-adapters/git-utils.ts +71 -0
  176. package/src/migration-adapters/ignored-files.ts +221 -0
  177. package/src/migration-adapters/index.ts +21 -0
  178. package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
  179. package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
  180. package/src/migration-adapters/registry.ts +61 -0
  181. package/src/migration-adapters/types.ts +68 -0
  182. package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
  183. package/src/onboarding/agents-generate.ts +443 -0
  184. package/src/onboarding/create.ts +427 -0
  185. package/src/onboarding/doctor.ts +1297 -0
  186. package/src/onboarding/init.ts +337 -0
  187. package/src/onboarding/invariant-engine.ts +448 -0
  188. package/src/onboarding/memory-scaffold.ts +189 -0
  189. package/src/onboarding/nested-agents-generate.ts +93 -0
  190. package/src/onboarding/nested-agents-templates.ts +233 -0
  191. package/src/onboarding/profile-validate.ts +139 -0
  192. package/src/onboarding/scaffold-project.ts +299 -0
  193. package/src/onboarding/scaffold.ts +143 -0
  194. package/src/onboarding/upgrade.ts +558 -0
  195. package/src/onboarding/workspace-discovery.ts +167 -0
  196. package/src/profiles/__tests__/profile-schema.test.ts +228 -0
  197. package/src/profiles/__tests__/stack-profile.test.ts +190 -0
  198. package/src/profiles/__tests__/terminology-utils.test.ts +71 -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 +54 -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 +221 -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/__tests__/fs-idempotent.test.ts +73 -0
  256. package/src/utils/__tests__/fs-trash.test.ts +45 -0
  257. package/src/utils/fs-atomic.ts +93 -0
  258. package/src/utils/fs-idempotent.ts +41 -0
  259. package/src/utils/fs-trash-sync.ts +37 -0
  260. package/src/utils/fs-trash.ts +24 -0
  261. package/src/utils/fs.ts +74 -0
  262. package/src/utils/generated-marker.ts +166 -0
  263. package/src/utils/hash.ts +19 -0
  264. package/src/utils/index.ts +29 -0
  265. package/src/utils/string-utils.ts +19 -0
  266. package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
  267. package/src/validators/note-frontmatter-validate.ts +142 -0
  268. package/src/validators/note-link-validate.ts +145 -0
  269. package/src/validators/note-orphan-detect.ts +146 -0
  270. package/src/validators/port-validate.ts +97 -0
  271. package/src/validators/skill-validate.ts +831 -0
@@ -0,0 +1,524 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Forge project configuration — forge.yaml schema, loader, forge root resolver, and bindings contract. Single source of truth for how forge is deployed in a project.</purpose>
4
+ <non-goals>
5
+ <item>Do not import from @warpgogol/* — this module is portable.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-0391: initial forge-config module with zod schema, loadForgeConfig, resolveForgeRoot.</item>
10
+ <item>RFC-0393: added ForgeBindings schema, resolveBinding with placeholder substitution.</item>
11
+ <item>RFC-0538: added optional compass binding section (fileExtensions, testPatterns).</item>
12
+ <item>RFC-0537: added sessionsDir to paths and bindings.paths schemas.</item>
13
+ <item>RFC-0539: added ForgeSkillPack schema and skillPacks to forge config for project-declared skill packs.</item>
14
+ <item>RFC-0540: added implementStamp to bindings schema, FORGE_CLI_BINDING_DEFAULTS, PM_RUNNER_MAP, resolvePmRunner, applyCliBindingDefaults.</item>
15
+ <item>RFC-0540 fix: eliminated type casts — applyCliBindingDefaults returns ForgeBindings["commands"] directly, resolvePackageManager validates pm against enum.</item>
16
+ <item>RFC-0543: added optional forge.syncedVersion field to forgeConfigSchema and defaultForgeConfig for consumer upgrade tracking.</item>
17
+ <item>RFC-0546: added optional migrationAdapters field to forgeConfigSchema for migration-adapter registry discovery.</item>
18
+ <item>RFC-0639: added 5 semantic command keys (validate, produce, verify, preview, lint), terminology promoted from .optional() to .default({}), resolveTerminology function.</item>
19
+ <item>RFC-0640: added optional domain field to project section for domain-aware bootstrapping and health checks.</item>
20
+ <item>RFC-0643: added optional profile field to forgeConfigSchema and ForgeConfig; loadForgeConfig loads profiles/<id>.yaml when present.</item>
21
+ <item>RFC-0661: added optional knowledge.budgets binding for hot/warm layer character budget overrides.</item>
22
+ <item>RFC-0662: added optional knowledge.retentionDays and knowledge.staleDays bindings for compaction overrides.</item>
23
+ <item>RFC-0704: added optional independentVersionPackages field to forgeConfigSchema for packages with autonomous npm versions.</item>
24
+ </CHANGE_SUMMARY>
25
+ */
26
+
27
+ import fs from "node:fs";
28
+ import path from "node:path";
29
+ import { z } from "zod";
30
+ import { parse as parseYaml } from "yaml";
31
+ import { TERMINOLOGY_DEFAULTS } from "../profiles/profile-schema.ts";
32
+ import { listStackProfiles, type StackProfile } from "../profiles/stack-profile.ts";
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Bindings schema (forge/bindings@1) — RFC-0393
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export const forgeBindingsSchema = z.object({
39
+ schema: z.literal("forge/bindings@1"),
40
+ commands: z.object({
41
+ validateRfc: z.string().nullable().default(null),
42
+ validateAdr: z.string().nullable().default(null),
43
+ implementStamp: z.string().nullable().default(null),
44
+ typecheck: z.string().nullable().default(null),
45
+ test: z.string().nullable().default(null),
46
+ scopedBuild: z.string().nullable().default(null),
47
+ specValidate: z.string().nullable().default(null),
48
+ sessionSave: z.string().nullable().default(null),
49
+ // Semantic keys (RFC-0639) — domain-neutral, optional
50
+ validate: z.string().nullable().default(null),
51
+ produce: z.string().nullable().default(null),
52
+ verify: z.string().nullable().default(null),
53
+ preview: z.string().nullable().default(null),
54
+ lint: z.string().nullable().default(null),
55
+ }),
56
+ paths: z.object({
57
+ invariantsFile: z.string().nullable().default(null),
58
+ compassDocs: z.array(z.string()).default([]),
59
+ reviewsDir: z.string().nullable().default(null),
60
+ handoffsDir: z.string().nullable().default(null),
61
+ sessionsDir: z.string().nullable().default(null),
62
+ }),
63
+ terminology: z.record(z.string(), z.string()).default({}),
64
+ compass: z
65
+ .object({
66
+ fileExtensions: z.array(z.string()).optional(),
67
+ testPatterns: z.array(z.string()).optional(),
68
+ })
69
+ .optional(),
70
+ // RFC-0661: optional knowledge layer character budget overrides
71
+ // RFC-0662: optional retention/stale day overrides
72
+ knowledge: z
73
+ .object({
74
+ budgets: z
75
+ .object({
76
+ hot: z.number().positive(),
77
+ warm: z.number().positive(),
78
+ })
79
+ .optional(),
80
+ retentionDays: z.number().positive().optional(),
81
+ staleDays: z.number().positive().optional(),
82
+ })
83
+ .optional(),
84
+ // RFC-0664: optional memory layer budget override (MEMORY.md hot store)
85
+ memory: z
86
+ .object({
87
+ budget: z.number().int().positive().default(4096),
88
+ })
89
+ .optional(),
90
+ });
91
+
92
+ export interface ForgeBindings {
93
+ schema: "forge/bindings@1";
94
+ commands: {
95
+ validateRfc: string | null;
96
+ validateAdr: string | null;
97
+ implementStamp: string | null;
98
+ typecheck: string | null;
99
+ test: string | null;
100
+ scopedBuild: string | null;
101
+ specValidate: string | null;
102
+ sessionSave: string | null;
103
+ // Semantic keys (RFC-0639) — domain-neutral, optional
104
+ validate: string | null;
105
+ produce: string | null;
106
+ verify: string | null;
107
+ preview: string | null;
108
+ lint: string | null;
109
+ };
110
+ paths: {
111
+ invariantsFile: string | null;
112
+ compassDocs: string[];
113
+ reviewsDir: string | null;
114
+ handoffsDir: string | null;
115
+ sessionsDir: string | null;
116
+ };
117
+ terminology: Record<string, string>;
118
+ compass?: {
119
+ fileExtensions?: string[];
120
+ testPatterns?: string[];
121
+ };
122
+ // RFC-0661: optional knowledge layer character budget overrides
123
+ // RFC-0662: optional retention/stale day overrides
124
+ knowledge?: {
125
+ budgets?: {
126
+ hot: number;
127
+ warm: number;
128
+ };
129
+ retentionDays?: number;
130
+ staleDays?: number;
131
+ };
132
+ // RFC-0664: optional memory layer budget override (MEMORY.md hot store)
133
+ memory?: {
134
+ budget: number;
135
+ };
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Skill packs schema (RFC-0539)
140
+ // ---------------------------------------------------------------------------
141
+
142
+ export const forgeSkillPackSchema = z.object({
143
+ prefix: z
144
+ .string()
145
+ .regex(
146
+ /^[a-z][a-z0-9]{1,7}$/,
147
+ "prefix must be 2-8 chars: lowercase letter followed by lowercase alphanumeric",
148
+ )
149
+ .refine((v) => v !== "fo", "fo prefix is reserved for forge"),
150
+ dir: z.string().min(1),
151
+ });
152
+
153
+ export interface ForgeSkillPack {
154
+ prefix: string;
155
+ dir: string;
156
+ }
157
+
158
+ // ---------------------------------------------------------------------------
159
+ // Migration adapters schema (RFC-0546)
160
+ // ---------------------------------------------------------------------------
161
+
162
+ export const forgeMigrationAdapterSchema = z.object({
163
+ id: z.string().min(1),
164
+ module: z.string().optional(),
165
+ });
166
+
167
+ export interface ForgeMigrationAdapter {
168
+ id: string;
169
+ module?: string;
170
+ }
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Schema (forge/config@1)
174
+ // ---------------------------------------------------------------------------
175
+
176
+ export const forgeConfigSchema = z.object({
177
+ schema: z.literal("forge/config@1"),
178
+ project: z.object({
179
+ name: z.string().min(1),
180
+ stack: z.array(z.string()).default([]),
181
+ packageManager: z.enum(["pnpm", "npm", "yarn", "bun", "none"]).default("pnpm"),
182
+ // RFC-0640: optional domain field — absent means software-domain fallback
183
+ domain: z.string().optional(),
184
+ }),
185
+ paths: z.object({
186
+ rfcsDir: z.string().default("docs/rfcs"),
187
+ adrsDir: z.string().default("docs/adrs"),
188
+ plansDir: z.string().default("docs/plans"),
189
+ auditsDir: z.string().default("docs/audits"),
190
+ specsDir: z.string().default("docs/specs"),
191
+ skillsDir: z.string().default(".agents/skills"),
192
+ sessionsDir: z.string().default("docs/sessions"),
193
+ }),
194
+ /** Bindings contract (RFC-0393). Optional — absent means no bindings. */
195
+ bindings: forgeBindingsSchema.optional(),
196
+ /** Skill packs (RFC-0539). Optional — absent means no project-local packs. */
197
+ skillPacks: z.array(forgeSkillPackSchema).optional(),
198
+ /** Migration adapters (RFC-0546). Optional — absent means built-in adapters only. */
199
+ migrationAdapters: z.array(forgeMigrationAdapterSchema).optional(),
200
+ /** Forge sync metadata (RFC-0543). Optional — absent means never synced. */
201
+ forge: z
202
+ .object({
203
+ syncedVersion: z.string().nullable().default(null),
204
+ })
205
+ .optional(),
206
+ /** RFC-0643: profile id — when present, loadForgeConfig loads the corresponding profiles/<id>.yaml */
207
+ profile: z.string().optional(),
208
+ /** RFC-0704: packages with autonomous npm versions — ecosystem.commit skips platform bump when all staged files belong to these packages */
209
+ independentVersionPackages: z.array(z.string()).optional(),
210
+ });
211
+
212
+ export interface ForgeConfig {
213
+ schema: "forge/config@1";
214
+ project: {
215
+ name: string;
216
+ stack: string[];
217
+ packageManager: "pnpm" | "npm" | "yarn" | "bun" | "none";
218
+ /** RFC-0640: optional domain field — absent means software-domain fallback */
219
+ domain?: string;
220
+ };
221
+ paths: {
222
+ rfcsDir: string;
223
+ adrsDir: string;
224
+ plansDir: string;
225
+ auditsDir: string;
226
+ specsDir: string;
227
+ skillsDir: string;
228
+ sessionsDir: string;
229
+ };
230
+ /** Bindings contract (RFC-0393). Optional — absent means no bindings. */
231
+ bindings?: ForgeBindings;
232
+ /** Skill packs (RFC-0539). Optional — absent means no project-local packs. */
233
+ skillPacks?: ForgeSkillPack[];
234
+ /** Migration adapters (RFC-0546). Optional — absent means built-in adapters only. */
235
+ migrationAdapters?: ForgeMigrationAdapter[];
236
+ /** Forge sync metadata (RFC-0543). Optional — absent means never synced. */
237
+ forge?: { syncedVersion: string | null };
238
+ /** RFC-0643: loaded stack profile — present when forge.yaml has a `profile` field */
239
+ profile?: StackProfile;
240
+ /** RFC-0704: packages with autonomous npm versions — ecosystem.commit skips platform bump when all staged files belong to these packages */
241
+ independentVersionPackages?: string[];
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // CLI binding defaults (RFC-0540)
246
+ // ---------------------------------------------------------------------------
247
+
248
+ export interface ForgeCliBindingDefault {
249
+ key: string;
250
+ template: string;
251
+ }
252
+
253
+ export const FORGE_CLI_BINDING_DEFAULTS: ForgeCliBindingDefault[] = [
254
+ { key: "commands.validateRfc", template: "forge rfc.validate --id {id} --json" },
255
+ { key: "commands.validateAdr", template: "forge adr.validate --id {id} --json" },
256
+ {
257
+ key: "commands.implementStamp",
258
+ template: "forge rfc.implement.stamp --id {id} --implementation-commit {commit}",
259
+ },
260
+ { key: "commands.specValidate", template: "forge spec.validate --spec={id} --json" },
261
+ { key: "commands.sessionSave", template: "forge session.save --json" },
262
+ ];
263
+
264
+ export const PM_RUNNER_MAP: Record<string, string> = {
265
+ pnpm: "pnpm exec",
266
+ npm: "npx",
267
+ yarn: "yarn exec",
268
+ bun: "bunx",
269
+ none: "npx",
270
+ };
271
+
272
+ export function resolvePmRunner(pm: string): string {
273
+ return PM_RUNNER_MAP[pm] ?? "npx";
274
+ }
275
+
276
+ export const PM_INSTALL_MAP: Record<string, string> = {
277
+ pnpm: "pnpm add",
278
+ npm: "npm install",
279
+ yarn: "yarn add",
280
+ bun: "bun add",
281
+ none: "npm install",
282
+ };
283
+
284
+ export function resolvePmInstall(pm: string): string {
285
+ return PM_INSTALL_MAP[pm] ?? "npm install";
286
+ }
287
+
288
+ export function applyCliBindingDefaults(pm: string): ForgeBindings["commands"] {
289
+ const runner = resolvePmRunner(pm);
290
+ const commands: ForgeBindings["commands"] = {
291
+ validateRfc: null,
292
+ validateAdr: null,
293
+ implementStamp: null,
294
+ typecheck: null,
295
+ test: null,
296
+ scopedBuild: null,
297
+ specValidate: null,
298
+ sessionSave: null,
299
+ // Semantic keys (RFC-0639) — stack-dependent, not CLI-backed
300
+ validate: null,
301
+ produce: null,
302
+ verify: null,
303
+ preview: null,
304
+ lint: null,
305
+ };
306
+ for (const entry of FORGE_CLI_BINDING_DEFAULTS) {
307
+ const bareKey = entry.key.replace("commands.", "") as keyof ForgeBindings["commands"];
308
+ commands[bareKey] = `${runner} ${entry.template}`;
309
+ }
310
+ return commands;
311
+ }
312
+
313
+ // ---------------------------------------------------------------------------
314
+ // Defaults
315
+ // ---------------------------------------------------------------------------
316
+
317
+ export type ForgePackageManager = ForgeConfig["project"]["packageManager"];
318
+
319
+ const VALID_PACKAGE_MANAGERS: readonly string[] = ["pnpm", "npm", "yarn", "bun", "none"];
320
+
321
+ export function resolvePackageManager(pm: string): ForgePackageManager {
322
+ return VALID_PACKAGE_MANAGERS.includes(pm) ? (pm as ForgePackageManager) : "pnpm";
323
+ }
324
+
325
+ export function defaultForgeConfig(projectName: string, packageManager?: string): ForgeConfig {
326
+ const pm = resolvePackageManager(packageManager ?? "pnpm");
327
+ return {
328
+ schema: "forge/config@1",
329
+ project: {
330
+ name: projectName,
331
+ stack: [],
332
+ packageManager: pm,
333
+ },
334
+ paths: {
335
+ rfcsDir: "docs/rfcs",
336
+ adrsDir: "docs/adrs",
337
+ plansDir: "docs/plans",
338
+ auditsDir: "docs/audits",
339
+ specsDir: "docs/specs",
340
+ skillsDir: ".agents/skills",
341
+ sessionsDir: "docs/sessions",
342
+ },
343
+ bindings: {
344
+ schema: "forge/bindings@1",
345
+ commands: applyCliBindingDefaults(pm),
346
+ paths: {
347
+ invariantsFile: null,
348
+ compassDocs: [],
349
+ reviewsDir: "docs/reviews",
350
+ handoffsDir: "docs/handoffs",
351
+ sessionsDir: "docs/sessions",
352
+ },
353
+ terminology: {},
354
+ compass: {},
355
+ },
356
+ forge: { syncedVersion: null },
357
+ };
358
+ }
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // Loader
362
+ // ---------------------------------------------------------------------------
363
+
364
+ export function loadForgeConfig(workspaceRoot: string, forgeRootOverride?: string): ForgeConfig {
365
+ const configPath = path.join(workspaceRoot, "forge.yaml");
366
+
367
+ if (!fs.existsSync(configPath)) {
368
+ throw new Error(
369
+ `forge.yaml not found at ${configPath}. Run 'forge create' to create project configuration.`,
370
+ );
371
+ }
372
+
373
+ const raw = fs.readFileSync(configPath, "utf8");
374
+ let parsed: unknown;
375
+ try {
376
+ parsed = parseYaml(raw);
377
+ } catch (err) {
378
+ throw new Error(`forge.yaml at ${configPath} is not valid YAML: ${(err as Error).message}`);
379
+ }
380
+
381
+ const result = forgeConfigSchema.safeParse(parsed);
382
+ if (!result.success) {
383
+ const issues = result.error.issues
384
+ .map((i) => ` - ${i.path.join(".")}: ${i.message}`)
385
+ .join("\n");
386
+ throw new Error(
387
+ `forge.yaml at ${configPath} failed schema validation:\n${issues}\nFix the listed fields or run 'forge create' to regenerate defaults.`,
388
+ );
389
+ }
390
+
391
+ const rawData = result.data as Record<string, unknown>;
392
+ const profileId = rawData["profile"] as string | undefined;
393
+
394
+ // RFC-0643: load stack profile when `profile` field is present in forge.yaml
395
+ let loadedProfile: StackProfile | undefined;
396
+ if (profileId) {
397
+ try {
398
+ const forgeRoot = forgeRootOverride ?? resolveForgeRoot(workspaceRoot);
399
+ const profiles = listStackProfiles(forgeRoot);
400
+ loadedProfile = profiles.find((p) => p.id === profileId);
401
+ } catch {
402
+ // forge root not resolvable — profile not loaded
403
+ }
404
+ }
405
+
406
+ const config = { ...rawData } as unknown as ForgeConfig;
407
+ if (loadedProfile) {
408
+ config.profile = loadedProfile;
409
+ } else {
410
+ delete config.profile;
411
+ }
412
+
413
+ return config;
414
+ }
415
+
416
+ // ---------------------------------------------------------------------------
417
+ // Forge root resolver — the single place that decides monorepo vs npm-installed
418
+ // ---------------------------------------------------------------------------
419
+
420
+ export function resolveForgeRoot(workspaceRoot: string): string {
421
+ const monorepoPath = path.join(workspaceRoot, "packages", "forge");
422
+ if (fs.existsSync(path.join(monorepoPath, "package.json"))) {
423
+ return monorepoPath;
424
+ }
425
+
426
+ const npmPath = path.join(workspaceRoot, "node_modules", "@warpgogol", "forge");
427
+ if (fs.existsSync(path.join(npmPath, "package.json"))) {
428
+ return npmPath;
429
+ }
430
+
431
+ if (
432
+ fs.existsSync(path.join(workspaceRoot, "profiles")) &&
433
+ fs.existsSync(path.join(workspaceRoot, "package.json"))
434
+ ) {
435
+ return workspaceRoot;
436
+ }
437
+
438
+ throw new Error(
439
+ `Could not resolve forge root. Checked:\n ${monorepoPath}\n ${npmPath}\nEnsure @warpgogol/forge is installed or packages/forge exists.`,
440
+ );
441
+ }
442
+
443
+ // ---------------------------------------------------------------------------
444
+ // Binding resolver — RFC-0393
445
+ // ---------------------------------------------------------------------------
446
+
447
+ /**
448
+ * Resolve a binding key (e.g. "commands.validateRfc") from the config.
449
+ * Returns the resolved string/array, or null if the binding is absent or explicitly null.
450
+ * Supports placeholder substitution: {id}, {workspace}, {file}.
451
+ */
452
+ export function resolveBinding(
453
+ config: ForgeConfig,
454
+ key: string,
455
+ placeholders?: Record<string, string>,
456
+ ): string | string[] | null {
457
+ if (!config.bindings) {
458
+ return null;
459
+ }
460
+
461
+ const parts = key.split(".");
462
+ let current: unknown = config.bindings;
463
+
464
+ for (const part of parts) {
465
+ if (current === null || current === undefined) {
466
+ return null;
467
+ }
468
+ if (typeof current !== "object" || !(part in current)) {
469
+ return null;
470
+ }
471
+ current = (current as Record<string, unknown>)[part];
472
+ }
473
+
474
+ if (current === null || current === undefined) {
475
+ return null;
476
+ }
477
+
478
+ if (Array.isArray(current)) {
479
+ return current as string[];
480
+ }
481
+
482
+ if (typeof current === "string") {
483
+ if (placeholders) {
484
+ return substitutePlaceholders(current, placeholders);
485
+ }
486
+ return current;
487
+ }
488
+
489
+ return null;
490
+ }
491
+
492
+ function substitutePlaceholders(template: string, placeholders: Record<string, string>): string {
493
+ let result = template;
494
+ for (const [key, value] of Object.entries(placeholders)) {
495
+ result = result.replaceAll(`{${key}}`, value);
496
+ }
497
+ return result;
498
+ }
499
+
500
+ // ---------------------------------------------------------------------------
501
+ // Terminology resolver — RFC-0639
502
+ // ---------------------------------------------------------------------------
503
+
504
+ /**
505
+ * Resolve a terminology key using a three-tier chain:
506
+ * 1. Per-project override in bindings.terminology
507
+ * 2. Caller-provided terminology map (typically from profile.terminology, RFC-0638)
508
+ * 3. Universal default from TERMINOLOGY_DEFAULTS (RFC-0638)
509
+ *
510
+ * If the key is not found in any tier, the key itself is returned.
511
+ */
512
+ export function resolveTerminology(
513
+ config: ForgeConfig,
514
+ terminology: Record<string, string> | undefined,
515
+ key: string,
516
+ ): string {
517
+ if (config.bindings?.terminology?.[key]) {
518
+ return config.bindings.terminology[key];
519
+ }
520
+ if (terminology?.[key]) {
521
+ return terminology[key];
522
+ }
523
+ return TERMINOLOGY_DEFAULTS[key] ?? key;
524
+ }
@@ -0,0 +1,39 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>ForgeModule interfaces — structurally compatible with KernelModule from @warpgogol/site-kernel. Forge does NOT import from site-kernel.</purpose>
4
+ <non-goals>
5
+ <item>Do not import from @warpgogol/site-kernel — forge must be installable without it.</item>
6
+ <item>Do not add project-specific fields to ForgeModule — keep it minimal and portable.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0374: initial ForgeModule, ForgeModuleRegistry, ForgeCommandDefinition, ForgePipelineStep interfaces.</item>
11
+ <item>Forge autonomy refactor: ForgeCommandDefinition and ForgeCommandResult now reference canonical types from types.ts.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import type { ForgeCommandDefinition } from "./types.ts";
16
+
17
+ // ForgeModule is structurally compatible with KernelModule from @warpgogol/site-kernel.
18
+ // Forge does NOT import from site-kernel — TypeScript structural typing ensures
19
+ // compatibility. If the kernel's KernelModule interface changes, the build fails
20
+ // at the point where forge modules are imported into kernel.config.ts.
21
+
22
+ export interface ForgeModuleRegistry {
23
+ registerCommand(command: ForgeCommandDefinition): void;
24
+ registerPipeline(name: string, steps: ForgePipelineStep[]): void;
25
+ }
26
+
27
+ export interface ForgeModule {
28
+ name: string;
29
+ version: string;
30
+ register(registry: ForgeModuleRegistry): void | Promise<void>;
31
+ }
32
+
33
+ // Re-export canonical types for convenience
34
+ export type { ForgeCommandDefinition, ForgeCommandResult, ForgeFlagSpec } from "./types.ts";
35
+
36
+ export interface ForgePipelineStep {
37
+ command: string;
38
+ args?: string[];
39
+ }