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,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enumerateOperations = enumerateOperations;
4
+ exports.deriveVerb = deriveVerb;
5
+ exports.computeParityDiff = computeParityDiff;
6
+ /**
7
+ * Flatten `modules` into every declared operation, sorted deterministically by `(module.name,
8
+ * operation.name)` (REQ-SYS-07 — no unordered `Object.values`/iteration in a context-building
9
+ * path). Both `src/cli` and `src/mcp` register from this single enumerator, never from their own
10
+ * ad hoc traversal of `modules`/`Object.keys`.
11
+ */
12
+ function enumerateOperations(modules) {
13
+ const out = [];
14
+ const sortedModules = [...modules].sort((a, b) => a.name.localeCompare(b.name));
15
+ for (const module of sortedModules) {
16
+ const operationNames = Object.keys(module.operations).sort();
17
+ for (const operationName of operationNames) {
18
+ const operation = module.operations[operationName];
19
+ if (operation)
20
+ out.push({ module, operation });
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+ /** camelCase -> kebab-case (`"setTeam"` -> `"set-team"`), used by {@link deriveVerb}. */
26
+ function camelToKebab(value) {
27
+ return value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
28
+ }
29
+ /**
30
+ * Derive an operation's CLI verb / MCP verb segment from its camelCase `{module}{Verb}` name
31
+ * (spec-006 §5), mechanically — this plus `mutates` is "the one place surface-routing logic
32
+ * lives" (§2): given `moduleName = "memory"` and `operationName = "memoryApprove"`, returns
33
+ * `"approve"`; a multi-word verb (`"workflowSetTeam"`) becomes kebab-case (`"set-team"`) so it
34
+ * renders as a single CLI token / Tool-name segment. Falls back to the full operation name,
35
+ * kebab-cased, when it does not start with the module's name — a config mistake the caller should
36
+ * catch earlier, but this function degrades rather than throwing, so a single bad registration
37
+ * cannot crash an adapter's entire registration pass.
38
+ */
39
+ function deriveVerb(moduleName, operationName) {
40
+ // A "self-named" operation — its name is EXACTLY the module name (e.g. module `paths`, operation
41
+ // `paths`) — derives the empty string, not a verb: this is spec-008-cli-grammar §1's flat/no-verb
42
+ // command form (`wingfoil <noun> [args] [flags]`, e.g. `wingfoil paths [category]`), as opposed to
43
+ // the `<noun> <verb>` form every other operation today derives (`wingfoil dna show`). Both
44
+ // `src/cli/program.ts` and `src/mcp/registrar.ts` special-case an empty verb to skip the
45
+ // subcommand/URI-segment nesting they otherwise add (task-028-implement-paths-category).
46
+ if (operationName === moduleName)
47
+ return '';
48
+ const startsWithModule = operationName.startsWith(moduleName) && operationName.length > moduleName.length;
49
+ const suffix = startsWithModule ? operationName.slice(moduleName.length) : operationName;
50
+ const withLeadingLower = suffix.length > 0 ? suffix[0].toLowerCase() + suffix.slice(1) : suffix;
51
+ return camelToKebab(withLeadingLower);
52
+ }
53
+ /**
54
+ * Set-difference both ways between two enumerations of the same kind of key (e.g. `"noun verb"`
55
+ * CLI commands vs. the same keys' MCP Tool names, normalized to a common form by the caller) —
56
+ * the REQ-SYS-05 fit-criterion primitive: "reports 0 [operations] without a matching [counterpart]"
57
+ * in each direction. Both output lists are sorted, independent of input order (REQ-SYS-07).
58
+ */
59
+ function computeParityDiff(a, b) {
60
+ const setA = new Set(a);
61
+ const setB = new Set(b);
62
+ const onlyInA = [...setA].filter((key) => !setB.has(key)).sort();
63
+ const onlyInB = [...setB].filter((key) => !setA.has(key)).sort();
64
+ return { onlyInA, onlyInB };
65
+ }
66
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":";;AAiGA,kDAWC;AAiBD,gCAYC;AAyFD,8CAMC;AA7ID;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,OAA8B;IAChE,MAAM,GAAG,GAA0B,EAAE,CAAC;IACtC,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAChF,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7D,KAAK,MAAM,aAAa,IAAI,cAAc,EAAE,CAAC;YAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YACnD,IAAI,SAAS;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yFAAyF;AACzF,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,UAAU,CAAC,UAAkB,EAAE,aAAqB;IAClE,iGAAiG;IACjG,kGAAkG;IAClG,mGAAmG;IACnG,2FAA2F;IAC3F,yFAAyF;IACzF,yFAAyF;IACzF,IAAI,aAAa,KAAK,UAAU;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,gBAAgB,GAAG,aAAa,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC1G,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;IACzF,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACjG,OAAO,YAAY,CAAC,gBAAgB,CAAC,CAAC;AACxC,CAAC;AAmFD;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,CAAoB,EAAE,CAAoB;IAC1E,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,86 @@
1
+ import type { MemoryYaml } from '../memory/schema';
2
+ /**
3
+ * Caps that keep an assembled context "bounded, not a full dump" (spec-012 §6) — the defaults match
4
+ * spec-012's own defaults verbatim: `maxDocs: 40`, `maxBytes: 262144` (256 KiB).
5
+ */
6
+ export interface ContextLimits {
7
+ /** Maximum number of Memory documents included. */
8
+ readonly maxDocs: number;
9
+ /** Maximum total Memory-document body bytes (UTF-8) included. */
10
+ readonly maxBytes: number;
11
+ }
12
+ /** spec-012 §6's default {@link ContextLimits} (`maxDocs: 40`, `maxBytes: 262144` / 256 KiB). */
13
+ export declare const DEFAULT_CONTEXT_LIMITS: ContextLimits;
14
+ /**
15
+ * The active Memory element (usually a `task`) relevance is scored against — already resolved by the
16
+ * caller (spec-012 §3 stage 1, `resolve-element`, is a separate pipeline stage; this primitive is a
17
+ * pure `(state) -> selection` function and never reads the element document itself, per REQ-SYS-07).
18
+ */
19
+ export interface RelevanceElementRef {
20
+ /** The element's `memory.yaml` type (e.g. `"task"`). */
21
+ readonly type: string;
22
+ /** The element's `id` frontmatter value. */
23
+ readonly id: string;
24
+ /** The element's already-parsed frontmatter (used to derive T1 links, T2 release scope, T3
25
+ * traceability keys, and T4 keyword/tag overlap — see {@link filterRelevantMemoryDocuments}). */
26
+ readonly frontmatter: Record<string, unknown>;
27
+ }
28
+ /** One Memory document selected as relevant, carrying the {@link ContextLimits}-bounding fields plus
29
+ * its computed spec-012 §6 tier `score` (exposed for callers that want to display/debug ranking). */
30
+ export interface RelevantMemoryDocument {
31
+ readonly path: string;
32
+ readonly type?: string;
33
+ readonly id?: string;
34
+ readonly title?: string;
35
+ readonly status?: string;
36
+ readonly frontmatter: Record<string, unknown>;
37
+ readonly body: string;
38
+ /** `1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)` (spec-012 §6) — always `> 0` for an included doc. */
39
+ readonly score: number;
40
+ }
41
+ /** P5.3.3-relevance-filtering.feature's "Edge - no documents pass the relevance threshold" note,
42
+ * verbatim: `'a note "no relevant Memory found for task" is recorded'`. */
43
+ export declare const NO_RELEVANT_MEMORY_NOTE = "no relevant Memory found for task";
44
+ /**
45
+ * {@link filterRelevantMemoryDocuments}'s result: the bounded, ordered set of relevant documents, plus
46
+ * {@link NO_RELEVANT_MEMORY_NOTE} when — and only when — **nothing passed the relevance threshold**
47
+ * (mirrors `memorySearchFn`'s `message`-only-when-empty convention in `src/core/index.ts`).
48
+ *
49
+ * The note is keyed to the *scored* set, never to the *bounded* one: "no relevant Memory found for
50
+ * task" is a factual claim, and documents that scored as relevant but did not fit inside
51
+ * {@link ContextLimits} were found — they were merely not carried. In that case `documents` is empty
52
+ * and `note` is `undefined`, because the filter cannot honestly assert the stronger statement.
53
+ */
54
+ export interface RelevantMemoryResult {
55
+ readonly documents: readonly RelevantMemoryDocument[];
56
+ readonly note?: string;
57
+ }
58
+ /**
59
+ * Rank and bound the Memory documents relevant to `element` (spec-012 §6, REQ-PERF-05). Deterministic
60
+ * end to end (REQ-SYS-07):
61
+ *
62
+ * 1. **Scan** every document `memoryYaml` declares under `root` ({@link listMemoryDocumentPaths}'s
63
+ * already-sorted order, task-008) — no second directory walk.
64
+ * 2. **Exclude** the element's own document (never relevant to itself) and any document
65
+ * {@link isExcludedFromContext} bars — `draft` plus the shared archived set `{deprecated,
66
+ * superseded}` (`dl-028`; the archived half is the same predicate REQ-STATE-06 applies to default
67
+ * `memory search`, the `draft` half is context-only).
68
+ * 3. **Score** each remaining document: `1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)` — T1 explicit
69
+ * link, T2 same release scope, T3 shared traceability key, T4 keyword/tag overlap count
70
+ * (spec-012 §6's exact formula). A document scoring `0` (no tier hit at all) is **not relevant**
71
+ * and is dropped — this is the relevance threshold P5.3.3-relevance-filtering.feature's "Edge - no
72
+ * documents pass the relevance threshold" scenario exercises.
73
+ * 4. **Order**: score DESC, then `type` ASC, then `id` ASC (falling back to `path` when a document has
74
+ * no `id`) — a total, deterministic tie-break (spec-012 §6/REQ-SYS-07).
75
+ * 5. **Bound**: walk in that order, including documents until either `limits.maxDocs` or
76
+ * `limits.maxBytes` (summed UTF-8 body bytes) would be exceeded, then **stop** — never skip a
77
+ * lower-ranked document to fit under a cap while a higher-ranked one was excluded, and never
78
+ * partially include a document (spec-012 §6's "deterministic truncation").
79
+ *
80
+ * Returns {@link NO_RELEVANT_MEMORY_NOTE} in `note` only when **step 3 left nothing** — i.e. no
81
+ * document passed the relevance threshold at all (P5.3.3's edge-case scenario, verbatim wording).
82
+ * When documents scored as relevant but step 5's caps admitted none of them, `documents` is empty and
83
+ * `note` is omitted: relevant Memory *was* found, so the note would assert something false.
84
+ */
85
+ export declare function filterRelevantMemoryDocuments(root: string, memoryYaml: MemoryYaml, element: RelevanceElementRef, limits?: ContextLimits): RelevantMemoryResult;
86
+ //# sourceMappingURL=relevance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relevance.d.ts","sourceRoot":"","sources":["../../src/core/relevance.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,EAAE,aAAiD,CAAC;AAEvF;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;qGACiG;IACjG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAED;qGACqG;AACrG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;2EAC2E;AAC3E,eAAO,MAAM,uBAAuB,sCAAsC,CAAC;AAE3E;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAoID;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,mBAAmB,EAC5B,MAAM,GAAE,aAAsC,GAC7C,oBAAoB,CAsDtB"}
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NO_RELEVANT_MEMORY_NOTE = exports.DEFAULT_CONTEXT_LIMITS = void 0;
4
+ exports.filterRelevantMemoryDocuments = filterRelevantMemoryDocuments;
5
+ /**
6
+ * `relevance-filter` (task-035-bounded-context-relevance, REQ-PERF-05) —
7
+ * spec-012-context-loader-relevance-filtering §6's deterministic Memory-document selection unit:
8
+ * rank and bound the set of Memory documents relevant to an active element (usually a `task`), so an
9
+ * assembled agent context stays "curated, not full dump" (Product Brief) and satisfies REQ-PERF-05's
10
+ * Fit Criterion verbatim — "given 1,000 Memory documents of which K are relevant to the task, the
11
+ * assembled context contains exactly the K relevant (non-deprecated) documents and 0 others."
12
+ *
13
+ * This module implements ONLY spec-012 §6 (`relevance-filter`), one of the four cooperating units
14
+ * spec-012 defines (`dna-loader`, `directive-loader`, `relevance-filter`, `context-builder`) — the
15
+ * other three, and the canonical serialized envelope (§7), belong to
16
+ * task-037-role-task-scoped-context (REQ-STATE-05, the `context-builder`/envelope task). For the
17
+ * document scan itself it wraps, not reimplements, `src/memory/query.ts`'s scan primitives
18
+ * (task-008) — no second directory walk or frontmatter parser — and is placed in `src/core` per
19
+ * spec-012 §1 ("folded into the `core` module"). It is re-exported from `src/core`'s barrel, so
20
+ * `src/core` is the import surface for every consumer.
21
+ *
22
+ * **Exclusion set (`dl-028-archived-states-excluded-from-context`, `ready`).** Candidates are dropped
23
+ * when their `status` is archived — `{deprecated, superseded}`, via `src/memory/state-machine.ts`'s
24
+ * shared {@link isArchivedStatus} (the one predicate the default-search path, REQ-STATE-06, consumes
25
+ * too) — **or** `draft`. That `draft` clause is context-only and deliberately NOT mirrored into
26
+ * `memory search`: spec-012 §6 admits only "stable, decided and still-current content" into an
27
+ * execution context, whereas REQ-STATE-06 scopes default-search exclusion to archived content so a
28
+ * draft under active work stays findable. Two different sets, on purpose — see
29
+ * {@link DRAFT_STATUS}. dl-028 also dropped spec-012 §6's former `rejected` entry, a status
30
+ * `spec-001-memory-yaml-schema` removed from every type's machine.
31
+ *
32
+ * Determinism (REQ-SYS-07): no wall-clock, no randomness, no unordered map/set iteration in any
33
+ * output-affecting path. This function READS the live working tree under `root` — it is not pinned to
34
+ * a git revision, and `root`'s content is therefore part of its input, not a constant. What is
35
+ * guaranteed is *referential transparency over that observed state*: for one unchanged working tree,
36
+ * the selection and its order are a pure function of `(the Memory documents under root, memoryYaml,
37
+ * element, limits)`, so repeated calls return the identical array. See
38
+ * {@link filterRelevantMemoryDocuments}'s own doc comment for the exact ordering/bounding contract.
39
+ */
40
+ const query_1 = require("../memory/query");
41
+ const state_machine_1 = require("../memory/state-machine");
42
+ /** spec-012 §6's default {@link ContextLimits} (`maxDocs: 40`, `maxBytes: 262144` / 256 KiB). */
43
+ exports.DEFAULT_CONTEXT_LIMITS = { maxDocs: 40, maxBytes: 262144 };
44
+ /** P5.3.3-relevance-filtering.feature's "Edge - no documents pass the relevance threshold" note,
45
+ * verbatim: `'a note "no relevant Memory found for task" is recorded'`. */
46
+ exports.NO_RELEVANT_MEMORY_NOTE = 'no relevant Memory found for task';
47
+ /**
48
+ * spec-012 §6's per-tier score weights, isolated as named constants so the scoring formula
49
+ * (`1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)`) reads as tiers, not magic numbers. Each weight is a
50
+ * strict order of magnitude above the next so any T(n) hit outranks every combination of lower tiers
51
+ * (a single doc can carry at most a handful of T4 overlaps, far below `TIER_3_TRACEABILITY = 10`).
52
+ */
53
+ const TIER_1_EXPLICIT_LINK = 1000;
54
+ const TIER_2_RELEASE_SCOPE = 100;
55
+ const TIER_3_TRACEABILITY = 10;
56
+ // T4 (keyword/tag overlap) contributes its raw overlap count, weight 1 — spec-012 §6.
57
+ /** Frontmatter fields spec-012 §6 T1 treats as explicit links. Each may carry a single id or a list
58
+ * of them — see {@link asLinkIds} and `dl-045-absorbed-bug-back-reference`. */
59
+ const LINK_FRONTMATTER_FIELDS = ['adr', 'spec', 'dl', 'bug'];
60
+ /**
61
+ * `draft` — the one status excluded from an assembled context *beyond* the shared archived set.
62
+ * spec-012 §6 admits only "stable, decided and still-current content" into an execution context, so a
63
+ * draft — a document whose content is not yet submitted, let alone agreed — is dropped regardless of
64
+ * score. Kept as a named constant rather than folded into `isArchivedStatus` precisely because it is
65
+ * NOT archived: `memory search` must keep returning drafts (REQ-STATE-06 excludes archived content
66
+ * only), and collapsing the two sets would hide in-progress work from the search surface.
67
+ */
68
+ const DRAFT_STATUS = 'draft';
69
+ /**
70
+ * True when a candidate's `status` bars it from an assembled agent context (spec-012 §6): the shared
71
+ * archived set `{deprecated, superseded}` ({@link isArchivedStatus}, ratified by
72
+ * `dl-028-archived-states-excluded-from-context`) widened by {@link DRAFT_STATUS}.
73
+ * A document with no `status` frontmatter at all is not excluded — absence is not a decision.
74
+ */
75
+ function isExcludedFromContext(status) {
76
+ return status === DRAFT_STATUS || (0, state_machine_1.isArchivedStatus)(status);
77
+ }
78
+ /** A traceability token: a feature id (`P5.3.3`, `P1.9`, ...) or a SARD requirement id
79
+ * (`REQ-PERF-05`, `REQ-SYS-07`, ...) — spec-012 §6 T3's "shared traceability keys". */
80
+ const TRACEABILITY_PATTERN = /\bP\d+(?:\.\d+)+\b|\bREQ-[A-Z]+-\d+\b/g;
81
+ /** Lowercase alphanumeric word tokens — spec-012 §6 T4's "title tokens". */
82
+ const WORD_PATTERN = /[a-z0-9]+/g;
83
+ function asString(value) {
84
+ return typeof value === 'string' ? value : undefined;
85
+ }
86
+ function asStringArray(value) {
87
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
88
+ }
89
+ /**
90
+ * Read a {@link LINK_FRONTMATTER_FIELDS} value as the list of ids it names, accepting BOTH a single
91
+ * string and an array of them.
92
+ *
93
+ * `dl-045-absorbed-bug-back-reference` made the task `bug:` field a list, so a bug absorbed into an
94
+ * existing task's Acceptance Criteria — rather than given its own fix task — keeps the back-reference
95
+ * `bug.sync_state` binds to. Reading these fields with a bare {@link asString} would have made that
96
+ * change **silently lossy**: `asString` returns `undefined` for an array, so an absorbed bug would
97
+ * stop scoring as a T1 explicit link and drop out of its own host task's assembled context, with no
98
+ * error to notice.
99
+ *
100
+ * Both shapes stay valid on purpose. Every fix task written before dl-045 carries a single id, and
101
+ * `adr`/`spec`/`dl` are unchanged by that decision — narrowing to arrays would break them all.
102
+ */
103
+ function asLinkIds(value) {
104
+ const single = asString(value);
105
+ return single !== undefined ? [single] : asStringArray(value);
106
+ }
107
+ /** T1: the set of ids the element's frontmatter explicitly references (`adr`, `spec`, `dl`, `bug`,
108
+ * `depends_on`) — spec-012 §6. Insertion order follows {@link LINK_FRONTMATTER_FIELDS}'s fixed
109
+ * declaration order, then each field's own array order, then `depends_on`'s (REQ-SYS-07: no
110
+ * unordered iteration). */
111
+ function collectLinkedIds(frontmatter) {
112
+ const ids = new Set();
113
+ for (const field of LINK_FRONTMATTER_FIELDS) {
114
+ for (const value of asLinkIds(frontmatter[field])) {
115
+ if (value.length > 0)
116
+ ids.add(value);
117
+ }
118
+ }
119
+ for (const dependency of asStringArray(frontmatter.depends_on))
120
+ ids.add(dependency);
121
+ return ids;
122
+ }
123
+ /** T3: the set of `P*`/`REQ-*` traceability tokens found across every string-valued frontmatter field
124
+ * (`ref`, `tags`, ...) and, when supplied, the document body — spec-012 §6. */
125
+ function collectTraceabilityKeys(frontmatter, body) {
126
+ const keys = new Set();
127
+ const scan = (text) => {
128
+ for (const match of text.matchAll(TRACEABILITY_PATTERN))
129
+ keys.add(match[0]);
130
+ };
131
+ for (const value of Object.values(frontmatter)) {
132
+ if (typeof value === 'string')
133
+ scan(value);
134
+ else if (Array.isArray(value))
135
+ for (const entry of value)
136
+ if (typeof entry === 'string')
137
+ scan(entry);
138
+ }
139
+ if (body !== undefined)
140
+ scan(body);
141
+ return keys;
142
+ }
143
+ /** T4: the element/document's keyword set — its `tags:` entries plus its title's lowercase word
144
+ * tokens — spec-012 §6 ("docs whose tags: or title tokens intersect the element's tags/title
145
+ * tokens"). */
146
+ function collectKeywords(frontmatter) {
147
+ const keywords = new Set();
148
+ for (const tag of asStringArray(frontmatter.tags))
149
+ keywords.add(tag.toLowerCase());
150
+ const title = asString(frontmatter.title);
151
+ if (title !== undefined)
152
+ for (const word of title.toLowerCase().match(WORD_PATTERN) ?? [])
153
+ keywords.add(word);
154
+ return keywords;
155
+ }
156
+ function intersectionSize(a, b) {
157
+ let count = 0;
158
+ for (const value of a)
159
+ if (b.has(value))
160
+ count += 1;
161
+ return count;
162
+ }
163
+ function hasIntersection(a, b) {
164
+ for (const value of a)
165
+ if (b.has(value))
166
+ return true;
167
+ return false;
168
+ }
169
+ /** T2: is `documentPath` under the element's release scope? True when the element declares a
170
+ * `release` and either the candidate document's own `release:` frontmatter matches it exactly, or its
171
+ * path contains that release as a path segment (covers release-line-scoped docs whose own frontmatter
172
+ * has no `release` field) — spec-012 §6 ("docs under the element's release / release-line path"). */
173
+ function isSameReleaseScope(elementRelease, documentPath, documentFrontmatter) {
174
+ if (elementRelease === undefined)
175
+ return false;
176
+ return documentPath.includes(`/${elementRelease}/`) || asString(documentFrontmatter.release) === elementRelease;
177
+ }
178
+ /**
179
+ * Rank and bound the Memory documents relevant to `element` (spec-012 §6, REQ-PERF-05). Deterministic
180
+ * end to end (REQ-SYS-07):
181
+ *
182
+ * 1. **Scan** every document `memoryYaml` declares under `root` ({@link listMemoryDocumentPaths}'s
183
+ * already-sorted order, task-008) — no second directory walk.
184
+ * 2. **Exclude** the element's own document (never relevant to itself) and any document
185
+ * {@link isExcludedFromContext} bars — `draft` plus the shared archived set `{deprecated,
186
+ * superseded}` (`dl-028`; the archived half is the same predicate REQ-STATE-06 applies to default
187
+ * `memory search`, the `draft` half is context-only).
188
+ * 3. **Score** each remaining document: `1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)` — T1 explicit
189
+ * link, T2 same release scope, T3 shared traceability key, T4 keyword/tag overlap count
190
+ * (spec-012 §6's exact formula). A document scoring `0` (no tier hit at all) is **not relevant**
191
+ * and is dropped — this is the relevance threshold P5.3.3-relevance-filtering.feature's "Edge - no
192
+ * documents pass the relevance threshold" scenario exercises.
193
+ * 4. **Order**: score DESC, then `type` ASC, then `id` ASC (falling back to `path` when a document has
194
+ * no `id`) — a total, deterministic tie-break (spec-012 §6/REQ-SYS-07).
195
+ * 5. **Bound**: walk in that order, including documents until either `limits.maxDocs` or
196
+ * `limits.maxBytes` (summed UTF-8 body bytes) would be exceeded, then **stop** — never skip a
197
+ * lower-ranked document to fit under a cap while a higher-ranked one was excluded, and never
198
+ * partially include a document (spec-012 §6's "deterministic truncation").
199
+ *
200
+ * Returns {@link NO_RELEVANT_MEMORY_NOTE} in `note` only when **step 3 left nothing** — i.e. no
201
+ * document passed the relevance threshold at all (P5.3.3's edge-case scenario, verbatim wording).
202
+ * When documents scored as relevant but step 5's caps admitted none of them, `documents` is empty and
203
+ * `note` is omitted: relevant Memory *was* found, so the note would assert something false.
204
+ */
205
+ function filterRelevantMemoryDocuments(root, memoryYaml, element, limits = exports.DEFAULT_CONTEXT_LIMITS) {
206
+ const linkedIds = collectLinkedIds(element.frontmatter);
207
+ const elementRelease = asString(element.frontmatter.release);
208
+ const elementTraceability = collectTraceabilityKeys(element.frontmatter);
209
+ const elementKeywords = collectKeywords(element.frontmatter);
210
+ const scored = [];
211
+ for (const path of (0, query_1.listMemoryDocumentPaths)(root, memoryYaml)) {
212
+ const { frontmatter, body } = (0, query_1.loadMemoryDocumentSummary)(root, path);
213
+ const type = asString(frontmatter.type);
214
+ const id = asString(frontmatter.id);
215
+ const status = asString(frontmatter.status);
216
+ if (isExcludedFromContext(status))
217
+ continue;
218
+ if (type === element.type && id === element.id)
219
+ continue;
220
+ const t1 = id !== undefined && linkedIds.has(id);
221
+ const t2 = isSameReleaseScope(elementRelease, path, frontmatter);
222
+ const t3 = hasIntersection(elementTraceability, collectTraceabilityKeys(frontmatter, body));
223
+ const t4OverlapCount = intersectionSize(elementKeywords, collectKeywords(frontmatter));
224
+ const score = (t1 ? TIER_1_EXPLICIT_LINK : 0) +
225
+ (t2 ? TIER_2_RELEASE_SCOPE : 0) +
226
+ (t3 ? TIER_3_TRACEABILITY : 0) +
227
+ t4OverlapCount;
228
+ if (score <= 0)
229
+ continue;
230
+ scored.push({ path, type, id, title: asString(frontmatter.title), status, frontmatter, body, score });
231
+ }
232
+ scored.sort((a, b) => {
233
+ if (a.score !== b.score)
234
+ return b.score - a.score;
235
+ const typeA = a.type ?? '';
236
+ const typeB = b.type ?? '';
237
+ if (typeA !== typeB)
238
+ return typeA < typeB ? -1 : 1;
239
+ const idA = a.id ?? a.path;
240
+ const idB = b.id ?? b.path;
241
+ return idA < idB ? -1 : idA > idB ? 1 : 0;
242
+ });
243
+ const documents = [];
244
+ let totalBytes = 0;
245
+ for (const doc of scored) {
246
+ if (documents.length >= limits.maxDocs)
247
+ break;
248
+ const docBytes = Buffer.byteLength(doc.body, 'utf-8');
249
+ if (totalBytes + docBytes > limits.maxBytes)
250
+ break;
251
+ documents.push(doc);
252
+ totalBytes += docBytes;
253
+ }
254
+ // The note is keyed to `scored`, NOT to `documents`: an empty `documents` with a non-empty `scored`
255
+ // means relevant Memory existed and was bounded out, which the note must not claim away.
256
+ return scored.length === 0 ? { documents, note: exports.NO_RELEVANT_MEMORY_NOTE } : { documents };
257
+ }
258
+ //# sourceMappingURL=relevance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relevance.js","sourceRoot":"","sources":["../../src/core/relevance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Return the `--reason` value out of a `CoreFn`'s parsed `options` (the `ParamsContext.options`
3
+ * value-bearing seam, `../core/registry.ts` — the same one `memoryAddFn` reads `--type`/`--title`/
4
+ * `--tags` from), normalized to the form the commit trailer records; or throw a `UsageError` (exit 2)
5
+ * when it is absent (`options` itself undefined, or present without a `reason` key) or cannot be
6
+ * recorded. Call this BEFORE any registry/document lookup in a mutating `CoreFn` so a refused
7
+ * `--reason` makes no change.
8
+ *
9
+ * Returning the NORMALIZED text rather than the raw argument is what keeps the commit trailer and
10
+ * `memory reject`'s `rejection_reason` frontmatter field (`spec-010` — the one verb that writes the
11
+ * reason twice) carrying byte-identical text: the rule is applied once, here, not at each sink.
12
+ */
13
+ export declare function requireReason(options: Readonly<Record<string, string>> | undefined): string;
14
+ /**
15
+ * The same, for a verb whose `--reason` is OPTIONAL — `memory deprecate` (`dl-027` option (a)):
16
+ * `undefined` when none was given (the verb then exits `0` and commits a subject-only message), the
17
+ * normalized text when one was.
18
+ *
19
+ * A reason that is *declared but empty* is a usage error here too, exactly as on the approval gates
20
+ * (dl-067 S2, ratified). `buildOptionValues` (`src/cli/program.ts`) keeps any `typeof value ===
21
+ * 'string'` precisely so a core op can tell "not given" from "given empty"; honouring that
22
+ * distinction means refusing the empty one, not silently discarding it. This changes a merged verb's
23
+ * behaviour — nothing pinned the old one, and the old one was bug-042 F2 on the verb where it bites
24
+ * hardest, `deprecate` having no `Approver:` line of its own to lose.
25
+ */
26
+ export declare function optionalReason(options: Readonly<Record<string, string>> | undefined): string | undefined;
27
+ //# sourceMappingURL=require-reason.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-reason.d.ts","sourceRoot":"","sources":["../../src/core/require-reason.ts"],"names":[],"mappings":"AA2DA;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAM3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGxG"}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireReason = requireReason;
4
+ exports.optionalReason = optionalReason;
5
+ /**
6
+ * `--reason` mandatory-argument pre-flight check (REQ-SEC-04 — task-041-mandatory-reason-on-verbs).
7
+ *
8
+ * Decisions must be explainable in the audit trail: `memory approve` (P1.7) and `memory reject`
9
+ * (P1.8) both record an approver/rejecter identity, an ISO-8601 timestamp, and a reason in the
10
+ * resulting git commit body (P1.7/P1.8 BDD "the git commit records the approver identity, timestamp,
11
+ * and reason"). The reason text can only be recorded if the caller supplied one, so both operations
12
+ * refuse before any read/write when `--reason` is omitted — the argument-validation step every
13
+ * mutating `CoreFn` already performs inline for its own required options (e.g. `memoryAddFn`'s
14
+ * `--type`/`--title` checks, task-020), factored out here once both approve and reject need the
15
+ * identical check (this task is their shared prerequisite, per its own Implementation Notes).
16
+ *
17
+ * This mirrors `requireGitIdentity`'s (`./git-identity.ts`, REQ-SEC-01, task-014) role and calling
18
+ * convention: a mutating `CoreFn` calls it first, before any registry lookup or file access, so an
19
+ * omitted `--reason` makes no change (REQ-SEC-04's fit criterion). Unlike `requireGitIdentity` (an
20
+ * ambient precondition, not tied to a CLI argument, returning a `CoreResult.error` mapped to exit 1)
21
+ * this is an ARGUMENT-shape problem — spec-005-cli-command-contract §1's "missing required argument"
22
+ * case — so, matching `memoryAddFn`'s own `--type`/`--title` checks and `dnaSetFn`'s invalid-key-path
23
+ * check, it `throw`s a `UsageError` (exit **2**, `./exit-code.ts`'s `exitCodeForThrow`) rather than
24
+ * returning a `CoreResult`.
25
+ *
26
+ * `memory deprecate` (P1.9, task-048) deliberately does NOT call {@link requireReason}:
27
+ * `spec-008-cli-grammar.md` §2 marks `--reason` optional "elsewhere (e.g. `memory deprecate`)", and
28
+ * `P1.9-memory-deprecate.feature` has no "missing reason" error scenario (unlike P1.7/P1.8). Since
29
+ * `task-072` it calls {@link optionalReason} instead of reading `options?.reason` raw.
30
+ *
31
+ * `task-072-fix-reason-trailer-contract` (`dl-067-reason-trailer-contract`, fixing `bug-042`) widens
32
+ * both checks from "was a reason given?" to "can this reason be RECORDED?". Those turned out to be
33
+ * different questions: `--reason ""` *was* given, was accepted at exit `0`, and left a bare `Reason:`
34
+ * that made `wingfoil memory history` report an approval gate crossed by nobody, for no reason. The
35
+ * shape rule itself is not decided here — it lives once, in `src/memory/commit-message.ts`, beside the
36
+ * formatter that writes the trailer and consumed by the reader in `src/memory/audit.ts` (dl-067
37
+ * clause 5). This module only maps a defect onto the CLI's usage-error exit code.
38
+ */
39
+ const commit_message_1 = require("../memory/commit-message");
40
+ const usage_error_1 = require("./usage-error");
41
+ /** Exact refusal message required by REQ-SEC-04's fit criterion (spec-008-cli-grammar §2's ground-truth wording) — do not reword. */
42
+ const MISSING_REASON_ERROR = 'missing required argument: --reason';
43
+ /**
44
+ * The given reason in its declared normal form, or a `UsageError` (exit 2) naming the defect that
45
+ * makes it unrecordable — blank, carrying a reserved trailer line, or ending in one
46
+ * (`reasonDefect`, `src/memory/commit-message.ts`).
47
+ *
48
+ * The message is deliberately NOT {@link MISSING_REASON_ERROR}: that string answers the *omitted*
49
+ * case, and `spec-008-cli-grammar` §2 plus the `P1.7`/`P1.8` BDD features quote it verbatim for that
50
+ * case alone, so reusing it would make those scenarios ambiguous about which failure they pin
51
+ * (dl-067 S1).
52
+ */
53
+ function recordableReason(reason) {
54
+ const defect = (0, commit_message_1.reasonDefect)(reason);
55
+ if (defect !== null) {
56
+ throw new usage_error_1.UsageError((0, commit_message_1.reasonDefectMessage)(defect));
57
+ }
58
+ return (0, commit_message_1.normalizeReason)(reason);
59
+ }
60
+ /**
61
+ * Return the `--reason` value out of a `CoreFn`'s parsed `options` (the `ParamsContext.options`
62
+ * value-bearing seam, `../core/registry.ts` — the same one `memoryAddFn` reads `--type`/`--title`/
63
+ * `--tags` from), normalized to the form the commit trailer records; or throw a `UsageError` (exit 2)
64
+ * when it is absent (`options` itself undefined, or present without a `reason` key) or cannot be
65
+ * recorded. Call this BEFORE any registry/document lookup in a mutating `CoreFn` so a refused
66
+ * `--reason` makes no change.
67
+ *
68
+ * Returning the NORMALIZED text rather than the raw argument is what keeps the commit trailer and
69
+ * `memory reject`'s `rejection_reason` frontmatter field (`spec-010` — the one verb that writes the
70
+ * reason twice) carrying byte-identical text: the rule is applied once, here, not at each sink.
71
+ */
72
+ function requireReason(options) {
73
+ const reason = options?.reason;
74
+ if (reason === undefined) {
75
+ throw new usage_error_1.UsageError(MISSING_REASON_ERROR);
76
+ }
77
+ return recordableReason(reason);
78
+ }
79
+ /**
80
+ * The same, for a verb whose `--reason` is OPTIONAL — `memory deprecate` (`dl-027` option (a)):
81
+ * `undefined` when none was given (the verb then exits `0` and commits a subject-only message), the
82
+ * normalized text when one was.
83
+ *
84
+ * A reason that is *declared but empty* is a usage error here too, exactly as on the approval gates
85
+ * (dl-067 S2, ratified). `buildOptionValues` (`src/cli/program.ts`) keeps any `typeof value ===
86
+ * 'string'` precisely so a core op can tell "not given" from "given empty"; honouring that
87
+ * distinction means refusing the empty one, not silently discarding it. This changes a merged verb's
88
+ * behaviour — nothing pinned the old one, and the old one was bug-042 F2 on the verb where it bites
89
+ * hardest, `deprecate` having no `Approver:` line of its own to lose.
90
+ */
91
+ function optionalReason(options) {
92
+ const reason = options?.reason;
93
+ return reason === undefined ? undefined : recordableReason(reason);
94
+ }
95
+ //# sourceMappingURL=require-reason.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-reason.js","sourceRoot":"","sources":["../../src/core/require-reason.ts"],"names":[],"mappings":";;AAuEA,sCAMC;AAcD,wCAGC;AA9FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,6DAA8F;AAE9F,+CAA2C;AAE3C,qIAAqI;AACrI,MAAM,oBAAoB,GAAG,qCAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,MAAc;IACtC,MAAM,MAAM,GAAG,IAAA,6BAAY,EAAC,MAAM,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,wBAAU,CAAC,IAAA,oCAAmB,EAAC,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAA,gCAAe,EAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAAC,OAAqD;IACjF,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,wBAAU,CAAC,oBAAoB,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,cAAc,CAAC,OAAqD;IAClF,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Core result & error shapes (spec-006-core-domain-api §2 — "Function shape").
3
+ *
4
+ * Every core function returns `Promise<CoreResult<T>>`, a discriminated union — never a thrown
5
+ * error for *expected* domain failures (illegal state transition, missing element, schema
6
+ * violation). That is what lets `src/cli`'s exit-code/stderr rendering and `src/mcp`'s Tool
7
+ * `isError`/Resource-read-failure response be driven off the very same value, rather than each
8
+ * surface independently deciding what counts as a failure and how to report it.
9
+ */
10
+ /** The fixed `CoreError.code` enumeration (spec-006 §2, verbatim). */
11
+ export type CoreErrorCode = 'NOT_FOUND' | 'INVALID_TRANSITION' | 'VALIDATION' | 'CONFLICT' | 'IO';
12
+ /**
13
+ * An expected domain failure carried by a failed {@link CoreResult} (spec-006 §2): a fixed
14
+ * {@link CoreErrorCode}, a human-readable `message`, and optional structured `details`. The single
15
+ * shape both surfaces map to an exit code / MCP error response.
16
+ */
17
+ export interface CoreError {
18
+ readonly code: CoreErrorCode;
19
+ readonly message: string;
20
+ readonly details?: Record<string, unknown>;
21
+ }
22
+ /**
23
+ * The discriminated-union return of every {@link CoreFn} (spec-006 §2): `ok: true` with the operation's
24
+ * `value` (and, for a mutation, the `commit` it produced), or `ok: false` with a {@link CoreError}.
25
+ * Expected domain failures travel here, never as a thrown exception.
26
+ */
27
+ export type CoreResult<T> = {
28
+ readonly ok: true;
29
+ readonly value: T;
30
+ readonly commit?: {
31
+ readonly sha: string;
32
+ readonly message: string;
33
+ };
34
+ } | {
35
+ readonly ok: false;
36
+ readonly error: CoreError;
37
+ };
38
+ /** Build a success {@link CoreResult}, optionally carrying the git commit the operation produced. */
39
+ export declare function coreOk<T>(value: T, commit?: {
40
+ sha: string;
41
+ message: string;
42
+ }): CoreResult<T>;
43
+ /** Build a failure {@link CoreResult} from a {@link CoreError}. */
44
+ export declare function coreErr<T = never>(error: CoreError): CoreResult<T>;
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,oBAAoB,GAAG,YAAY,GAAG,UAAU,GAAG,IAAI,CAAC;AAElG;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IACpB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAEtD,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAE5F;AAED,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAElE"}