@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,458 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for forge.agents.generate — behavioral layer generation,
4
+ routing table, conditional extended layer, markers, and idempotency (RFC-0548, RFC-0549).</purpose>
5
+ </MODULE_CONTRACT>
6
+ <CHANGE_SUMMARY>
7
+ <item>RFC-0548: initial agents-generate tests for behavioral layer.</item>
8
+ <item>RFC-0549: added tests for nine extended sections and key policy phrases.</item>
9
+ </CHANGE_SUMMARY>
10
+ */
11
+
12
+ import { test, expect, beforeEach, afterEach } from "vitest";
13
+ import { mkdtemp, rm, writeFile, mkdir, readFile } from "node:fs/promises";
14
+ import { existsSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
17
+ import { runAgentsGenerate } from "../onboarding/agents-generate.ts";
18
+ import type { ForgeRuntimeContext } from "../types.ts";
19
+
20
+ const silentLogger = {
21
+ section: () => {},
22
+ info: () => {},
23
+ warn: () => {},
24
+ error: () => {},
25
+ success: () => {},
26
+ };
27
+
28
+ const FORGE_ROOT = join(import.meta.dirname, "..", "..");
29
+ const _WORKSPACE_ROOT = join(FORGE_ROOT, "..", "..");
30
+
31
+ function makeContext(workspaceRoot: string): ForgeRuntimeContext {
32
+ return {
33
+ workspaceRoot,
34
+ logger: silentLogger as never,
35
+ dryRun: false,
36
+ outputFormat: "json",
37
+ forgeRoot: FORGE_ROOT,
38
+ };
39
+ }
40
+
41
+ let tempDir: string;
42
+
43
+ beforeEach(async () => {
44
+ tempDir = await mkdtemp(join(tmpdir(), "agents-gen-test-"));
45
+ });
46
+
47
+ afterEach(async () => {
48
+ await rm(tempDir, { recursive: true, force: true });
49
+ });
50
+
51
+ async function makeForgeYaml(dir: string): Promise<void> {
52
+ const forgeYaml = [
53
+ "schema: forge/config@1",
54
+ "project:",
55
+ " name: test-project",
56
+ " stack: [typescript]",
57
+ " packageManager: pnpm",
58
+ "paths:",
59
+ " rfcsDir: docs/rfcs",
60
+ " adrsDir: docs/adrs",
61
+ " plansDir: docs/plans",
62
+ " auditsDir: docs/audits",
63
+ " specsDir: docs/specs",
64
+ " skillsDir: .agents/skills",
65
+ "bindings:",
66
+ " schema: forge/bindings@1",
67
+ " commands:",
68
+ " validateRfc: pnpm exec forge rfc.validate",
69
+ " validateAdr: pnpm exec forge adr.validate",
70
+ " typecheck: null",
71
+ " test: null",
72
+ " scopedBuild: null",
73
+ " implementStamp: pnpm exec forge rfc.implement.stamp",
74
+ " specValidate: pnpm exec forge spec.validate",
75
+ " sessionSave: pnpm exec forge session.save",
76
+ " paths:",
77
+ " invariantsFile: docs/architecture-dna.md",
78
+ " reviewsDir: docs/reviews",
79
+ " handoffsDir: docs/handoffs",
80
+ " sessionsDir: docs/sessions",
81
+ " compassDocs: [docs/requirements.xml]",
82
+ "forge:",
83
+ " syncedVersion: 0.1.2",
84
+ ].join("\n");
85
+ await writeFile(join(dir, "forge.yaml"), forgeYaml, "utf8");
86
+ }
87
+
88
+ test("agents-generate produces AGENTS.md with behavioral layer markers", async () => {
89
+ await makeForgeYaml(tempDir);
90
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
91
+ expect(result.exitCode).toBe(0);
92
+
93
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
94
+ expect(agentsMd).toContain("<!-- forge:begin behavioral-layer -->");
95
+ expect(agentsMd).toContain("<!-- forge:end behavioral-layer -->");
96
+ });
97
+
98
+ test("agents-generate includes intent-to-skill routing table", async () => {
99
+ await makeForgeYaml(tempDir);
100
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
101
+ expect(result.exitCode).toBe(0);
102
+
103
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
104
+ expect(agentsMd).toContain("### Intent-to-skill routing");
105
+ expect(agentsMd).toContain("| Operator says something like | Skill |");
106
+ // Should contain at least one skill name from the registry
107
+ expect(agentsMd).toContain("fo-");
108
+ });
109
+
110
+ test("agents-generate includes fixed policy text sections", async () => {
111
+ await makeForgeYaml(tempDir);
112
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
113
+ expect(result.exitCode).toBe(0);
114
+
115
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
116
+ expect(agentsMd).toContain("### Auto-grilling");
117
+ expect(agentsMd).toContain("### Creator-facing communication");
118
+ expect(agentsMd).toContain("### Safety net and graceful failure");
119
+ expect(agentsMd).toContain("### Adaptive learning");
120
+ expect(agentsMd).toContain("### Pushback policy");
121
+ });
122
+
123
+ test("agents-generate defaults to business register (no extended layer)", async () => {
124
+ await makeForgeYaml(tempDir);
125
+ // No PREFERENCES.md → defaults to business
126
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
127
+ expect(result.exitCode).toBe(0);
128
+
129
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
130
+ expect(agentsMd).toContain("The current register is **business**");
131
+ expect(agentsMd).not.toContain("Extended behavioral layer (RFC-0549)");
132
+ });
133
+
134
+ test("agents-generate includes extended layer when register is creative", async () => {
135
+ await makeForgeYaml(tempDir);
136
+ await writeFile(
137
+ join(tempDir, "PREFERENCES.md"),
138
+ "---\naiLanguage: en\ndocumentationLanguage: en\nregister: creative\n---\n",
139
+ "utf8",
140
+ );
141
+
142
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
143
+ expect(result.exitCode).toBe(0);
144
+
145
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
146
+ expect(agentsMd).toContain("The current register is **creative**");
147
+ expect(agentsMd).toContain("Extended behavioral layer (creative register)");
148
+ });
149
+
150
+ test("agents-generate extended layer includes all nine sections when creative", async () => {
151
+ await makeForgeYaml(tempDir);
152
+ await writeFile(
153
+ join(tempDir, "PREFERENCES.md"),
154
+ "---\naiLanguage: en\ndocumentationLanguage: en\nregister: creative\n---\n",
155
+ "utf8",
156
+ );
157
+
158
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
159
+ expect(result.exitCode).toBe(0);
160
+
161
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
162
+ expect(agentsMd).toContain("#### Personal connection");
163
+ expect(agentsMd).toContain("#### Creative memory");
164
+ expect(agentsMd).toContain("#### Emotional rhythm");
165
+ expect(agentsMd).toContain("#### Gentle accountability");
166
+ expect(agentsMd).toContain("#### Creative partnership");
167
+ expect(agentsMd).toContain("#### Visual thinking");
168
+ expect(agentsMd).toContain("#### Audience empathy");
169
+ expect(agentsMd).toContain("#### Creative companion");
170
+ expect(agentsMd).toContain("#### Creative confidence");
171
+ });
172
+
173
+ test("agents-generate extended layer includes key policy phrases when creative", async () => {
174
+ await makeForgeYaml(tempDir);
175
+ await writeFile(
176
+ join(tempDir, "PREFERENCES.md"),
177
+ "---\naiLanguage: en\ndocumentationLanguage: en\nregister: creative\n---\n",
178
+ "utf8",
179
+ );
180
+
181
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
182
+ expect(result.exitCode).toBe(0);
183
+
184
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
185
+ // Questions not declarations
186
+ expect(agentsMd).toContain("do NOT declare");
187
+ // Outcome-based praise
188
+ expect(agentsMd).toContain("outcome-based praise");
189
+ // Never refuse creative direction
190
+ expect(agentsMd).toContain("Never refuse creative direction");
191
+ // Companion mode session saving flag
192
+ expect(agentsMd).toContain("saveCompanionSessions");
193
+ // Inspiration feed pull-only
194
+ expect(agentsMd).toContain("pull-only");
195
+ });
196
+
197
+ test("agents-generate extended layer is absent when register is business", async () => {
198
+ await makeForgeYaml(tempDir);
199
+ await writeFile(
200
+ join(tempDir, "PREFERENCES.md"),
201
+ "---\naiLanguage: en\ndocumentationLanguage: en\nregister: business\n---\n",
202
+ "utf8",
203
+ );
204
+
205
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
206
+ expect(result.exitCode).toBe(0);
207
+
208
+ const agentsMd = await readFile(join(tempDir, "AGENTS.md"), "utf8");
209
+ expect(agentsMd).not.toContain("Extended behavioral layer (creative register)");
210
+ expect(agentsMd).not.toContain("#### Personal connection");
211
+ expect(agentsMd).not.toContain("#### Creative companion");
212
+ });
213
+
214
+ test("agents-generate is idempotent — running twice produces the same content", async () => {
215
+ await makeForgeYaml(tempDir);
216
+
217
+ const ctx = makeContext(tempDir);
218
+ await runAgentsGenerate({ argv: [], flags: {} }, ctx);
219
+ const first = await readFile(join(tempDir, "AGENTS.md"), "utf8");
220
+
221
+ await runAgentsGenerate({ argv: [], flags: {} }, ctx);
222
+ const second = await readFile(join(tempDir, "AGENTS.md"), "utf8");
223
+
224
+ expect(second).toBe(first);
225
+ });
226
+
227
+ test("agents-generate refuses to overwrite hand-written AGENTS.md", async () => {
228
+ await makeForgeYaml(tempDir);
229
+ await writeFile(join(tempDir, "AGENTS.md"), "# Hand-written\nNo marker.\n", "utf8");
230
+
231
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
232
+ expect(result.exitCode).toBe(1);
233
+ expect(result.data?.status).toBe("fail");
234
+ });
235
+
236
+ // RFC-0611: nested AGENTS.md generation tests
237
+
238
+ test("agents-generate generates nested AGENTS.md for workspace directories", async () => {
239
+ await makeForgeYaml(tempDir);
240
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
241
+ await writeFile(
242
+ join(tempDir, "packages", "my-pkg", "package.json"),
243
+ JSON.stringify({
244
+ name: "@test/my-pkg",
245
+ description: "A test package",
246
+ exports: { ".": { types: "./src/index.ts", default: "./src/index.ts" } },
247
+ scripts: { "build:check": "tsc --noEmit" },
248
+ dependencies: { zod: "^4.4.3" },
249
+ }),
250
+ );
251
+
252
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
253
+ expect(result.exitCode).toBe(0);
254
+ expect(result.data?.generated).toContain("packages/my-pkg/AGENTS.md");
255
+
256
+ const nested = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
257
+ expect(nested).toContain("GENERATED");
258
+ expect(nested).toContain("`@test/my-pkg` — Agent Guide");
259
+ expect(nested).toContain("A test package");
260
+ expect(nested).toContain("Package");
261
+ expect(nested).toContain("## Entry points");
262
+ expect(nested).toContain("## Scripts");
263
+ expect(nested).toContain("## Dependencies");
264
+ });
265
+
266
+ test("agents-generate skips hand-written nested AGENTS.md", async () => {
267
+ await makeForgeYaml(tempDir);
268
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
269
+ await writeFile(
270
+ join(tempDir, "packages", "my-pkg", "package.json"),
271
+ JSON.stringify({ name: "@test/my-pkg" }),
272
+ );
273
+ await writeFile(
274
+ join(tempDir, "packages", "my-pkg", "AGENTS.md"),
275
+ "# Custom\nNo marker.\n",
276
+ "utf8",
277
+ );
278
+
279
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
280
+ expect(result.exitCode).toBe(0);
281
+ expect(result.data?.skipped).toContain("packages/my-pkg/AGENTS.md (hand-written)");
282
+
283
+ const nested = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
284
+ expect(nested).toBe("# Custom\nNo marker.\n");
285
+ });
286
+
287
+ test("agents-generate regenerates stale generated nested AGENTS.md", async () => {
288
+ await makeForgeYaml(tempDir);
289
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
290
+ await writeFile(
291
+ join(tempDir, "packages", "my-pkg", "package.json"),
292
+ JSON.stringify({ name: "@test/my-pkg" }),
293
+ );
294
+ await writeFile(
295
+ join(tempDir, "packages", "my-pkg", "AGENTS.md"),
296
+ "<!--\n GENERATED. Do not change this line unless the file contains project specific changes.\n-->\n# Old content\n",
297
+ "utf8",
298
+ );
299
+
300
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
301
+ expect(result.exitCode).toBe(0);
302
+ expect(result.data?.generated).toContain("packages/my-pkg/AGENTS.md");
303
+
304
+ const nested = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
305
+ expect(nested).toContain("`@test/my-pkg` — Agent Guide");
306
+ expect(nested).not.toContain("Old content");
307
+ });
308
+
309
+ test("agents-generate nested idempotency — running twice produces same content", async () => {
310
+ await makeForgeYaml(tempDir);
311
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
312
+ await writeFile(
313
+ join(tempDir, "packages", "my-pkg", "package.json"),
314
+ JSON.stringify({ name: "@test/my-pkg", description: "Test" }),
315
+ );
316
+
317
+ const ctx = makeContext(tempDir);
318
+ await runAgentsGenerate({ argv: [], flags: {} }, ctx);
319
+ const first = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
320
+
321
+ await runAgentsGenerate({ argv: [], flags: {} }, ctx);
322
+ const second = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
323
+
324
+ expect(second).toBe(first);
325
+ });
326
+
327
+ test("agents-generate with no workspaces generates only root AGENTS.md", async () => {
328
+ await makeForgeYaml(tempDir);
329
+
330
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
331
+ expect(result.exitCode).toBe(0);
332
+ expect(result.data?.generated).toEqual(["AGENTS.md"]);
333
+ expect(result.data?.skipped).toEqual([]);
334
+ });
335
+
336
+ test("agents-generate dryRun mode produces renderedFiles without writing", async () => {
337
+ await makeForgeYaml(tempDir);
338
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
339
+ await writeFile(
340
+ join(tempDir, "packages", "my-pkg", "package.json"),
341
+ JSON.stringify({ name: "@test/my-pkg" }),
342
+ );
343
+
344
+ const ctx = makeContext(tempDir);
345
+ ctx.dryRun = true;
346
+
347
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, ctx);
348
+ expect(result.exitCode).toBe(0);
349
+ expect(result.data?.renderedFiles).toBeDefined();
350
+ expect(result.data?.renderedFiles?.["AGENTS.md"]).toContain("Agent Guide: test-project");
351
+ expect(result.data?.renderedFiles?.["packages/my-pkg/AGENTS.md"]).toContain(
352
+ "`@test/my-pkg` — Agent Guide",
353
+ );
354
+
355
+ // Files should NOT exist on disk
356
+ expect(existsSync(join(tempDir, "AGENTS.md"))).toBe(false);
357
+ expect(existsSync(join(tempDir, "packages", "my-pkg", "AGENTS.md"))).toBe(false);
358
+ });
359
+
360
+ test("agents-generate dryRun mode skips edit guard for hand-written root", async () => {
361
+ await makeForgeYaml(tempDir);
362
+ await writeFile(join(tempDir, "AGENTS.md"), "# Hand-written\nNo marker.\n", "utf8");
363
+
364
+ const ctx = makeContext(tempDir);
365
+ ctx.dryRun = true;
366
+
367
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, ctx);
368
+ expect(result.exitCode).toBe(0);
369
+ expect(result.data?.renderedFiles?.["AGENTS.md"]).toContain("Agent Guide: test-project");
370
+ });
371
+
372
+ test("agents-generate nested with empty package.json falls back to path-based title", async () => {
373
+ await makeForgeYaml(tempDir);
374
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
375
+ await writeFile(join(tempDir, "packages", "my-pkg", "package.json"), "{}");
376
+
377
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
378
+ expect(result.exitCode).toBe(0);
379
+
380
+ const nested = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
381
+ expect(nested).toContain("`packages/my-pkg` — Agent Guide");
382
+ expect(nested).not.toContain("## Entry points");
383
+ expect(nested).not.toContain("## Scripts");
384
+ expect(nested).not.toContain("## Dependencies");
385
+ });
386
+
387
+ test("agents-generate nested renders entry points from exports map", async () => {
388
+ await makeForgeYaml(tempDir);
389
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
390
+ await writeFile(
391
+ join(tempDir, "packages", "my-pkg", "package.json"),
392
+ JSON.stringify({
393
+ name: "@test/my-pkg",
394
+ exports: {
395
+ ".": { types: "./src/index.ts", default: "./src/index.ts" },
396
+ "./port": { types: "./src/port.ts", default: "./src/port.ts" },
397
+ "./client": "./src/client.ts",
398
+ },
399
+ }),
400
+ );
401
+
402
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
403
+ expect(result.exitCode).toBe(0);
404
+
405
+ const nested = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
406
+ expect(nested).toContain("## Entry points");
407
+ expect(nested).toContain("| `@test/my-pkg` | `./src/index.ts` |");
408
+ expect(nested).toContain("| `@test/my-pkg/port` | `./src/port.ts` |");
409
+ expect(nested).toContain("| `@test/my-pkg/client` | `./src/client.ts` |");
410
+ });
411
+
412
+ test("agents-generate nested separates workspace and external dependencies", async () => {
413
+ await makeForgeYaml(tempDir);
414
+ await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
415
+ await writeFile(
416
+ join(tempDir, "packages", "my-pkg", "package.json"),
417
+ JSON.stringify({
418
+ name: "@test/my-pkg",
419
+ dependencies: {
420
+ "@warpgogol/werkstatt-shared/share": "workspace:*",
421
+ zod: "^4.4.3",
422
+ },
423
+ }),
424
+ );
425
+
426
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
427
+ expect(result.exitCode).toBe(0);
428
+
429
+ const nested = await readFile(join(tempDir, "packages", "my-pkg", "AGENTS.md"), "utf8");
430
+ expect(nested).toContain("**Workspace:**");
431
+ expect(nested).toContain("- `@warpgogol/werkstatt-shared/share`");
432
+ expect(nested).toContain("**External:**");
433
+ expect(nested).toContain("- `zod` `^4.4.3`");
434
+ });
435
+
436
+ test("agents-generate nested for service workspace omits entry points", async () => {
437
+ await makeForgeYaml(tempDir);
438
+ await mkdir(join(tempDir, "services", "my-svc"), { recursive: true });
439
+ await writeFile(
440
+ join(tempDir, "services", "my-svc", "package.json"),
441
+ JSON.stringify({
442
+ name: "@test/my-svc",
443
+ scripts: { "build:check": "tsc --noEmit", "run:once": "tsx src/run.ts" },
444
+ dependencies: { "@warpgogol/werkstatt-shared/observability": "workspace:*" },
445
+ }),
446
+ );
447
+ await writeFile(join(tempDir, "services", "my-svc", "Dockerfile"), "FROM node:20\n");
448
+
449
+ const result = await runAgentsGenerate({ argv: [], flags: {} }, makeContext(tempDir));
450
+ expect(result.exitCode).toBe(0);
451
+
452
+ const nested = await readFile(join(tempDir, "services", "my-svc", "AGENTS.md"), "utf8");
453
+ expect(nested).toContain("`@test/my-svc` — Agent Guide");
454
+ expect(nested).toContain("Service");
455
+ expect(nested).not.toContain("## Entry points");
456
+ expect(nested).toContain("## Scripts");
457
+ expect(nested).toContain("## Dependencies");
458
+ });