@jamie-tam/forge 6.0.0

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/agents/architect.md +92 -0
  4. package/agents/builder.md +122 -0
  5. package/agents/code-reviewer.md +107 -0
  6. package/agents/concept-designer.md +207 -0
  7. package/agents/craft-reviewer.md +132 -0
  8. package/agents/critic.md +130 -0
  9. package/agents/doc-writer.md +85 -0
  10. package/agents/dreamer.md +129 -0
  11. package/agents/e2e-runner.md +89 -0
  12. package/agents/gotcha-hunter.md +127 -0
  13. package/agents/prototype-builder.md +193 -0
  14. package/agents/prototype-codifier.md +204 -0
  15. package/agents/prototype-reviewer.md +163 -0
  16. package/agents/security-reviewer.md +108 -0
  17. package/agents/spec-reviewer.md +94 -0
  18. package/agents/tracer.md +98 -0
  19. package/agents/wireframer.md +109 -0
  20. package/commands/abort.md +25 -0
  21. package/commands/bugfix.md +151 -0
  22. package/commands/evolve.md +118 -0
  23. package/commands/feature.md +236 -0
  24. package/commands/forge.md +100 -0
  25. package/commands/greenfield.md +185 -0
  26. package/commands/hotfix.md +98 -0
  27. package/commands/refactor.md +147 -0
  28. package/commands/resume.md +25 -0
  29. package/commands/setup.md +201 -0
  30. package/commands/status.md +27 -0
  31. package/commands/task-force.md +110 -0
  32. package/commands/validate.md +12 -0
  33. package/dist/__tests__/active-manifest.test.js +272 -0
  34. package/dist/__tests__/copy.test.js +96 -0
  35. package/dist/__tests__/gate-check.test.js +384 -0
  36. package/dist/__tests__/wiki.test.js +472 -0
  37. package/dist/__tests__/work-manifest.test.js +304 -0
  38. package/dist/active-manifest.js +229 -0
  39. package/dist/cli.js +158 -0
  40. package/dist/copy.js +124 -0
  41. package/dist/gate-check.js +326 -0
  42. package/dist/hooks.js +60 -0
  43. package/dist/init.js +140 -0
  44. package/dist/manifest.js +90 -0
  45. package/dist/merge.js +77 -0
  46. package/dist/paths.js +36 -0
  47. package/dist/uninstall.js +216 -0
  48. package/dist/update.js +158 -0
  49. package/dist/verify-manifest.js +65 -0
  50. package/dist/verify.js +98 -0
  51. package/dist/wiki-ui.js +310 -0
  52. package/dist/wiki.js +364 -0
  53. package/dist/work-manifest.js +798 -0
  54. package/hooks/config/gate-requirements.json +79 -0
  55. package/hooks/hooks.json +143 -0
  56. package/hooks/scripts/analyze-telemetry.sh +114 -0
  57. package/hooks/scripts/gate-enforcer.sh +164 -0
  58. package/hooks/scripts/pre-compact.sh +90 -0
  59. package/hooks/scripts/session-start.sh +81 -0
  60. package/hooks/scripts/telemetry.sh +41 -0
  61. package/hooks/scripts/wiki-lint.sh +87 -0
  62. package/hooks/templates/AGENTS.md.template +48 -0
  63. package/hooks/templates/CLAUDE.md.template +45 -0
  64. package/package.json +55 -0
  65. package/protocols/README.md +40 -0
  66. package/protocols/codex.md +151 -0
  67. package/protocols/graphify.md +156 -0
  68. package/references/common/agent-coordination.md +65 -0
  69. package/references/common/coding-standards.md +54 -0
  70. package/references/common/feature-tracking.md +21 -0
  71. package/references/common/io-protocol.md +36 -0
  72. package/references/common/phases.md +57 -0
  73. package/references/common/quality-gates.md +130 -0
  74. package/references/common/skill-authoring.md +154 -0
  75. package/references/common/skill-compliance.md +30 -0
  76. package/references/python/standards.md +44 -0
  77. package/references/react/standards.md +61 -0
  78. package/references/typescript/standards.md +42 -0
  79. package/rules/common/forge-system.md +59 -0
  80. package/rules/common/git-workflow.md +40 -0
  81. package/rules/common/guardrails.md +37 -0
  82. package/rules/common/quality-gates.md +18 -0
  83. package/rules/common/security.md +50 -0
  84. package/rules/common/skill-selection.md +78 -0
  85. package/rules/common/testing.md +58 -0
  86. package/rules/common/verification.md +39 -0
  87. package/skills/build-pr-workflow/SKILL.md +301 -0
  88. package/skills/build-pr-workflow/references/pr-template.md +62 -0
  89. package/skills/build-pr-workflow/references/subagent-merge.md +47 -0
  90. package/skills/build-pr-workflow/references/worktree-setup.md +125 -0
  91. package/skills/build-prototype/SKILL.md +264 -0
  92. package/skills/build-scaffold/SKILL.md +340 -0
  93. package/skills/build-tdd/SKILL.md +89 -0
  94. package/skills/build-wireframe/SKILL.md +110 -0
  95. package/skills/build-wireframe/assets/baseline-template.html +486 -0
  96. package/skills/build-wireframe/references/demo-walkthroughs.md +170 -0
  97. package/skills/build-wireframe/references/gotchas.md +188 -0
  98. package/skills/build-wireframe/references/legend-lines.md +141 -0
  99. package/skills/concept-slides/SKILL.md +192 -0
  100. package/skills/deliver-db-migration/SKILL.md +466 -0
  101. package/skills/deliver-deploy/SKILL.md +407 -0
  102. package/skills/deliver-onboarding/SKILL.md +198 -0
  103. package/skills/deliver-onboarding/references/document-templates.md +393 -0
  104. package/skills/deliver-onboarding/templates/getting-started.md +122 -0
  105. package/skills/discover-codebase-analysis/SKILL.md +448 -0
  106. package/skills/discover-requirements/SKILL.md +418 -0
  107. package/skills/discover-requirements/templates/prd.md +99 -0
  108. package/skills/discover-requirements/templates/technical-spec.md +123 -0
  109. package/skills/discover-requirements/templates/user-stories.md +76 -0
  110. package/skills/harden/SKILL.md +214 -0
  111. package/skills/iterate-prototype/SKILL.md +241 -0
  112. package/skills/plan-architecture/SKILL.md +457 -0
  113. package/skills/plan-architecture/templates/adr-template.md +52 -0
  114. package/skills/plan-architecture/templates/api-contract.md +99 -0
  115. package/skills/plan-architecture/templates/db-schema.md +81 -0
  116. package/skills/plan-architecture/templates/system-design.md +111 -0
  117. package/skills/plan-brainstorm/SKILL.md +433 -0
  118. package/skills/plan-design-system/SKILL.md +279 -0
  119. package/skills/plan-task-decompose/SKILL.md +454 -0
  120. package/skills/quality-code-review/SKILL.md +286 -0
  121. package/skills/quality-security-audit/SKILL.md +292 -0
  122. package/skills/quality-security-audit/references/audit-report-template.md +89 -0
  123. package/skills/quality-security-audit/references/owasp-checks.md +178 -0
  124. package/skills/quality-test-execution/SKILL.md +435 -0
  125. package/skills/quality-test-plan/SKILL.md +297 -0
  126. package/skills/quality-test-plan/references/test-type-guide.md +263 -0
  127. package/skills/quality-test-plan/templates/e2e-test-plan.md +72 -0
  128. package/skills/quality-test-plan/templates/integration-test-plan.md +74 -0
  129. package/skills/quality-test-plan/templates/load-test-plan.md +111 -0
  130. package/skills/quality-test-plan/templates/smoke-test-plan.md +68 -0
  131. package/skills/quality-test-plan/templates/unit-test-plan.md +56 -0
  132. package/skills/quality-uiux/SKILL.md +481 -0
  133. package/skills/support-debug/SKILL.md +464 -0
  134. package/skills/support-dream/SKILL.md +213 -0
  135. package/skills/support-gotcha/SKILL.md +249 -0
  136. package/skills/support-runtime-reachability/SKILL.md +190 -0
  137. package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/app.ts +7 -0
  138. package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/handlers/cases.ts +7 -0
  139. package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/app.ts +8 -0
  140. package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/handlers/cases.ts +7 -0
  141. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/App.tsx +5 -0
  142. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/components/RingingBanner.tsx +7 -0
  143. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/hooks/useTwilio.ts +6 -0
  144. package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/App.tsx +5 -0
  145. package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/components/MyComp.tsx +3 -0
  146. package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/App.tsx +3 -0
  147. package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/components/Orphan.tsx +3 -0
  148. package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/lib/Service.ts +6 -0
  149. package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/main.ts +4 -0
  150. package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/lib/Lonely.ts +5 -0
  151. package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/main.ts +2 -0
  152. package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/handler.ts +3 -0
  153. package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/main.ts +3 -0
  154. package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/handler.ts +3 -0
  155. package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/main.ts +2 -0
  156. package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/lib.ts +5 -0
  157. package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/main.ts +3 -0
  158. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/index.ts +1 -0
  159. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/internal.ts +3 -0
  160. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/main.ts +3 -0
  161. package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.test.ts +5 -0
  162. package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.ts +3 -0
  163. package/skills/support-runtime-reachability/scripts/__fixtures__/case-13-gated-pending-annotation/src/future.ts +4 -0
  164. package/skills/support-runtime-reachability/scripts/__fixtures__/case-14-untraceable-annotation/src/decorated.ts +4 -0
  165. package/skills/support-runtime-reachability/scripts/__fixtures__/case-15-untraceable-empty/src/lazy.ts +4 -0
  166. package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/lib.py +15 -0
  167. package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/main.py +5 -0
  168. package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/parent.ts +5 -0
  169. package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/routes/cases.ts +5 -0
  170. package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/lib/foo.ts +3 -0
  171. package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/other.ts +8 -0
  172. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/cases.ts +4 -0
  173. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/users.ts +4 -0
  174. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/main.ts +5 -0
  175. package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/handlers/cases.ts +3 -0
  176. package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/main.ts +4 -0
  177. package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/lib.ts +5 -0
  178. package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/main.ts +5 -0
  179. package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/lib.ts +3 -0
  180. package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/main.ts +8 -0
  181. package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/lib.ts +3 -0
  182. package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/main.ts +7 -0
  183. package/skills/support-runtime-reachability/scripts/check.mjs +638 -0
  184. package/skills/support-runtime-reachability/scripts/check.test.mjs +244 -0
  185. package/skills/support-skill-validator/SKILL.md +194 -0
  186. package/skills/support-skill-validator/references/false-positives.md +59 -0
  187. package/skills/support-skill-validator/references/validation-checks.md +280 -0
  188. package/skills/support-system-guide/SKILL.md +311 -0
  189. package/skills/support-task-force/SKILL.md +265 -0
  190. package/skills/support-task-force/references/dispatch-pattern.md +178 -0
  191. package/skills/support-task-force/references/synthesis-template.md +126 -0
  192. package/skills/support-wiki-bootstrap/SKILL.md +37 -0
  193. package/skills/support-wiki-lint/SKILL.md +196 -0
  194. package/skills/support-wiki-lint/scripts/lint.mjs +488 -0
  195. package/skills/support-wiki-lint/scripts/lint.test.mjs +196 -0
  196. package/templates/README.md +23 -0
  197. package/templates/aiwiki/CLAUDE.md.template +78 -0
  198. package/templates/aiwiki/schemas/architecture.md +118 -0
  199. package/templates/aiwiki/schemas/convention.md +112 -0
  200. package/templates/aiwiki/schemas/decision.md +144 -0
  201. package/templates/aiwiki/schemas/gotcha.md +118 -0
  202. package/templates/aiwiki/schemas/oracle.md +105 -0
  203. package/templates/aiwiki/schemas/session.md +125 -0
  204. package/templates/manifests/bugfix.yaml +41 -0
  205. package/templates/manifests/feature.yaml +69 -0
  206. package/templates/manifests/greenfield.yaml +61 -0
  207. package/templates/manifests/hotfix.yaml +45 -0
  208. package/templates/manifests/refactor.yaml +44 -0
  209. package/templates/manifests/v5/SCHEMA.md +327 -0
  210. package/templates/manifests/v5/feature.yaml +77 -0
  211. package/templates/manifests/v6/SCHEMA.md +199 -0
  212. package/templates/wiki-html/dream-detail.html +378 -0
  213. package/templates/wiki-html/dreams-list.html +155 -0
@@ -0,0 +1,196 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import { execSync } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { lint, parseYaml } from './lint.mjs';
9
+
10
+ const here = path.dirname(fileURLToPath(import.meta.url));
11
+ const repoRoot = path.resolve(here, '../../..');
12
+ const templateSchemasDir = path.join(repoRoot, 'templates/aiwiki/schemas');
13
+
14
+ function mkTempRoot() {
15
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-lint-test-'));
16
+ // Copy schemas the lint script reads.
17
+ const schemasDir = path.join(root, 'schemas');
18
+ fs.mkdirSync(schemasDir, { recursive: true });
19
+ for (const entry of fs.readdirSync(templateSchemasDir)) {
20
+ fs.copyFileSync(path.join(templateSchemasDir, entry), path.join(schemasDir, entry));
21
+ }
22
+ return { root, schemasDir };
23
+ }
24
+
25
+ function writeFile(root, rel, content) {
26
+ const full = path.join(root, rel);
27
+ fs.mkdirSync(path.dirname(full), { recursive: true });
28
+ fs.writeFileSync(full, content);
29
+ }
30
+
31
+ const GOOD_GOTCHA = `---
32
+ schema_id: gotcha
33
+ schema_version: 1
34
+ severity: medium
35
+ date: 2026-05-12
36
+ occurrences: 1
37
+ status: active
38
+ ---
39
+
40
+ # Stub logger throws under server-rendered hydration
41
+
42
+ ## What broke
43
+
44
+ The logger stub threw \`NotImplemented\` when rendered twice (server + client).
45
+
46
+ ## Reproducer
47
+
48
+ Run a Next.js page that imports the stub logger; observe the throw on client mount.
49
+
50
+ ## Root cause
51
+
52
+ Logger was initialized twice but only one stub path was instrumented to throw.
53
+
54
+ ## Fix
55
+
56
+ Throw on both stub paths so the call site is forced to inject a real logger.
57
+
58
+ ## Prevention
59
+
60
+ When stubbing module-level singletons, throw on every code path that could fire.
61
+ `;
62
+
63
+ test('parseYaml handles a minimal mapping', () => {
64
+ const parsed = parseYaml('schema_id: gotcha\nseverity: medium\n');
65
+ assert.equal(parsed.schema_id, 'gotcha');
66
+ assert.equal(parsed.severity, 'medium');
67
+ });
68
+
69
+ test('parseYaml handles inline mapping values', () => {
70
+ const parsed = parseYaml('field: { type: enum, values: [low, high] }\n');
71
+ assert.deepEqual(parsed.field, { type: 'enum', values: ['low', 'high'] });
72
+ });
73
+
74
+ test('valid gotcha page passes lint', () => {
75
+ const { root, schemasDir } = mkTempRoot();
76
+ writeFile(root, 'aiwiki/gotchas/2026-05-12-stub-logger.md', GOOD_GOTCHA);
77
+ const result = lint({ file: 'aiwiki/gotchas/2026-05-12-stub-logger.md', schemasDir, root });
78
+ assert.equal(result.ok, true, `lint errors: ${JSON.stringify(result.errors)}`);
79
+ assert.equal(result.errors.length, 0);
80
+ });
81
+
82
+ test('missing required frontmatter field is flagged', () => {
83
+ const { root, schemasDir } = mkTempRoot();
84
+ const badFrontmatter = GOOD_GOTCHA.replace('severity: medium\n', '');
85
+ writeFile(root, 'aiwiki/gotchas/bad.md', badFrontmatter);
86
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
87
+ assert.equal(result.ok, false);
88
+ assert.ok(
89
+ result.errors.some((e) => e.kind === 'frontmatter_invalid' && e.field === 'severity'),
90
+ `expected severity field error, got: ${JSON.stringify(result.errors)}`,
91
+ );
92
+ });
93
+
94
+ test('bad enum value is flagged', () => {
95
+ const { root, schemasDir } = mkTempRoot();
96
+ const badEnum = GOOD_GOTCHA.replace('severity: medium', 'severity: lukewarm');
97
+ writeFile(root, 'aiwiki/gotchas/bad.md', badEnum);
98
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
99
+ assert.equal(result.ok, false);
100
+ assert.ok(
101
+ result.errors.some((e) => e.kind === 'frontmatter_invalid' && e.field === 'severity'),
102
+ `expected severity enum error, got: ${JSON.stringify(result.errors)}`,
103
+ );
104
+ });
105
+
106
+ test('missing required H2 section is flagged', () => {
107
+ const { root, schemasDir } = mkTempRoot();
108
+ const noSection = GOOD_GOTCHA.replace(/## Root cause[\s\S]*?## Fix/, '## Fix');
109
+ writeFile(root, 'aiwiki/gotchas/bad.md', noSection);
110
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
111
+ assert.equal(result.ok, false);
112
+ assert.ok(
113
+ result.errors.some((e) => e.kind === 'missing_section' && e.section === '## Root cause'),
114
+ `expected missing-section error, got: ${JSON.stringify(result.errors)}`,
115
+ );
116
+ });
117
+
118
+ test('out-of-order sections trip section_order_violation', () => {
119
+ const { root, schemasDir } = mkTempRoot();
120
+ const swapped = GOOD_GOTCHA
121
+ .replace(/## What broke[\s\S]*?(?=## Reproducer)/, (m) => m)
122
+ // Move ## Fix before ## Root cause
123
+ .replace(/## Root cause([\s\S]*?)## Fix([\s\S]*?)## Prevention/, '## Fix$2## Root cause$1## Prevention');
124
+ writeFile(root, 'aiwiki/gotchas/bad.md', swapped);
125
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
126
+ assert.equal(result.ok, false);
127
+ assert.ok(
128
+ result.errors.some((e) => e.kind === 'section_order_violation'),
129
+ `expected section_order_violation, got: ${JSON.stringify(result.errors)}`,
130
+ );
131
+ });
132
+
133
+ test('hard_cap_lines exceeded is flagged', () => {
134
+ const { root, schemasDir } = mkTempRoot();
135
+ // gotcha schema's hard_cap_lines is 150. Pad with bullet lines.
136
+ const padding = Array.from({ length: 200 }, (_, i) => `- padding line ${i}`).join('\n');
137
+ const tooLong = GOOD_GOTCHA.replace('## Prevention', `## Prevention\n\n${padding}\n\n## Other`);
138
+ writeFile(root, 'aiwiki/gotchas/bad.md', tooLong);
139
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
140
+ assert.equal(result.ok, false);
141
+ assert.ok(
142
+ result.errors.some((e) => e.kind === 'hard_cap_exceeded'),
143
+ `expected hard_cap_exceeded, got: ${JSON.stringify(result.errors)}`,
144
+ );
145
+ });
146
+
147
+ test('missing schema_id is rejected', () => {
148
+ const { root, schemasDir } = mkTempRoot();
149
+ const noSchemaId = GOOD_GOTCHA.replace('schema_id: gotcha\n', '');
150
+ writeFile(root, 'aiwiki/gotchas/bad.md', noSchemaId);
151
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
152
+ assert.equal(result.ok, false);
153
+ assert.ok(
154
+ result.errors.some((e) => e.kind === 'frontmatter_invalid' && /schema_id/.test(e.message)),
155
+ `expected schema_id error, got: ${JSON.stringify(result.errors)}`,
156
+ );
157
+ });
158
+
159
+ test('unknown schema_id is rejected', () => {
160
+ const { root, schemasDir } = mkTempRoot();
161
+ const wrongSchema = GOOD_GOTCHA.replace('schema_id: gotcha', 'schema_id: nonexistent');
162
+ writeFile(root, 'aiwiki/gotchas/bad.md', wrongSchema);
163
+ const result = lint({ file: 'aiwiki/gotchas/bad.md', schemasDir, root });
164
+ assert.equal(result.ok, false);
165
+ assert.ok(
166
+ result.errors.some((e) => e.kind === 'missing_schema'),
167
+ `expected missing_schema error, got: ${JSON.stringify(result.errors)}`,
168
+ );
169
+ });
170
+
171
+ test('bare citation auto-backfills @<sha7>', () => {
172
+ const { root, schemasDir } = mkTempRoot();
173
+ // Create a real source file inside a git repo so the lint script can compute its hash.
174
+ execSync('git init -q', { cwd: root });
175
+ execSync('git config user.email test@example.com', { cwd: root });
176
+ execSync('git config user.name Test', { cwd: root });
177
+ writeFile(root, 'src/handler.ts', 'export function handler() {\n return 1;\n}\n');
178
+ execSync('git add . && git commit -q -m init', { cwd: root });
179
+
180
+ const pageWithBareCite = GOOD_GOTCHA.replace(
181
+ '## Reproducer',
182
+ '## Reproducer\n\nSee src/handler.ts:1 for the broken path.',
183
+ );
184
+ writeFile(root, 'aiwiki/gotchas/cite.md', pageWithBareCite);
185
+
186
+ const result = lint({ file: 'aiwiki/gotchas/cite.md', schemasDir, root });
187
+ // Either backfilled (auto-fix) or flagged — both are valid lint outcomes
188
+ // depending on whether updates[] auto-applies. Check that the lint at least
189
+ // processed the citation rather than crashing.
190
+ assert.ok(result.errors !== undefined, 'lint should return an errors array');
191
+ // If an update was emitted, the file should now contain an @<sha7> citation.
192
+ if (result.updates && result.updates.length > 0) {
193
+ const updated = fs.readFileSync(path.join(root, 'aiwiki/gotchas/cite.md'), 'utf8');
194
+ assert.match(updated, /src\/handler\.ts:1@[0-9a-f]{7}/, 'expected backfilled citation hash');
195
+ }
196
+ });
@@ -0,0 +1,23 @@
1
+ # templates/
2
+
3
+ Templates copied into user projects by `npx @jamie-tam/forge init`, plus schema docs used internally by forge maintainers.
4
+
5
+ ## User-installed templates
6
+
7
+ Installed into the target project by `npx @jamie-tam/forge init` (or kept in-sync by subsequent updates). These are the files real users of forge see.
8
+
9
+ - **`manifests/`** — Canonical work-manifest templates, one per work type (`feature.yaml`, `greenfield.yaml`, `bugfix.yaml`, `hotfix.yaml`, `refactor.yaml`). Written to `.forge/work/{type}/{name}/manifest.yaml` when a work command starts. Each manifest is the source of truth for its schema shape — do not duplicate schema documentation elsewhere.
10
+ - **`aiwiki/`** — Seed contents for the project's `aiwiki/` directory: the `CLAUDE.md.template` that auto-loads wiki usage rules, plus the `schemas/` folder defining the file shape for conventions, gotchas, architecture, decisions, and sessions.
11
+ - **`wiki-html/`** — Single-file HTML pages (e.g. `dreams-list.html`, `dream-detail.html`) used by the wiki review UI surfaced via the forge CLI.
12
+
13
+ ## Legacy
14
+
15
+ Kept on-disk so older manifests still parse and so `/evolve` can migrate them forward. Not installed into new projects.
16
+
17
+ - **`manifests/v5/`** — v5 schema (`SCHEMA.md` plus a `feature.yaml` reference). v5 manifests still parse against the v6 reader (see `manifests/v6/SCHEMA.md` §4). v4 manifests also still parse but no template is retained — the reader synthesizes the missing fields.
18
+
19
+ ## Internal
20
+
21
+ Used by forge maintainers writing the parser/verifier and the migration logic. Not consumed at user runtime.
22
+
23
+ - **`manifests/v6/SCHEMA.md`** — Source of truth for v6 manifest validation rules. The reader and verifier in `src/work-manifest.ts` implement against this document. v6's delta over v5 is the additive `phase_plan:` block (see §3). Update this file alongside any change to manifest shape or allowed plan-status values.
@@ -0,0 +1,78 @@
1
+ # Wiki usage rules (auto-loaded every session)
2
+
3
+ This file is loaded automatically by Claude Code at session start. It tells you (the AI) how to use the `aiwiki/` directory in this project.
4
+
5
+ ## Reading
6
+
7
+ Before answering any non-trivial question, check the wiki:
8
+
9
+ - `aiwiki/conventions/` — for "how does this codebase do X?"
10
+ - `aiwiki/gotchas/` — for "have we hit this before?"
11
+ - `aiwiki/architecture/` — for "what is the system shape?"
12
+ - `aiwiki/decisions/` — for "why was this chosen?"
13
+ - `aiwiki/oracles/` — for "what behavior must the production code reproduce from the prototype?"
14
+
15
+ The wiki is curated to answer recurring questions. **Use it before asking the user.**
16
+
17
+ ## Writing
18
+
19
+ When you make a decision that's hard to reverse — architectural choice, public-surface naming (APIs / schemas / file paths users import), security or data-handling tradeoff, schema design, or a contract that other parts of the codebase depend on:
20
+ - Write an ADR in `aiwiki/decisions/`. Format per `aiwiki/schemas/decision.md`. Invoke `/second-opinion` to get adversarial review before marking `status: accepted`.
21
+
22
+ When you encounter a recurring failure:
23
+ - Write a gotcha in `aiwiki/gotchas/`. Format per `aiwiki/schemas/gotcha.md`.
24
+ - If the gotcha file's `occurrences` reaches 3+, the gotcha is auto-drafted as a `proposed_rule:` block; the next session-start hard-interrupts to require user approval before promoting the rule.
25
+
26
+ When you discover a codebase convention:
27
+ - Write to `aiwiki/conventions/`. Format per `aiwiki/schemas/convention.md`.
28
+
29
+ When you make a system-shape change:
30
+ - Write to `aiwiki/architecture/{topic}.md`. Format per `aiwiki/schemas/architecture.md`.
31
+ - One file per topic — do NOT collapse multiple subsystems into a single mega-file.
32
+
33
+ When unsure where something belongs:
34
+ - Write to `aiwiki/raw/{YYYY-MM-DD}-{slug}.md`. Phase-close dream consolidates raw entries into typed pages.
35
+
36
+ ## Citations
37
+
38
+ Cite code with `file:line@<sha7>` (e.g. `src/auth.ts:42@a3f2bc1`) or `symbol` form (e.g. `src/auth.ts#login`). The `@<sha7>` part is auto-filled by LINT on first save.
39
+
40
+ When the cited code moves, LINT flags the citation as stale on the next wiki write. Resolve by:
41
+ - Updating the citation to the new location, OR
42
+ - Removing the claim that depended on it, OR
43
+ - Annotating `// ack-stale: <reason>` on the citing line if the staleness is acceptable for now.
44
+
45
+ ## Reviewing dream output
46
+
47
+ The wiki is consolidated periodically by **dream** (forge's wiki-consolidation mechanism — see `support-dream` skill). Dream output goes to `aiwiki/proposed/{dream_id}/` and is **never auto-applied** to `aiwiki/`.
48
+
49
+ When pending dreams exist:
50
+ - Run `forge wiki status` to see the queue
51
+ - Run `forge wiki review [dream_id]` to review per-page diffs
52
+ - Run `forge wiki accept [dream_id]` or `forge wiki reject [dream_id] --reason "..."` to resolve
53
+
54
+ You will be notified at session start (soft) and at phase-close (hard interrupt if a phase-close dream is pending).
55
+
56
+ ## Phase vocabulary
57
+
58
+ When a skill or agent states its phase context, the canonical numbering and names are defined in `.claude/references/common/phases.md`. Load it when in doubt — different files reusing the wrong phase numbers is the dominant source of operational confusion. Phases are defaults, not requirements; manifest tracks deviations.
59
+
60
+ ## Rules and references — what applies when
61
+
62
+ Forge tiers code-standards content by phase to match its prototype-driven SDLC. The thesis: prototype iteration should be fast and unconstrained by style/convention rules; production-grade standards take effect when the prototype is codified.
63
+
64
+ | Tier | Files | Active during |
65
+ |---|---|---|
66
+ | Always-on (safety floor) | `.claude/rules/common/security.md`, `guardrails.md`, `verification.md` | Every phase. Prevents classes of issues that are expensive to retrofit (injection, missing input validation, unverified completion claims). |
67
+ | Phase-conditional | `.claude/rules/common/testing.md`, `quality-gates.md`, `git-workflow.md` | Phase 5 (codify) onward. Headers in those files mark the transition explicitly. |
68
+ | References (load on demand) | `.claude/references/common/coding-standards.md`, `.claude/references/{language}/standards.md` (typescript, react, python) | Loaded by the `harden` skill / `prototype-codifier` agent at codify time. Not active during prototype iteration. |
69
+
70
+ **Implication for AI agents:** during Phases 1–4 (concept → wireframe → prototype → iterate), follow the safety floor only — do not preemptively apply style/convention/testing standards. During Phase 5 (codify/harden) onward, load the references and phase-conditional rules listed above.
71
+
72
+ ## Forbidden
73
+
74
+ - **Never edit `aiwiki/proposed/` directly** — that's dream output for review. Edit `aiwiki/` if you need to change something; dream re-runs on the new state next cycle.
75
+ - **Never duplicate code in wiki entries** — cite instead. The code is the truth; the wiki points at it.
76
+ - **Never write speculation, conversation history, or "we might want to revisit this" notes** — those don't answer recurring questions. They're noise.
77
+ - **Never write to `.forge/work/manifest.yaml` from a wiki context** — the manifest is operational state for hooks/gates, not knowledge. Wiki and manifest are separate.
78
+ - **Every section in a wiki page must answer a recurring AI question.** If a section doesn't get re-read, it doesn't belong. When in doubt, omit.
@@ -0,0 +1,118 @@
1
+ ---
2
+ schema_id: architecture
3
+ schema_version: 1
4
+ applies_to: aiwiki/architecture/**/*.md
5
+ filename_pattern: "{topic}.md"
6
+ hard_cap_lines: 400
7
+ soft_target_lines: [100, 250]
8
+ required_frontmatter:
9
+ schema_id: { type: string, equals: architecture }
10
+ schema_version: { type: integer }
11
+ scope: { type: string, pattern: "^(project|subsystem:.+)$" }
12
+ date: { type: date }
13
+ status: { type: enum, values: [active, proposed, superseded] }
14
+ required_sections:
15
+ - "## The shape"
16
+ - "## Components"
17
+ - "## Boundaries"
18
+ - "## Tradeoffs"
19
+ section_order: strict
20
+ citation_rule: required-in-components
21
+ ---
22
+
23
+ # Schema: architecture (system-shape doc)
24
+
25
+ ## Purpose
26
+
27
+ An architecture file describes the shape of a subsystem — what its parts are, what's in and out of scope, what tradeoffs were taken. The page answers "what is this system? what does each piece do? where does it stop?" so future contributors don't have to reverse-engineer.
28
+
29
+ **One file per topic.** Multiple focused files (`data-layer.md`, `auth-flow.md`, `event-bus.md`) — NOT one giant `architecture.md`. A 400-line file forced into existence by combining unrelated subsystems is unreadable.
30
+
31
+ ## When to write one
32
+
33
+ - During Phase 5 codification (`harden`) — extract architecture from the locked prototype
34
+ - When a subsystem grows past ~3 files and a "what does this do" overview becomes valuable
35
+ - After a refactor that meaningfully reshapes a subsystem (write the new shape; mark the old one `superseded`)
36
+
37
+ Do NOT write an architecture file for: a single function or class (write a comment in the code), a feature you haven't built yet (write a plan in `.forge/work/`), an entire codebase summary (split by subsystem).
38
+
39
+ ## File location and naming
40
+
41
+ - Path: `aiwiki/architecture/{topic}.md`
42
+ - Topic: kebab-case, names the subsystem or concern (not the document type)
43
+
44
+ Examples: `data-layer.md`, `auth-flow.md`, `event-bus.md`, `frontend-state-model.md`.
45
+
46
+ ## Required frontmatter
47
+
48
+ | Field | Type | Notes |
49
+ |---|---|---|
50
+ | `schema_id` | string | Must equal `architecture` |
51
+ | `schema_version` | integer | — |
52
+ | `scope` | string | `project` / `subsystem:<name>` |
53
+ | `date` | ISO date | When this shape was codified or last refreshed |
54
+ | `status` | enum | `active` (current) / `proposed` (not yet implemented) / `superseded` (old shape; link successor) |
55
+
56
+ ## Required sections
57
+
58
+ | Section | Purpose | Format |
59
+ |---|---|---|
60
+ | `## The shape` | What it looks like at a glance | Mermaid diagram OR ≤5-sentence prose summary |
61
+ | `## Components` | What each part does | Bulleted list with citations to the actual code |
62
+ | `## Boundaries` | What's in scope, what's out, and where the seams are | Bullets — explicit "in" and "out" |
63
+ | `## Tradeoffs` | What was rejected and why | Link to ADRs in `aiwiki/decisions/` |
64
+
65
+ ## Line caps
66
+
67
+ - Hard cap: 400 lines (LINT fails above)
68
+ - Soft target: 100-250 lines
69
+
70
+ If a subsystem genuinely needs >400 lines to describe, split it: `auth-flow.md` + `auth-token-storage.md` + `auth-session-lifecycle.md` rather than one mega-file.
71
+
72
+ ## Citation rules
73
+
74
+ - `## Components` MUST cite the responsible files/symbols with `file:line@<sha7>` or `symbol` form
75
+ - `## The shape` may use Mermaid (no citations needed) or prose (cite if making code claims)
76
+ - `## Tradeoffs` should link to ADRs that capture the decisions
77
+ - LINT auto-fills missing `@<sha7>` on first save
78
+
79
+ ## Skeleton
80
+
81
+ ```markdown
82
+ ---
83
+ schema_id: architecture
84
+ schema_version: 1
85
+ scope: subsystem:auth
86
+ date: 2026-05-10
87
+ status: active
88
+ ---
89
+
90
+ ## The shape
91
+
92
+ ```mermaid
93
+ flowchart LR
94
+ Client -->|credentials| AuthHandler
95
+ AuthHandler -->|verified| TokenIssuer
96
+ TokenIssuer -->|signed token| Client
97
+ Client -->|token| ProtectedRoute
98
+ ProtectedRoute -->|verify| TokenValidator
99
+ ```
100
+
101
+ ## Components
102
+
103
+ - `AuthHandler` ([src/auth/handler.ts#authHandler](src/auth/handler.ts)) — accepts credentials, dispatches to credential validator
104
+ - `TokenIssuer` ([src/auth/token.ts#issueToken](src/auth/token.ts)) — signs JWT with HS256; lifetime 24h
105
+ - `TokenValidator` ([src/auth/middleware.ts#validateToken](src/auth/middleware.ts)) — verifies signature + expiry on every protected request
106
+
107
+ ## Boundaries
108
+
109
+ **In scope**: credential validation, token issuance, token verification middleware.
110
+
111
+ **Out of scope**: user CRUD (lives in [aiwiki/architecture/users.md](aiwiki/architecture/users.md)), password reset flow (lives in [aiwiki/architecture/password-reset.md](aiwiki/architecture/password-reset.md)), MFA (deferred — see [aiwiki/decisions/0023-mfa-deferral.md](aiwiki/decisions/0023-mfa-deferral.md)).
112
+
113
+ ## Tradeoffs
114
+
115
+ - HS256 over RS256: chosen for single-service deployment ([aiwiki/decisions/0008-jwt-algorithm.md](aiwiki/decisions/0008-jwt-algorithm.md))
116
+ - 24h token lifetime over refresh-token pair: chosen for simplicity at MVP scale ([aiwiki/decisions/0009-token-lifetime.md](aiwiki/decisions/0009-token-lifetime.md))
117
+ - Stateless JWT over session-store pattern: chosen for horizontal scalability ([aiwiki/decisions/0007-stateless-auth.md](aiwiki/decisions/0007-stateless-auth.md))
118
+ ```
@@ -0,0 +1,112 @@
1
+ ---
2
+ schema_id: convention
3
+ schema_version: 1
4
+ applies_to: aiwiki/conventions/**/*.md
5
+ filename_pattern: "{slug}.md"
6
+ hard_cap_lines: 100
7
+ soft_target_lines: [30, 60]
8
+ required_frontmatter:
9
+ schema_id: { type: string, equals: convention }
10
+ schema_version: { type: integer }
11
+ scope: { type: string, pattern: "^(project|folder:.+|module:.+)$" }
12
+ date: { type: date }
13
+ status: { type: enum, values: [active, superseded, retired] }
14
+ required_sections:
15
+ - "## The convention"
16
+ - "## Rationale"
17
+ - "## Example"
18
+ - "## Counter-example"
19
+ section_order: strict
20
+ citation_rule: required-in-example-and-counter-example
21
+ ---
22
+
23
+ # Schema: convention (codebase pattern)
24
+
25
+ ## Purpose
26
+
27
+ A convention records a "how this codebase does X" pattern. The page answers "what's the rule? show me what right looks like, and what wrong looks like." Conventions are short by nature; if you need more than a paragraph to state the rule, it's probably an ADR or an architecture doc.
28
+
29
+ ## When to write one
30
+
31
+ - A pattern emerges across multiple files during prototype iteration or production build (e.g. how route handlers are named, where shared types live, how errors are formatted)
32
+ - A code reviewer flags "this doesn't match how we usually do X" — write the convention so the next contributor knows
33
+ - A gotcha cluster signals a missing convention — the convention prevents future occurrences
34
+
35
+ Do NOT write a convention for: language-level idioms (use a linter or formatter instead), things that are obvious from a reasonable read of the codebase, one-off patterns that exist in a single file.
36
+
37
+ ## File location and naming
38
+
39
+ - Path: `aiwiki/conventions/{slug}.md`
40
+ - Slug: kebab-case, ≤6 words, naming the pattern (not the rationale)
41
+
42
+ Examples: `route-handler-naming.md`, `error-shape.md`, `shared-types-location.md`.
43
+
44
+ ## Required frontmatter
45
+
46
+ | Field | Type | Notes |
47
+ |---|---|---|
48
+ | `schema_id` | string | Must equal `convention` |
49
+ | `schema_version` | integer | — |
50
+ | `scope` | string | `project` / `folder:<path>` / `module:<name>` — what this convention applies to |
51
+ | `date` | ISO date | When the convention was codified |
52
+ | `status` | enum | `active` / `superseded` (newer convention takes priority — link it) / `retired` |
53
+
54
+ ## Required sections
55
+
56
+ | Section | Purpose | Format |
57
+ |---|---|---|
58
+ | `## The convention` | One paragraph in imperative mood — the rule itself | No fluff, no rationale |
59
+ | `## Rationale` | Why this convention exists | Brief; link to ADR or gotcha if one motivates it |
60
+ | `## Example` | Code that follows the convention | Cite the example code with `file:line@<sha7>` |
61
+ | `## Counter-example` | Code that violates the convention (or what it would look like) | Cite if real, or write a synthetic counter-example |
62
+
63
+ ## Line caps
64
+
65
+ - Hard cap: 100 lines (LINT fails above)
66
+ - Soft target: 30-60 lines
67
+
68
+ A convention that needs more than 100 lines is probably an architecture doc or a tutorial — write it as one of those instead.
69
+
70
+ ## Citation rules
71
+
72
+ - `## Example` and `## Counter-example` MUST cite real code (or be marked synthetic explicitly)
73
+ - Code references use `file:line@<sha7>` or `symbol` form
74
+ - LINT auto-fills missing `@<sha7>` on first save
75
+
76
+ ## Skeleton
77
+
78
+ ```markdown
79
+ ---
80
+ schema_id: convention
81
+ schema_version: 1
82
+ scope: folder:src/routes
83
+ date: 2026-05-10
84
+ status: active
85
+ ---
86
+
87
+ ## The convention
88
+
89
+ Route handlers live in `src/routes/{resource}.ts` and export a `{resource}Router` constant. Each handler function is exported separately for testing; the router is the wiring layer that mounts them.
90
+
91
+ ## Rationale
92
+
93
+ Separates handler logic (testable in isolation) from routing (testable as integration). See [aiwiki/decisions/0017-handler-router-split.md](aiwiki/decisions/0017-handler-router-split.md).
94
+
95
+ ## Example
96
+
97
+ [src/routes/users.ts:1-24@e5f6789](src/routes/users.ts) — `usersRouter` mounts three handlers (`listUsers`, `getUser`, `createUser`) that are individually exported.
98
+
99
+ ## Counter-example
100
+
101
+ <!-- synthetic example; replace with real counter-example before publishing -->
102
+
103
+ ```ts
104
+ // DO NOT do this — handler logic and routing collapsed into a single anonymous function
105
+ app.get('/users', async (req, res) => {
106
+ const users = await db.users.findAll();
107
+ res.json(users);
108
+ });
109
+ ```
110
+
111
+ The closure can't be unit-tested without spinning up the router; the route registration and the handler are entangled.
112
+ ```
@@ -0,0 +1,144 @@
1
+ ---
2
+ schema_id: decision
3
+ schema_version: 1
4
+ applies_to: aiwiki/decisions/**/*.md
5
+ filename_pattern: "{nnnn}-{slug}.md"
6
+ hard_cap_lines: 400
7
+ soft_target_lines: [100, 200]
8
+ required_frontmatter:
9
+ schema_id: { type: string, equals: decision }
10
+ schema_version: { type: integer }
11
+ status: { type: enum, values: [proposed, accepted, superseded, deprecated] }
12
+ date: { type: date }
13
+ supersedes: { type: string, optional: true }
14
+ superseded_by: { type: string, optional: true }
15
+ required_sections:
16
+ - "## Context"
17
+ - "## Decision"
18
+ - "## Consequences"
19
+ - "## Review"
20
+ optional_sections:
21
+ - "## Alternatives"
22
+ section_order: strict
23
+ citation_rule: required-where-claims-about-code
24
+ ---
25
+
26
+ # Schema: decision (ADR)
27
+
28
+ ## Purpose
29
+
30
+ An ADR records a decision that is hard to reverse — architectural choice, public-surface naming, security/data tradeoff, schema design, cross-slice contract. The page answers "why was this chosen?" so future sessions don't relitigate it.
31
+
32
+ ## When to write one
33
+
34
+ Write an ADR for any decision that's hard to reverse. Specifically:
35
+
36
+ - Architectural choice (system shape, data flow, technology selection)
37
+ - Public-surface naming (APIs, schemas, file paths users will import)
38
+ - Security or data-handling tradeoff
39
+ - Schema design (DB, API, file format) — anything other code will depend on
40
+ - Cross-module contract — what one module exposes that other modules depend on
41
+ - Any other irreversible design choice that survives prototype iteration into production
42
+
43
+ Do NOT write an ADR for: variable naming, inline-vs-extract refactors, choosing between known-good libraries with no real tradeoff. Agent judgment is sufficient there.
44
+
45
+ ## File location and naming
46
+
47
+ - Path: `aiwiki/decisions/{nnnn}-{slug}.md`
48
+ - Numbering: zero-padded sequential, project-wide (e.g. `0042-token-storage.md`)
49
+ - Slug: kebab-case, ≤6 words, descriptive
50
+
51
+ ## Required frontmatter
52
+
53
+ | Field | Type | Notes |
54
+ |---|---|---|
55
+ | `schema_id` | string | Must equal `decision` |
56
+ | `schema_version` | integer | Bumped only when schema itself changes |
57
+ | `status` | enum | `proposed` / `accepted` / `superseded` / `deprecated` |
58
+ | `date` | ISO date | When the decision was accepted (not when drafted) |
59
+ | `supersedes` | string (optional) | Path to ADR this replaces |
60
+ | `superseded_by` | string (optional) | Set when this ADR is itself replaced |
61
+
62
+ ## Required sections
63
+
64
+ | Section | Purpose | Citation requirement |
65
+ |---|---|---|
66
+ | `## Context` | What's the situation that demands a decision | Cite code if context is grounded in specific code |
67
+ | `## Decision` | What we chose (one paragraph, imperative mood) | Cite the prototype file or convention this comes from |
68
+ | `## Alternatives` (optional) | What we rejected and why | Omit if the alternatives are obvious |
69
+ | `## Consequences` | What changes downstream | Cite affected files where known |
70
+ | `## Review` | `review:` block — reviewers, raised objections, how each was resolved, final verdict | — |
71
+
72
+ The `## Review` block is required for `accepted` status. Format:
73
+
74
+ ```yaml
75
+ review:
76
+ reviewers: [critic, codex]
77
+ claims: |
78
+ <what the decision claims to be true/best>
79
+ objections:
80
+ - reviewer: critic
81
+ objection: "..."
82
+ resolution: "..."
83
+ verdict: approved | escalate | reject
84
+ reviewed_at: 2026-05-10T11:42:00Z
85
+ ```
86
+
87
+ ## Line caps
88
+
89
+ - Hard cap: 400 lines (LINT fails above)
90
+ - Soft target: 100-200 lines
91
+
92
+ ## Citation rules
93
+
94
+ - Code references use `file:line@<sha7>` (e.g. `src/auth.ts:42@a3f2bc1`) or `symbol` form (e.g. `src/auth.ts#login`)
95
+ - LINT auto-fills missing `@<sha7>` on first save
96
+ - Stale citations (hash mismatch) fail LINT — resolve by updating the citation, removing the claim, or annotating `// ack-stale: <reason>`
97
+
98
+ ## Skeleton
99
+
100
+ ```markdown
101
+ ---
102
+ schema_id: decision
103
+ schema_version: 1
104
+ status: accepted
105
+ date: 2026-05-10
106
+ ---
107
+
108
+ ## Context
109
+
110
+ The cache layer needs durable storage. Read-heavy workload (~95% reads); single writer pattern; ≤1M entries projected.
111
+
112
+ ## Decision
113
+
114
+ Use SQLite for the cache layer.
115
+
116
+ ## Alternatives
117
+
118
+ - **Postgres**: rejected — overkill for single-writer cache; operational burden of a separate service.
119
+ - **In-memory only**: rejected — process restart loses cache; eviction strategy adds complexity not warranted at projected size.
120
+
121
+ ## Consequences
122
+
123
+ - One additional dependency (`better-sqlite3`)
124
+ - WAL mode enabled for concurrent reader safety
125
+ - Migration path documented at [src/cache/migrations/README.md](src/cache/migrations/README.md)
126
+
127
+ ## Review
128
+
129
+ ```yaml
130
+ review:
131
+ reviewers: [critic, codex]
132
+ claims: |
133
+ SQLite is sufficient for the cache layer at projected scale and read pattern.
134
+ objections:
135
+ - reviewer: critic
136
+ objection: "Concurrent write throughput at scale"
137
+ resolution: "Cache is read-heavy; single-writer pattern documented; WAL mode handles reader concurrency"
138
+ - reviewer: codex
139
+ objection: "No atomic multi-row updates"
140
+ resolution: "Acknowledged; not needed for cache semantics (per-key invalidation only)"
141
+ verdict: approved
142
+ reviewed_at: 2026-05-10T11:42:00Z
143
+ ```
144
+ ```