@encore-os/eos-spec 0.3.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 (256) hide show
  1. package/LICENSE +17 -0
  2. package/bin/check-dist-fresh.js +51 -0
  3. package/bin/eos-spec.js +7 -0
  4. package/dist/commands/ac-backfill.d.ts +31 -0
  5. package/dist/commands/ac-backfill.js +198 -0
  6. package/dist/commands/ac-backfill.js.map +1 -0
  7. package/dist/commands/archive.d.ts +2 -0
  8. package/dist/commands/archive.js +101 -0
  9. package/dist/commands/archive.js.map +1 -0
  10. package/dist/commands/audit.d.ts +30 -0
  11. package/dist/commands/audit.js +107 -0
  12. package/dist/commands/audit.js.map +1 -0
  13. package/dist/commands/backfill.d.ts +41 -0
  14. package/dist/commands/backfill.js +239 -0
  15. package/dist/commands/backfill.js.map +1 -0
  16. package/dist/commands/checkArtifacts.d.ts +2 -0
  17. package/dist/commands/checkArtifacts.js +48 -0
  18. package/dist/commands/checkArtifacts.js.map +1 -0
  19. package/dist/commands/checkPlacement.d.ts +2 -0
  20. package/dist/commands/checkPlacement.js +31 -0
  21. package/dist/commands/checkPlacement.js.map +1 -0
  22. package/dist/commands/claim.d.ts +4 -0
  23. package/dist/commands/claim.js +46 -0
  24. package/dist/commands/claim.js.map +1 -0
  25. package/dist/commands/complete.d.ts +2 -0
  26. package/dist/commands/complete.js +138 -0
  27. package/dist/commands/complete.js.map +1 -0
  28. package/dist/commands/conformance.d.ts +23 -0
  29. package/dist/commands/conformance.js +124 -0
  30. package/dist/commands/conformance.js.map +1 -0
  31. package/dist/commands/create.d.ts +6 -0
  32. package/dist/commands/create.js +277 -0
  33. package/dist/commands/create.js.map +1 -0
  34. package/dist/commands/deferred.d.ts +2 -0
  35. package/dist/commands/deferred.js +85 -0
  36. package/dist/commands/deferred.js.map +1 -0
  37. package/dist/commands/delta.d.ts +17 -0
  38. package/dist/commands/delta.js +292 -0
  39. package/dist/commands/delta.js.map +1 -0
  40. package/dist/commands/freshness.d.ts +13 -0
  41. package/dist/commands/freshness.js +72 -0
  42. package/dist/commands/freshness.js.map +1 -0
  43. package/dist/commands/funnel.d.ts +2 -0
  44. package/dist/commands/funnel.js +61 -0
  45. package/dist/commands/funnel.js.map +1 -0
  46. package/dist/commands/gen-tests.d.ts +2 -0
  47. package/dist/commands/gen-tests.js +53 -0
  48. package/dist/commands/gen-tests.js.map +1 -0
  49. package/dist/commands/init.d.ts +2 -0
  50. package/dist/commands/init.js +64 -0
  51. package/dist/commands/init.js.map +1 -0
  52. package/dist/commands/list.d.ts +9 -0
  53. package/dist/commands/list.js +61 -0
  54. package/dist/commands/list.js.map +1 -0
  55. package/dist/commands/metrics.d.ts +2 -0
  56. package/dist/commands/metrics.js +66 -0
  57. package/dist/commands/metrics.js.map +1 -0
  58. package/dist/commands/next.d.ts +48 -0
  59. package/dist/commands/next.js +344 -0
  60. package/dist/commands/next.js.map +1 -0
  61. package/dist/commands/precedent.d.ts +66 -0
  62. package/dist/commands/precedent.js +343 -0
  63. package/dist/commands/precedent.js.map +1 -0
  64. package/dist/commands/ready.d.ts +28 -0
  65. package/dist/commands/ready.js +147 -0
  66. package/dist/commands/ready.js.map +1 -0
  67. package/dist/commands/reconcile.d.ts +44 -0
  68. package/dist/commands/reconcile.js +187 -0
  69. package/dist/commands/reconcile.js.map +1 -0
  70. package/dist/commands/rollup.d.ts +2 -0
  71. package/dist/commands/rollup.js +43 -0
  72. package/dist/commands/rollup.js.map +1 -0
  73. package/dist/commands/scorecard.d.ts +11 -0
  74. package/dist/commands/scorecard.js +55 -0
  75. package/dist/commands/scorecard.js.map +1 -0
  76. package/dist/commands/signoff.d.ts +2 -0
  77. package/dist/commands/signoff.js +114 -0
  78. package/dist/commands/signoff.js.map +1 -0
  79. package/dist/commands/status.d.ts +2 -0
  80. package/dist/commands/status.js +100 -0
  81. package/dist/commands/status.js.map +1 -0
  82. package/dist/commands/tasks.d.ts +13 -0
  83. package/dist/commands/tasks.js +81 -0
  84. package/dist/commands/tasks.js.map +1 -0
  85. package/dist/commands/validate.d.ts +16 -0
  86. package/dist/commands/validate.js +89 -0
  87. package/dist/commands/validate.js.map +1 -0
  88. package/dist/commands/verify-acs.d.ts +15 -0
  89. package/dist/commands/verify-acs.js +196 -0
  90. package/dist/commands/verify-acs.js.map +1 -0
  91. package/dist/commands/verifyRollout.d.ts +2 -0
  92. package/dist/commands/verifyRollout.js +125 -0
  93. package/dist/commands/verifyRollout.js.map +1 -0
  94. package/dist/index.d.ts +21 -0
  95. package/dist/index.js +138 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/lib/ac-draft-prompt.d.ts +18 -0
  98. package/dist/lib/ac-draft-prompt.js +38 -0
  99. package/dist/lib/ac-draft-prompt.js.map +1 -0
  100. package/dist/lib/ac-response-parser.d.ts +18 -0
  101. package/dist/lib/ac-response-parser.js +57 -0
  102. package/dist/lib/ac-response-parser.js.map +1 -0
  103. package/dist/lib/ai-call.d.ts +13 -0
  104. package/dist/lib/ai-call.js +17 -0
  105. package/dist/lib/ai-call.js.map +1 -0
  106. package/dist/lib/archive.d.ts +21 -0
  107. package/dist/lib/archive.js +136 -0
  108. package/dist/lib/archive.js.map +1 -0
  109. package/dist/lib/artifact-integrity.d.ts +5 -0
  110. package/dist/lib/artifact-integrity.js +65 -0
  111. package/dist/lib/artifact-integrity.js.map +1 -0
  112. package/dist/lib/backfill-queue.d.ts +11 -0
  113. package/dist/lib/backfill-queue.js +14 -0
  114. package/dist/lib/backfill-queue.js.map +1 -0
  115. package/dist/lib/blockers.d.ts +66 -0
  116. package/dist/lib/blockers.js +103 -0
  117. package/dist/lib/blockers.js.map +1 -0
  118. package/dist/lib/claims.d.ts +39 -0
  119. package/dist/lib/claims.js +133 -0
  120. package/dist/lib/claims.js.map +1 -0
  121. package/dist/lib/classify-debt.d.ts +17 -0
  122. package/dist/lib/classify-debt.js +13 -0
  123. package/dist/lib/classify-debt.js.map +1 -0
  124. package/dist/lib/code-evidence.d.ts +35 -0
  125. package/dist/lib/code-evidence.js +171 -0
  126. package/dist/lib/code-evidence.js.map +1 -0
  127. package/dist/lib/coherence.d.ts +14 -0
  128. package/dist/lib/coherence.js +188 -0
  129. package/dist/lib/coherence.js.map +1 -0
  130. package/dist/lib/completion-gate.d.ts +17 -0
  131. package/dist/lib/completion-gate.js +28 -0
  132. package/dist/lib/completion-gate.js.map +1 -0
  133. package/dist/lib/compliance-signoff.d.ts +39 -0
  134. package/dist/lib/compliance-signoff.js +81 -0
  135. package/dist/lib/compliance-signoff.js.map +1 -0
  136. package/dist/lib/config.d.ts +63 -0
  137. package/dist/lib/config.js +40 -0
  138. package/dist/lib/config.js.map +1 -0
  139. package/dist/lib/cost.d.ts +12 -0
  140. package/dist/lib/cost.js +12 -0
  141. package/dist/lib/cost.js.map +1 -0
  142. package/dist/lib/debt-dashboard.d.ts +25 -0
  143. package/dist/lib/debt-dashboard.js +36 -0
  144. package/dist/lib/debt-dashboard.js.map +1 -0
  145. package/dist/lib/embedding-store.d.ts +103 -0
  146. package/dist/lib/embedding-store.js +133 -0
  147. package/dist/lib/embedding-store.js.map +1 -0
  148. package/dist/lib/embeddings-source.d.ts +36 -0
  149. package/dist/lib/embeddings-source.js +62 -0
  150. package/dist/lib/embeddings-source.js.map +1 -0
  151. package/dist/lib/enforcement-allowlist.d.ts +12 -0
  152. package/dist/lib/enforcement-allowlist.js +25 -0
  153. package/dist/lib/enforcement-allowlist.js.map +1 -0
  154. package/dist/lib/events.d.ts +47 -0
  155. package/dist/lib/events.js +75 -0
  156. package/dist/lib/events.js.map +1 -0
  157. package/dist/lib/exec.d.ts +8 -0
  158. package/dist/lib/exec.js +25 -0
  159. package/dist/lib/exec.js.map +1 -0
  160. package/dist/lib/frontmatter.d.ts +112 -0
  161. package/dist/lib/frontmatter.js +261 -0
  162. package/dist/lib/frontmatter.js.map +1 -0
  163. package/dist/lib/funnel.d.ts +22 -0
  164. package/dist/lib/funnel.js +41 -0
  165. package/dist/lib/funnel.js.map +1 -0
  166. package/dist/lib/git-mtime.d.ts +4 -0
  167. package/dist/lib/git-mtime.js +23 -0
  168. package/dist/lib/git-mtime.js.map +1 -0
  169. package/dist/lib/metrics.d.ts +42 -0
  170. package/dist/lib/metrics.js +92 -0
  171. package/dist/lib/metrics.js.map +1 -0
  172. package/dist/lib/migration-collision.d.ts +20 -0
  173. package/dist/lib/migration-collision.js +61 -0
  174. package/dist/lib/migration-collision.js.map +1 -0
  175. package/dist/lib/next-action.d.ts +44 -0
  176. package/dist/lib/next-action.js +138 -0
  177. package/dist/lib/next-action.js.map +1 -0
  178. package/dist/lib/ollama-embed.d.ts +21 -0
  179. package/dist/lib/ollama-embed.js +65 -0
  180. package/dist/lib/ollama-embed.js.map +1 -0
  181. package/dist/lib/output.d.ts +35 -0
  182. package/dist/lib/output.js +46 -0
  183. package/dist/lib/output.js.map +1 -0
  184. package/dist/lib/pipeline-config.d.ts +20 -0
  185. package/dist/lib/pipeline-config.js +208 -0
  186. package/dist/lib/pipeline-config.js.map +1 -0
  187. package/dist/lib/pipeline-map.d.ts +68 -0
  188. package/dist/lib/pipeline-map.js +256 -0
  189. package/dist/lib/pipeline-map.js.map +1 -0
  190. package/dist/lib/pipeline-schema.d.ts +48 -0
  191. package/dist/lib/pipeline-schema.js +22 -0
  192. package/dist/lib/pipeline-schema.js.map +1 -0
  193. package/dist/lib/placement.d.ts +6 -0
  194. package/dist/lib/placement.js +40 -0
  195. package/dist/lib/placement.js.map +1 -0
  196. package/dist/lib/precedent-search.d.ts +18 -0
  197. package/dist/lib/precedent-search.js +160 -0
  198. package/dist/lib/precedent-search.js.map +1 -0
  199. package/dist/lib/predicate-registry.d.ts +6 -0
  200. package/dist/lib/predicate-registry.js +40 -0
  201. package/dist/lib/predicate-registry.js.map +1 -0
  202. package/dist/lib/recall-cases.d.ts +15 -0
  203. package/dist/lib/recall-cases.js +39 -0
  204. package/dist/lib/recall-cases.js.map +1 -0
  205. package/dist/lib/repo.d.ts +20 -0
  206. package/dist/lib/repo.js +82 -0
  207. package/dist/lib/repo.js.map +1 -0
  208. package/dist/lib/requirements.d.ts +91 -0
  209. package/dist/lib/requirements.js +179 -0
  210. package/dist/lib/requirements.js.map +1 -0
  211. package/dist/lib/rollup.d.ts +24 -0
  212. package/dist/lib/rollup.js +34 -0
  213. package/dist/lib/rollup.js.map +1 -0
  214. package/dist/lib/scorecard-run.d.ts +6 -0
  215. package/dist/lib/scorecard-run.js +48 -0
  216. package/dist/lib/scorecard-run.js.map +1 -0
  217. package/dist/lib/scorecard.d.ts +69 -0
  218. package/dist/lib/scorecard.js +113 -0
  219. package/dist/lib/scorecard.js.map +1 -0
  220. package/dist/lib/semantic-search.d.ts +36 -0
  221. package/dist/lib/semantic-search.js +56 -0
  222. package/dist/lib/semantic-search.js.map +1 -0
  223. package/dist/lib/session-state.d.ts +8 -0
  224. package/dist/lib/session-state.js +25 -0
  225. package/dist/lib/session-state.js.map +1 -0
  226. package/dist/lib/spec-debt.d.ts +8 -0
  227. package/dist/lib/spec-debt.js +17 -0
  228. package/dist/lib/spec-debt.js.map +1 -0
  229. package/dist/lib/spec-pure.d.ts +59 -0
  230. package/dist/lib/spec-pure.js +215 -0
  231. package/dist/lib/spec-pure.js.map +1 -0
  232. package/dist/lib/specs.d.ts +148 -0
  233. package/dist/lib/specs.js +373 -0
  234. package/dist/lib/specs.js.map +1 -0
  235. package/dist/lib/test-generator.d.ts +5 -0
  236. package/dist/lib/test-generator.js +72 -0
  237. package/dist/lib/test-generator.js.map +1 -0
  238. package/dist/lib/user-stories.d.ts +6 -0
  239. package/dist/lib/user-stories.js +56 -0
  240. package/dist/lib/user-stories.js.map +1 -0
  241. package/dist/lib/verifies-scan.d.ts +9 -0
  242. package/dist/lib/verifies-scan.js +73 -0
  243. package/dist/lib/verifies-scan.js.map +1 -0
  244. package/dist/lib/verify-core.d.ts +23 -0
  245. package/dist/lib/verify-core.js +48 -0
  246. package/dist/lib/verify-core.js.map +1 -0
  247. package/dist/lib/with-event-log.d.ts +12 -0
  248. package/dist/lib/with-event-log.js +65 -0
  249. package/dist/lib/with-event-log.js.map +1 -0
  250. package/dist/lib/wizard-specs.d.ts +17 -0
  251. package/dist/lib/wizard-specs.js +34 -0
  252. package/dist/lib/wizard-specs.js.map +1 -0
  253. package/dist/pure.d.ts +19 -0
  254. package/dist/pure.js +31 -0
  255. package/dist/pure.js.map +1 -0
  256. package/package.json +69 -0
@@ -0,0 +1,40 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
+ import { coresList, specsRoot } from './repo.js';
4
+ import { extractSpecId, isDerivativeArtifact } from './specs.js';
5
+ export function findMisplacedSpecs(repoRoot) {
6
+ const out = [];
7
+ for (const core of coresList(repoRoot)) {
8
+ const coreDir = join(specsRoot(repoRoot), core);
9
+ if (!existsSync(coreDir))
10
+ continue;
11
+ const stack = [coreDir];
12
+ while (stack.length > 0) {
13
+ const dir = stack.pop();
14
+ const dirName = basename(dir);
15
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
16
+ const full = join(dir, entry.name);
17
+ if (entry.isSymbolicLink())
18
+ continue; // skip symlinks to avoid loops
19
+ if (entry.isDirectory()) {
20
+ stack.push(full);
21
+ continue;
22
+ }
23
+ if (!entry.name.endsWith('.md'))
24
+ continue;
25
+ if (isDerivativeArtifact(dirName, entry.name))
26
+ continue;
27
+ const id = extractSpecId(entry.name, core);
28
+ if (!id)
29
+ continue;
30
+ const rel = full.slice(coreDir.length + 1);
31
+ const inCanonical = /^(?:specs|ux|archive)[\\/]/.test(rel);
32
+ if (!inCanonical) {
33
+ out.push({ id, path: full, expectedDir: join(coreDir, 'specs') });
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return out;
39
+ }
40
+ //# sourceMappingURL=placement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.js","sourceRoot":"","sources":["../../src/lib/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAIjE,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QACnC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;YACzB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9B,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnC,IAAI,KAAK,CAAC,cAAc,EAAE;oBAAE,SAAS,CAAC,+BAA+B;gBACrE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACjB,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,SAAS;gBAC1C,IAAI,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACxD,MAAM,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC3C,IAAI,CAAC,EAAE;oBAAE,SAAS;gBAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC3C,MAAM,WAAW,GAAG,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC3D,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,18 @@
1
+ /** One ranked hit from the lexical precedent search over specs/spec-index.json. */
2
+ export interface PrecedentHit {
3
+ id: string;
4
+ title: string;
5
+ file: string;
6
+ core: string;
7
+ status: string;
8
+ score: number;
9
+ matchedFields: string[];
10
+ }
11
+ /** Ranked lexical search over specs/spec-index.json (no markdown re-parse).
12
+ * Throws when the index is missing — callers that must fail open catch. */
13
+ export declare function searchSpecs(topic: string, opts: {
14
+ core?: string;
15
+ repoRoot: string;
16
+ includeArchived?: boolean;
17
+ limit?: number;
18
+ }): PrecedentHit[];
@@ -0,0 +1,160 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const DEFAULT_LIMIT = 15;
4
+ // Score tiers, strongest first: word-boundary phrase > all-tokens > partial
5
+ // (per matched token, title x2). The body's all-tokens tier is cross-field-UNION
6
+ // based: tokens matched anywhere across the body fields are unioned, and the
7
+ // tier fires when that union covers every query token — no single field has to
8
+ // contain them all. Title and body are scored independently and summed, with
9
+ // one cross-field rule: when the TITLE's winning tier is the partial tier, the
10
+ // tokens it matched are "claimed" and earn no second partial credit in the body
11
+ // — otherwise a single token scattered across title + body (2 + 1) would tie or
12
+ // beat a genuine all-tokens body match (3). When the title wins via
13
+ // phrase/all-tokens, body partial credit is unrestricted.
14
+ const TITLE_WEIGHTS = { phrase: 10, all: 6, perToken: 2 };
15
+ const BODY_WEIGHTS = { phrase: 5, all: 3, perToken: 1 };
16
+ // Index body fields scored, in matchedFields reporting order (after 'title').
17
+ // `summary` is bounded Summary/Overview prose: the structured arrays index only a
18
+ // thin slice, so the domain vocabulary paraphrase queries hit lives here. Partial
19
+ // credit is per-distinct-query-token (capped at the token count), so a longer
20
+ // summary lifts recall without inflating any single spec's score past coverage.
21
+ const BODY_FIELDS = [
22
+ 'summary',
23
+ 'user_stories',
24
+ 'acceptance_criteria',
25
+ 'functional_requirements',
26
+ 'non_functional_requirements',
27
+ 'success_metrics',
28
+ ];
29
+ function escapeRegExp(s) {
30
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
31
+ }
32
+ /** Lowercased tokens; length >= 2 so acronyms (AI, EDI, MAT, UDS) survive. */
33
+ function tokenize(topic) {
34
+ return topic
35
+ .toLowerCase()
36
+ .split(/[^a-z0-9]+/)
37
+ .filter((t) => t.length >= 2);
38
+ }
39
+ /** Whole-topic phrase as a word-boundary regex; punctuation between words is
40
+ * flexible, so "credentialing compliance" matches "Credentialing & Compliance". */
41
+ function phraseRegex(tokens) {
42
+ if (tokens.length === 0)
43
+ return null;
44
+ return new RegExp(`(?:^|[^a-z0-9])${tokens.map(escapeRegExp).join('[^a-z0-9]+')}(?:$|[^a-z0-9])`);
45
+ }
46
+ function tokenRegexes(tokens) {
47
+ return tokens.map((t) => new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(t)}(?:$|[^a-z0-9])`));
48
+ }
49
+ /** Runtime guard for untrusted index fields: anything non-array becomes []. */
50
+ function asArray(value) {
51
+ return Array.isArray(value) ? value : [];
52
+ }
53
+ /** ACs carry given/when/then arrays instead of a .text field — flatten them. */
54
+ function acText(ac) {
55
+ if (!ac)
56
+ return '';
57
+ return [ac.title ?? '', ...asArray(ac.given), ...asArray(ac.when), ...asArray(ac.then)].join(' ');
58
+ }
59
+ function fieldText(spec, field) {
60
+ if (field === 'summary')
61
+ return spec.summary ?? '';
62
+ if (field === 'acceptance_criteria')
63
+ return asArray(spec.acceptance_criteria).map(acText).join('\n');
64
+ return asArray(spec[field])
65
+ .map((i) => i?.text ?? '')
66
+ .join('\n');
67
+ }
68
+ function isArchived(spec) {
69
+ return spec.status === 'archived' || (spec.file ?? '').includes('/archive/');
70
+ }
71
+ /** Token indexes (into the query token list) that word-boundary match the haystack. */
72
+ function matchedTokenIdxs(haystack, tokenRes) {
73
+ const matched = new Set();
74
+ if (!haystack)
75
+ return matched;
76
+ tokenRes.forEach((re, i) => {
77
+ if (re.test(haystack))
78
+ matched.add(i);
79
+ });
80
+ return matched;
81
+ }
82
+ /** Ranked lexical search over specs/spec-index.json (no markdown re-parse).
83
+ * Throws when the index is missing — callers that must fail open catch. */
84
+ export function searchSpecs(topic, opts) {
85
+ const indexPath = join(opts.repoRoot, 'specs', 'spec-index.json');
86
+ if (!existsSync(indexPath)) {
87
+ throw new Error(`spec index not found at ${indexPath} — run \`npm run spec:index\` first`);
88
+ }
89
+ const tokens = tokenize(topic);
90
+ if (tokens.length === 0)
91
+ return [];
92
+ const phrase = phraseRegex(tokens);
93
+ const tokenRes = tokenRegexes(tokens);
94
+ const core = opts.core?.toLowerCase();
95
+ const limit = opts.limit ?? DEFAULT_LIMIT;
96
+ const index = JSON.parse(readFileSync(indexPath, 'utf8'));
97
+ const hits = [];
98
+ for (const spec of asArray(index.specs)) {
99
+ if (!spec)
100
+ continue;
101
+ if (core && spec.core?.toLowerCase() !== core)
102
+ continue;
103
+ if (!opts.includeArchived && isArchived(spec))
104
+ continue;
105
+ // --- Title ---
106
+ const title = (spec.title ?? '').toLowerCase();
107
+ const titleTokens = matchedTokenIdxs(title, tokenRes);
108
+ const titleHolistic = Math.max(phrase?.test(title) ? TITLE_WEIGHTS.phrase : 0, titleTokens.size === tokens.length && tokens.length > 0 ? TITLE_WEIGHTS.all : 0);
109
+ const titlePartial = titleTokens.size * TITLE_WEIGHTS.perToken;
110
+ const titleScore = Math.max(titleHolistic, titlePartial);
111
+ // Partial tier won the title → those tokens are claimed (see weights note).
112
+ const titleClaimsTokens = titlePartial > titleHolistic;
113
+ // --- Body (per-field, then unioned) ---
114
+ const fieldTexts = BODY_FIELDS.map((f) => fieldText(spec, f).toLowerCase());
115
+ const fieldPhrase = fieldTexts.map((t) => (t !== '' && phrase ? phrase.test(t) : false));
116
+ const fieldTokens = fieldTexts.map((t) => matchedTokenIdxs(t, tokenRes));
117
+ const bodyTokens = new Set(fieldTokens.flatMap((s) => [...s]));
118
+ const countedTokens = new Set([...bodyTokens].filter((i) => !(titleClaimsTokens && titleTokens.has(i))));
119
+ const bodyPhrase = fieldPhrase.some(Boolean);
120
+ const bodyAll = bodyTokens.size === tokens.length && tokens.length > 0;
121
+ const bodyPartial = countedTokens.size * BODY_WEIGHTS.perToken;
122
+ const bodyScore = Math.max(bodyPhrase ? BODY_WEIGHTS.phrase : 0, bodyAll ? BODY_WEIGHTS.all : 0, bodyPartial);
123
+ const score = titleScore + bodyScore;
124
+ if (score <= 0)
125
+ continue;
126
+ // matchedFields = title (if it scored) + body fields contributing to the
127
+ // WINNING body tier — a stray single token in another field is not a match.
128
+ const matchedFields = [];
129
+ if (titleScore > 0)
130
+ matchedFields.push('title');
131
+ if (bodyScore > 0) {
132
+ const tier = bodyPhrase && bodyScore === BODY_WEIGHTS.phrase
133
+ ? 'phrase'
134
+ : bodyAll && bodyScore === BODY_WEIGHTS.all
135
+ ? 'all'
136
+ : 'partial';
137
+ BODY_FIELDS.forEach((field, i) => {
138
+ const contributes = tier === 'phrase'
139
+ ? fieldPhrase[i]
140
+ : tier === 'all'
141
+ ? fieldTokens[i].size > 0
142
+ : [...fieldTokens[i]].some((t) => countedTokens.has(t));
143
+ if (contributes)
144
+ matchedFields.push(field);
145
+ });
146
+ }
147
+ hits.push({
148
+ id: spec.id,
149
+ title: spec.title ?? '',
150
+ file: spec.file ?? '',
151
+ core: spec.core,
152
+ status: spec.status ?? 'unknown',
153
+ score,
154
+ matchedFields,
155
+ });
156
+ }
157
+ hits.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
158
+ return hits.slice(0, limit);
159
+ }
160
+ //# sourceMappingURL=precedent-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precedent-search.js","sourceRoot":"","sources":["../../src/lib/precedent-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { PredicateRegistry } from './pipeline-schema.js';
2
+ /** The default named-predicate registry for the encoreos-1 pipeline. Each body is
3
+ * the EXACT `isDone` closure lifted from the current `PIPELINE` in pipeline-map.ts,
4
+ * plus the two `computeStage` cap predicates (`hasResearch`/`hasAcceptanceCriteria`,
5
+ * reused as caps). Field names verified against SpecFile/SpecSignals in specs.ts. */
6
+ export declare const DEFAULT_PREDICATES: PredicateRegistry;
@@ -0,0 +1,40 @@
1
+ /** The default named-predicate registry for the encoreos-1 pipeline. Each body is
2
+ * the EXACT `isDone` closure lifted from the current `PIPELINE` in pipeline-map.ts,
3
+ * plus the two `computeStage` cap predicates (`hasResearch`/`hasAcceptanceCriteria`,
4
+ * reused as caps). Field names verified against SpecFile/SpecSignals in specs.ts. */
5
+ export const DEFAULT_PREDICATES = {
6
+ // clarify-spec: CONTEXT.md is the clarify artifact when present (top-level flag).
7
+ hasContext: (s) => s.hasContext,
8
+ // validate-spec: <!-- validated: --> marker.
9
+ hasValidationSignoff: (s) => s.signals.hasValidationSignoff,
10
+ // ground-research (regulatedOnly) — also reused as the research cap predicate.
11
+ hasResearch: (s) => s.signals.hasResearch,
12
+ // spec-review.
13
+ hasSpecReview: (s) => s.signals.hasSpecReview,
14
+ // compliance-review (regulatedOnly).
15
+ hasComplianceSignoff: (s) => s.signals.hasComplianceSignoff,
16
+ // draft-acs: done when there are no user stories, or ACs are drafted.
17
+ acsDraftedOrNoStories: (s) => !s.signals.hasUserStories || s.signals.hasAcceptanceCriteria,
18
+ // generate-tests: ACs absent, OR an active generated suite / verified ACs / linked local test exists (B5 + C1).
19
+ testsGeneratedOrProven: (s) => !s.signals.hasAcceptanceCriteria ||
20
+ s.signals.hasActiveGeneratedTests ||
21
+ s.signals.hasVerifiedAcceptanceCriteria ||
22
+ s.signals.hasLinkedLocalTest,
23
+ // generate-tests (BUILDER layout): keys on hasGeneratedTests (e2e_tests LISTED)
24
+ // rather than the fs-verified hasActiveGeneratedTests — matching the retired
25
+ // builder fork's gate, and computable by a read-economy consumer (Spec Studio)
26
+ // that can't read the test files to confirm an active suite.
27
+ testsListedOrProven: (s) => !s.signals.hasAcceptanceCriteria ||
28
+ s.signals.hasGeneratedTests ||
29
+ s.signals.hasVerifiedAcceptanceCriteria ||
30
+ s.signals.hasLinkedLocalTest,
31
+ // a11y-audit (optional, completes ux_reviewed).
32
+ hasUxSignoff: (s) => s.signals.hasUxSignoff,
33
+ // create-plan (top-level flag).
34
+ hasPlan: (s) => s.hasPlan,
35
+ // generate-tasks (top-level flag).
36
+ hasTasks: (s) => s.hasTasks,
37
+ // AC cap predicate (computeStage): caps below tasks_generated when ACs are absent.
38
+ hasAcceptanceCriteria: (s) => s.signals.hasAcceptanceCriteria,
39
+ };
40
+ //# sourceMappingURL=predicate-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicate-registry.js","sourceRoot":"","sources":["../../src/lib/predicate-registry.ts"],"names":[],"mappings":"AAEA;;;sFAGsF;AACtF,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,kFAAkF;IAClF,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU;IAC/B,6CAA6C;IAC7C,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB;IAC3D,+EAA+E;IAC/E,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW;IACzC,eAAe;IACf,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;IAC7C,qCAAqC;IACrC,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB;IAC3D,sEAAsE;IACtE,qBAAqB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC,OAAO,CAAC,qBAAqB;IAC1F,gHAAgH;IAChH,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAC5B,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB;QAChC,CAAC,CAAC,OAAO,CAAC,uBAAuB;QACjC,CAAC,CAAC,OAAO,CAAC,6BAA6B;QACvC,CAAC,CAAC,OAAO,CAAC,kBAAkB;IAC9B,gFAAgF;IAChF,6EAA6E;IAC7E,+EAA+E;IAC/E,6DAA6D;IAC7D,mBAAmB,EAAE,CAAC,CAAC,EAAE,EAAE,CACzB,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB;QAChC,CAAC,CAAC,OAAO,CAAC,iBAAiB;QAC3B,CAAC,CAAC,OAAO,CAAC,6BAA6B;QACvC,CAAC,CAAC,OAAO,CAAC,kBAAkB;IAC9B,gDAAgD;IAChD,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY;IAC3C,gCAAgC;IAChC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO;IACzB,mCAAmC;IACnC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ;IAC3B,mFAAmF;IACnF,qBAAqB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB;CAC9D,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The 8 precedent-recall benchmark cases (8 exact-wording + 8 paraphrase queries),
3
+ * from the 2026-06-09 dogfood. Shared by the lexical recall test, the semantic
4
+ * recall test, and the embedding build script (which embeds these queries into the
5
+ * committed `recall-query-vectors.json` fixture) so all three stay in lockstep.
6
+ *
7
+ * Targets are matched by FILE, not id (RH-06-EN-3 carries the parent id RH-06).
8
+ * If a target spec is renamed, update its `targetFile`; never delete a case.
9
+ */
10
+ export interface RecallCase {
11
+ exact: string;
12
+ paraphrase: string;
13
+ targetFile: string;
14
+ }
15
+ export declare const RECALL_CASES: RecallCase[];
@@ -0,0 +1,39 @@
1
+ export const RECALL_CASES = [
2
+ {
3
+ exact: 'exit interview',
4
+ paraphrase: 'staff offboarding termination',
5
+ targetFile: 'HR-41-employee-separation-exit-interview-management.md',
6
+ },
7
+ {
8
+ exact: 'fixed assets depreciation',
9
+ paraphrase: 'capital equipment amortization',
10
+ targetFile: 'FA-11-fixed-assets-depreciation.md',
11
+ },
12
+ {
13
+ exact: 'fire inspection',
14
+ paraphrase: 'building safety walkthrough',
15
+ targetFile: 'RH-06-EN-3-fire-inspection-compliance.md',
16
+ },
17
+ {
18
+ exact: 'denial prediction',
19
+ paraphrase: 'claim rejection forecasting',
20
+ targetFile: 'PM-50-ai-denial-prediction-prevention.md',
21
+ },
22
+ {
23
+ exact: 'consent management',
24
+ paraphrase: 'patient authorization privacy releases',
25
+ targetFile: 'CL-11-consent-management-42cfr-part2.md',
26
+ },
27
+ {
28
+ exact: 'predictive maintenance',
29
+ paraphrase: 'equipment failure forecasting',
30
+ targetFile: 'FM-28-predictive-maintenance-ai.md',
31
+ },
32
+ { exact: 'incident reporting', paraphrase: 'adverse event tracking', targetFile: 'GR-09-incident-reporting.md' },
33
+ {
34
+ exact: 'tamper-evident hash chain',
35
+ paraphrase: 'immutable audit log integrity',
36
+ targetFile: 'FW-62-audit-trail-tamper-evident-hash-chain.md',
37
+ },
38
+ ];
39
+ //# sourceMappingURL=recall-cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recall-cases.js","sourceRoot":"","sources":["../../src/lib/recall-cases.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC;QACE,KAAK,EAAE,gBAAgB;QACvB,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,wDAAwD;KACrE;IACD;QACE,KAAK,EAAE,2BAA2B;QAClC,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,oCAAoC;KACjD;IACD;QACE,KAAK,EAAE,iBAAiB;QACxB,UAAU,EAAE,6BAA6B;QACzC,UAAU,EAAE,0CAA0C;KACvD;IACD;QACE,KAAK,EAAE,mBAAmB;QAC1B,UAAU,EAAE,6BAA6B;QACzC,UAAU,EAAE,0CAA0C;KACvD;IACD;QACE,KAAK,EAAE,oBAAoB;QAC3B,UAAU,EAAE,wCAAwC;QACpD,UAAU,EAAE,yCAAyC;KACtD;IACD;QACE,KAAK,EAAE,wBAAwB;QAC/B,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,oCAAoC;KACjD;IACD,EAAE,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,wBAAwB,EAAE,UAAU,EAAE,6BAA6B,EAAE;IAChH;QACE,KAAK,EAAE,2BAA2B;QAClC,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,gDAAgD;KAC7D;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ export declare function getRepoRoot(override?: string): string;
2
+ export declare function specsRoot(repoRoot: string): string;
3
+ /**
4
+ * Canonical fallback core ids. Mirrors `scripts/database/core-prefixes.ts` (CORE_PREFIXES).
5
+ * A direct cross-workspace import is not possible: this package compiles with
6
+ * `rootDir: ./src` (TS6059 on any import outside src/), and `bin/eos-spec.js` runs compiled
7
+ * `dist/` under plain node, which cannot load a `.ts` file from `scripts/` at runtime.
8
+ * Drift protection: repo.test.ts parses core-prefixes.ts at test time and asserts equality.
9
+ */
10
+ export declare const FALLBACK_CORES: string[];
11
+ export declare function coresList(repoRoot?: string): string[];
12
+ /**
13
+ * Membership check against the configured core list — intentionally NOT dir-filtered:
14
+ * `create` must accept the first spec of a new core before its specs/{core}/ dir exists.
15
+ */
16
+ export declare function isKnownCore(core: string, repoRoot?: string): boolean;
17
+ /** Current git branch for repoRoot, or '' if undeterminable. */
18
+ export declare function currentBranch(repoRoot: string): string;
19
+ /** True on main/master — commands that mutate tracked files must refuse to --write here. */
20
+ export declare function isProtectedBranch(repoRoot: string): boolean;
@@ -0,0 +1,82 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { coresFromConfig, isKnownCoreFromConfig, loadConfig } from './config.js';
5
+ let cachedRepoRoot = null;
6
+ export function getRepoRoot(override) {
7
+ if (override) {
8
+ if (!existsSync(override)) {
9
+ throw new Error(`Repo root override does not exist: ${override}`);
10
+ }
11
+ return override;
12
+ }
13
+ if (cachedRepoRoot)
14
+ return cachedRepoRoot;
15
+ try {
16
+ cachedRepoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
17
+ return cachedRepoRoot;
18
+ }
19
+ catch {
20
+ throw new Error('Could not determine repo root. Run from inside the encoreos repo or pass --repo-root.');
21
+ }
22
+ }
23
+ export function specsRoot(repoRoot) {
24
+ return join(repoRoot, 'specs');
25
+ }
26
+ /**
27
+ * Canonical fallback core ids. Mirrors `scripts/database/core-prefixes.ts` (CORE_PREFIXES).
28
+ * A direct cross-workspace import is not possible: this package compiles with
29
+ * `rootDir: ./src` (TS6059 on any import outside src/), and `bin/eos-spec.js` runs compiled
30
+ * `dist/` under plain node, which cannot load a `.ts` file from `scripts/` at runtime.
31
+ * Drift protection: repo.test.ts parses core-prefixes.ts at test time and asserts equality.
32
+ */
33
+ export const FALLBACK_CORES = ['pf', 'ce', 'rh', 'fa', 'hr', 'gr', 'fw', 'fm', 'lo', 'it', 'cl', 'pm'];
34
+ /** Keep only cores that actually have a `specs/{core}/` directory. specs/config.yaml lists
35
+ * `tr` (transcription module), which has no specs dir — scanning it yields phantom empty cores. */
36
+ function withSpecsDir(cores, repoRoot) {
37
+ return cores.filter((core) => existsSync(join(repoRoot, 'specs', core)));
38
+ }
39
+ export function coresList(repoRoot) {
40
+ if (repoRoot) {
41
+ try {
42
+ const config = loadConfig(repoRoot);
43
+ return withSpecsDir(coresFromConfig(config), repoRoot);
44
+ }
45
+ catch {
46
+ // config unreadable — fall back to the canonical list, still dir-filtered
47
+ }
48
+ return withSpecsDir(FALLBACK_CORES, repoRoot);
49
+ }
50
+ return FALLBACK_CORES;
51
+ }
52
+ /**
53
+ * Membership check against the configured core list — intentionally NOT dir-filtered:
54
+ * `create` must accept the first spec of a new core before its specs/{core}/ dir exists.
55
+ */
56
+ export function isKnownCore(core, repoRoot) {
57
+ if (repoRoot) {
58
+ try {
59
+ const config = loadConfig(repoRoot);
60
+ return isKnownCoreFromConfig(config, core);
61
+ }
62
+ catch {
63
+ // fall through
64
+ }
65
+ }
66
+ return FALLBACK_CORES.includes(core.toLowerCase());
67
+ }
68
+ /** Current git branch for repoRoot, or '' if undeterminable. */
69
+ export function currentBranch(repoRoot) {
70
+ try {
71
+ return execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();
72
+ }
73
+ catch {
74
+ return '';
75
+ }
76
+ }
77
+ /** True on main/master — commands that mutate tracked files must refuse to --write here. */
78
+ export function isProtectedBranch(repoRoot) {
79
+ const b = currentBranch(repoRoot);
80
+ return b === 'main' || b === 'master';
81
+ }
82
+ //# sourceMappingURL=repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjF,IAAI,cAAc,GAAkB,IAAI,CAAC;AAEzC,MAAM,UAAU,WAAW,CAAC,QAAiB;IAC3C,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,QAAQ,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAC1C,IAAI,CAAC;QACH,cAAc,GAAG,QAAQ,CAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxF,OAAO,cAAc,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uFAAuF,CAAC,CAAC;IAC3G,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEvG;mGACmG;AACnG,SAAS,YAAY,CAAC,KAAe,EAAE,QAAgB;IACrD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAiB;IACzC,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO,YAAY,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;QAC5E,CAAC;QACD,OAAO,YAAY,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,QAAiB;IACzD,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;IACD,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,QAAQ,CAAC;AACxC,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Typed acceptance-criteria / user-story / functional-requirement schema for spec
3
+ * frontmatter, plus a lenient normalizer. Adapted from encoreos's
4
+ * `ac-response-parser.ts`, but reads already-parsed YAML frontmatter values
5
+ * (objects) rather than an LLM JSON response, and coerces scalar Given/When/Then
6
+ * strings into single-element arrays so scalar-shaped legacy specs parse unchanged.
7
+ */
8
+ /**
9
+ * AC/US/FR status lifecycle. Two of these are terminal-but-not-auto-proven and
10
+ * the audit's "AC Validated" gate treats them as closed (TOOL — issue #174):
11
+ * - `hand_closed`: proven by a CI suite the src-lane `verify-acs` prover can't
12
+ * reach (runner/tool/e2e). REQUIRES a `## Verification` backlink in the spec
13
+ * (the audit flags a hand_closed AC with no Verification section as a violation).
14
+ * - `deferred`: an explicit planned-by-design non-goal.
15
+ * A genuinely un-proven AC still reads `planned` and still fails the gate.
16
+ */
17
+ export type RequirementStatus = 'planned' | 'verified' | 'hand_closed' | 'deferred';
18
+ /** The status values the audit's "AC Validated" gate accepts as closed
19
+ * (everything except `planned`). */
20
+ export declare const TERMINAL_AC_STATUSES: readonly RequirementStatus[];
21
+ export type AcceptanceCriterion = {
22
+ id: string;
23
+ userStory?: string;
24
+ status: RequirementStatus;
25
+ given: string[];
26
+ when: string[];
27
+ then: string[];
28
+ statementOnly: boolean;
29
+ };
30
+ export type UserStory = {
31
+ id: string;
32
+ text: string;
33
+ };
34
+ export type Requirement = {
35
+ id: string;
36
+ text: string;
37
+ status: RequirementStatus;
38
+ };
39
+ /** A structural problem found while normalizing — mapped to a ValidateIssue by the caller. */
40
+ export type ShapeIssue = {
41
+ id?: string;
42
+ field: string;
43
+ message: string;
44
+ };
45
+ export declare const AC_ID_RE: RegExp;
46
+ export declare const US_ID_RE: RegExp;
47
+ export declare const FR_ID_RE: RegExp;
48
+ /** Scalar string -> `[string]`; array -> trimmed non-empty strings; anything else -> `[]`. */
49
+ export declare function coerceStringArray(v: unknown): string[];
50
+ /**
51
+ * Normalizes a raw YAML frontmatter `acceptance_criteria` value into typed
52
+ * `AcceptanceCriterion` objects, collecting `ShapeIssue`s for any structural
53
+ * problems found. An entry with an unrecognized status is still included with
54
+ * `status` defaulted to `'planned'` plus a ShapeIssue — callers must check
55
+ * `issues` before trusting `status`.
56
+ */
57
+ export declare function normalizeAcceptanceCriteria(raw: unknown): {
58
+ acs: AcceptanceCriterion[];
59
+ issues: ShapeIssue[];
60
+ };
61
+ export declare function normalizeUserStories(raw: unknown): {
62
+ uss: UserStory[];
63
+ issues: ShapeIssue[];
64
+ };
65
+ export declare function normalizeRequirements(raw: unknown): {
66
+ frs: Requirement[];
67
+ issues: ShapeIssue[];
68
+ };
69
+ /** Per-AC status tally for the pipeline status reader (consumed by the P3 audit).
70
+ * `hand_closed` and `deferred` are terminal-but-not-`planned` statuses the audit
71
+ * treats as closed (issue #174); they are counted separately so the audit can
72
+ * still apply the Verification-backlink guard to `hand_closed`. */
73
+ export declare function acStatusCounts(raw: unknown): AcStatusCounts;
74
+ export type AcStatusCounts = {
75
+ planned: number;
76
+ verified: number;
77
+ handClosed: number;
78
+ deferred: number;
79
+ };
80
+ /**
81
+ * True when a spec has ≥1 acceptance criterion and EVERY (well-formed) AC is in a
82
+ * terminal status (`verified` | `hand_closed` | `deferred`) — i.e. none remain
83
+ * `planned`. This is the typed, ADR-0015-aware replacement for the legacy
84
+ * `status === 'verified'` check `specs.ts` used to hand-roll: a `hand_closed`
85
+ * (proven by a CI suite the src-lane prover can't reach) or `deferred` AC
86
+ * terminally resolves the generate-tests gate exactly as a `verified` one does
87
+ * (issue #174). Malformed (id-less) AC entries are dropped by
88
+ * `normalizeAcceptanceCriteria` — and `validate` flags them — so they are not
89
+ * counted here; an empty / non-array value is `false`.
90
+ */
91
+ export declare function allAcceptanceCriteriaTerminal(raw: unknown): boolean;