@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,350 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Domain-neutral profile schema extensions — optional fields that allow a stack profile to declare its domain model (terminology, artifacts, workspace types, invariants, register) without breaking backward compatibility.</purpose>
4
+ <non-goals>
5
+ <item>Do not import from @warpgogol/* — this module is portable.</item>
6
+ <item>Do not define enforcement logic for invariants — that lives in the invariant engine module.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0638: initial domain-neutral profile schema extensions with six optional fields.</item>
11
+ <item>RFC-0674: add profileDevServerSchema and devServer field for lifecycle commands.</item>
12
+ <item>RFC-0675: add profileInvariantCheckSchema and check field to profileInvariantSchema for enforcement.</item>
13
+ <item>RFC-0679: add profileAssetSchema and assets field for asset management commands.</item>
14
+ <item>RFC-0680: add profileReleaseSchema and release field for release lifecycle commands.</item>
15
+ <item>RFC-0694: replace html-attribute-pattern with attribute-pattern (elements array) for HTML+JSX support.</item>
16
+ <item>Add prerequisites field for profile-declared system dependency checks (e.g. FFmpeg).</item>
17
+ <item>Add templates field for multi-template profiles (e.g. React + HTML).</item>
18
+ <item>ADR-0043: add scriptDir field for agent-generated script directory convention.</item>
19
+ <item>RFC-0808: add link-resolution, frontmatter-required, path-exclusion check kinds for note vault validation.</item>
20
+ <item>Add gitignore-entry and secret-scan check kinds for godot-csharp profile.</item>
21
+ </CHANGE_SUMMARY>
22
+ */
23
+
24
+ import { z } from "zod";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Universal terminology key catalog
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export const UNIVERSAL_TERMINOLOGY_KEYS = [
31
+ "artifact",
32
+ "artifactPlural",
33
+ "module",
34
+ "source",
35
+ "output",
36
+ "verify",
37
+ "operator",
38
+ ] as const;
39
+
40
+ export const TERMINOLOGY_DEFAULTS: Record<string, string> = {
41
+ artifact: "artifact",
42
+ artifactPlural: "artifacts",
43
+ module: "module",
44
+ source: "source file",
45
+ output: "output",
46
+ verify: "verify",
47
+ operator: "operator",
48
+ };
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Profile artifact schema
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export const profileArtifactSchema = z.object({
55
+ id: z.string().min(1),
56
+ extensions: z.array(z.string().min(1)),
57
+ produce: z
58
+ .object({
59
+ command: z.string().min(1),
60
+ output: z.string().optional(),
61
+ })
62
+ .optional(),
63
+ validate: z
64
+ .object({
65
+ command: z.string().min(1),
66
+ outputFormat: z.enum(["plain", "json"]).optional(),
67
+ violationPattern: z.string().optional(),
68
+ })
69
+ .optional(),
70
+ determinism: z
71
+ .object({
72
+ hashable: z.boolean(),
73
+ inputs: z.array(z.string()),
74
+ })
75
+ .optional(),
76
+ });
77
+
78
+ export interface ProfileArtifact {
79
+ id: string;
80
+ extensions: string[];
81
+ produce?: {
82
+ command: string;
83
+ output?: string;
84
+ };
85
+ validate?: {
86
+ command: string;
87
+ outputFormat?: "plain" | "json";
88
+ violationPattern?: string;
89
+ };
90
+ determinism?: {
91
+ hashable: boolean;
92
+ inputs: string[];
93
+ };
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Profile workspace type schema
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export const profileWorkspaceTypeSchema = z.object({
101
+ id: z.string().min(1),
102
+ detect: z.object({
103
+ glob: z.string().optional(),
104
+ contains: z.string().optional(),
105
+ packageJsonDep: z.string().optional(),
106
+ }),
107
+ skills: z.array(z.string()).optional(),
108
+ agentsMdTemplate: z.string().optional(),
109
+ });
110
+
111
+ export interface ProfileWorkspaceType {
112
+ id: string;
113
+ detect: {
114
+ glob?: string;
115
+ contains?: string;
116
+ packageJsonDep?: string;
117
+ };
118
+ skills?: string[];
119
+ agentsMdTemplate?: string;
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Profile invariant schema
124
+ // ---------------------------------------------------------------------------
125
+
126
+ export const profileInvariantCheckSchema = z
127
+ .object({
128
+ kind: z.enum([
129
+ "filename-pattern",
130
+ "file-contains",
131
+ "file-not-contains",
132
+ "attribute-pattern",
133
+ "link-resolution",
134
+ "frontmatter-required",
135
+ "path-exclusion",
136
+ "gitignore-entry",
137
+ "secret-scan",
138
+ ]),
139
+ glob: z.string().optional(),
140
+ pattern: z.string().optional(),
141
+ negatedPattern: z.string().optional(),
142
+ elements: z.array(z.string()).optional(),
143
+ attribute: z.string().optional(),
144
+ fields: z.array(z.string()).optional(),
145
+ })
146
+ .refine(
147
+ (v) =>
148
+ v.kind !== "attribute-pattern" ||
149
+ (v.elements != null && v.elements.length > 0 && v.attribute != null && v.pattern != null),
150
+ {
151
+ message:
152
+ "elements (non-empty array), attribute, and pattern are required for kind: attribute-pattern",
153
+ },
154
+ )
155
+ .refine((v) => v.kind !== "frontmatter-required" || (v.fields != null && v.fields.length > 0), {
156
+ message: "fields (non-empty array) is required for kind: frontmatter-required",
157
+ });
158
+
159
+ export interface ProfileInvariantCheck {
160
+ kind:
161
+ | "filename-pattern"
162
+ | "file-contains"
163
+ | "file-not-contains"
164
+ | "attribute-pattern"
165
+ | "link-resolution"
166
+ | "frontmatter-required"
167
+ | "path-exclusion"
168
+ | "gitignore-entry"
169
+ | "secret-scan";
170
+ glob?: string;
171
+ pattern?: string;
172
+ negatedPattern?: string;
173
+ elements?: string[];
174
+ attribute?: string;
175
+ fields?: string[];
176
+ }
177
+
178
+ export const profileInvariantSchema = z.object({
179
+ id: z
180
+ .string()
181
+ .min(1)
182
+ .regex(/^[A-Z]+-\d+$/, "Invariant id must match ^[A-Z]+-\\d+$ (e.g. VIDEO-01)"),
183
+ rule: z.string().min(1),
184
+ severity: z.enum(["error", "warning"]),
185
+ check: profileInvariantCheckSchema.optional(),
186
+ });
187
+
188
+ export interface ProfileInvariant {
189
+ id: string;
190
+ rule: string;
191
+ severity: "error" | "warning";
192
+ check?: ProfileInvariantCheck;
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Profile dev server schema (RFC-0674)
197
+ // ---------------------------------------------------------------------------
198
+
199
+ export const profileDevServerSchema = z.object({
200
+ command: z.string().min(1),
201
+ port: z.number().int().positive().optional(),
202
+ readinessTimeout: z.number().int().positive().optional(),
203
+ });
204
+
205
+ export interface ProfileDevServer {
206
+ command: string;
207
+ port?: number;
208
+ readinessTimeout?: number;
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Profile asset schema (RFC-0679)
213
+ // ---------------------------------------------------------------------------
214
+
215
+ export const profileAssetTypeSchema = z.object({
216
+ id: z.string().min(1),
217
+ extensions: z.array(z.string().min(1)),
218
+ referencePattern: z.string().optional(),
219
+ });
220
+
221
+ export interface ProfileAssetType {
222
+ id: string;
223
+ extensions: string[];
224
+ referencePattern?: string;
225
+ }
226
+
227
+ export const profileAssetSchema = z.object({
228
+ dir: z.string().min(1),
229
+ types: z.array(profileAssetTypeSchema),
230
+ });
231
+
232
+ export interface ProfileAsset {
233
+ dir: string;
234
+ types: ProfileAssetType[];
235
+ }
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Profile release schema (RFC-0680)
239
+ // ---------------------------------------------------------------------------
240
+
241
+ export const profileReleaseSchema = z.object({
242
+ target: z.enum(["local", "r2", "s3"]),
243
+ outputDir: z.string().min(1),
244
+ manifestName: z.string().default("release-manifest.json"),
245
+ includeArtifacts: z.array(z.string()).optional(),
246
+ r2: z
247
+ .object({
248
+ bucket: z.string().min(1),
249
+ accountId: z.string().min(1),
250
+ prefix: z.string().default(""),
251
+ })
252
+ .optional(),
253
+ });
254
+
255
+ export interface ProfileRelease {
256
+ target: "local" | "r2" | "s3";
257
+ outputDir: string;
258
+ manifestName: string;
259
+ includeArtifacts?: string[];
260
+ r2?: {
261
+ bucket: string;
262
+ accountId: string;
263
+ prefix: string;
264
+ };
265
+ }
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // Profile prerequisite schema
269
+ // ---------------------------------------------------------------------------
270
+
271
+ export const profilePrerequisiteSchema = z.object({
272
+ id: z.string().min(1),
273
+ name: z.string().min(1),
274
+ check: z.string().min(1),
275
+ installHint: z.string().optional(),
276
+ severity: z.enum(["error", "warning"]).default("error"),
277
+ });
278
+
279
+ export interface ProfilePrerequisite {
280
+ id: string;
281
+ name: string;
282
+ check: string;
283
+ installHint?: string;
284
+ severity: "error" | "warning";
285
+ }
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // Profile template schema (multi-template profiles)
289
+ // ---------------------------------------------------------------------------
290
+
291
+ export const profileTemplateSchema = z.object({
292
+ id: z.string().min(1),
293
+ default: z.boolean().optional(),
294
+ firstWorkspace: z.object({
295
+ path: z.string().min(1),
296
+ files: z.array(
297
+ z.object({
298
+ path: z.string().min(1),
299
+ content: z.string(),
300
+ }),
301
+ ),
302
+ install: z.array(z.string()).default([]),
303
+ }),
304
+ });
305
+
306
+ export interface ProfileTemplate {
307
+ id: string;
308
+ default?: boolean;
309
+ firstWorkspace: {
310
+ path: string;
311
+ files: Array<{ path: string; content: string }>;
312
+ install: string[];
313
+ };
314
+ }
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // Stack profile domain fields
318
+ // ---------------------------------------------------------------------------
319
+
320
+ export const stackProfileDomainFieldsSchema = z.object({
321
+ domain: z.string().optional(),
322
+ terminology: z.record(z.string(), z.string()).optional(),
323
+ artifacts: z.array(profileArtifactSchema).optional(),
324
+ workspaceTypes: z.array(profileWorkspaceTypeSchema).optional(),
325
+ invariants: z.array(profileInvariantSchema).optional(),
326
+ register: z.enum(["business", "creative"]).optional(),
327
+ devServer: profileDevServerSchema.optional(),
328
+ assets: profileAssetSchema.optional(),
329
+ release: profileReleaseSchema.optional(),
330
+ prerequisites: z.array(profilePrerequisiteSchema).optional(),
331
+ templates: z.array(profileTemplateSchema).optional(),
332
+ scriptDir: z.string().min(1).optional(),
333
+ rootAgentsMdTemplate: z.string().optional(),
334
+ });
335
+
336
+ export interface StackProfileDomainFields {
337
+ domain?: string;
338
+ terminology?: Record<string, string>;
339
+ artifacts?: ProfileArtifact[];
340
+ workspaceTypes?: ProfileWorkspaceType[];
341
+ invariants?: ProfileInvariant[];
342
+ register?: "business" | "creative";
343
+ devServer?: ProfileDevServer;
344
+ assets?: ProfileAsset;
345
+ release?: ProfileRelease;
346
+ prerequisites?: ProfilePrerequisite[];
347
+ templates?: ProfileTemplate[];
348
+ scriptDir?: string;
349
+ rootAgentsMdTemplate?: string;
350
+ }
@@ -0,0 +1,188 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Stack profile schema, loader, and detector. Defines what a supported stack looks like and how to detect it in an existing project.</purpose>
4
+ <non-goals>
5
+ <item>Do not scaffold projects — that is scaffold-project.ts.</item>
6
+ <item>Do not import from @warpgogol/* — this module is portable.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0392: initial stack profile module with zod schema, listStackProfiles, detectStack.</item>
11
+ <item>RFC-0638: extended stackProfileSchema with optional domain-neutral fields (domain, terminology, artifacts, workspaceTypes, invariants, register).</item>
12
+ <item>RFC-0674: add devServer field to stackProfileSchema for lifecycle commands.</item>
13
+ <item>RFC-0679: add assets field to stackProfileSchema for asset management commands.</item>
14
+ <item>RFC-0680: add release field to stackProfileSchema for release lifecycle commands.</item>
15
+ <item>ADR-0043: add scriptDir field for agent-generated script directory convention.</item>
16
+ </CHANGE_SUMMARY>
17
+ */
18
+
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+ import { z } from "zod";
22
+ import { parse as parseYaml } from "yaml";
23
+ import { stackProfileDomainFieldsSchema, type StackProfileDomainFields } from "./profile-schema.ts";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Schema (forge/stack-profile@1)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export const stackProfileSchema = z.object({
30
+ schema: z.literal("forge/stack-profile@1"),
31
+ id: z.string().min(1),
32
+ displayName: z.string().min(1),
33
+ detect: z.object({
34
+ anyOf: z.array(z.string()).min(1),
35
+ }),
36
+ workspace: z.object({
37
+ dirs: z.array(z.string()).min(1),
38
+ files: z.array(
39
+ z.object({
40
+ path: z.string().min(1),
41
+ content: z.string(),
42
+ }),
43
+ ),
44
+ }),
45
+ install: z.array(z.string()).default([]),
46
+ firstWorkspace: z
47
+ .object({
48
+ path: z.string().min(1),
49
+ files: z.array(
50
+ z.object({
51
+ path: z.string().min(1),
52
+ content: z.string(),
53
+ }),
54
+ ),
55
+ install: z.array(z.string()).default([]),
56
+ })
57
+ .optional(),
58
+ // RFC-0638: Domain-neutral optional fields
59
+ domain: stackProfileDomainFieldsSchema.shape.domain,
60
+ terminology: stackProfileDomainFieldsSchema.shape.terminology,
61
+ artifacts: stackProfileDomainFieldsSchema.shape.artifacts,
62
+ workspaceTypes: stackProfileDomainFieldsSchema.shape.workspaceTypes,
63
+ invariants: stackProfileDomainFieldsSchema.shape.invariants,
64
+ register: stackProfileDomainFieldsSchema.shape.register,
65
+ // RFC-0674: Dev server declaration for lifecycle commands
66
+ devServer: stackProfileDomainFieldsSchema.shape.devServer,
67
+ // RFC-0679: Asset management declaration
68
+ assets: stackProfileDomainFieldsSchema.shape.assets,
69
+ // RFC-0680: Release lifecycle declaration
70
+ release: stackProfileDomainFieldsSchema.shape.release,
71
+ // Prerequisites: system-level dependency checks (e.g. FFmpeg)
72
+ prerequisites: stackProfileDomainFieldsSchema.shape.prerequisites,
73
+ // Templates: multi-template profiles (e.g. React + HTML)
74
+ templates: stackProfileDomainFieldsSchema.shape.templates,
75
+ // ADR-0043: Agent-generated script directory override
76
+ scriptDir: stackProfileDomainFieldsSchema.shape.scriptDir,
77
+ // Profile-driven root AGENTS.md template
78
+ rootAgentsMdTemplate: stackProfileDomainFieldsSchema.shape.rootAgentsMdTemplate,
79
+ });
80
+
81
+ export interface ProfileFile {
82
+ path: string;
83
+ content: string;
84
+ }
85
+
86
+ export interface StackProfile extends StackProfileDomainFields {
87
+ schema: "forge/stack-profile@1";
88
+ id: string;
89
+ displayName: string;
90
+ detect: { anyOf: string[] };
91
+ workspace: {
92
+ dirs: string[];
93
+ files: ProfileFile[];
94
+ };
95
+ install: string[];
96
+ firstWorkspace?: {
97
+ path: string;
98
+ files: ProfileFile[];
99
+ install: string[];
100
+ };
101
+ prerequisites?: import("./profile-schema.ts").ProfilePrerequisite[];
102
+ templates?: import("./profile-schema.ts").ProfileTemplate[];
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Loader
107
+ // ---------------------------------------------------------------------------
108
+
109
+ export function loadStackProfile(profilePath: string): StackProfile {
110
+ const raw = fs.readFileSync(profilePath, "utf8");
111
+ let parsed: unknown;
112
+ try {
113
+ parsed = parseYaml(raw);
114
+ } catch (err) {
115
+ throw new Error(`Stack profile at ${profilePath} is not valid YAML: ${(err as Error).message}`);
116
+ }
117
+
118
+ const result = stackProfileSchema.safeParse(parsed);
119
+ if (!result.success) {
120
+ const issues = result.error.issues
121
+ .map((i) => ` - ${i.path.join(".")}: ${i.message}`)
122
+ .join("\n");
123
+ throw new Error(`Stack profile at ${profilePath} failed schema validation:\n${issues}`);
124
+ }
125
+
126
+ return result.data as StackProfile;
127
+ }
128
+
129
+ export function listStackProfiles(forgeRoot: string): StackProfile[] {
130
+ const profilesDir = path.join(forgeRoot, "profiles");
131
+ if (!fs.existsSync(profilesDir)) {
132
+ return [];
133
+ }
134
+
135
+ const entries = fs.readdirSync(profilesDir);
136
+ const profiles: StackProfile[] = [];
137
+
138
+ for (const entry of entries) {
139
+ if (entry.endsWith(".yaml") && !entry.startsWith(".")) {
140
+ const profilePath = path.join(profilesDir, entry);
141
+ profiles.push(loadStackProfile(profilePath));
142
+ }
143
+ }
144
+
145
+ return profiles;
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Detector
150
+ // ---------------------------------------------------------------------------
151
+
152
+ export function detectStack(projectRoot: string, profiles: StackProfile[]): StackProfile | null {
153
+ for (const profile of profiles) {
154
+ if (matchesAnyOf(projectRoot, profile.detect.anyOf)) {
155
+ return profile;
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+
161
+ function matchesAnyOf(projectRoot: string, patterns: string[]): boolean {
162
+ for (const pattern of patterns) {
163
+ if (globExists(projectRoot, pattern)) {
164
+ return true;
165
+ }
166
+ }
167
+ return false;
168
+ }
169
+
170
+ function globExists(dir: string, pattern: string): boolean {
171
+ // Simple glob: support * and *.ext patterns at the root level
172
+ if (!pattern.includes("*")) {
173
+ return fs.existsSync(path.join(dir, pattern));
174
+ }
175
+
176
+ try {
177
+ const entries = fs.readdirSync(dir);
178
+ const regex = globToRegex(pattern);
179
+ return entries.some((e) => regex.test(e));
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+
185
+ function globToRegex(pattern: string): RegExp {
186
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
187
+ return new RegExp(`^${escaped}$`);
188
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Shared terminology resolution utility (RFC-0643). Resolves all terminology keys
4
+ from config + profile, used by both root and nested AGENTS.md generation.</purpose>
5
+ <non-goals>
6
+ <item>Do not import from @warpgogol/* — this module is portable.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0643: extracted resolveAllTerminology from agents-generate.ts and nested-agents-generate.ts to eliminate duplication.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ import { UNIVERSAL_TERMINOLOGY_KEYS } from "./profile-schema.ts";
15
+ import { resolveTerminology, type ForgeConfig } from "../config/forge-config.ts";
16
+ import type { StackProfile } from "./stack-profile.ts";
17
+
18
+ /**
19
+ * Resolve all terminology keys (universal + profile-specific) from config and profile.
20
+ * Used by both root AGENTS.md generation and nested AGENTS.md generation.
21
+ */
22
+ export function resolveAllTerminology(
23
+ config: ForgeConfig,
24
+ profile: StackProfile | undefined,
25
+ ): Record<string, string> {
26
+ const terminology: Record<string, string> = {};
27
+ for (const key of UNIVERSAL_TERMINOLOGY_KEYS) {
28
+ terminology[key] = resolveTerminology(config, profile?.terminology, key);
29
+ }
30
+ if (profile?.terminology) {
31
+ for (const key of Object.keys(profile.terminology)) {
32
+ terminology[key] = resolveTerminology(config, profile.terminology, key);
33
+ }
34
+ }
35
+ return terminology;
36
+ }