@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,113 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for domain-aware forge.doctor (RFC-0640).</purpose>
4
+ </MODULE_CONTRACT>
5
+ <CHANGE_SUMMARY>
6
+ <item>RFC-0640: initial domain-aware doctor tests.</item>
7
+ </CHANGE_SUMMARY>
8
+ */
9
+
10
+ import { test, expect, beforeEach, afterEach } from "vitest";
11
+ import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { runDoctor } from "../onboarding/doctor.ts";
15
+ import type { ForgeCommandInput, ForgeRuntimeContext } from "../types.ts";
16
+
17
+ let tempDir: string;
18
+
19
+ beforeEach(async () => {
20
+ tempDir = await mkdtemp(join(tmpdir(), "doctor-domain-test-"));
21
+ });
22
+
23
+ afterEach(async () => {
24
+ await rm(tempDir, { recursive: true, force: true });
25
+ });
26
+
27
+ function makeContext(): ForgeRuntimeContext {
28
+ return {
29
+ workspaceRoot: tempDir,
30
+ logger: {
31
+ section: () => {},
32
+ success: () => {},
33
+ warn: () => {},
34
+ error: () => {},
35
+ info: () => {},
36
+ },
37
+ dryRun: false,
38
+ outputFormat: "json",
39
+ };
40
+ }
41
+
42
+ test("doctor reports default software domain when no forge.yaml", async () => {
43
+ const input: ForgeCommandInput = { argv: [], flags: {} };
44
+ const result = await runDoctor(input, makeContext());
45
+
46
+ expect(result.data!.domain).toBeDefined();
47
+ expect(result.data!.domain!.domain).toBe("software");
48
+ expect(result.data!.domain!.source).toBe("default");
49
+ });
50
+
51
+ test("doctor reports domain from forge.yaml when present", async () => {
52
+ await writeFile(
53
+ join(tempDir, "forge.yaml"),
54
+ `schema: "forge/config@1"\nproject:\n name: test-project\n stack: []\n packageManager: pnpm\n domain: video\npaths:\n rfcsDir: docs/rfcs\n adrsDir: docs/adrs\n plansDir: docs/plans\n auditsDir: docs/audits\n specsDir: docs/specs\n skillsDir: .agents/skills\n sessionsDir: docs/sessions\n`,
55
+ "utf8",
56
+ );
57
+
58
+ const input: ForgeCommandInput = { argv: [], flags: {} };
59
+ const result = await runDoctor(input, makeContext());
60
+
61
+ expect(result.data!.domain).toBeDefined();
62
+ expect(result.data!.domain!.domain).toBe("video");
63
+ expect(result.data!.domain!.source).toBe("forge.yaml");
64
+ });
65
+
66
+ test("doctor includes domain-info check in checks array", async () => {
67
+ const input: ForgeCommandInput = { argv: [], flags: {} };
68
+ const result = await runDoctor(input, makeContext());
69
+
70
+ const domainCheck = result.data!.checks.find((c) => c.name === "domain-info");
71
+ expect(domainCheck).toBeDefined();
72
+ expect(domainCheck!.status).toBe("pass");
73
+ });
74
+
75
+ test("doctor --strict does not affect invariant reporting (reserved for future use)", async () => {
76
+ await mkdir(join(tempDir, "packages", "forge", "profiles"), { recursive: true });
77
+ await writeFile(
78
+ join(tempDir, "packages", "forge", "profiles", "test.yaml"),
79
+ `schema: "forge/stack-profile@1"\nid: test\ndisplayName: Test\ndetect:\n anyOf: ["package.json"]\nworkspace:\n dirs: ["packages"]\n files: []\ninstall: []\ndomain: video\ninvariants:\n - id: VIDEO-01\n rule: "All videos must have audio"\n severity: error\n`,
80
+ "utf8",
81
+ );
82
+ await writeFile(
83
+ join(tempDir, "forge.yaml"),
84
+ `schema: "forge/config@1"\nproject:\n name: test-project\n stack: ["test"]\n packageManager: pnpm\npaths:\n rfcsDir: docs/rfcs\n adrsDir: docs/adrs\n plansDir: docs/plans\n auditsDir: docs/audits\n specsDir: docs/specs\n skillsDir: .agents/skills\n sessionsDir: docs/sessions\n`,
85
+ "utf8",
86
+ );
87
+
88
+ const input: ForgeCommandInput = { argv: [], flags: { strict: true } };
89
+ const result = await runDoctor(input, makeContext());
90
+
91
+ const invariantsCheck = result.data!.checks.find((c) => c.name === "domain-invariants");
92
+ expect(invariantsCheck).toBeDefined();
93
+ // --strict elevates warn to fail for domain-invariants and profile-validate,
94
+ // but invariants check is "pass" here (invariants declared), so no elevation
95
+ expect(invariantsCheck!.status).toBe("pass");
96
+ });
97
+
98
+ test("doctor runs nested AGENTS.md check for non-software domain (RFC-0640 fix)", async () => {
99
+ await writeFile(
100
+ join(tempDir, "forge.yaml"),
101
+ `schema: "forge/config@1"\nproject:\n name: test-project\n stack: []\n packageManager: pnpm\n domain: video\npaths:\n rfcsDir: docs/rfcs\n adrsDir: docs/adrs\n plansDir: docs/plans\n auditsDir: docs/audits\n specsDir: docs/specs\n skillsDir: .agents/skills\n sessionsDir: docs/sessions\n`,
102
+ "utf8",
103
+ );
104
+
105
+ const input: ForgeCommandInput = { argv: [], flags: {} };
106
+ const result = await runDoctor(input, makeContext());
107
+
108
+ // RFC-0640 fix: nested AGENTS.md check now runs for all domains
109
+ // (previously gated by isSoftwareDomain, which skipped non-software domains)
110
+ const nestedCheck = result.data!.checks.find((c) => c.name === "nested-AGENTS.md");
111
+ expect(nestedCheck).toBeDefined();
112
+ expect(nestedCheck!.status).toBe("pass");
113
+ });
@@ -0,0 +1,256 @@
1
+ import { test, expect, describe, beforeAll, afterAll } from "vitest";
2
+ import { join } from "node:path";
3
+ import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import type { ForgeCommandInput, ForgeRuntimeContext, ForgeFlagValue } from "../../src/types.ts";
6
+ import { runExplorationList } from "../../os/exploration/handlers/list.ts";
7
+ import { runExplorationShow } from "../../os/exploration/handlers/show.ts";
8
+ import { runExplorationArchive } from "../../os/exploration/handlers/archive.ts";
9
+ import type {
10
+ ExplorationListResult,
11
+ ExplorationShowResult,
12
+ ExplorationArchiveResult,
13
+ } from "../../os/exploration/types.ts";
14
+
15
+ async function makeTempDir(): Promise<string> {
16
+ return mkdtemp(join(tmpdir(), "forge-exploration-test-"));
17
+ }
18
+
19
+ function makeInput(flags: Record<string, ForgeFlagValue> = {}): ForgeCommandInput {
20
+ return { argv: [], flags };
21
+ }
22
+
23
+ function makeContext(workspaceRoot: string): ForgeRuntimeContext {
24
+ return {
25
+ workspaceRoot,
26
+ logger: {
27
+ info: () => {},
28
+ warn: () => {},
29
+ error: () => {},
30
+ success: () => {},
31
+ section: () => {},
32
+ },
33
+ dryRun: false,
34
+ outputFormat: "json",
35
+ };
36
+ }
37
+
38
+ const SAMPLE_NOTE = `---
39
+ id: self-hosted-fonts
40
+ title: "Self-hosted fonts vs CDN"
41
+ createdAt: 2026-08-06
42
+ status: open
43
+ related: []
44
+ ---
45
+
46
+ # Exploration: Self-hosted fonts vs CDN
47
+
48
+ ## Idea
49
+
50
+ Should we self-host fonts or keep using the CDN?
51
+
52
+ ## Options
53
+
54
+ ### Option 1: Self-host
55
+ - **Approach:** Download fonts and serve locally.
56
+ - **Trade-offs:** More bandwidth, better privacy.
57
+
58
+ ### Option 2: Keep CDN
59
+ - **Approach:** Continue using Google Fonts CDN.
60
+ - **Trade-offs:** Less control, faster initial load.
61
+ `;
62
+
63
+ const ARCHIVED_NOTE = `---
64
+ id: old-idea
65
+ title: "Old idea that was explored"
66
+ createdAt: 2026-07-01
67
+ status: archived
68
+ related: [RFC-0700]
69
+ ---
70
+
71
+ # Exploration: Old idea
72
+
73
+ Already archived.
74
+ `;
75
+
76
+ describe("exploration.list", () => {
77
+ let dir: string;
78
+
79
+ beforeAll(async () => {
80
+ dir = await makeTempDir();
81
+ await mkdir(join(dir, "docs/explorations"), { recursive: true });
82
+ await writeFile(join(dir, "docs/explorations/self-hosted-fonts.md"), SAMPLE_NOTE, "utf-8");
83
+ await writeFile(join(dir, "docs/explorations/old-idea.md"), ARCHIVED_NOTE, "utf-8");
84
+ });
85
+
86
+ afterAll(async () => {
87
+ await rm(dir, { recursive: true, force: true });
88
+ });
89
+
90
+ test("lists all exploration notes", async () => {
91
+ const result = await runExplorationList(makeInput(), makeContext(dir));
92
+ const data = result.data as ExplorationListResult;
93
+
94
+ expect(data.command).toBe("exploration.list");
95
+ expect(data.status).toBe("ok");
96
+ expect(data.count).toBe(2);
97
+ expect(data.explorations).toHaveLength(2);
98
+
99
+ const ids = data.explorations.map((e) => e.id).sort();
100
+ expect(ids).toEqual(["old-idea", "self-hosted-fonts"]);
101
+ });
102
+
103
+ test("filters by status", async () => {
104
+ const result = await runExplorationList(makeInput({ status: "archived" }), makeContext(dir));
105
+ const data = result.data as ExplorationListResult;
106
+
107
+ expect(data.count).toBe(1);
108
+ expect(data.explorations[0]!.id).toBe("old-idea");
109
+ expect(data.explorations[0]!.status).toBe("archived");
110
+ });
111
+
112
+ test("returns empty list when directory does not exist", async () => {
113
+ const emptyDir = await makeTempDir();
114
+ try {
115
+ const result = await runExplorationList(makeInput(), makeContext(emptyDir));
116
+ const data = result.data as ExplorationListResult;
117
+
118
+ expect(data.command).toBe("exploration.list");
119
+ expect(data.status).toBe("ok");
120
+ expect(data.count).toBe(0);
121
+ expect(data.explorations).toEqual([]);
122
+ expect(result.exitCode).toBeUndefined();
123
+ } finally {
124
+ await rm(emptyDir, { recursive: true, force: true });
125
+ }
126
+ });
127
+ });
128
+
129
+ describe("exploration.show", () => {
130
+ let dir: string;
131
+
132
+ beforeAll(async () => {
133
+ dir = await makeTempDir();
134
+ await mkdir(join(dir, "docs/explorations"), { recursive: true });
135
+ await writeFile(join(dir, "docs/explorations/self-hosted-fonts.md"), SAMPLE_NOTE, "utf-8");
136
+ });
137
+
138
+ afterAll(async () => {
139
+ await rm(dir, { recursive: true, force: true });
140
+ });
141
+
142
+ test("shows the full content of an exploration note", async () => {
143
+ const result = await runExplorationShow(
144
+ makeInput({ id: "self-hosted-fonts" }),
145
+ makeContext(dir),
146
+ );
147
+ const data = result.data as ExplorationShowResult;
148
+
149
+ expect(data.command).toBe("exploration.show");
150
+ expect(data.status).toBe("ok");
151
+ expect(data.note.id).toBe("self-hosted-fonts");
152
+ expect(data.note.title).toBe("Self-hosted fonts vs CDN");
153
+ expect(data.note.status).toBe("open");
154
+ expect(data.note.createdAt).toBe("2026-08-06");
155
+ expect(data.note.related).toEqual([]);
156
+ expect(data.note.body).toContain("# Exploration: Self-hosted fonts vs CDN");
157
+ });
158
+
159
+ test("returns exit code 1 when slug not found", async () => {
160
+ const result = await runExplorationShow(makeInput({ id: "nonexistent" }), makeContext(dir));
161
+ const data = result.data as ExplorationShowResult;
162
+
163
+ expect(data.status).toBe("error");
164
+ expect(result.exitCode).toBe(1);
165
+ });
166
+
167
+ test("returns exit code 1 when --id is missing", async () => {
168
+ const result = await runExplorationShow(makeInput(), makeContext(dir));
169
+ const data = result.data as ExplorationShowResult;
170
+
171
+ expect(data.status).toBe("error");
172
+ expect(result.exitCode).toBe(1);
173
+ });
174
+ });
175
+
176
+ describe("exploration.archive", () => {
177
+ let dir: string;
178
+
179
+ beforeAll(async () => {
180
+ dir = await makeTempDir();
181
+ await mkdir(join(dir, "docs/explorations"), { recursive: true });
182
+ await writeFile(join(dir, "docs/explorations/self-hosted-fonts.md"), SAMPLE_NOTE, "utf-8");
183
+ await writeFile(join(dir, "docs/explorations/old-idea.md"), ARCHIVED_NOTE, "utf-8");
184
+ });
185
+
186
+ afterAll(async () => {
187
+ await rm(dir, { recursive: true, force: true });
188
+ });
189
+
190
+ test("transitions status from open to archived", async () => {
191
+ const result = await runExplorationArchive(
192
+ makeInput({ id: "self-hosted-fonts" }),
193
+ makeContext(dir),
194
+ );
195
+ const data = result.data as ExplorationArchiveResult;
196
+
197
+ expect(data.command).toBe("exploration.archive");
198
+ expect(data.status).toBe("ok");
199
+ expect(data.id).toBe("self-hosted-fonts");
200
+ expect(data.previousStatus).toBe("open");
201
+ expect(data.newStatus).toBe("archived");
202
+
203
+ const content = await readFile(join(dir, "docs/explorations/self-hosted-fonts.md"), "utf-8");
204
+ expect(content).toContain("status: archived");
205
+ });
206
+
207
+ test("adds RFC id to related field", async () => {
208
+ const result = await runExplorationArchive(
209
+ makeInput({ id: "self-hosted-fonts", rfc: "RFC-0710" }),
210
+ makeContext(dir),
211
+ );
212
+ const data = result.data as ExplorationArchiveResult;
213
+
214
+ expect(data.related).toContain("RFC-0710");
215
+
216
+ const content = await readFile(join(dir, "docs/explorations/self-hosted-fonts.md"), "utf-8");
217
+ expect(content).toContain("RFC-0710");
218
+ });
219
+
220
+ test("is idempotent when already archived", async () => {
221
+ const result = await runExplorationArchive(makeInput({ id: "old-idea" }), makeContext(dir));
222
+ const data = result.data as ExplorationArchiveResult;
223
+
224
+ expect(data.status).toBe("ok");
225
+ expect(data.previousStatus).toBe("archived");
226
+ expect(data.newStatus).toBe("archived");
227
+ expect(result.exitCode).toBe(0);
228
+ });
229
+
230
+ test("returns exit code 1 when slug not found", async () => {
231
+ const result = await runExplorationArchive(makeInput({ id: "nonexistent" }), makeContext(dir));
232
+ const data = result.data as ExplorationArchiveResult;
233
+
234
+ expect(data.status).toBe("error");
235
+ expect(result.exitCode).toBe(1);
236
+ });
237
+
238
+ test("returns exit code 1 when --id is missing", async () => {
239
+ const result = await runExplorationArchive(makeInput(), makeContext(dir));
240
+ const data = result.data as ExplorationArchiveResult;
241
+
242
+ expect(data.status).toBe("error");
243
+ expect(result.exitCode).toBe(1);
244
+ });
245
+
246
+ test("rejects invalid slug (non-kebab-case)", async () => {
247
+ const result = await runExplorationArchive(
248
+ makeInput({ id: "Invalid_Slug!" }),
249
+ makeContext(dir),
250
+ );
251
+ const data = result.data as ExplorationArchiveResult;
252
+
253
+ expect(data.status).toBe("error");
254
+ expect(result.exitCode).toBe(1);
255
+ });
256
+ });
@@ -0,0 +1,288 @@
1
+ <!--
2
+ GENERATED. Do not change this line unless the file contains project specific changes.
3
+ DO NOT EDIT THIS FILE. Changes are overwritten on the next build.
4
+ Owner command: forge.agents.generate
5
+ Edit instead: the forge.agents.generate generator source (not this file).
6
+ Regenerate: forge forge.agents.generate
7
+ -->
8
+
9
+
10
+ <!-- Root AGENTS.md template for business register projects.
11
+ Static prose only — dynamic sections (skills, capabilities, behavioral layer)
12
+ are inserted at the dynamicSections marker by runAgentsGenerate.
13
+ Placeholders: projectName, projectStack, projectPm,
14
+ rfcsDir, adrsDir, plansDir, auditsDir, specsDir,
15
+ skillsDir, dynamicSections -->
16
+ # Agent Guide: test-project
17
+
18
+ > This file is generated by `forge.agents.generate` from `forge.yaml`.
19
+ > Do not edit by hand — edit `forge.yaml` and regenerate.
20
+
21
+ ## Project
22
+
23
+ - **Name:** test-project
24
+ - **Stack:** typescript
25
+ - **Package manager:** pnpm
26
+
27
+ ## Paths
28
+
29
+ - RFCs: `docs/rfcs`
30
+ - ADRs: `docs/adrs`
31
+ - Plans: `docs/plans`
32
+ - Audits: `docs/audits`
33
+ - Specs: `docs/specs`
34
+ - Skills: `.agents/skills`
35
+
36
+ ## Skills
37
+
38
+ | Name | Category | Invocation | Concerns |
39
+ | --- | --- | --- | --- |
40
+ | fo-add-tests | fo | user | code-mutation |
41
+ | fo-architecture | fo | user | document-only |
42
+ | fo-compass-annotate | fo | user | content-mutation |
43
+ | fo-design-summit | fo | user | document-only |
44
+ | fo-doc-audit | fo | user | document-only |
45
+ | fo-explore | fo | user | document-only |
46
+ | fo-extract-dna | fo | user | document-only |
47
+ | fo-fix | fo | user | code-mutation |
48
+ | fo-handoff | fo | user | document-only |
49
+ | fo-harvest | fo | user | code-mutation |
50
+ | fo-idea | fo | user | document-only |
51
+ | fo-idea-audit | fo | user | read-only |
52
+ | fo-idea-create-adr | fo | user | document-only |
53
+ | fo-idea-create-rfc | fo | user | document-only |
54
+ | fo-idea-enhance | fo | user | document-only |
55
+ | fo-idea-i-just-want-to-see-the-plan | fo | user | document-only |
56
+ | fo-idea-i-just-want-to-see-the-result | fo | user | code-mutation |
57
+ | fo-idea-implement | fo | user | code-mutation |
58
+ | fo-idea-plan | fo | user | document-only |
59
+ | fo-idea-status | fo | user | read-only |
60
+ | fo-knowledge-distill | fo | user | document-only |
61
+ | fo-memory-sync | fo | user | content-mutation |
62
+ | fo-qa | fo | user | document-only |
63
+ | fo-review | fo | user | read-only |
64
+ | fo-session-retro | fo | user | document-only |
65
+ | fo-session-save | fo | user | document-only |
66
+ | fo-spec-ingest | fo | user | document-only |
67
+ | fo-step-commit | fo | model | code-mutation |
68
+ | fo-triage | fo | user | document-only |
69
+ | forge-bootstrap | meta | user | content-mutation |
70
+ | grilling | shared | user | read-only |
71
+ | my-preferences | shared | user | document-only |
72
+ | port-to-forge | meta | user | code-mutation |
73
+ | skill-create | meta | user | document-only |
74
+ | windows-ai-tooling | shared | user | code-mutation |
75
+ | writing-great-skills | shared | user | read-only |
76
+
77
+ ## Capabilities
78
+
79
+ Bindings resolved from `forge.yaml`:
80
+
81
+ | Key | Status | Value |
82
+ | --- | --- | --- |
83
+ | commands.validateRfc | resolved | `pnpm exec forge rfc.validate` |
84
+ | commands.validateAdr | resolved | `pnpm exec forge adr.validate` |
85
+ | commands.typecheck | absent | — |
86
+ | commands.test | absent | — |
87
+ | commands.scopedBuild | absent | — |
88
+ | commands.specValidate | resolved | `pnpm exec forge spec.validate` |
89
+ | commands.sessionSave | resolved | `pnpm exec forge session.save` |
90
+ | paths.invariantsFile | resolved | `docs/architecture-dna.md` |
91
+ | paths.reviewsDir | resolved | `docs/reviews` |
92
+ | paths.handoffsDir | resolved | `docs/handoffs` |
93
+ | paths.sessionsDir | resolved | `docs/sessions` |
94
+ | paths.compassDocs | resolved | `docs/requirements.xml` |
95
+
96
+ <!-- forge:begin behavioral-layer -->
97
+
98
+ ## Behavioral layer
99
+
100
+ This section defines the agent's core behavioral contract. It is generated from skill `triggers` and fixed policy text. The agent MUST follow these behaviors in every session.
101
+
102
+ ### Intent-to-skill routing
103
+
104
+ When the operator expresses an intent in natural language, the agent routes to the matching skill. The routing table is generated from `triggers` fields in skill frontmatter.
105
+
106
+ | Operator says something like | Skill |
107
+ | --- | --- |
108
+
109
+ The agent uses judgment to calibrate routing — minor edits (typo fixes, small CSS changes) do not require skill invocation, while significant changes (new features, architectural decisions) do.
110
+
111
+ ### Auto-grilling
112
+
113
+ When the operator describes a significant idea or change, the agent SHOULD proactively invoke grilling to stress-test the plan before building.
114
+
115
+ - **Significant:** new feature, architectural change, new RFC/ADR, cross-workspace refactor.
116
+ - **Minor:** typo fix, small CSS change, renaming a variable, updating a dependency version.
117
+ - The operator can say "just do it" to skip grilling — the agent invokes `fo-idea-i-just-want-to-see-the-result` instead.
118
+
119
+ ### Auto-session-save
120
+
121
+ The agent SHOULD auto-save sessions at the end of each session unless opted out via `PREFERENCES.md` (`saveSessions: false`). Companion-mode session saving can be opted out separately.
122
+
123
+ Session save mechanism:
124
+ 1. Export the conversation as a raw ATIF file to `docs/sessions/.raw/` (create the directory with `mkdir -p docs/sessions/.raw` if it does not exist).
125
+ 2. Run `ref(forge.yaml bindings.commands.sessionSave)` to convert the raw file to structured markdown in `docs/sessions/`.
126
+ 3. If the command fails or the binding is null, create the session file manually in `docs/sessions/` with frontmatter (id, date, type, relatedRfcs).
127
+
128
+ ### Auto-review
129
+
130
+ The agent SHOULD auto-run `fo-review` after implementing a significant change. Review results are presented in creator language — only actionable issues are highlighted.
131
+
132
+ ### Context awareness
133
+
134
+ Before starting significant work, the agent SHOULD read recent ADRs, RFCs, and session transcripts (last 5-10 documents) to understand prior decisions and avoid conflicts. Minor edits do not require context reading.
135
+
136
+ ### Creator-facing communication
137
+
138
+ The agent communicates in creator language — no CLI commands, no skill names, no internal jargon in user-facing text.
139
+
140
+ - **Forbidden terms in user-facing text:** `pnpm`, `git commit`, `vitest`, `tsc`, `AGENTS.md`, `forge.yaml`, `RFC-XXXX`, `fo-idea`, `fo-fix`, `fo-review`.
141
+ - **Use instead:** "I'll review the plan with you", "I'll check the code quality", "I'll prepare the changes for you".
142
+ - CLI output and internal logs remain technical — only agent chat output uses creator language.
143
+
144
+ ### Adaptive learning
145
+
146
+ The agent reads `.agents/operator-profile.md` at the start of each session and calibrates behavior based on the operator's known preferences, communication style, and past feedback.
147
+
148
+ - The profile is local to the project, git-tracked, and can be deleted by the operator at any time.
149
+ - Developer handoff summaries MUST NOT include `operator-profile.md` contents — only technical architecture, decisions, and code structure.
150
+ - Sections are tagged with Zugangsstufen (Öffentlich/Vertraulich). Only Öffentlich sections are visible to co-creators.
151
+ - Entries in `## Emotional rhythm` and `## Feedback history` expire after 90 days unless refreshed. Stale entries are marked `[expired YYYY-MM-DD]`.
152
+ - The profile is gitignored by default to protect operator privacy.
153
+
154
+ ### Proactive guidance
155
+
156
+ The agent offers proactive guidance at the right moment — at most once per session per topic. If declined, the suggestion is not repeated.
157
+
158
+ Built-in guidance triggers:
159
+ - **Long session** (>2 hours): suggest a break or session save.
160
+ - **Complex change** (3+ files in one area): suggest grilling or planning.
161
+ - **Multiple topics** in one session: suggest splitting into separate sessions.
162
+ - **Large scope** (>500 lines changed): suggest an RFC or ADR.
163
+ - **Unclear request**: ask clarifying questions before proceeding.
164
+
165
+ ### Live operator feedback
166
+
167
+ The agent updates `.agents/operator-profile.md` immediately when the operator expresses a behavior preference (e.g., "I prefer shorter responses", "don't ask me about tests"). The agent confirms understanding before updating: "Just to make sure I understand — you want me to [X] from now on?"
168
+
169
+ ### Register parameter
170
+
171
+ The current register is **business**.
172
+
173
+ - **Business register:** core behavioral layer only — professional, efficient communication.
174
+ - **Creative register:** core + extended behavioral layer — creative partnership, emotional support, companion mode.
175
+ - The register can be changed at any time via live operator feedback. The change takes effect immediately.
176
+
177
+ ### Pushback policy
178
+
179
+ The agent exercises two classes of pushback:
180
+
181
+ 1. **Purpose-drift (soft):** when the operator's request drifts from the project's stated purpose, the agent offers a gentle reminder. The operator can override without explicit confirmation.
182
+ 2. **Legal/compliance (hard):** when the operator's request may violate copyright, GDPR/DSGVO, accessibility, or license requirements, the agent refuses and explains the risk. The operator can override only with explicit confirmation that they accept the risk.
183
+
184
+ ### External capabilities (MCP)
185
+
186
+ The agent uses a two-tier model for external capabilities:
187
+
188
+ 1. **Read-only autonomous:** the agent may use read-only MCP tools (web search, documentation lookup) without asking.
189
+ 2. **Connectable (offered):** the agent offers connectable capabilities (email, calendar, analytics) and lets the operator choose. The agent MUST NOT auto-select a specific provider.
190
+
191
+ ### Safety net and graceful failure
192
+
193
+ The agent provides a safety net for the operator:
194
+
195
+ - **Undo/rollback:** the agent offers undo or rollback for significant changes.
196
+ - **Auto-recovery:** when something goes wrong, the agent attempts automatic recovery before reporting.
197
+ - **No technical errors shown:** the agent MUST NOT show technical errors, stack traces, or error codes to the operator. Errors are translated to creator language: "Something went wrong with the preview. Let me try again."
198
+
199
+ ### Invisible quality
200
+
201
+ The agent handles performance, accessibility, SEO, and optimization automatically. Quality is communicated as human impact, not technical metrics.
202
+
203
+ - Instead of "Lighthouse score 98", say "Your site loads quickly for visitors."
204
+ - Instead of "WCAG 2.1 AA compliant", say "Your site is accessible to all visitors."
205
+
206
+ ### First creation moment
207
+
208
+ The first creation moment is special — the agent celebrates the operator's first creation and sets a welcoming tone.
209
+
210
+ ### Creative health and time awareness
211
+
212
+ The agent monitors creative health and time investment:
213
+
214
+ - **Project health dashboard:** the agent can provide a snapshot of project health (progress, pending items, areas needing attention).
215
+ - **Creative balance:** the agent suggests breaks when sessions are long and reminds the operator that sustainable creative work matters more than marathon sessions.
216
+ - **Time investment:** the agent helps the operator understand where time is being spent.
217
+ - **Rhythm insights:** the agent shares patterns about the operator's creative rhythm (based on `operator-profile.md`).
218
+
219
+ ### Sharing and feedback
220
+
221
+ The agent helps the operator share work and collect feedback:
222
+
223
+ - **Share preview:** the agent can prepare a shareable preview without deploying.
224
+ - **External feedback:** feedback from external reviewers is recorded in `operator-profile.md` for future reference.
225
+
226
+ ### Cultural awareness and multilingual support
227
+
228
+ The agent is culturally aware and supports multilingual communication:
229
+
230
+ - The agent uses `aiLanguage` from `PREFERENCES.md` for all communication.
231
+ - The agent respects cultural norms and communication styles.
232
+ - The agent does not assume a specific cultural context.
233
+
234
+ ### Indirect teaching
235
+
236
+ The agent explains significant decisions briefly in creator language — not as lectures, but as natural context. The operator learns by seeing the agent's reasoning, not by being taught.
237
+
238
+ ### Ownership and collaboration
239
+
240
+ Everything the operator creates belongs to them. The agent makes ownership explicit.
241
+
242
+ - **Co-creation:** the agent supports collaborative work with co-creators.
243
+ - **Developer handoff:** when the operator needs professional development help, the agent prepares a handoff with technical architecture, decisions, and code structure — excluding `operator-profile.md` contents.
244
+
245
+ ### Commit policy
246
+
247
+ In the creative register, the agent commits all changes automatically after each completed logical step (e.g. after implementing a feature, after fixing a bug, after creating a file). The operator is never asked about git, commits, or version control. No dirty files remain at any pause point. In the business register, the agent asks before committing.
248
+
249
+ - **Auto-commit does not skip verification** — the agent still runs typecheck/build before committing. Auto-commit means the agent does not ask for permission, not that it skips quality checks.
250
+ - **Auto-commit does not fire in companion mode** — companion mode is pure creative exploration without code changes, so there is nothing to commit.
251
+ - **Auto-commit applies to forge projects** (bootstrapped projects using `forge create`).
252
+
253
+ <!-- forge:end behavioral-layer -->
254
+
255
+ ## Project memory layer
256
+
257
+ `.agents/memory/MEMORY.md` is the curated hot store (versioned). `.agents/memory/daily/YYYY-MM-DD.md` are append-only warm logs (git-ignored).
258
+
259
+ **Session-start read discipline (advisory):** At session start, read `MEMORY.md` (always), then `daily/<today>.md` and `daily/<yesterday>.md` (if present). Older daily files are cold — use grep when a task references past context.
260
+
261
+ ## RTK — Token Optimization
262
+
263
+ Always prefix shell commands with `rtk` to minimize token consumption. RTK filters and compresses command output before it reaches the LLM context, cutting up to 90% of bash output on common operations.
264
+
265
+ ```bash
266
+ rtk git status
267
+ rtk pnpm test
268
+ rtk ls src/
269
+ rtk grep "pattern" src/
270
+ rtk find "*.ts" .
271
+ ```
272
+
273
+ Meta commands:
274
+
275
+ ```bash
276
+ rtk gain # Show token savings
277
+ rtk gain --history # Command history with savings
278
+ rtk discover # Find missed RTK opportunities
279
+ rtk proxy <cmd> # Run raw (no filtering, for debugging)
280
+ ```
281
+
282
+ Use default RTK settings only — do not enable `--ultra-compact` or aggressive `read` modes unless the operator explicitly requests it.
283
+
284
+ ## Conventions
285
+
286
+ - Use `forge.yaml` as the single source of truth for project configuration.
287
+ - Regenerate this file with `forge.agents.generate` after changing `forge.yaml`.
288
+ - Follow the closest `AGENTS.md` for workspace or directory details.