@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
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Copyright (c) 2026 Encore-OS. All rights reserved.
2
+
3
+ This software and the associated documentation files (the "Software") are the
4
+ proprietary and confidential property of Encore-OS. The Software is licensed,
5
+ not sold, for internal use within Encore-OS and its authorized projects only.
6
+
7
+ No license, express or implied, by estoppel or otherwise, is granted to any
8
+ third party to use, copy, modify, merge, publish, distribute, sublicense, or
9
+ sell copies of the Software, in whole or in part, without the prior written
10
+ permission of Encore-OS.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
14
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ENCORE-OS BE
15
+ LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF
16
+ CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
17
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ /** Newest mtime (ms) among .ts files under dir, recursively. 0 when none/unreadable. */
5
+ function newestTsMtime(dir) {
6
+ let newest = 0;
7
+ let entries;
8
+ try {
9
+ entries = readdirSync(dir, { withFileTypes: true });
10
+ } catch {
11
+ return newest;
12
+ }
13
+ for (const e of entries) {
14
+ const full = join(dir, e.name);
15
+ try {
16
+ if (e.isDirectory()) {
17
+ newest = Math.max(newest, newestTsMtime(full));
18
+ } else if (e.name.endsWith('.ts')) {
19
+ newest = Math.max(newest, statSync(full).mtimeMs);
20
+ }
21
+ } catch {
22
+ // unreadable entry — this check must never break the CLI
23
+ }
24
+ }
25
+ return newest;
26
+ }
27
+
28
+ /**
29
+ * One-line warning when dist/index.js is missing or older than the newest
30
+ * src/**\/*.ts, else null. The bin runs compiled dist/ that only rebuilds on
31
+ * `npm ci`, so src edits are silently invisible without this hint. Detect and
32
+ * warn only: never auto-builds, never throws (fail-open).
33
+ */
34
+ export function distStalenessWarning(pkgRoot) {
35
+ try {
36
+ const srcMtime = newestTsMtime(join(pkgRoot, 'src'));
37
+ if (srcMtime === 0) return null; // no sources to compare (published artifact)
38
+ let distMtime;
39
+ try {
40
+ distMtime = statSync(join(pkgRoot, 'dist', 'index.js')).mtimeMs;
41
+ } catch {
42
+ return 'eos-spec: dist/ build missing — run `npm --workspace tools/eos-spec run build` first.';
43
+ }
44
+ if (srcMtime > distMtime) {
45
+ return 'eos-spec: warning — dist/ is stale (older than src/); run `npm --workspace tools/eos-spec run build` to pick up source changes.';
46
+ }
47
+ return null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // Runtime-neutral: Node and Bun both import the compiled ESM in dist/.
3
+ const { run } = await import('../dist/index.js');
4
+ run(process.argv).catch((err) => {
5
+ console.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
6
+ process.exit(1);
7
+ });
@@ -0,0 +1,31 @@
1
+ import type { Command } from 'commander';
2
+ import { type UserStory } from '../lib/ac-draft-prompt.js';
3
+ import { type AcEntry } from '../lib/frontmatter.js';
4
+ import { type SpecFile } from '../lib/specs.js';
5
+ export type CallModel = (prompt: string) => Promise<string>;
6
+ export declare function isBackfillTarget(spec: SpecFile): boolean;
7
+ export type Tranche = {
8
+ targets: SpecFile[];
9
+ skipped: SpecFile[];
10
+ };
11
+ export declare function planTranche(specs: SpecFile[], opts?: {
12
+ limit?: number;
13
+ }): Tranche;
14
+ export type DraftInput = {
15
+ specId: string;
16
+ core: string;
17
+ title: string;
18
+ userStories: UserStory[];
19
+ research?: string;
20
+ existingAcCount: number;
21
+ callModel: CallModel;
22
+ };
23
+ export type DraftResult = {
24
+ ok: true;
25
+ acs: AcEntry[];
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
30
+ export declare function draftOneSpec(input: DraftInput): Promise<DraftResult>;
31
+ export declare function registerAcBackfill(program: Command, callModel?: CallModel): void;
@@ -0,0 +1,198 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { buildAcDraftPrompt } from '../lib/ac-draft-prompt.js';
4
+ import { parseAcResponse } from '../lib/ac-response-parser.js';
5
+ import { logAiCall } from '../lib/ai-call.js';
6
+ import { appendAcceptanceCriteria } from '../lib/frontmatter.js';
7
+ import { emit, getGlobalOptions } from '../lib/output.js';
8
+ import { getRepoRoot } from '../lib/repo.js';
9
+ import { listSpecs } from '../lib/specs.js';
10
+ import { extractUserStories } from '../lib/user-stories.js';
11
+ import { withEventLog } from '../lib/with-event-log.js';
12
+ const AC_DRAFT_MODEL = process.env.AC_DRAFT_MODEL ?? process.env.JUDGE_MODEL ?? 'anthropic/claude-sonnet-4';
13
+ /** Lazily load the OPTIONAL `ai` SDK — only `ac-backfill` (1 of 29 commands) needs
14
+ * it, so it is an optionalDependency + dynamic import: the other 28 commands and
15
+ * any `/pure` browser consumer never pull the AI SDK into their graph. */
16
+ async function loadGenerateText() {
17
+ try {
18
+ return (await import('ai')).generateText;
19
+ }
20
+ catch {
21
+ throw new Error('ac-backfill needs the optional `ai` package to draft ACs. Install it: `npm i ai` (or `bun add ai`).');
22
+ }
23
+ }
24
+ const defaultCallModel = async (prompt) => {
25
+ const start = Date.now();
26
+ const generateText = await loadGenerateText();
27
+ try {
28
+ const result = await generateText({ model: AC_DRAFT_MODEL, prompt });
29
+ const u = result.usage;
30
+ const promptCount = u.promptTokens ?? u.inputTokens ?? 0;
31
+ const completionCount = u.completionTokens ?? u.outputTokens ?? 0;
32
+ const tokens = {
33
+ prompt: promptCount,
34
+ completion: completionCount,
35
+ total: u.totalTokens ?? promptCount + completionCount,
36
+ };
37
+ logAiCall({ command: 'ac-backfill', model: AC_DRAFT_MODEL, tokens, ms: Date.now() - start, ok: true });
38
+ return result.text;
39
+ }
40
+ catch (e) {
41
+ logAiCall({
42
+ command: 'ac-backfill',
43
+ model: AC_DRAFT_MODEL,
44
+ ms: Date.now() - start,
45
+ ok: false,
46
+ error: e instanceof Error ? e.message : String(e),
47
+ });
48
+ throw e;
49
+ }
50
+ };
51
+ export function isBackfillTarget(spec) {
52
+ if (spec.archived)
53
+ return false;
54
+ if (spec.status && /^✅|complete/i.test(spec.status))
55
+ return false;
56
+ return spec.signals.hasUserStories && !spec.signals.hasAcceptanceCriteria;
57
+ }
58
+ export function planTranche(specs, opts = {}) {
59
+ const targets = [];
60
+ const skipped = [];
61
+ for (const s of specs) {
62
+ if (isBackfillTarget(s)) {
63
+ if (opts.limit === undefined || targets.length < opts.limit)
64
+ targets.push(s);
65
+ }
66
+ else if (s.signals.hasUserStories) {
67
+ skipped.push(s);
68
+ }
69
+ }
70
+ return { targets, skipped };
71
+ }
72
+ export async function draftOneSpec(input) {
73
+ const prompt = buildAcDraftPrompt({
74
+ specId: input.specId,
75
+ core: input.core,
76
+ title: input.title,
77
+ userStories: input.userStories,
78
+ research: input.research,
79
+ existingAcCount: input.existingAcCount,
80
+ });
81
+ const raw = await input.callModel(prompt);
82
+ const parsed = parseAcResponse(raw);
83
+ if (!parsed.ok)
84
+ return { ok: false, error: parsed.error };
85
+ return { ok: true, acs: parsed.acs };
86
+ }
87
+ /** Read minimal context for drafting from the spec file on disk. */
88
+ function readSpecContext(spec) {
89
+ const text = readFileSync(spec.path, 'utf8');
90
+ const stories = extractUserStories(text);
91
+ const acMatches = [...text.matchAll(/^\s*-\s*id:\s*AC-(\d+)/gim)];
92
+ const existingAcCount = acMatches.reduce((max, m) => Math.max(max, parseInt(m[1], 10)), 0);
93
+ const researchPath = spec.path.replace(/\/specs\/[^/]+\.md$/, `/research/${spec.id}-RESEARCH.md`);
94
+ let research;
95
+ try {
96
+ research = readFileSync(researchPath, 'utf8');
97
+ }
98
+ catch {
99
+ /* no research */
100
+ }
101
+ return { userStories: stories, research, existingAcCount };
102
+ }
103
+ export function registerAcBackfill(program, callModel = defaultCallModel) {
104
+ program
105
+ .command('ac-backfill')
106
+ .description('Draft Gherkin ACs for storied-but-no-ACs specs in one core (AI-gateway-backed; opens one PR).')
107
+ .requiredOption('--core <core>', 'Target core (cl, pm, hr, …)')
108
+ .option('--limit <n>', 'Stop after drafting N specs (default: all targets in the core)')
109
+ .option('--dry-run', 'Plan only — no AI calls, no writes, no commits', false)
110
+ .action(withEventLog('ac-backfill', async (options, cmd) => {
111
+ const global = getGlobalOptions(cmd);
112
+ const repoRoot = getRepoRoot(global.repoRoot);
113
+ const all = listSpecs(repoRoot, options.core);
114
+ const limit = options.limit ? parseInt(options.limit, 10) : undefined;
115
+ const plan = planTranche(all, { limit });
116
+ if (options.dryRun) {
117
+ emit(global, {
118
+ ok: true,
119
+ data: {
120
+ core: options.core,
121
+ targets: plan.targets.map((s) => s.id),
122
+ skipped: plan.skipped.length,
123
+ dryRun: true,
124
+ },
125
+ text: `ac-backfill (dry-run, ${options.core}): ${plan.targets.length} target(s), ${plan.skipped.length} skipped (have ACs already)\n` +
126
+ plan.targets.map((s) => ` - ${s.id}`).join('\n'),
127
+ });
128
+ return;
129
+ }
130
+ if (!process.env.AI_GATEWAY_API_KEY) {
131
+ emit(global, {
132
+ ok: false,
133
+ data: null,
134
+ text: 'ac-backfill: AI_GATEWAY_API_KEY not set; pass --dry-run to plan only.',
135
+ });
136
+ return;
137
+ }
138
+ const drafted = [];
139
+ const failed = [];
140
+ for (const spec of plan.targets) {
141
+ const ctx = readSpecContext(spec);
142
+ if (ctx.userStories.length === 0) {
143
+ // Targeted by deriveSignals (has a "User Stories" heading) but no stories are
144
+ // extractable in a supported format — skip before spending an AI call.
145
+ failed.push({ id: spec.id, error: 'no extractable user stories (unsupported story format)' });
146
+ continue;
147
+ }
148
+ const r = await draftOneSpec({
149
+ specId: spec.id,
150
+ core: spec.core,
151
+ title: spec.title ?? spec.id,
152
+ userStories: ctx.userStories,
153
+ research: ctx.research,
154
+ existingAcCount: ctx.existingAcCount,
155
+ callModel,
156
+ });
157
+ if (!r.ok) {
158
+ failed.push({ id: spec.id, error: r.error });
159
+ continue;
160
+ }
161
+ const text = readFileSync(spec.path, 'utf8');
162
+ const updated = appendAcceptanceCriteria(text, r.acs);
163
+ if (updated === text) {
164
+ // Draft produced no usable ACs (empty / unparseable response) — skipping avoids
165
+ // an empty commit that would otherwise abort the whole campaign.
166
+ failed.push({ id: spec.id, error: 'no ACs produced (empty/unparseable draft)' });
167
+ continue;
168
+ }
169
+ writeFileSync(spec.path, updated, 'utf8');
170
+ try {
171
+ execFileSync('git', ['add', spec.path], { cwd: repoRoot });
172
+ execFileSync('git', ['commit', '-m', `feat(${spec.core}): draft ACs for ${spec.id} [ai-draft]`], {
173
+ cwd: repoRoot,
174
+ });
175
+ }
176
+ catch (e) {
177
+ // A pre-commit gate (or empty commit) rejected this spec — revert the working-tree
178
+ // edit and record the failure, but keep processing the rest of the tranche.
179
+ try {
180
+ execFileSync('git', ['checkout', '--', spec.path], { cwd: repoRoot });
181
+ }
182
+ catch {
183
+ /* best-effort revert */
184
+ }
185
+ failed.push({ id: spec.id, error: `commit rejected: ${e instanceof Error ? e.message : String(e)}` });
186
+ continue;
187
+ }
188
+ drafted.push(spec.id);
189
+ }
190
+ emit(global, {
191
+ ok: true,
192
+ data: { core: options.core, drafted, failed },
193
+ text: `ac-backfill (${options.core}): drafted=${drafted.length} failed=${failed.length}\n` +
194
+ (failed.length ? `failures:\n${failed.map((f) => ` - ${f.id}: ${f.error}`).join('\n')}` : ''),
195
+ });
196
+ }));
197
+ }
198
+ //# sourceMappingURL=ac-backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ac-backfill.js","sourceRoot":"","sources":["../../src/commands/ac-backfill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerArchive(program: Command): void;
@@ -0,0 +1,101 @@
1
+ import { archiveSpec, archiveSpecsByFilter, isArchiveEligible } from '../lib/archive.js';
2
+ import { logEvent, RUN_ID } from '../lib/events.js';
3
+ import { emit, emitContinuing, getGlobalOptions } from '../lib/output.js';
4
+ import { getRepoRoot } from '../lib/repo.js';
5
+ import { findSpec } from '../lib/specs.js';
6
+ import { withEventLog } from '../lib/with-event-log.js';
7
+ function followUpNotes(results) {
8
+ const archived = results.filter((r) => r.moved.length > 0).map((r) => r.specId);
9
+ if (archived.length === 0)
10
+ return '';
11
+ const ids = archived.join(', ');
12
+ return [
13
+ '',
14
+ 'Manual follow-up:',
15
+ ` - Update SPEC_STATUS_REGISTRY.md — mark ${ids} as 🗄️ Archived`,
16
+ ` - Remove ${ids} from active section of SPEC_INDEX.md (if present)`,
17
+ ` - Remove ${ids} from DEFERRED_DASHBOARD.md (if present)`,
18
+ ].join('\n');
19
+ }
20
+ export function registerArchive(program) {
21
+ program
22
+ .command('archive')
23
+ .description('Archive completed spec files to specs/{core}/archive/.')
24
+ .option('--spec <id>', 'Single spec id to archive (e.g. PF-37)')
25
+ .option('--core <core>', 'Limit bulk archive to one core')
26
+ .option('--status <status>', 'Only archive specs matching this status; "complete" also matches "implemented" (default: complete)', 'complete')
27
+ .option('--older-than <days>', 'Only archive specs whose last git commit is older than N days')
28
+ .option('--dry-run', 'Show what would be archived without moving files', false)
29
+ .option('--force', 'Override the status guard (archive a spec whose status is not archive-eligible)', false)
30
+ .action(withEventLog('archive', async (options, cmd) => {
31
+ const global = getGlobalOptions(cmd);
32
+ const repoRoot = getRepoRoot(global.repoRoot);
33
+ const logger = global.json || global.quiet ? null : (m) => emitContinuing(global, m);
34
+ if (options.spec) {
35
+ // Look up the spec to check eligibility (used by both dry-run and telemetry below).
36
+ const specForGuard = findSpec(repoRoot, options.spec);
37
+ const eligible = !specForGuard || isArchiveEligible(specForGuard.status);
38
+ if (options.dryRun) {
39
+ // Fix: honour the status guard in dry-run so the reported outcome matches a real run.
40
+ const willArchive = options.force || eligible;
41
+ emit(global, {
42
+ ok: willArchive,
43
+ data: {
44
+ specId: options.spec,
45
+ dryRun: true,
46
+ moved: [],
47
+ skipped: [],
48
+ ...(willArchive ? {} : { refused: true }),
49
+ },
50
+ text: willArchive
51
+ ? `archive (dry-run): would archive ${options.spec}`
52
+ : `archive (dry-run): would refuse ${options.spec} — status "${specForGuard?.status ?? '(none)'}" is not archive-eligible; pass --force to override`,
53
+ });
54
+ }
55
+ const result = archiveSpec(repoRoot, options.spec, logger, global.config, { force: options.force });
56
+ // Gate telemetry (design §4.6): emit whenever the status guard was exercised.
57
+ if (!eligible) {
58
+ logEvent({
59
+ ts: new Date().toISOString(),
60
+ command: 'archive',
61
+ runId: RUN_ID,
62
+ ms: 0,
63
+ ok: !result.refused,
64
+ specId: result.specId,
65
+ gate: 'archive-status-guard',
66
+ verdict: 'block',
67
+ overridden: options.force === true,
68
+ });
69
+ }
70
+ emit(global, {
71
+ ok: result.refused ? false : result.moved.length > 0,
72
+ data: result,
73
+ text: result.refused
74
+ ? `archive: refused ${result.specId} — ${result.reason}; pass --force to override`
75
+ : (result.moved.length > 0
76
+ ? `archive: moved ${result.moved.length} file(s) for ${result.specId}`
77
+ : `archive: nothing to move for ${result.specId}${result.skipped.length ? ` (${result.skipped.length} already archived)` : ''}`) +
78
+ followUpNotes([result]),
79
+ });
80
+ }
81
+ const olderThanDays = options.olderThan ? parseInt(options.olderThan, 10) : undefined;
82
+ const results = archiveSpecsByFilter(repoRoot, {
83
+ core: options.core,
84
+ status: options.status,
85
+ olderThanDays,
86
+ dryRun: options.dryRun,
87
+ force: options.force,
88
+ }, logger, global.config);
89
+ const totalMoved = results.reduce((n, r) => n + r.moved.length, 0);
90
+ const refusedCount = results.filter((r) => r.refused).length;
91
+ emit(global, {
92
+ ok: true,
93
+ data: { results, totalMoved, dryRun: options.dryRun },
94
+ text: (options.dryRun
95
+ ? `archive (dry-run): ${results.filter((r) => !r.refused).length} spec(s) would be archived${refusedCount ? ` (${refusedCount} refused by status guard)` : ''}`
96
+ : `archive: ${totalMoved} file(s) moved across ${results.filter((r) => r.moved.length > 0).length} spec(s)${refusedCount ? ` (${refusedCount} refused by status guard; --force to override)` : ''}`) +
97
+ followUpNotes(results),
98
+ });
99
+ }));
100
+ }
101
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/commands/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { Command } from 'commander';
2
+ import { type SpecFile } from '../lib/specs.js';
3
+ export type AuditRow = {
4
+ id: string;
5
+ status: string;
6
+ acValidated: '✅' | '❌';
7
+ backlinked: '✅' | '❌';
8
+ e2eListed: '✅' | '❌';
9
+ prodReady: '✅' | '❌';
10
+ /** Set when the row fails the audit for a reason the ✅/❌ columns can't show
11
+ * (e.g. a `hand_closed` AC with no `## Verification` backlink). */
12
+ note?: string;
13
+ };
14
+ export type AuditData = {
15
+ date: string;
16
+ rows: Array<AuditRow & {
17
+ core: string;
18
+ }>;
19
+ };
20
+ /**
21
+ * Filesystem-is-truth row for one spec. The "AC Validated" gate passes when every
22
+ * AC has reached a terminal state — `verified | hand_closed | deferred`, i.e.
23
+ * nothing left `planned` (issue #174) — with at least one AC authored. A
24
+ * `hand_closed` AC must additionally be backed by a `## Verification` section;
25
+ * a hand_closed AC with no Verification backlink is itself a violation that fails
26
+ * the gate and is surfaced via `note`.
27
+ */
28
+ export declare function buildAuditRow(spec: SpecFile, backlinkedIds: Set<string>): AuditRow;
29
+ export declare function collectAuditData(repoRoot: string, date: string): AuditData;
30
+ export declare function registerAudit(program: Command): void;
@@ -0,0 +1,107 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { emit, getGlobalOptions } from '../lib/output.js';
4
+ import { coresList, getRepoRoot, isProtectedBranch } from '../lib/repo.js';
5
+ import { listSpecs } from '../lib/specs.js';
6
+ import { scanVerifies } from '../lib/verifies-scan.js';
7
+ /**
8
+ * Filesystem-is-truth row for one spec. The "AC Validated" gate passes when every
9
+ * AC has reached a terminal state — `verified | hand_closed | deferred`, i.e.
10
+ * nothing left `planned` (issue #174) — with at least one AC authored. A
11
+ * `hand_closed` AC must additionally be backed by a `## Verification` section;
12
+ * a hand_closed AC with no Verification backlink is itself a violation that fails
13
+ * the gate and is surfaced via `note`.
14
+ */
15
+ export function buildAuditRow(spec, backlinkedIds) {
16
+ const ac = spec.signals.acStatus ?? { planned: 0, verified: 0, handClosed: 0, deferred: 0 };
17
+ const total = ac.planned + ac.verified + ac.handClosed + ac.deferred;
18
+ // A hand_closed AC asserts a CI suite the src-lane prover can't reach proved it;
19
+ // that assertion is only legible if the spec carries a `## Verification` backlink.
20
+ const handClosedUnbacked = ac.handClosed > 0 && !spec.signals.hasVerificationSection;
21
+ const allTerminal = total > 0 && ac.planned === 0;
22
+ const acValidated = allTerminal && !handClosedUnbacked ? '✅' : '❌';
23
+ const backlinked = backlinkedIds.size > 0 ? '✅' : '❌';
24
+ const e2eListed = spec.signals.hasGeneratedTests ? '✅' : '❌';
25
+ const prodReady = acValidated === '✅' && e2eListed === '✅' ? '✅' : '❌';
26
+ const note = handClosedUnbacked
27
+ ? `${ac.handClosed} hand_closed AC(s) without a "## Verification" section`
28
+ : undefined;
29
+ return {
30
+ id: spec.id,
31
+ status: spec.status ?? 'unknown',
32
+ acValidated,
33
+ backlinked,
34
+ e2eListed,
35
+ prodReady,
36
+ ...(note ? { note } : {}),
37
+ };
38
+ }
39
+ export function collectAuditData(repoRoot, date) {
40
+ const verifies = scanVerifies(repoRoot);
41
+ const rows = [];
42
+ for (const core of coresList(repoRoot)) {
43
+ // `verifies` is scanned once here and consulted per spec for the Backlinked
44
+ // column; the @verifies map is not threaded into listSpecs (this engine's
45
+ // listSpecs derives its own on-disk backlink signals).
46
+ const specs = listSpecs(repoRoot, core);
47
+ for (const s of specs.sort((a, b) => a.id.localeCompare(b.id))) {
48
+ rows.push({ core, ...buildAuditRow(s, verifies.get(s.id) ?? new Set()) });
49
+ }
50
+ }
51
+ return { date, rows };
52
+ }
53
+ function renderMarkdown(data) {
54
+ const lines = [
55
+ `# Completion & Readiness Audit`,
56
+ ``,
57
+ `**Date:** ${data.date} · **Method:** filesystem-is-truth (eos-spec audit). Every ✅ is backed by on-disk evidence; no hand-edited green.`,
58
+ ``,
59
+ ];
60
+ const cores = [...new Set(data.rows.map((r) => r.core))];
61
+ for (const core of cores) {
62
+ const coreRows = data.rows.filter((r) => r.core === core);
63
+ lines.push(`## ${core}`, ``, `| Spec | Status | AC Validated | Backlinked | E2E Listed | Prod Ready |`, `|------|--------|--------------|------------|------------|------------|`);
64
+ for (const r of coreRows) {
65
+ lines.push(`| ${r.id} | ${r.status} | ${r.acValidated} | ${r.backlinked} | ${r.e2eListed} | ${r.prodReady} |`);
66
+ }
67
+ lines.push(``);
68
+ const violations = coreRows.filter((r) => r.note);
69
+ if (violations.length > 0) {
70
+ lines.push(`> Audit violations:`);
71
+ for (const r of violations)
72
+ lines.push(`> - ${r.id}: ${r.note}`);
73
+ lines.push(``);
74
+ }
75
+ }
76
+ return lines.join('\n');
77
+ }
78
+ export function registerAudit(program) {
79
+ program
80
+ .command('audit')
81
+ .description('Render a filesystem-is-truth completion/readiness audit per core (no hand-edited green). --write saves a draft under docs/audits/ (refused on a protected branch).')
82
+ .option('--write', 'Write the audit to docs/audits/<date>-completion-audit.md (working-tree only; refused on main/master)', false)
83
+ .option('--date <date>', 'Override the date stamp (YYYY-MM-DD)')
84
+ .action((options, cmd) => {
85
+ const global = getGlobalOptions(cmd);
86
+ const repoRoot = getRepoRoot(global.repoRoot);
87
+ const date = options.date ?? new Date().toISOString().slice(0, 10);
88
+ const data = collectAuditData(repoRoot, date);
89
+ const md = renderMarkdown(data);
90
+ if (options.write) {
91
+ if (isProtectedBranch(repoRoot))
92
+ emit(global, {
93
+ ok: false,
94
+ data: {},
95
+ text: 'audit: refusing to --write on a protected branch (main); run on a feature branch / draft PR',
96
+ exitCode: 2,
97
+ });
98
+ const dir = join(repoRoot, 'docs', 'audits');
99
+ mkdirSync(dir, { recursive: true });
100
+ const out = join(dir, `${date}-completion-audit.md`);
101
+ writeFileSync(out, md);
102
+ emit(global, { ok: true, data: { ...data, written: out }, text: `audit: wrote ${out}` });
103
+ }
104
+ emit(global, { ok: true, data, text: md });
105
+ });
106
+ }
107
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Command } from 'commander';
2
+ import { type TierResult } from '../lib/classify-debt.js';
3
+ import { type DebtRow, type RelinkEntry } from '../lib/debt-dashboard.js';
4
+ import type { ScorecardReport, ScorecardRow } from '../lib/scorecard.js';
5
+ /** A computed debt row plus the raw classification + the spec path, for the command's apply path. */
6
+ export interface BackfillEntry {
7
+ row: DebtRow;
8
+ tier: TierResult;
9
+ scorecardRow: ScorecardRow;
10
+ specPath: string;
11
+ }
12
+ /**
13
+ * Rewrite ONLY the frontmatter `status:` line to `newStatus`, preserving the trailing newline —
14
+ * the exact single-line replacement approach as scripts/specs/remap-status-values.ts. Returns the
15
+ * text unchanged when no `status:` line is present. Pure (string in / string out) so the apply path
16
+ * is unit-testable without touching the real repo.
17
+ */
18
+ export declare function rewriteStatusLine(text: string, newStatus: string): {
19
+ text: string;
20
+ changed: boolean;
21
+ };
22
+ /** Append a one-line evidence receipt as an HTML comment near the frontmatter (after the closing `---`). */
23
+ export declare function appendEvidenceReceipt(text: string, receipt: string): string;
24
+ /** Apply a downgrade to one spec's text: rewrite status + append the receipt. Pure. */
25
+ export declare function applyDowngrade(text: string, newStatus: string, receipt: string): {
26
+ text: string;
27
+ changed: boolean;
28
+ };
29
+ /** Compute the list of missing verification artifacts for a claimed-complete spec. */
30
+ export declare function computeMissing(row: ScorecardRow, report: ScorecardReport, tier: TierResult): string[];
31
+ /** Effort heuristic: S for T1 (relink only); M for T2 with a small requirement count; L for T4 / large. */
32
+ export declare function computeEffort(tier: TierResult, requirementsTotal: number): 'S' | 'M' | 'L';
33
+ /**
34
+ * Build one BackfillEntry per claimed-complete spec in the core. Pure-ish: reads the filesystem via
35
+ * the scorecard + code-evidence probe but performs NO writes. The caller decides dry-run vs apply.
36
+ */
37
+ export declare function buildBackfill(repoRoot: string, core: string): {
38
+ entries: BackfillEntry[];
39
+ relinkQueue: RelinkEntry[];
40
+ };
41
+ export declare function registerBackfill(program: Command): void;