wingfoil 0.2.1

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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +334 -0
  3. package/dist/cli/error.d.ts +11 -0
  4. package/dist/cli/error.d.ts.map +1 -0
  5. package/dist/cli/error.js +27 -0
  6. package/dist/cli/error.js.map +1 -0
  7. package/dist/cli/exit.d.ts +19 -0
  8. package/dist/cli/exit.d.ts.map +1 -0
  9. package/dist/cli/exit.js +14 -0
  10. package/dist/cli/exit.js.map +1 -0
  11. package/dist/cli/index.d.ts +28 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +36 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/init-command.d.ts +34 -0
  16. package/dist/cli/init-command.d.ts.map +1 -0
  17. package/dist/cli/init-command.js +100 -0
  18. package/dist/cli/init-command.js.map +1 -0
  19. package/dist/cli/mcp-command.d.ts +24 -0
  20. package/dist/cli/mcp-command.d.ts.map +1 -0
  21. package/dist/cli/mcp-command.js +42 -0
  22. package/dist/cli/mcp-command.js.map +1 -0
  23. package/dist/cli/output.d.ts +13 -0
  24. package/dist/cli/output.d.ts.map +1 -0
  25. package/dist/cli/output.js +28 -0
  26. package/dist/cli/output.js.map +1 -0
  27. package/dist/cli/program.d.ts +12 -0
  28. package/dist/cli/program.d.ts.map +1 -0
  29. package/dist/cli/program.js +279 -0
  30. package/dist/cli/program.js.map +1 -0
  31. package/dist/cli/registrar.d.ts +69 -0
  32. package/dist/cli/registrar.d.ts.map +1 -0
  33. package/dist/cli/registrar.js +82 -0
  34. package/dist/cli/registrar.js.map +1 -0
  35. package/dist/cli.d.ts +3 -0
  36. package/dist/cli.d.ts.map +1 -0
  37. package/dist/cli.js +45 -0
  38. package/dist/cli.js.map +1 -0
  39. package/dist/core/approval-authority.d.ts +83 -0
  40. package/dist/core/approval-authority.d.ts.map +1 -0
  41. package/dist/core/approval-authority.js +112 -0
  42. package/dist/core/approval-authority.js.map +1 -0
  43. package/dist/core/builtin-asset.d.ts +71 -0
  44. package/dist/core/builtin-asset.d.ts.map +1 -0
  45. package/dist/core/builtin-asset.js +137 -0
  46. package/dist/core/builtin-asset.js.map +1 -0
  47. package/dist/core/builtin-integrity.d.ts +32 -0
  48. package/dist/core/builtin-integrity.d.ts.map +1 -0
  49. package/dist/core/builtin-integrity.js +148 -0
  50. package/dist/core/builtin-integrity.js.map +1 -0
  51. package/dist/core/confinement.d.ts +57 -0
  52. package/dist/core/confinement.d.ts.map +1 -0
  53. package/dist/core/confinement.js +108 -0
  54. package/dist/core/confinement.js.map +1 -0
  55. package/dist/core/context.d.ts +187 -0
  56. package/dist/core/context.d.ts.map +1 -0
  57. package/dist/core/context.js +192 -0
  58. package/dist/core/context.js.map +1 -0
  59. package/dist/core/directive-assign.d.ts +131 -0
  60. package/dist/core/directive-assign.d.ts.map +1 -0
  61. package/dist/core/directive-assign.js +352 -0
  62. package/dist/core/directive-assign.js.map +1 -0
  63. package/dist/core/directives-list.d.ts +80 -0
  64. package/dist/core/directives-list.d.ts.map +1 -0
  65. package/dist/core/directives-list.js +148 -0
  66. package/dist/core/directives-list.js.map +1 -0
  67. package/dist/core/exit-code.d.ts +85 -0
  68. package/dist/core/exit-code.d.ts.map +1 -0
  69. package/dist/core/exit-code.js +163 -0
  70. package/dist/core/exit-code.js.map +1 -0
  71. package/dist/core/git-identity.d.ts +32 -0
  72. package/dist/core/git-identity.d.ts.map +1 -0
  73. package/dist/core/git-identity.js +72 -0
  74. package/dist/core/git-identity.js.map +1 -0
  75. package/dist/core/index.d.ts +388 -0
  76. package/dist/core/index.d.ts.map +1 -0
  77. package/dist/core/index.js +1331 -0
  78. package/dist/core/index.js.map +1 -0
  79. package/dist/core/init.d.ts +78 -0
  80. package/dist/core/init.d.ts.map +1 -0
  81. package/dist/core/init.js +167 -0
  82. package/dist/core/init.js.map +1 -0
  83. package/dist/core/loaders.d.ts +174 -0
  84. package/dist/core/loaders.d.ts.map +1 -0
  85. package/dist/core/loaders.js +364 -0
  86. package/dist/core/loaders.js.map +1 -0
  87. package/dist/core/memory-add-type.d.ts +43 -0
  88. package/dist/core/memory-add-type.d.ts.map +1 -0
  89. package/dist/core/memory-add-type.js +151 -0
  90. package/dist/core/memory-add-type.js.map +1 -0
  91. package/dist/core/memory-transition.d.ts +111 -0
  92. package/dist/core/memory-transition.d.ts.map +1 -0
  93. package/dist/core/memory-transition.js +269 -0
  94. package/dist/core/memory-transition.js.map +1 -0
  95. package/dist/core/registry.d.ts +189 -0
  96. package/dist/core/registry.d.ts.map +1 -0
  97. package/dist/core/registry.js +66 -0
  98. package/dist/core/registry.js.map +1 -0
  99. package/dist/core/relevance.d.ts +86 -0
  100. package/dist/core/relevance.d.ts.map +1 -0
  101. package/dist/core/relevance.js +258 -0
  102. package/dist/core/relevance.js.map +1 -0
  103. package/dist/core/require-reason.d.ts +27 -0
  104. package/dist/core/require-reason.d.ts.map +1 -0
  105. package/dist/core/require-reason.js +95 -0
  106. package/dist/core/require-reason.js.map +1 -0
  107. package/dist/core/types.d.ts +45 -0
  108. package/dist/core/types.d.ts.map +1 -0
  109. package/dist/core/types.js +22 -0
  110. package/dist/core/types.js.map +1 -0
  111. package/dist/core/usage-error.d.ts +20 -0
  112. package/dist/core/usage-error.d.ts.map +1 -0
  113. package/dist/core/usage-error.js +29 -0
  114. package/dist/core/usage-error.js.map +1 -0
  115. package/dist/core/write-guard.d.ts +99 -0
  116. package/dist/core/write-guard.d.ts.map +1 -0
  117. package/dist/core/write-guard.js +247 -0
  118. package/dist/core/write-guard.js.map +1 -0
  119. package/dist/directives/create.d.ts +47 -0
  120. package/dist/directives/create.d.ts.map +1 -0
  121. package/dist/directives/create.js +82 -0
  122. package/dist/directives/create.js.map +1 -0
  123. package/dist/directives/index.d.ts +20 -0
  124. package/dist/directives/index.d.ts.map +1 -0
  125. package/dist/directives/index.js +32 -0
  126. package/dist/directives/index.js.map +1 -0
  127. package/dist/directives/roles-edit.d.ts +53 -0
  128. package/dist/directives/roles-edit.d.ts.map +1 -0
  129. package/dist/directives/roles-edit.js +286 -0
  130. package/dist/directives/roles-edit.js.map +1 -0
  131. package/dist/directives/schema.d.ts +65 -0
  132. package/dist/directives/schema.d.ts.map +1 -0
  133. package/dist/directives/schema.js +68 -0
  134. package/dist/directives/schema.js.map +1 -0
  135. package/dist/dna/edit.d.ts +61 -0
  136. package/dist/dna/edit.d.ts.map +1 -0
  137. package/dist/dna/edit.js +429 -0
  138. package/dist/dna/edit.js.map +1 -0
  139. package/dist/dna/index.d.ts +24 -0
  140. package/dist/dna/index.d.ts.map +1 -0
  141. package/dist/dna/index.js +59 -0
  142. package/dist/dna/index.js.map +1 -0
  143. package/dist/dna/mutate.d.ts +74 -0
  144. package/dist/dna/mutate.d.ts.map +1 -0
  145. package/dist/dna/mutate.js +318 -0
  146. package/dist/dna/mutate.js.map +1 -0
  147. package/dist/dna/path.d.ts +175 -0
  148. package/dist/dna/path.d.ts.map +1 -0
  149. package/dist/dna/path.js +289 -0
  150. package/dist/dna/path.js.map +1 -0
  151. package/dist/dna/roles.d.ts +57 -0
  152. package/dist/dna/roles.d.ts.map +1 -0
  153. package/dist/dna/roles.js +50 -0
  154. package/dist/dna/roles.js.map +1 -0
  155. package/dist/dna/schema.d.ts +172 -0
  156. package/dist/dna/schema.d.ts.map +1 -0
  157. package/dist/dna/schema.js +182 -0
  158. package/dist/dna/schema.js.map +1 -0
  159. package/dist/dna/set.d.ts +145 -0
  160. package/dist/dna/set.d.ts.map +1 -0
  161. package/dist/dna/set.js +428 -0
  162. package/dist/dna/set.js.map +1 -0
  163. package/dist/mcp/dna-resource.d.ts +16 -0
  164. package/dist/mcp/dna-resource.d.ts.map +1 -0
  165. package/dist/mcp/dna-resource.js +56 -0
  166. package/dist/mcp/dna-resource.js.map +1 -0
  167. package/dist/mcp/index.d.ts +49 -0
  168. package/dist/mcp/index.d.ts.map +1 -0
  169. package/dist/mcp/index.js +60 -0
  170. package/dist/mcp/index.js.map +1 -0
  171. package/dist/mcp/memory-resource.d.ts +17 -0
  172. package/dist/mcp/memory-resource.d.ts.map +1 -0
  173. package/dist/mcp/memory-resource.js +133 -0
  174. package/dist/mcp/memory-resource.js.map +1 -0
  175. package/dist/mcp/prompt.d.ts +48 -0
  176. package/dist/mcp/prompt.d.ts.map +1 -0
  177. package/dist/mcp/prompt.js +176 -0
  178. package/dist/mcp/prompt.js.map +1 -0
  179. package/dist/mcp/read-only.d.ts +42 -0
  180. package/dist/mcp/read-only.d.ts.map +1 -0
  181. package/dist/mcp/read-only.js +93 -0
  182. package/dist/mcp/read-only.js.map +1 -0
  183. package/dist/mcp/registrar.d.ts +54 -0
  184. package/dist/mcp/registrar.d.ts.map +1 -0
  185. package/dist/mcp/registrar.js +72 -0
  186. package/dist/mcp/registrar.js.map +1 -0
  187. package/dist/mcp/server.d.ts +58 -0
  188. package/dist/mcp/server.d.ts.map +1 -0
  189. package/dist/mcp/server.js +64 -0
  190. package/dist/mcp/server.js.map +1 -0
  191. package/dist/mcp/workflow-resource.d.ts +17 -0
  192. package/dist/mcp/workflow-resource.d.ts.map +1 -0
  193. package/dist/mcp/workflow-resource.js +63 -0
  194. package/dist/mcp/workflow-resource.js.map +1 -0
  195. package/dist/memory/add.d.ts +50 -0
  196. package/dist/memory/add.d.ts.map +1 -0
  197. package/dist/memory/add.js +131 -0
  198. package/dist/memory/add.js.map +1 -0
  199. package/dist/memory/audit.d.ts +177 -0
  200. package/dist/memory/audit.d.ts.map +1 -0
  201. package/dist/memory/audit.js +287 -0
  202. package/dist/memory/audit.js.map +1 -0
  203. package/dist/memory/commit-message.d.ts +146 -0
  204. package/dist/memory/commit-message.d.ts.map +1 -0
  205. package/dist/memory/commit-message.js +203 -0
  206. package/dist/memory/commit-message.js.map +1 -0
  207. package/dist/memory/entry.d.ts +56 -0
  208. package/dist/memory/entry.d.ts.map +1 -0
  209. package/dist/memory/entry.js +28 -0
  210. package/dist/memory/entry.js.map +1 -0
  211. package/dist/memory/frontmatter-edit.d.ts +68 -0
  212. package/dist/memory/frontmatter-edit.d.ts.map +1 -0
  213. package/dist/memory/frontmatter-edit.js +333 -0
  214. package/dist/memory/frontmatter-edit.js.map +1 -0
  215. package/dist/memory/git-log.d.ts +32 -0
  216. package/dist/memory/git-log.d.ts.map +1 -0
  217. package/dist/memory/git-log.js +100 -0
  218. package/dist/memory/git-log.js.map +1 -0
  219. package/dist/memory/history.d.ts +142 -0
  220. package/dist/memory/history.d.ts.map +1 -0
  221. package/dist/memory/history.js +250 -0
  222. package/dist/memory/history.js.map +1 -0
  223. package/dist/memory/index.d.ts +58 -0
  224. package/dist/memory/index.d.ts.map +1 -0
  225. package/dist/memory/index.js +107 -0
  226. package/dist/memory/index.js.map +1 -0
  227. package/dist/memory/query.d.ts +150 -0
  228. package/dist/memory/query.d.ts.map +1 -0
  229. package/dist/memory/query.js +307 -0
  230. package/dist/memory/query.js.map +1 -0
  231. package/dist/memory/reject.d.ts +11 -0
  232. package/dist/memory/reject.d.ts.map +1 -0
  233. package/dist/memory/reject.js +32 -0
  234. package/dist/memory/reject.js.map +1 -0
  235. package/dist/memory/schema.d.ts +104 -0
  236. package/dist/memory/schema.d.ts.map +1 -0
  237. package/dist/memory/schema.js +161 -0
  238. package/dist/memory/schema.js.map +1 -0
  239. package/dist/memory/state-machine.d.ts +166 -0
  240. package/dist/memory/state-machine.d.ts.map +1 -0
  241. package/dist/memory/state-machine.js +431 -0
  242. package/dist/memory/state-machine.js.map +1 -0
  243. package/dist/memory/submit.d.ts +11 -0
  244. package/dist/memory/submit.d.ts.map +1 -0
  245. package/dist/memory/submit.js +44 -0
  246. package/dist/memory/submit.js.map +1 -0
  247. package/dist/storage/builtin-directives.d.ts +54 -0
  248. package/dist/storage/builtin-directives.d.ts.map +1 -0
  249. package/dist/storage/builtin-directives.js +152 -0
  250. package/dist/storage/builtin-directives.js.map +1 -0
  251. package/dist/storage/commit.d.ts +92 -0
  252. package/dist/storage/commit.d.ts.map +1 -0
  253. package/dist/storage/commit.js +201 -0
  254. package/dist/storage/commit.js.map +1 -0
  255. package/dist/storage/confinement.d.ts +93 -0
  256. package/dist/storage/confinement.d.ts.map +1 -0
  257. package/dist/storage/confinement.js +164 -0
  258. package/dist/storage/confinement.js.map +1 -0
  259. package/dist/storage/document.d.ts +27 -0
  260. package/dist/storage/document.d.ts.map +1 -0
  261. package/dist/storage/document.js +52 -0
  262. package/dist/storage/document.js.map +1 -0
  263. package/dist/storage/errors.d.ts +36 -0
  264. package/dist/storage/errors.d.ts.map +1 -0
  265. package/dist/storage/errors.js +46 -0
  266. package/dist/storage/errors.js.map +1 -0
  267. package/dist/storage/frontmatter.d.ts +27 -0
  268. package/dist/storage/frontmatter.d.ts.map +1 -0
  269. package/dist/storage/frontmatter.js +37 -0
  270. package/dist/storage/frontmatter.js.map +1 -0
  271. package/dist/storage/git-root.d.ts +17 -0
  272. package/dist/storage/git-root.d.ts.map +1 -0
  273. package/dist/storage/git-root.js +49 -0
  274. package/dist/storage/git-root.js.map +1 -0
  275. package/dist/storage/index.d.ts +29 -0
  276. package/dist/storage/index.d.ts.map +1 -0
  277. package/dist/storage/index.js +66 -0
  278. package/dist/storage/index.js.map +1 -0
  279. package/dist/storage/init-state.d.ts +9 -0
  280. package/dist/storage/init-state.d.ts.map +1 -0
  281. package/dist/storage/init-state.js +24 -0
  282. package/dist/storage/init-state.js.map +1 -0
  283. package/dist/storage/layout.d.ts +51 -0
  284. package/dist/storage/layout.d.ts.map +1 -0
  285. package/dist/storage/layout.js +88 -0
  286. package/dist/storage/layout.js.map +1 -0
  287. package/dist/storage/memory-path.d.ts +72 -0
  288. package/dist/storage/memory-path.d.ts.map +1 -0
  289. package/dist/storage/memory-path.js +127 -0
  290. package/dist/storage/memory-path.js.map +1 -0
  291. package/dist/storage/snapshot.d.ts +16 -0
  292. package/dist/storage/snapshot.d.ts.map +1 -0
  293. package/dist/storage/snapshot.js +72 -0
  294. package/dist/storage/snapshot.js.map +1 -0
  295. package/dist/storage/templates.d.ts +88 -0
  296. package/dist/storage/templates.d.ts.map +1 -0
  297. package/dist/storage/templates.js +422 -0
  298. package/dist/storage/templates.js.map +1 -0
  299. package/dist/validation/error-mapper.d.ts +16 -0
  300. package/dist/validation/error-mapper.d.ts.map +1 -0
  301. package/dist/validation/error-mapper.js +18 -0
  302. package/dist/validation/error-mapper.js.map +1 -0
  303. package/dist/validation/errors.d.ts +69 -0
  304. package/dist/validation/errors.d.ts.map +1 -0
  305. package/dist/validation/errors.js +69 -0
  306. package/dist/validation/errors.js.map +1 -0
  307. package/dist/validation/id.d.ts +16 -0
  308. package/dist/validation/id.d.ts.map +1 -0
  309. package/dist/validation/id.js +177 -0
  310. package/dist/validation/id.js.map +1 -0
  311. package/dist/validation/index.d.ts +18 -0
  312. package/dist/validation/index.d.ts.map +1 -0
  313. package/dist/validation/index.js +37 -0
  314. package/dist/validation/index.js.map +1 -0
  315. package/dist/validation/secret-scan.d.ts +129 -0
  316. package/dist/validation/secret-scan.d.ts.map +1 -0
  317. package/dist/validation/secret-scan.js +367 -0
  318. package/dist/validation/secret-scan.js.map +1 -0
  319. package/dist/validation/two-pass.d.ts +24 -0
  320. package/dist/validation/two-pass.d.ts.map +1 -0
  321. package/dist/validation/two-pass.js +24 -0
  322. package/dist/validation/two-pass.js.map +1 -0
  323. package/dist/validation/warning.d.ts +31 -0
  324. package/dist/validation/warning.d.ts.map +1 -0
  325. package/dist/validation/warning.js +81 -0
  326. package/dist/validation/warning.js.map +1 -0
  327. package/dist/validation/yaml.d.ts +9 -0
  328. package/dist/validation/yaml.d.ts.map +1 -0
  329. package/dist/validation/yaml.js +30 -0
  330. package/dist/validation/yaml.js.map +1 -0
  331. package/dist/workflow/index.d.ts +10 -0
  332. package/dist/workflow/index.d.ts.map +1 -0
  333. package/dist/workflow/index.js +16 -0
  334. package/dist/workflow/index.js.map +1 -0
  335. package/dist/workflow/schema.d.ts +89 -0
  336. package/dist/workflow/schema.d.ts.map +1 -0
  337. package/dist/workflow/schema.js +73 -0
  338. package/dist/workflow/schema.js.map +1 -0
  339. package/package.json +78 -0
@@ -0,0 +1,177 @@
1
+ /** One commit's attribution, as audited against `pathspecs` (REQ-SEC-02's "0 unknown author"). */
2
+ export interface AttributionEntry {
3
+ readonly sha: string;
4
+ readonly authorName: string;
5
+ readonly authorEmail: string;
6
+ /** ISO-8601 author date (`git log --format=%aI`) — sourced from git, never file content. */
7
+ readonly date: string;
8
+ readonly subject: string;
9
+ /** Whether this commit's author identity is non-empty and not a git-guessed placeholder. */
10
+ readonly valid: boolean;
11
+ }
12
+ /**
13
+ * Whether `name`/`email` look like a real, deliberately-configured git identity rather than an
14
+ * empty or git-guessed placeholder, as recorded on a *historical* commit. The base check — non-empty
15
+ * `name` AND non-empty `email` — is NOT re-derived here: it delegates to
16
+ * {@link isConfiguredIdentity} from `src/core/git-identity.ts`, the single source of truth also used
17
+ * by `requireGitIdentity`'s write-time precondition (REQ-SEC-01). On top of that shared base, this
18
+ * audit (REQ-SEC-02) layers two read-only augmentations that only make sense when inspecting
19
+ * *historical* commits rather than live config — `requireGitIdentity` has no need for either, because
20
+ * it only ever looks at the identity a caller is about to write with:
21
+ *
22
+ * - Rejects the `GIT_GUESSED_DOMAIN_MARKER` (`.(none)`): git appends this to an author email when it
23
+ * fell back to a guessed identity at commit time. `requireGitIdentity` now PREVENTS this going
24
+ * forward (task-014), but older history predating that guard can still carry it, so the read audit
25
+ * must flag it — it is a legacy-history concern, not part of the live write-time rule.
26
+ * - Rejects a non-empty-but-malformed email (`EMAIL_RE`): `requireGitIdentity` never needs this check
27
+ * because it only reads a git config value (itself always syntactically well-formed or absent);
28
+ * historical commit authors, however, can carry hand-edited or otherwise malformed values, so the
29
+ * audit validates the shape too.
30
+ *
31
+ * Pure predicate — no filesystem/git access.
32
+ */
33
+ export declare function isValidAttribution(name: string, email: string): boolean;
34
+ /**
35
+ * Walk every commit touching any of `pathspecs` under `root`, oldest first, annotated with whether
36
+ * its author attribution is valid ({@link isValidAttribution}) — the audit primitive behind
37
+ * REQ-SEC-02's "0 'unknown author'" fit criterion. `pathspecs` is sorted before being passed to git
38
+ * so the invocation itself is deterministic regardless of caller-supplied order (REQ-SYS-07); a
39
+ * commit touching more than one of the given pathspecs still appears exactly once (git's own log
40
+ * de-duplicates by commit, not by path). Returns `[]` (never throws) when none of `pathspecs` has
41
+ * any history, mirroring {@link getMemoryHistory}.
42
+ */
43
+ export declare function auditAttribution(root: string, pathspecs: readonly string[]): AttributionEntry[];
44
+ /** Parsed `Approver:`/`Reason:` lines from an `approve`/`reject` commit body (CLAUDE.md §5.1). */
45
+ export interface ApprovalMetadata {
46
+ readonly approverName: string;
47
+ readonly approverEmail: string;
48
+ readonly approverRole: string;
49
+ /**
50
+ * The `Reason:` block this commit records, or `null` when it records none that can be read
51
+ * (`dl-067-reason-trailer-contract` clause 6). Widened from `string` by
52
+ * `task-072-fix-reason-trailer-contract`: an unreadable reason must not take a successfully parsed
53
+ * approver identity down with it, and `MemoryTransition.reason` was already `string | null`.
54
+ */
55
+ readonly reason: string | null;
56
+ }
57
+ /**
58
+ * The `Reason:` a commit body records, on its own — `null` when the body has none (a plain
59
+ * `add`/`submit` body, which carries no trailer by convention), or carries a bare `Reason:` with no
60
+ * value. Split out of {@link parseApprovalMetadata} by `task-049-memory-history` (P1.10) because the
61
+ * two trailers do not always travel together: an APPROVAL-gate commit (`approve`/`reject`) must carry
62
+ * both `Approver:` and `Reason:` (CLAUDE.md §5.1, P1.7), but a `deprecate` commit records a `Reason:`
63
+ * with **no** `Approver:` line at all — deprecate is explicitly not an approval gate. Reading the
64
+ * reason through `parseApprovalMetadata` would therefore drop every deprecate reason from the audit
65
+ * trail it exists to surface.
66
+ *
67
+ * The reason's extent is NOT decided here: this is a thin alias over `parseReasonBlock`
68
+ * (`./commit-message.ts`), the module that also writes the trailer — one grammar, consumed by both
69
+ * sides, which is `dl-067-reason-trailer-contract` clause 5 and the property that module's doc has
70
+ * claimed for itself since `task-045-memory-submit`.
71
+ *
72
+ * The limitation this function used to document — "a hypothetical multi-paragraph reason would be
73
+ * silently truncated to its first line here" — is gone with the defect
74
+ * (`bug-042-reason-text-has-no-contract-against-commit-trailer`, F1). It was never hypothetical: 79 of
75
+ * `main`'s 171 approve/reject commits carry a reason continuing past line one, and the reader kept as
76
+ * little as 2% of one (dl-067 E1/E2).
77
+ */
78
+ export declare function parseCommitReason(body: string): string | null;
79
+ /**
80
+ * Parse a commit body for the `Approver: Name <email> (role)` and `Reason: ...` trailers
81
+ * (CLAUDE.md §5.1; REQ-SEC-02/REQ-SEC-04). Returns `null` when there is no `Approver:` trailer line —
82
+ * e.g. for a plain `add`/`submit` body, or a `deprecate` one (not an approval gate), neither of which
83
+ * records an approval at all. A caller that wants such a commit's reason wants
84
+ * {@link parseCommitReason} instead.
85
+ *
86
+ * All-or-nothing on the APPROVER, and only on the approver (`dl-067` clause 6,
87
+ * `task-072-fix-reason-trailer-contract`). It used to be all-or-nothing in both directions, so an
88
+ * unreadable `Reason:` discarded the identity this function had already parsed successfully — and
89
+ * since an empty `--reason` was accepted at exit `0` and git's cleanup turned it into a bare
90
+ * `Reason:`, `wingfoil memory history` could report an approval gate crossed by nobody, for no reason
91
+ * (bug-042 F2). Now the record degrades instead of vanishing: the approver stands, `reason` reads
92
+ * `null`. Nothing on `main` reads differently for it — no commit there has a bare `Reason:` or is
93
+ * missing a trailer line (dl-067 E6) — so this is protection for bodies written before the fix.
94
+ */
95
+ export declare function parseApprovalMetadata(body: string): ApprovalMetadata | null;
96
+ /** The CLAUDE.md §5.1 verb a commit's subject declares, when it matches the `wf({type}): {verb} ...` shape. */
97
+ type MemoryOperation = 'add' | 'submit' | 'approve' | 'reject' | 'deprecate';
98
+ /**
99
+ * One reconstructed transition in a Memory element's history — author/timestamp from git itself,
100
+ * `fromState`/`toState` derived from the document's own frontmatter `status:` field at that commit
101
+ * (ADR-007: state lives in frontmatter, not the commit message), and `approval` parsed from the
102
+ * commit body when present. `fromState` is `null` only for the element's first commit (creation —
103
+ * there is no prior state to name).
104
+ */
105
+ export interface MemoryTransition {
106
+ readonly sha: string;
107
+ readonly authorName: string;
108
+ readonly authorEmail: string;
109
+ readonly date: string;
110
+ readonly subject: string;
111
+ readonly operation: MemoryOperation | null;
112
+ readonly fromState: string | null;
113
+ readonly toState: string | null;
114
+ readonly approval: ApprovalMetadata | null;
115
+ /**
116
+ * The `Reason:` this commit's body records, read INDEPENDENTLY of `Approver:`
117
+ * ({@link parseCommitReason}) — `null` when the body records none. Deliberately not the same field
118
+ * as `approval.reason`: `approval` is `null` for a `deprecate` commit (it has no `Approver:` line),
119
+ * yet such a commit does record a reason, and `wingfoil memory history` (P1.10) must surface it.
120
+ * When `approval` is non-null the two always agree, by construction.
121
+ */
122
+ readonly reason: string | null;
123
+ }
124
+ /**
125
+ * Reconstruct every transition `relativePath` went through, oldest first, entirely from git log
126
+ * (`getMemoryHistory`) plus a frontmatter read at each commit (`git show`) — no separate log file
127
+ * (ADR-007). This is the derivation `wingfoil memory history` (P1.10) renders — `src/core`'s
128
+ * `memoryHistory` operation (`task-049-memory-history`) renames and string-formats these fields into
129
+ * its own entry shape, deriving no further state of its own; this function is the reconstruction
130
+ * itself, not that command's output formatting.
131
+ *
132
+ * **Renames are followed on both halves of the derivation**, which is the correction
133
+ * `task-097-memory-history-reads-each-commit-at-its-historical-path` made. `getMemoryHistory` always
134
+ * walked with `git log --follow`, so it *found* the commits preceding a rename; `readStatusAt` then
135
+ * read each of them at the CURRENT path, which those trees do not contain, so all of them reported
136
+ * `null` states (`bug-080-…`). The walk now carries the path each commit actually used
137
+ * (`MemoryHistoryEntry.path`, from `--follow --name-status`) and this function reads at that path —
138
+ * the walk is the source of truth for "where was this file at this commit", and the read no longer
139
+ * guesses.
140
+ *
141
+ * This was never the hypothetical the TSDoc here used to call "a documented edge case, not a live
142
+ * defect": `docs/self/docs/04_memory/planning/v1/*` became `planning/rl-v1/*` in a single commit,
143
+ * moving five `release` elements at once, precisely BECAUSE the `release` type's `path` pattern
144
+ * interpolates a component — the release-line id — that itself changed. Any edit to a `path`
145
+ * pattern, or to an id one interpolates, renames documents.
146
+ *
147
+ * `toState` is still `null` where a commit in the walk genuinely has no document to read — before
148
+ * the element's creation, or after a deletion. That is a legitimate answer, distinct from the defect
149
+ * above, and it no longer arrives with a `fatal:` on the operator's stderr (`bug-071`; see
150
+ * {@link readStatusAt}).
151
+ */
152
+ export declare function reconstructMemoryTransitions(root: string, relativePath: string): MemoryTransition[];
153
+ /** One disagreement between the commit subject's `[old → new]` bracket and the frontmatter actually committed. */
154
+ export interface ConsistencyMismatch {
155
+ readonly sha: string;
156
+ readonly subject: string;
157
+ readonly declared: {
158
+ readonly from: string;
159
+ readonly to: string;
160
+ };
161
+ readonly derived: {
162
+ readonly from: string | null;
163
+ readonly to: string | null;
164
+ };
165
+ }
166
+ /**
167
+ * Cross-check, for every transition that carries a `[old → new]` bracket in its subject (`approve`/
168
+ * `reject`/`deprecate`, per CLAUDE.md §5.1 — a plain `add`/`submit` subject has no bracket and is
169
+ * skipped), that the bracket's declared states agree with the states independently derived from the
170
+ * document's own frontmatter at that commit ({@link reconstructMemoryTransitions}). Returns `[]` when
171
+ * every bracketed transition agrees — REQ-SEC-02/REQ-STATE-02's "no drift between the two views";
172
+ * any entry in the returned array is a genuine inconsistency (e.g. a hand-edited commit message that
173
+ * doesn't match what was actually written to disk).
174
+ */
175
+ export declare function verifyTransitionConsistency(root: string, relativePath: string): ConsistencyMismatch[];
176
+ export {};
177
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/memory/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidAttribution = isValidAttribution;
4
+ exports.auditAttribution = auditAttribution;
5
+ exports.parseCommitReason = parseCommitReason;
6
+ exports.parseApprovalMetadata = parseApprovalMetadata;
7
+ exports.reconstructMemoryTransitions = reconstructMemoryTransitions;
8
+ exports.verifyTransitionConsistency = verifyTransitionConsistency;
9
+ /**
10
+ * Audit-trail verification & reconstruction (task-015-complete-audit-trail, REQ-SEC-02) — the
11
+ * READ/verify half of "every state change is a git commit with author + timestamp" (ADR-001,
12
+ * ADR-007): git history is the *only* audit trail (no secondary `.wingfoil/state/` log), so this
13
+ * module audits and reconstructs that trail directly from `git log`/`git show`, never from a value
14
+ * written into file content. It builds on task-011's {@link getMemoryHistory} (the raw commit walk)
15
+ * rather than re-implementing it.
16
+ *
17
+ * This module deliberately does **not** enforce attribution at write time (checking that a commit
18
+ * *can* be made) — that precondition belongs to task-014-git-identity-required (REQ-SEC-01,
19
+ * `requireGitIdentity` in `src/core/git-identity.ts`). This module only ever reads commits that
20
+ * already exist and reports on them; nothing here blocks or gates a write. The base "non-empty
21
+ * name and non-empty email" rule the two share lives in exactly one place — the
22
+ * `isConfiguredIdentity` predicate exported by `src/core/git-identity.ts` — which
23
+ * {@link isValidAttribution} below consumes rather than re-deriving; see that function's
24
+ * doc-comment for the read-only augmentations it layers on top for historical commits.
25
+ *
26
+ * REQ-SEC-02's fit criterion, mapped to the functions below:
27
+ *
28
+ * - "`git log` verification ... shows author + timestamp for every change with 0 'unknown
29
+ * author'" -> {@link auditAttribution} / {@link isValidAttribution}.
30
+ * - Commit timestamp (never file content) supplies the ISO-8601 date -> already true of
31
+ * `getMemoryHistory`'s `date` field (`%aI`, sourced from git itself); this module never re-derives
32
+ * a timestamp from anywhere else.
33
+ * - "`approve`/`reject` commits carry an explicit `Approver:`/`Reason:` line" ->
34
+ * {@link parseApprovalMetadata}.
35
+ * - "`memory history <id>` lists each transition ... author, timestamp, and reason" ->
36
+ * {@link reconstructMemoryTransitions}.
37
+ * - "Recomputing an element's state at any historical commit ... agrees with the transition history
38
+ * derived from git log ... no drift" -> {@link verifyTransitionConsistency}.
39
+ */
40
+ const child_process_1 = require("child_process");
41
+ const core_1 = require("../core");
42
+ const validation_1 = require("../validation");
43
+ const commit_message_1 = require("./commit-message");
44
+ const history_1 = require("./history");
45
+ const git_log_1 = require("./git-log");
46
+ const storage_1 = require("../storage");
47
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@()]+$/;
48
+ // The literal marker git itself appends to an author email when it falls back to a guessed identity
49
+ // (user@hostname) and cannot determine a real domain — e.g. "root@buildhost.(none)". A commit
50
+ // carrying this is not a deliberately-configured identity, so it counts as "unknown author".
51
+ const GIT_GUESSED_DOMAIN_MARKER = '.(none)';
52
+ /**
53
+ * Whether `name`/`email` look like a real, deliberately-configured git identity rather than an
54
+ * empty or git-guessed placeholder, as recorded on a *historical* commit. The base check — non-empty
55
+ * `name` AND non-empty `email` — is NOT re-derived here: it delegates to
56
+ * {@link isConfiguredIdentity} from `src/core/git-identity.ts`, the single source of truth also used
57
+ * by `requireGitIdentity`'s write-time precondition (REQ-SEC-01). On top of that shared base, this
58
+ * audit (REQ-SEC-02) layers two read-only augmentations that only make sense when inspecting
59
+ * *historical* commits rather than live config — `requireGitIdentity` has no need for either, because
60
+ * it only ever looks at the identity a caller is about to write with:
61
+ *
62
+ * - Rejects the `GIT_GUESSED_DOMAIN_MARKER` (`.(none)`): git appends this to an author email when it
63
+ * fell back to a guessed identity at commit time. `requireGitIdentity` now PREVENTS this going
64
+ * forward (task-014), but older history predating that guard can still carry it, so the read audit
65
+ * must flag it — it is a legacy-history concern, not part of the live write-time rule.
66
+ * - Rejects a non-empty-but-malformed email (`EMAIL_RE`): `requireGitIdentity` never needs this check
67
+ * because it only reads a git config value (itself always syntactically well-formed or absent);
68
+ * historical commit authors, however, can carry hand-edited or otherwise malformed values, so the
69
+ * audit validates the shape too.
70
+ *
71
+ * Pure predicate — no filesystem/git access.
72
+ */
73
+ function isValidAttribution(name, email) {
74
+ const trimmedName = name.trim();
75
+ const trimmedEmail = email.trim();
76
+ if (!(0, core_1.isConfiguredIdentity)(trimmedName, trimmedEmail))
77
+ return false;
78
+ if (trimmedEmail.includes(GIT_GUESSED_DOMAIN_MARKER))
79
+ return false;
80
+ return EMAIL_RE.test(trimmedEmail);
81
+ }
82
+ const AUDIT_LOG_FIELDS = ['%H', '%an', '%ae', '%aI', '%s'];
83
+ /**
84
+ * Walk every commit touching any of `pathspecs` under `root`, oldest first, annotated with whether
85
+ * its author attribution is valid ({@link isValidAttribution}) — the audit primitive behind
86
+ * REQ-SEC-02's "0 'unknown author'" fit criterion. `pathspecs` is sorted before being passed to git
87
+ * so the invocation itself is deterministic regardless of caller-supplied order (REQ-SYS-07); a
88
+ * commit touching more than one of the given pathspecs still appears exactly once (git's own log
89
+ * de-duplicates by commit, not by path). Returns `[]` (never throws) when none of `pathspecs` has
90
+ * any history, mirroring {@link getMemoryHistory}.
91
+ */
92
+ function auditAttribution(root, pathspecs) {
93
+ const sortedPathspecs = [...pathspecs].sort();
94
+ const records = (0, git_log_1.walkGitLogFields)(root, AUDIT_LOG_FIELDS, sortedPathspecs);
95
+ return records.map(([sha = '', authorName = '', authorEmail = '', date = '', subject = '']) => ({
96
+ sha,
97
+ authorName,
98
+ authorEmail,
99
+ date,
100
+ subject,
101
+ valid: isValidAttribution(authorName, authorEmail),
102
+ }));
103
+ }
104
+ /**
105
+ * The identity on an `Approver:` trailer line. Applied to that ONE line — located by
106
+ * {@link parseApproverTrailerLine}, which anchors it to the body's first — rather than searched for
107
+ * anywhere in the body with `/m`, so a second `Approver:` line sitting inside some reason's text can
108
+ * never be read as an approval record (`dl-067` clause 5; bug-042 F3).
109
+ */
110
+ const APPROVER_LINE_RE = /^Approver:\s*(.+?)\s*<([^>]+)>\s*\(([^)]+)\)\s*$/;
111
+ /**
112
+ * The `Reason:` a commit body records, on its own — `null` when the body has none (a plain
113
+ * `add`/`submit` body, which carries no trailer by convention), or carries a bare `Reason:` with no
114
+ * value. Split out of {@link parseApprovalMetadata} by `task-049-memory-history` (P1.10) because the
115
+ * two trailers do not always travel together: an APPROVAL-gate commit (`approve`/`reject`) must carry
116
+ * both `Approver:` and `Reason:` (CLAUDE.md §5.1, P1.7), but a `deprecate` commit records a `Reason:`
117
+ * with **no** `Approver:` line at all — deprecate is explicitly not an approval gate. Reading the
118
+ * reason through `parseApprovalMetadata` would therefore drop every deprecate reason from the audit
119
+ * trail it exists to surface.
120
+ *
121
+ * The reason's extent is NOT decided here: this is a thin alias over `parseReasonBlock`
122
+ * (`./commit-message.ts`), the module that also writes the trailer — one grammar, consumed by both
123
+ * sides, which is `dl-067-reason-trailer-contract` clause 5 and the property that module's doc has
124
+ * claimed for itself since `task-045-memory-submit`.
125
+ *
126
+ * The limitation this function used to document — "a hypothetical multi-paragraph reason would be
127
+ * silently truncated to its first line here" — is gone with the defect
128
+ * (`bug-042-reason-text-has-no-contract-against-commit-trailer`, F1). It was never hypothetical: 79 of
129
+ * `main`'s 171 approve/reject commits carry a reason continuing past line one, and the reader kept as
130
+ * little as 2% of one (dl-067 E1/E2).
131
+ */
132
+ function parseCommitReason(body) {
133
+ return (0, commit_message_1.parseReasonBlock)(body);
134
+ }
135
+ /**
136
+ * Parse a commit body for the `Approver: Name <email> (role)` and `Reason: ...` trailers
137
+ * (CLAUDE.md §5.1; REQ-SEC-02/REQ-SEC-04). Returns `null` when there is no `Approver:` trailer line —
138
+ * e.g. for a plain `add`/`submit` body, or a `deprecate` one (not an approval gate), neither of which
139
+ * records an approval at all. A caller that wants such a commit's reason wants
140
+ * {@link parseCommitReason} instead.
141
+ *
142
+ * All-or-nothing on the APPROVER, and only on the approver (`dl-067` clause 6,
143
+ * `task-072-fix-reason-trailer-contract`). It used to be all-or-nothing in both directions, so an
144
+ * unreadable `Reason:` discarded the identity this function had already parsed successfully — and
145
+ * since an empty `--reason` was accepted at exit `0` and git's cleanup turned it into a bare
146
+ * `Reason:`, `wingfoil memory history` could report an approval gate crossed by nobody, for no reason
147
+ * (bug-042 F2). Now the record degrades instead of vanishing: the approver stands, `reason` reads
148
+ * `null`. Nothing on `main` reads differently for it — no commit there has a bare `Reason:` or is
149
+ * missing a trailer line (dl-067 E6) — so this is protection for bodies written before the fix.
150
+ */
151
+ function parseApprovalMetadata(body) {
152
+ const approverLine = (0, commit_message_1.parseApproverTrailerLine)(body);
153
+ const approverMatch = approverLine === null ? null : APPROVER_LINE_RE.exec(approverLine);
154
+ if (!approverMatch)
155
+ return null;
156
+ const [, approverName = '', approverEmail = '', approverRole = ''] = approverMatch;
157
+ return { approverName, approverEmail, approverRole, reason: parseCommitReason(body) };
158
+ }
159
+ const OPERATION_RE = /^wf\([^)]+\):\s*(add|submit|approve|reject|deprecate)\b/;
160
+ /**
161
+ * Read `historicalPath`'s frontmatter `status:` field as it existed at `sha` (`git show sha:path`),
162
+ * without validating it against any type's Zod schema — this is a historical-snapshot read, not a
163
+ * live document validation. Returns `null` if the path didn't exist at `sha`, has no frontmatter
164
+ * block, or the frontmatter has no string `status` field.
165
+ *
166
+ * `historicalPath` is the path the element occupied **at that commit** — `MemoryHistoryEntry.path`,
167
+ * produced by the same `--follow` walk that selected the commit — never the caller's current path.
168
+ * Passing the current one was `bug-080-read-status-at-reads-the-current-path-at-pre-rename-commits`:
169
+ * every commit older than a rename asked git for a path that tree does not contain, the read failed,
170
+ * and the transition reported `null` states. `dl-080` — a read that gates an operation resolves at
171
+ * `HEAD` — does not reach here: this read gates nothing, and its subject is by construction *other*
172
+ * commits.
173
+ *
174
+ * The explicit `stdio` is `bug-071-read-status-at-leaks-git-stderr`, and it is not made redundant by
175
+ * the paragraph above. `execFileSync` hands the child this process's own stderr by default, so on
176
+ * the failure that remains legitimate — a commit where the document genuinely does not exist, before
177
+ * its creation or after a deletion — git's `fatal: path ... does not exist in '<sha>'` went straight
178
+ * to the operator's terminal while the command reported success. Capturing it into a pipe this
179
+ * process owns (and discarding it, the failure being handled right here) is what keeps a genuine
180
+ * `fatal:` distinguishable from a routine one — that line is how `bug-050`'s forged history
181
+ * announced itself.
182
+ */
183
+ function readStatusAt(root, sha, historicalPath) {
184
+ let raw;
185
+ try {
186
+ raw = (0, child_process_1.execFileSync)('git', ['-C', root, 'show', `${sha}:${historicalPath}`], {
187
+ encoding: 'utf-8',
188
+ stdio: ['ignore', 'pipe', 'pipe'],
189
+ });
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ const { frontmatter } = (0, storage_1.splitFrontmatter)(raw);
195
+ if (!frontmatter)
196
+ return null;
197
+ const parsed = (0, validation_1.parseYaml)(frontmatter, `${historicalPath}@${sha}`);
198
+ if (parsed === null || typeof parsed !== 'object')
199
+ return null;
200
+ const status = parsed.status;
201
+ return typeof status === 'string' ? status : null;
202
+ }
203
+ /**
204
+ * Reconstruct every transition `relativePath` went through, oldest first, entirely from git log
205
+ * (`getMemoryHistory`) plus a frontmatter read at each commit (`git show`) — no separate log file
206
+ * (ADR-007). This is the derivation `wingfoil memory history` (P1.10) renders — `src/core`'s
207
+ * `memoryHistory` operation (`task-049-memory-history`) renames and string-formats these fields into
208
+ * its own entry shape, deriving no further state of its own; this function is the reconstruction
209
+ * itself, not that command's output formatting.
210
+ *
211
+ * **Renames are followed on both halves of the derivation**, which is the correction
212
+ * `task-097-memory-history-reads-each-commit-at-its-historical-path` made. `getMemoryHistory` always
213
+ * walked with `git log --follow`, so it *found* the commits preceding a rename; `readStatusAt` then
214
+ * read each of them at the CURRENT path, which those trees do not contain, so all of them reported
215
+ * `null` states (`bug-080-…`). The walk now carries the path each commit actually used
216
+ * (`MemoryHistoryEntry.path`, from `--follow --name-status`) and this function reads at that path —
217
+ * the walk is the source of truth for "where was this file at this commit", and the read no longer
218
+ * guesses.
219
+ *
220
+ * This was never the hypothetical the TSDoc here used to call "a documented edge case, not a live
221
+ * defect": `docs/self/docs/04_memory/planning/v1/*` became `planning/rl-v1/*` in a single commit,
222
+ * moving five `release` elements at once, precisely BECAUSE the `release` type's `path` pattern
223
+ * interpolates a component — the release-line id — that itself changed. Any edit to a `path`
224
+ * pattern, or to an id one interpolates, renames documents.
225
+ *
226
+ * `toState` is still `null` where a commit in the walk genuinely has no document to read — before
227
+ * the element's creation, or after a deletion. That is a legitimate answer, distinct from the defect
228
+ * above, and it no longer arrives with a `fatal:` on the operator's stderr (`bug-071`; see
229
+ * {@link readStatusAt}).
230
+ */
231
+ function reconstructMemoryTransitions(root, relativePath) {
232
+ const history = (0, history_1.getMemoryHistory)(root, relativePath); // oldest first already
233
+ const transitions = [];
234
+ let previousState = null;
235
+ for (const entry of history) {
236
+ const toState = readStatusAt(root, entry.sha, entry.path);
237
+ const operationMatch = OPERATION_RE.exec(entry.subject);
238
+ transitions.push({
239
+ sha: entry.sha,
240
+ authorName: entry.authorName,
241
+ authorEmail: entry.authorEmail,
242
+ date: entry.date,
243
+ subject: entry.subject,
244
+ operation: operationMatch?.[1] ?? null,
245
+ fromState: previousState,
246
+ toState,
247
+ approval: parseApprovalMetadata(entry.body),
248
+ reason: parseCommitReason(entry.body),
249
+ });
250
+ previousState = toState;
251
+ }
252
+ return transitions;
253
+ }
254
+ // Matches the `[old-state → new-state]` bracket CLAUDE.md §5.1 mandates on approve/reject/deprecate
255
+ // subjects (the arrow is the U+2192 character used throughout CLAUDE.md/the memory templates).
256
+ const BRACKET_RE = /\[([^[\]→]+?)\s*→\s*([^[\]]+?)\]\s*$/;
257
+ /**
258
+ * Cross-check, for every transition that carries a `[old → new]` bracket in its subject (`approve`/
259
+ * `reject`/`deprecate`, per CLAUDE.md §5.1 — a plain `add`/`submit` subject has no bracket and is
260
+ * skipped), that the bracket's declared states agree with the states independently derived from the
261
+ * document's own frontmatter at that commit ({@link reconstructMemoryTransitions}). Returns `[]` when
262
+ * every bracketed transition agrees — REQ-SEC-02/REQ-STATE-02's "no drift between the two views";
263
+ * any entry in the returned array is a genuine inconsistency (e.g. a hand-edited commit message that
264
+ * doesn't match what was actually written to disk).
265
+ */
266
+ function verifyTransitionConsistency(root, relativePath) {
267
+ const transitions = reconstructMemoryTransitions(root, relativePath);
268
+ const mismatches = [];
269
+ for (const transition of transitions) {
270
+ const match = BRACKET_RE.exec(transition.subject);
271
+ if (!match)
272
+ continue;
273
+ const [, declaredFromRaw = '', declaredToRaw = ''] = match;
274
+ const declaredFrom = declaredFromRaw.trim();
275
+ const declaredTo = declaredToRaw.trim();
276
+ if (declaredFrom !== transition.fromState || declaredTo !== transition.toState) {
277
+ mismatches.push({
278
+ sha: transition.sha,
279
+ subject: transition.subject,
280
+ declared: { from: declaredFrom, to: declaredTo },
281
+ derived: { from: transition.fromState, to: transition.toState },
282
+ });
283
+ }
284
+ }
285
+ return mismatches;
286
+ }
287
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/memory/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The one commit-message formatter for Memory state-transition verbs (task-045-memory-submit).
3
+ *
4
+ * Every verb — `submit` today, `approve`/`reject`/`deprecate` next — produces exactly one commit whose
5
+ * subject is `wf({type}): {verb} {id1}, {id2}` (`spec-004-mcp-surface-contract` §4.3), optionally
6
+ * followed by a ` [from → to]` bracket and an `Approver:` / `Reason:` body for the verbs whose
7
+ * evidence rules require them (P1.7/P1.8, REQ-SEC-04). Those are exactly the shapes
8
+ * `./audit.ts` reads back for `wingfoil memory history` (P1.10): `OPERATION_RE` on the subject,
9
+ * `BRACKET_RE` on the bracket, `parseApprovalMetadata`/`parseCommitReason` on the body. Building the
10
+ * message in one place is what keeps writer and reader from drifting apart.
11
+ *
12
+ * Since `task-072-fix-reason-trailer-contract` this module also owns the **reason grammar** —
13
+ * {@link normalizeReason}, {@link reasonDefect}, {@link parseReasonBlock} and
14
+ * {@link parseApproverTrailerLine} — ratified by `dl-067-reason-trailer-contract` and fixing
15
+ * `bug-042-reason-text-has-no-contract-against-commit-trailer`. `--reason <text>` is arbitrary free
16
+ * text and the trailer is line-oriented; before dl-067 no rule said what happened when the two met, so
17
+ * a blank reason was recorded as a bare `Reason:` that destroyed the whole approval record, a
18
+ * multi-line one was truncated to its first line on read, and one shaped like a trailer could forge a
19
+ * second `Approver:` line. Putting the grammar HERE, next to the formatter, and having `./audit.ts`
20
+ * consume it is what makes this module doc's "one place" claim true rather than aspirational: the
21
+ * writer cannot emit a reason the reader cannot recover.
22
+ *
23
+ * Pure and deterministic (REQ-SYS-07): the output is a function of the input alone; ids keep the order
24
+ * the caller gives.
25
+ */
26
+ import type { TransitionOp } from './state-machine';
27
+ /** Why a `--reason` value cannot be recorded in the trailer, per `dl-067` clause 4 and its corollary. */
28
+ export type ReasonDefect =
29
+ /** Empty, or nothing but whitespace: git would store a bare `Reason:` and both parsers would fail. */
30
+ 'blank'
31
+ /** Carries a line starting `Approver:` or `Reason:` — a forged trailer line (bug-042 F3). */
32
+ | 'reserved-trailer-line'
33
+ /**
34
+ * Ends in a paragraph made entirely of trailer-shaped lines, which {@link parseReasonBlock} would
35
+ * read as git's own trailer block and drop. The corollary of the block's termination rule: without
36
+ * it, the round-trip equality dl-067 clause 3 promises would be false for this one input shape. 0 of
37
+ * `main`'s 172 reason blocks end this way, so the refusal costs the existing corpus nothing.
38
+ */
39
+ | 'trailing-trailer-paragraph';
40
+ /**
41
+ * The refusal message for `defect` — the single source of the wording the CLI prints and
42
+ * `spec-008-cli-grammar` §2 pins. Deliberately distinct from `missing required argument: --reason`
43
+ * (`src/core/require-reason.ts`), which answers the *omitted* case that spec-008 §2 and the P1.7/P1.8
44
+ * BDD features already quote verbatim (dl-067 S1).
45
+ */
46
+ export declare function reasonDefectMessage(defect: ReasonDefect): string;
47
+ /**
48
+ * The declared normal form of a reason (`dl-067` clause 3) — two rules, from two different sources,
49
+ * and it is worth keeping them apart:
50
+ *
51
+ * 1. **git's**, which applies whether or not this function exists. `commitPaths`
52
+ * (`src/storage/commit.ts`) commits with `-m`, so git's `cleanup=whitespace` strips per-line
53
+ * trailing whitespace, collapses runs of blank lines to one, and drops leading and trailing blank
54
+ * lines. This is why `spec-008-cli-grammar` §2's original "Recorded verbatim" was already false
55
+ * for any multi-line text, independently of bug-042.
56
+ * 2. **This module's**: the first line's leading whitespace is trimmed. git does NOT do this. It is
57
+ * needed because that line sits after `Reason: ` on the same physical line and
58
+ * {@link parseReasonBlock} consumes the key together with the whitespace that follows it — so
59
+ * without the trim, a reason beginning with spaces would not round-trip equally.
60
+ *
61
+ * Applying both HERE, on the way in, is what turns "approximately what you typed" into an assertable
62
+ * equality: what {@link parseReasonBlock} reads back out of the commit is exactly this function's
63
+ * output. Interior indentation is preserved by both rules — it carries meaning in real approval prose.
64
+ *
65
+ * Idempotent, and a pure function of its input (REQ-SYS-07).
66
+ */
67
+ export declare function normalizeReason(reason: string): string;
68
+ /**
69
+ * Why `reason` cannot be recorded in the `Reason:` trailer, or `null` when it can (`dl-067` clause 4).
70
+ *
71
+ * Judged on the NORMALIZED text ({@link normalizeReason}), because that is what would actually be
72
+ * stored — otherwise a defect could hide behind trailing whitespace git strips on the way in. This is
73
+ * the ONE place the rule is decided: `src/core/require-reason.ts` turns a non-`null` result into the
74
+ * `UsageError` (exit 2) the CLI boundary reports, and {@link formatMemoryCommitMessage} refuses the
75
+ * same values so no caller can route around that boundary.
76
+ */
77
+ export declare function reasonDefect(reason: string): ReasonDefect | null;
78
+ /**
79
+ * The `Reason:` a commit body records, read as a BLOCK (`dl-067` clause 2): the remainder of the
80
+ * `Reason:` line plus every following body line, up to (exclusive) git's trailing trailer paragraph or
81
+ * the end of the body. `null` when the body records no reason, or records a bare `Reason:` with no
82
+ * value — the shape git's cleanup leaves behind when an empty reason is written (bug-042 F2), which
83
+ * `parseApprovalMetadata` degrades around rather than treating as an absent approval.
84
+ *
85
+ * This replaces the first-line capture `/^Reason:\s*(.+)$/m` that preceded it, and that is a real
86
+ * change to how existing history reads: 79 of `main`'s 171 approve/reject commits (46%) carry a reason
87
+ * continuing past line one, and every one of them was silently halved. It is a recovery, not a
88
+ * reinterpretation — the text was always in the commit; only the reader dropped it.
89
+ *
90
+ * A generic `Key: value` line INSIDE the block is ordinary prose and does not end it; only a trailing
91
+ * paragraph made entirely of trailer lines does (dl-067 E5).
92
+ */
93
+ export declare function parseReasonBlock(body: string): string | null;
94
+ /**
95
+ * The commit body's `Approver:` trailer line, or `null` when it has none — anchored to the body's
96
+ * FIRST non-empty line (`dl-067` clause 5). {@link formatMemoryCommitMessage} has always emitted it
97
+ * there, and it is the first body line in 171 of `main`'s 171 approve/reject commits (dl-067 E4), so
98
+ * anchoring costs no existing commit its approver while closing bug-042 F3 on the read side: an
99
+ * `Approver:` line appearing anywhere else is text some reason happens to contain, never an approval
100
+ * record. `./audit.ts` destructures the returned line into an identity — that shape is its concern;
101
+ * the line's POSITION is this module's.
102
+ */
103
+ export declare function parseApproverTrailerLine(body: string): string | null;
104
+ /** The identity recorded on an approval-gate commit's `Approver:` line. */
105
+ export interface CommitApprover {
106
+ readonly name: string;
107
+ readonly email: string;
108
+ readonly role: string;
109
+ }
110
+ /** Everything one Memory transition commit message is built from. */
111
+ export interface MemoryCommitMessageInput {
112
+ /** The Memory element type all `ids` belong to — one commit is scoped to one type. */
113
+ readonly type: string;
114
+ /** The verb, which is also the subject's operation word. `add` is included for completeness. */
115
+ readonly op: TransitionOp | 'add';
116
+ /** The element ids moved by this commit, in the order they are listed in the subject. */
117
+ readonly ids: readonly string[];
118
+ /** When given, appended to the subject as ` [from → to]`. Omitted for `add`/`submit` (spec-004 §4.3). */
119
+ readonly transition?: {
120
+ readonly from: string;
121
+ readonly to: string;
122
+ };
123
+ /** When given, an `Approver: Name <email> (role)` body line. */
124
+ readonly approver?: CommitApprover;
125
+ /**
126
+ * When given, a `Reason:` block ({@link parseReasonBlock}) — written in the declared normal form
127
+ * ({@link normalizeReason}), and refused outright when {@link reasonDefect} says it could not be
128
+ * read back.
129
+ */
130
+ readonly reason?: string;
131
+ }
132
+ /**
133
+ * Format a Memory transition commit message: the subject, then — only when an approver or reason is
134
+ * given — a blank line and the `Approver:` / `Reason:` lines in that order.
135
+ *
136
+ * The reason is emitted in its declared normal form and, when it carries a defect
137
+ * ({@link reasonDefect}), refused rather than written. The CLI boundary
138
+ * (`src/core/require-reason.ts`) already refuses the same values with the same messages at exit 2, so
139
+ * reaching this throw means a caller bypassed that boundary — it exists so no *future* caller can
140
+ * (dl-067 clause 5; bug-042 F2/F3).
141
+ *
142
+ * @throws `Error` when `ids` is empty: a transition commit always names what it moved.
143
+ * @throws `Error` when `reason` cannot be recorded in the trailer, carrying that defect's message.
144
+ */
145
+ export declare function formatMemoryCommitMessage(input: MemoryCommitMessageInput): string;
146
+ //# sourceMappingURL=commit-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-message.d.ts","sourceRoot":"","sources":["../../src/memory/commit-message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AA6BpD,yGAAyG;AACzG,MAAM,MAAM,YAAY;AACtB,sGAAsG;AACpG,OAAO;AACT,6FAA6F;GAC3F,uBAAuB;AACzB;;;;;GAKG;GACD,4BAA4B,CAAC;AAUjC;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEhE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAUtD;AAYD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAahE;AAsBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMpE;AAED,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,QAAQ,CAAC,EAAE,EAAE,YAAY,GAAG,KAAK,CAAC;IAClC,yFAAyF;IACzF,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,yGAAyG;IACzG,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CAmBjF"}