@aperant/framework 0.6.3 → 0.6.5

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 (244) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +64 -10
  3. package/agents/apt-improver.md +99 -0
  4. package/agents/apt-planner.md +115 -10
  5. package/dist/__test-helpers/run-cmd.d.mts +4 -2
  6. package/dist/__test-helpers/run-cmd.d.mts.map +1 -1
  7. package/dist/__test-helpers/run-cmd.mjs +56 -15
  8. package/dist/__test-helpers/run-cmd.mjs.map +1 -1
  9. package/dist/cli/artifacts/classification.d.mts.map +1 -1
  10. package/dist/cli/artifacts/classification.mjs +10 -0
  11. package/dist/cli/artifacts/classification.mjs.map +1 -1
  12. package/dist/cli/ci-watch/lock.d.mts +53 -0
  13. package/dist/cli/ci-watch/lock.d.mts.map +1 -0
  14. package/dist/cli/ci-watch/lock.mjs +172 -0
  15. package/dist/cli/ci-watch/lock.mjs.map +1 -0
  16. package/dist/cli/ci-watch/state.d.mts +36 -0
  17. package/dist/cli/ci-watch/state.d.mts.map +1 -0
  18. package/dist/cli/ci-watch/state.mjs +103 -0
  19. package/dist/cli/ci-watch/state.mjs.map +1 -0
  20. package/dist/cli/ci-watch/stop-matrix.d.mts +58 -0
  21. package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -0
  22. package/dist/cli/ci-watch/stop-matrix.mjs +164 -0
  23. package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -0
  24. package/dist/cli/cli-wrappers/ci-watch.d.mts +2 -0
  25. package/dist/cli/cli-wrappers/ci-watch.d.mts.map +1 -0
  26. package/dist/cli/cli-wrappers/ci-watch.mjs +9 -0
  27. package/dist/cli/cli-wrappers/ci-watch.mjs.map +1 -0
  28. package/dist/cli/commands/adr.d.mts +5 -0
  29. package/dist/cli/commands/adr.d.mts.map +1 -0
  30. package/dist/cli/commands/adr.mjs +228 -0
  31. package/dist/cli/commands/adr.mjs.map +1 -0
  32. package/dist/cli/commands/ci-watch.d.mts +7 -0
  33. package/dist/cli/commands/ci-watch.d.mts.map +1 -0
  34. package/dist/cli/commands/ci-watch.mjs +465 -0
  35. package/dist/cli/commands/ci-watch.mjs.map +1 -0
  36. package/dist/cli/commands/context.d.mts +7 -0
  37. package/dist/cli/commands/context.d.mts.map +1 -0
  38. package/dist/cli/commands/context.mjs +224 -0
  39. package/dist/cli/commands/context.mjs.map +1 -0
  40. package/dist/cli/commands/event.d.mts.map +1 -1
  41. package/dist/cli/commands/event.mjs +59 -24
  42. package/dist/cli/commands/event.mjs.map +1 -1
  43. package/dist/cli/commands/host-detect.d.mts +1 -1
  44. package/dist/cli/commands/host-detect.d.mts.map +1 -1
  45. package/dist/cli/commands/host-detect.mjs +30 -3
  46. package/dist/cli/commands/host-detect.mjs.map +1 -1
  47. package/dist/cli/commands/init.d.mts.map +1 -1
  48. package/dist/cli/commands/init.mjs +73 -5
  49. package/dist/cli/commands/init.mjs.map +1 -1
  50. package/dist/cli/commands/modes.d.mts +13 -0
  51. package/dist/cli/commands/modes.d.mts.map +1 -0
  52. package/dist/cli/commands/modes.mjs +220 -0
  53. package/dist/cli/commands/modes.mjs.map +1 -0
  54. package/dist/cli/commands/pr-review-audit-fixer.d.mts +41 -2
  55. package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
  56. package/dist/cli/commands/pr-review-audit-fixer.mjs +91 -14
  57. package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
  58. package/dist/cli/commands/route.d.mts.map +1 -1
  59. package/dist/cli/commands/route.mjs +11 -2
  60. package/dist/cli/commands/route.mjs.map +1 -1
  61. package/dist/cli/commands/task.d.mts.map +1 -1
  62. package/dist/cli/commands/task.mjs +136 -6
  63. package/dist/cli/commands/task.mjs.map +1 -1
  64. package/dist/cli/commands/tokens.d.mts.map +1 -1
  65. package/dist/cli/commands/tokens.mjs +150 -6
  66. package/dist/cli/commands/tokens.mjs.map +1 -1
  67. package/dist/cli/commands/triage.d.mts +8 -0
  68. package/dist/cli/commands/triage.d.mts.map +1 -0
  69. package/dist/cli/commands/triage.mjs +259 -0
  70. package/dist/cli/commands/triage.mjs.map +1 -0
  71. package/dist/cli/config/post-merge-sweep.d.mts +18 -0
  72. package/dist/cli/config/post-merge-sweep.d.mts.map +1 -1
  73. package/dist/cli/config/post-merge-sweep.mjs +48 -3
  74. package/dist/cli/config/post-merge-sweep.mjs.map +1 -1
  75. package/dist/cli/consistency/registry.d.mts +11 -0
  76. package/dist/cli/consistency/registry.d.mts.map +1 -1
  77. package/dist/cli/consistency/registry.mjs +13 -0
  78. package/dist/cli/consistency/registry.mjs.map +1 -1
  79. package/dist/cli/coordination/agent-identity.d.mts +9 -0
  80. package/dist/cli/coordination/agent-identity.d.mts.map +1 -1
  81. package/dist/cli/coordination/agent-identity.mjs +11 -0
  82. package/dist/cli/coordination/agent-identity.mjs.map +1 -1
  83. package/dist/cli/coordination/event-log.d.mts +27 -0
  84. package/dist/cli/coordination/event-log.d.mts.map +1 -1
  85. package/dist/cli/coordination/event-log.mjs +72 -2
  86. package/dist/cli/coordination/event-log.mjs.map +1 -1
  87. package/dist/cli/design/frontmatter-schema.d.mts +3 -3
  88. package/dist/cli/design/frontmatter-schema.d.mts.map +1 -1
  89. package/dist/cli/design/frontmatter-schema.mjs +3 -1
  90. package/dist/cli/design/frontmatter-schema.mjs.map +1 -1
  91. package/dist/cli/dispatch.d.mts.map +1 -1
  92. package/dist/cli/dispatch.mjs +14 -1
  93. package/dist/cli/dispatch.mjs.map +1 -1
  94. package/dist/cli/gate/registry.d.mts +11 -0
  95. package/dist/cli/gate/registry.d.mts.map +1 -1
  96. package/dist/cli/gate/registry.mjs +13 -0
  97. package/dist/cli/gate/registry.mjs.map +1 -1
  98. package/dist/cli/help.d.mts.map +1 -1
  99. package/dist/cli/help.mjs +1 -0
  100. package/dist/cli/help.mjs.map +1 -1
  101. package/dist/cli/host/detect.d.mts +1 -0
  102. package/dist/cli/host/detect.d.mts.map +1 -1
  103. package/dist/cli/host/detect.mjs +5 -0
  104. package/dist/cli/host/detect.mjs.map +1 -1
  105. package/dist/cli/route/envelope.d.mts +68 -4
  106. package/dist/cli/route/envelope.d.mts.map +1 -1
  107. package/dist/cli/route/envelope.mjs +140 -103
  108. package/dist/cli/route/envelope.mjs.map +1 -1
  109. package/dist/cli/route/skill-discover.d.mts +11 -0
  110. package/dist/cli/route/skill-discover.d.mts.map +1 -1
  111. package/dist/cli/route/skill-discover.mjs +46 -1
  112. package/dist/cli/route/skill-discover.mjs.map +1 -1
  113. package/dist/cli/skill-author/contract.d.mts +20 -0
  114. package/dist/cli/skill-author/contract.d.mts.map +1 -1
  115. package/dist/cli/skill-author/contract.mjs +27 -0
  116. package/dist/cli/skill-author/contract.mjs.map +1 -1
  117. package/dist/cli/skill-author/skill-template.d.mts.map +1 -1
  118. package/dist/cli/skill-author/skill-template.mjs +4 -3
  119. package/dist/cli/skill-author/skill-template.mjs.map +1 -1
  120. package/dist/cli/task/worktree-cleanup.d.mts +9 -1
  121. package/dist/cli/task/worktree-cleanup.d.mts.map +1 -1
  122. package/dist/cli/task/worktree-cleanup.mjs +190 -9
  123. package/dist/cli/task/worktree-cleanup.mjs.map +1 -1
  124. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  125. package/dist/plugin/agents/apt-planner.md +1 -1
  126. package/dist/plugin/skills/apt/SKILL.md +111 -5
  127. package/dist/plugin/skills/apt-author-skill/SKILL.md +11 -0
  128. package/dist/plugin/skills/apt-bootstrap/SKILL.md +1 -0
  129. package/dist/plugin/skills/apt-classify/SKILL.md +1 -0
  130. package/dist/plugin/skills/apt-close-task/SKILL.md +1 -0
  131. package/dist/plugin/skills/apt-create-docs/SKILL.md +1 -0
  132. package/dist/plugin/skills/apt-debug/SKILL.md +2 -0
  133. package/dist/plugin/skills/apt-design/SKILL.md +2 -0
  134. package/dist/plugin/skills/apt-discuss/SKILL.md +2 -0
  135. package/dist/plugin/skills/apt-docs/SKILL.md +2 -0
  136. package/dist/plugin/skills/apt-execute/SKILL.md +1 -0
  137. package/dist/plugin/skills/apt-mockup/SKILL.md +2 -0
  138. package/dist/plugin/skills/apt-pause/SKILL.md +1 -0
  139. package/dist/plugin/skills/apt-personas/SKILL.md +1 -0
  140. package/dist/plugin/skills/apt-plan/SKILL.md +2 -0
  141. package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -0
  142. package/dist/plugin/skills/apt-quick/SKILL.md +2 -0
  143. package/dist/plugin/skills/apt-resume/SKILL.md +1 -0
  144. package/dist/plugin/skills/apt-review/SKILL.md +1 -0
  145. package/dist/plugin/skills/apt-roadmap/SKILL.md +1 -0
  146. package/dist/plugin/skills/apt-roundtable/SKILL.md +2 -0
  147. package/dist/plugin/skills/apt-run/SKILL.md +1 -0
  148. package/dist/plugin/skills/apt-scan/SKILL.md +1 -0
  149. package/dist/plugin/skills/apt-setup/SKILL.md +1 -0
  150. package/dist/plugin/skills/apt-ship/SKILL.md +6 -5
  151. package/dist/plugin/skills/apt-stress-test/SKILL.md +1 -0
  152. package/dist/plugin/skills/apt-terminal/SKILL.md +1 -0
  153. package/dist/plugin/skills/apt-update/SKILL.md +5 -0
  154. package/dist/plugin/skills/apt-verify/SKILL.md +1 -0
  155. package/dist/plugin/skills/apt-verify-proof/SKILL.md +1 -0
  156. package/dist/types/config.d.ts +85 -0
  157. package/dist/types/config.d.ts.map +1 -1
  158. package/package.json +125 -122
  159. package/prompts/coder.md +2 -0
  160. package/prompts/planner.md +12 -0
  161. package/prompts/spec_writer.md +9 -0
  162. package/skills/apt/SKILL.md +112 -5
  163. package/skills/apt-author-skill/SKILL.md +11 -0
  164. package/skills/apt-bootstrap/SKILL.md +1 -0
  165. package/skills/apt-classify/SKILL.md +1 -0
  166. package/skills/apt-close-task/SKILL.md +33 -1
  167. package/skills/apt-create-docs/SKILL.md +1 -0
  168. package/skills/apt-debug/SKILL.md +41 -6
  169. package/skills/apt-debug/appendices/diagnose-discipline.md +119 -0
  170. package/skills/apt-design/SKILL.md +2 -0
  171. package/skills/apt-diagram/SKILL.md +342 -0
  172. package/skills/apt-diagram/appendices/design-discipline.md +97 -0
  173. package/skills/apt-discuss/SKILL.md +27 -0
  174. package/skills/apt-discuss/appendices/grill-discipline.md +104 -0
  175. package/skills/apt-discuss/appendices/zoom-out-helper.md +79 -0
  176. package/skills/apt-docs/SKILL.md +2 -0
  177. package/skills/apt-execute/SKILL.md +49 -5
  178. package/skills/apt-execute/appendices/tdd-mode.md +107 -0
  179. package/skills/apt-improve/DEEPENING.md +84 -0
  180. package/skills/apt-improve/INTERFACE-DESIGN.md +97 -0
  181. package/skills/apt-improve/LANGUAGE.md +104 -0
  182. package/skills/apt-improve/SKILL.md +141 -0
  183. package/skills/apt-mockup/SKILL.md +2 -0
  184. package/skills/apt-pause/SKILL.md +1 -0
  185. package/skills/apt-personas/SKILL.md +1 -0
  186. package/skills/apt-plan/SKILL.md +149 -4
  187. package/skills/apt-planner.md +42 -1
  188. package/skills/apt-pr-review/SKILL.md +47 -16
  189. package/skills/apt-prototype/LOGIC.md +109 -0
  190. package/skills/apt-prototype/SKILL.md +143 -0
  191. package/skills/apt-prototype/UI.md +90 -0
  192. package/skills/apt-quick/SKILL.md +32 -0
  193. package/skills/apt-resume/SKILL.md +1 -0
  194. package/skills/apt-review/SKILL.md +3 -0
  195. package/skills/apt-roadmap/SKILL.md +1 -0
  196. package/skills/apt-roundtable/SKILL.md +2 -0
  197. package/skills/apt-run/SKILL.md +33 -4
  198. package/skills/apt-scan/SKILL.md +1 -0
  199. package/skills/apt-setup/SKILL.md +129 -2
  200. package/skills/apt-ship/SKILL.md +52 -4
  201. package/skills/apt-stress-test/SKILL.md +1 -0
  202. package/skills/apt-terminal/SKILL.md +1 -0
  203. package/skills/apt-triage/AGENT-BRIEF.md +84 -0
  204. package/skills/apt-triage/OUT-OF-SCOPE.md +75 -0
  205. package/skills/apt-triage/SKILL.md +169 -0
  206. package/skills/apt-update/SKILL.md +3 -0
  207. package/skills/apt-verify/SKILL.md +4 -0
  208. package/skills/apt-verify-proof/SKILL.md +4 -0
  209. package/skills/apt-watch-ci/SKILL.md +163 -0
  210. package/skills/apt-zoom-out/SKILL.md +130 -0
  211. package/src/cli/artifacts/classification.mjs +10 -0
  212. package/src/cli/ci-watch/lock.mjs +178 -0
  213. package/src/cli/ci-watch/state.mjs +103 -0
  214. package/src/cli/ci-watch/stop-matrix.mjs +181 -0
  215. package/src/cli/cli-wrappers/ci-watch.mjs +9 -0
  216. package/src/cli/commands/adr.mjs +243 -0
  217. package/src/cli/commands/ci-watch.mjs +503 -0
  218. package/src/cli/commands/context.mjs +244 -0
  219. package/src/cli/commands/event.mjs +63 -24
  220. package/src/cli/commands/host-detect.mjs +33 -7
  221. package/src/cli/commands/init.mjs +83 -5
  222. package/src/cli/commands/modes.mjs +215 -0
  223. package/src/cli/commands/pr-review-audit-fixer.mjs +95 -16
  224. package/src/cli/commands/route.mjs +11 -2
  225. package/src/cli/commands/task.mjs +152 -18
  226. package/src/cli/commands/tokens.mjs +157 -6
  227. package/src/cli/commands/triage.mjs +277 -0
  228. package/src/cli/config/post-merge-sweep.mjs +49 -3
  229. package/src/cli/consistency/registry.mjs +14 -0
  230. package/src/cli/coordination/agent-identity.mjs +12 -0
  231. package/src/cli/coordination/event-log.mjs +73 -2
  232. package/src/cli/design/frontmatter-schema.mjs +3 -1
  233. package/src/cli/dispatch.mjs +15 -1
  234. package/src/cli/gate/registry.mjs +14 -0
  235. package/src/cli/help.mjs +1 -0
  236. package/src/cli/host/detect.mjs +5 -0
  237. package/src/cli/route/envelope.mjs +140 -106
  238. package/src/cli/route/skill-discover.mjs +46 -1
  239. package/src/cli/skill-author/contract.mjs +29 -0
  240. package/src/cli/skill-author/skill-template.mjs +4 -3
  241. package/src/cli/task/worktree-cleanup.mjs +191 -9
  242. package/templates/adr-format.md +56 -0
  243. package/templates/config.json +4 -0
  244. package/templates/context-format.md +34 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * commands/adr.mjs — `apt-tools adr write` (Pocock adoption ST-6, AC5).
3
+ *
4
+ * Writes an ADR under `docs/adr/NNNN-slug.md` ONLY when all three Nygard
5
+ * gates pass: hard-to-reverse AND surprising-without-context AND
6
+ * real-trade-off. When any gate fails, the writer returns a `filtered`
7
+ * envelope and writes nothing — the decision stays in
8
+ * `.aperant/context/notes/{task-id}.md` instead.
9
+ *
10
+ * CLI:
11
+ * apt-tools adr write <project-dir>
12
+ * --title <slug>
13
+ * --hard-to-reverse <true|false>
14
+ * --surprising-without-context <true|false>
15
+ * --real-trade-off <true|false>
16
+ * --decision <text>
17
+ * --consequences <text>
18
+ * [--context <text>]
19
+ * [--status Proposed|Accepted|Deprecated|Superseded]
20
+ *
21
+ * Output: JSON envelope.
22
+ * On filter: { status: 'filtered', failed_gates: string[], reason: 'three-gate-filter' }
23
+ * On write: { status: 'ok', command: 'adr-write', file: <abs path>, number: N, slug: '...' }
24
+ */
25
+
26
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
27
+ import { join, resolve } from 'node:path'
28
+ import { parseFlags } from '../util/args.mjs'
29
+ import { err, exitWith, ok } from '../util/result.mjs'
30
+
31
+ /**
32
+ * Parse a flag value as a strict boolean. Returns null on unrecognized form
33
+ * so callers can distinguish "missing" from "false".
34
+ *
35
+ * @param {unknown} v
36
+ * @returns {boolean|null}
37
+ */
38
+ function parseBool(v) {
39
+ if (v === true) return true
40
+ if (typeof v !== 'string') return null
41
+ const low = v.toLowerCase()
42
+ if (low === 'true' || low === 'yes' || low === '1') return true
43
+ if (low === 'false' || low === 'no' || low === '0') return false
44
+ return null
45
+ }
46
+
47
+ /**
48
+ * Slugify a title for the filename (NNNN-slug.md).
49
+ *
50
+ * @param {string} title
51
+ */
52
+ function slugify(title) {
53
+ return title
54
+ .toLowerCase()
55
+ .trim()
56
+ .replace(/[^\w\s-]/g, '')
57
+ .replace(/\s+/g, '-')
58
+ .replace(/-+/g, '-')
59
+ .replace(/^-|-$/g, '')
60
+ }
61
+
62
+ /**
63
+ * Find the next ADR sequence number by scanning `docs/adr/`.
64
+ *
65
+ * @param {string} adrDir
66
+ * @returns {number}
67
+ */
68
+ function nextAdrNumber(adrDir) {
69
+ if (!existsSync(adrDir)) return 1
70
+ const entries = readdirSync(adrDir)
71
+ let max = 0
72
+ for (const name of entries) {
73
+ const m = name.match(/^(\d{4})-/)
74
+ if (!m) continue
75
+ const n = Number.parseInt(m[1], 10)
76
+ if (n > max) max = n
77
+ }
78
+ return max + 1
79
+ }
80
+
81
+ /**
82
+ * Render the ADR markdown body.
83
+ *
84
+ * @param {{
85
+ * number: number,
86
+ * title: string,
87
+ * status: string,
88
+ * date: string,
89
+ * context: string,
90
+ * decision: string,
91
+ * consequences: string,
92
+ * hardToReverse: boolean,
93
+ * surprisingWithoutContext: boolean,
94
+ * realTradeOff: boolean,
95
+ * }} opts
96
+ */
97
+ function renderAdr(opts) {
98
+ const paddedNumber = String(opts.number).padStart(4, '0')
99
+ return [
100
+ `# ADR-${paddedNumber}: ${opts.title}`,
101
+ '',
102
+ `**Status:** ${opts.status}`,
103
+ `**Date:** ${opts.date}`,
104
+ '',
105
+ '## Context',
106
+ '',
107
+ opts.context,
108
+ '',
109
+ '## Decision',
110
+ '',
111
+ opts.decision,
112
+ '',
113
+ '## Consequences',
114
+ '',
115
+ opts.consequences,
116
+ '',
117
+ '---',
118
+ '',
119
+ '## Nygard 3-gate filter (audit trail)',
120
+ '',
121
+ 'This ADR was written via `apt-tools adr write` and passed all three of',
122
+ "Nygard's filter gates. Reversible / obvious / trade-off-free decisions",
123
+ 'stay in `.aperant/context/notes/{task-id}.md` and do NOT become ADRs.',
124
+ '',
125
+ `- **Hard to reverse:** ${opts.hardToReverse}`,
126
+ `- **Surprising without context:** ${opts.surprisingWithoutContext}`,
127
+ `- **Real trade-off:** ${opts.realTradeOff}`,
128
+ '',
129
+ ].join('\n')
130
+ }
131
+
132
+ /**
133
+ * CLI entry: `apt-tools adr <subcommand> <project-dir> [flags]`.
134
+ */
135
+ export function cmdAdr(subcommand, projectDir, extraArgs) {
136
+ if (subcommand !== 'write') {
137
+ return err(
138
+ 'Usage: apt-tools adr write <project-dir> --title <slug> --hard-to-reverse <bool> --surprising-without-context <bool> --real-trade-off <bool> --decision <text> --consequences <text> [--context <text>] [--status <status>]',
139
+ 1,
140
+ { command: 'adr' },
141
+ )
142
+ }
143
+ if (!projectDir) {
144
+ return err('project-dir is required', 1, { command: 'adr-write' })
145
+ }
146
+
147
+ const flags = parseFlags(extraArgs ?? [])
148
+ const title = flags.get('title')
149
+ if (typeof title !== 'string' || title.trim() === '') {
150
+ return err('--title <slug> is required', 1, { command: 'adr-write' })
151
+ }
152
+
153
+ // Three Nygard gates — every one must be parseable; missing means "not
154
+ // asked", which is the same as "no" for the filter.
155
+ const hardToReverse = parseBool(flags.get('hard-to-reverse'))
156
+ const surprisingWithoutContext = parseBool(flags.get('surprising-without-context'))
157
+ const realTradeOff = parseBool(flags.get('real-trade-off'))
158
+
159
+ for (const [name, value] of [
160
+ ['--hard-to-reverse', hardToReverse],
161
+ ['--surprising-without-context', surprisingWithoutContext],
162
+ ['--real-trade-off', realTradeOff],
163
+ ]) {
164
+ if (value === null) {
165
+ return err(`${name} <true|false> is required`, 1, { command: 'adr-write' })
166
+ }
167
+ }
168
+
169
+ const decision = flags.get('decision')
170
+ const consequences = flags.get('consequences')
171
+ if (typeof decision !== 'string' || decision.trim() === '') {
172
+ return err('--decision <text> is required', 1, { command: 'adr-write' })
173
+ }
174
+ if (typeof consequences !== 'string' || consequences.trim() === '') {
175
+ return err('--consequences <text> is required', 1, { command: 'adr-write' })
176
+ }
177
+
178
+ // Three-gate filter — every gate must be true to write.
179
+ const failed = []
180
+ if (!hardToReverse) failed.push('hard-to-reverse')
181
+ if (!surprisingWithoutContext) failed.push('surprising-without-context')
182
+ if (!realTradeOff) failed.push('real-trade-off')
183
+
184
+ if (failed.length > 0) {
185
+ return exitWith(
186
+ {
187
+ status: 'filtered',
188
+ command: 'adr-write',
189
+ reason: 'three-gate-filter',
190
+ failed_gates: failed,
191
+ note: 'Decision stays in .aperant/context/notes/{task-id}.md. ADRs are reserved for hard-to-reverse, surprising, real-trade-off decisions.',
192
+ },
193
+ 0,
194
+ )
195
+ }
196
+
197
+ // All three gates passed — write the ADR.
198
+ const absProjectDir = resolve(projectDir)
199
+ if (!existsSync(absProjectDir)) {
200
+ return err(`project directory not found: ${absProjectDir}`, 1, { command: 'adr-write' })
201
+ }
202
+
203
+ const adrDir = join(absProjectDir, 'docs', 'adr')
204
+ if (!existsSync(adrDir)) {
205
+ mkdirSync(adrDir, { recursive: true })
206
+ }
207
+ const number = nextAdrNumber(adrDir)
208
+ const slug = slugify(title)
209
+ const paddedNumber = String(number).padStart(4, '0')
210
+ const filename = `${paddedNumber}-${slug}.md`
211
+ const filePath = join(adrDir, filename)
212
+
213
+ const status = (typeof flags.get('status') === 'string' && flags.get('status')) || 'Proposed'
214
+ const context =
215
+ (typeof flags.get('context') === 'string' && flags.get('context')) ||
216
+ '{Context not supplied — provide via --context on a re-run.}'
217
+ const date = new Date().toISOString().slice(0, 10)
218
+
219
+ const body = renderAdr({
220
+ number,
221
+ title,
222
+ status: String(status),
223
+ date,
224
+ context: String(context),
225
+ decision,
226
+ consequences,
227
+ hardToReverse: true,
228
+ surprisingWithoutContext: true,
229
+ realTradeOff: true,
230
+ })
231
+
232
+ writeFileSync(filePath, body)
233
+
234
+ return ok({
235
+ status: 'ok',
236
+ command: 'adr-write',
237
+ file: filePath,
238
+ number,
239
+ slug,
240
+ title,
241
+ passed_gates: ['hard-to-reverse', 'surprising-without-context', 'real-trade-off'],
242
+ })
243
+ }