@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.
- package/LICENSE +17 -0
- package/bin/check-dist-fresh.js +51 -0
- package/bin/eos-spec.js +7 -0
- package/dist/commands/ac-backfill.d.ts +31 -0
- package/dist/commands/ac-backfill.js +198 -0
- package/dist/commands/ac-backfill.js.map +1 -0
- package/dist/commands/archive.d.ts +2 -0
- package/dist/commands/archive.js +101 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/audit.d.ts +30 -0
- package/dist/commands/audit.js +107 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/backfill.d.ts +41 -0
- package/dist/commands/backfill.js +239 -0
- package/dist/commands/backfill.js.map +1 -0
- package/dist/commands/checkArtifacts.d.ts +2 -0
- package/dist/commands/checkArtifacts.js +48 -0
- package/dist/commands/checkArtifacts.js.map +1 -0
- package/dist/commands/checkPlacement.d.ts +2 -0
- package/dist/commands/checkPlacement.js +31 -0
- package/dist/commands/checkPlacement.js.map +1 -0
- package/dist/commands/claim.d.ts +4 -0
- package/dist/commands/claim.js +46 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/complete.d.ts +2 -0
- package/dist/commands/complete.js +138 -0
- package/dist/commands/complete.js.map +1 -0
- package/dist/commands/conformance.d.ts +23 -0
- package/dist/commands/conformance.js +124 -0
- package/dist/commands/conformance.js.map +1 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.js +277 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/deferred.d.ts +2 -0
- package/dist/commands/deferred.js +85 -0
- package/dist/commands/deferred.js.map +1 -0
- package/dist/commands/delta.d.ts +17 -0
- package/dist/commands/delta.js +292 -0
- package/dist/commands/delta.js.map +1 -0
- package/dist/commands/freshness.d.ts +13 -0
- package/dist/commands/freshness.js +72 -0
- package/dist/commands/freshness.js.map +1 -0
- package/dist/commands/funnel.d.ts +2 -0
- package/dist/commands/funnel.js +61 -0
- package/dist/commands/funnel.js.map +1 -0
- package/dist/commands/gen-tests.d.ts +2 -0
- package/dist/commands/gen-tests.js +53 -0
- package/dist/commands/gen-tests.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +64 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +9 -0
- package/dist/commands/list.js +61 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/metrics.d.ts +2 -0
- package/dist/commands/metrics.js +66 -0
- package/dist/commands/metrics.js.map +1 -0
- package/dist/commands/next.d.ts +48 -0
- package/dist/commands/next.js +344 -0
- package/dist/commands/next.js.map +1 -0
- package/dist/commands/precedent.d.ts +66 -0
- package/dist/commands/precedent.js +343 -0
- package/dist/commands/precedent.js.map +1 -0
- package/dist/commands/ready.d.ts +28 -0
- package/dist/commands/ready.js +147 -0
- package/dist/commands/ready.js.map +1 -0
- package/dist/commands/reconcile.d.ts +44 -0
- package/dist/commands/reconcile.js +187 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/rollup.d.ts +2 -0
- package/dist/commands/rollup.js +43 -0
- package/dist/commands/rollup.js.map +1 -0
- package/dist/commands/scorecard.d.ts +11 -0
- package/dist/commands/scorecard.js +55 -0
- package/dist/commands/scorecard.js.map +1 -0
- package/dist/commands/signoff.d.ts +2 -0
- package/dist/commands/signoff.js +114 -0
- package/dist/commands/signoff.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +100 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/tasks.d.ts +13 -0
- package/dist/commands/tasks.js +81 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/validate.d.ts +16 -0
- package/dist/commands/validate.js +89 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/verify-acs.d.ts +15 -0
- package/dist/commands/verify-acs.js +196 -0
- package/dist/commands/verify-acs.js.map +1 -0
- package/dist/commands/verifyRollout.d.ts +2 -0
- package/dist/commands/verifyRollout.js +125 -0
- package/dist/commands/verifyRollout.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ac-draft-prompt.d.ts +18 -0
- package/dist/lib/ac-draft-prompt.js +38 -0
- package/dist/lib/ac-draft-prompt.js.map +1 -0
- package/dist/lib/ac-response-parser.d.ts +18 -0
- package/dist/lib/ac-response-parser.js +57 -0
- package/dist/lib/ac-response-parser.js.map +1 -0
- package/dist/lib/ai-call.d.ts +13 -0
- package/dist/lib/ai-call.js +17 -0
- package/dist/lib/ai-call.js.map +1 -0
- package/dist/lib/archive.d.ts +21 -0
- package/dist/lib/archive.js +136 -0
- package/dist/lib/archive.js.map +1 -0
- package/dist/lib/artifact-integrity.d.ts +5 -0
- package/dist/lib/artifact-integrity.js +65 -0
- package/dist/lib/artifact-integrity.js.map +1 -0
- package/dist/lib/backfill-queue.d.ts +11 -0
- package/dist/lib/backfill-queue.js +14 -0
- package/dist/lib/backfill-queue.js.map +1 -0
- package/dist/lib/blockers.d.ts +66 -0
- package/dist/lib/blockers.js +103 -0
- package/dist/lib/blockers.js.map +1 -0
- package/dist/lib/claims.d.ts +39 -0
- package/dist/lib/claims.js +133 -0
- package/dist/lib/claims.js.map +1 -0
- package/dist/lib/classify-debt.d.ts +17 -0
- package/dist/lib/classify-debt.js +13 -0
- package/dist/lib/classify-debt.js.map +1 -0
- package/dist/lib/code-evidence.d.ts +35 -0
- package/dist/lib/code-evidence.js +171 -0
- package/dist/lib/code-evidence.js.map +1 -0
- package/dist/lib/coherence.d.ts +14 -0
- package/dist/lib/coherence.js +188 -0
- package/dist/lib/coherence.js.map +1 -0
- package/dist/lib/completion-gate.d.ts +17 -0
- package/dist/lib/completion-gate.js +28 -0
- package/dist/lib/completion-gate.js.map +1 -0
- package/dist/lib/compliance-signoff.d.ts +39 -0
- package/dist/lib/compliance-signoff.js +81 -0
- package/dist/lib/compliance-signoff.js.map +1 -0
- package/dist/lib/config.d.ts +63 -0
- package/dist/lib/config.js +40 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/cost.d.ts +12 -0
- package/dist/lib/cost.js +12 -0
- package/dist/lib/cost.js.map +1 -0
- package/dist/lib/debt-dashboard.d.ts +25 -0
- package/dist/lib/debt-dashboard.js +36 -0
- package/dist/lib/debt-dashboard.js.map +1 -0
- package/dist/lib/embedding-store.d.ts +103 -0
- package/dist/lib/embedding-store.js +133 -0
- package/dist/lib/embedding-store.js.map +1 -0
- package/dist/lib/embeddings-source.d.ts +36 -0
- package/dist/lib/embeddings-source.js +62 -0
- package/dist/lib/embeddings-source.js.map +1 -0
- package/dist/lib/enforcement-allowlist.d.ts +12 -0
- package/dist/lib/enforcement-allowlist.js +25 -0
- package/dist/lib/enforcement-allowlist.js.map +1 -0
- package/dist/lib/events.d.ts +47 -0
- package/dist/lib/events.js +75 -0
- package/dist/lib/events.js.map +1 -0
- package/dist/lib/exec.d.ts +8 -0
- package/dist/lib/exec.js +25 -0
- package/dist/lib/exec.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +112 -0
- package/dist/lib/frontmatter.js +261 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/funnel.d.ts +22 -0
- package/dist/lib/funnel.js +41 -0
- package/dist/lib/funnel.js.map +1 -0
- package/dist/lib/git-mtime.d.ts +4 -0
- package/dist/lib/git-mtime.js +23 -0
- package/dist/lib/git-mtime.js.map +1 -0
- package/dist/lib/metrics.d.ts +42 -0
- package/dist/lib/metrics.js +92 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/migration-collision.d.ts +20 -0
- package/dist/lib/migration-collision.js +61 -0
- package/dist/lib/migration-collision.js.map +1 -0
- package/dist/lib/next-action.d.ts +44 -0
- package/dist/lib/next-action.js +138 -0
- package/dist/lib/next-action.js.map +1 -0
- package/dist/lib/ollama-embed.d.ts +21 -0
- package/dist/lib/ollama-embed.js +65 -0
- package/dist/lib/ollama-embed.js.map +1 -0
- package/dist/lib/output.d.ts +35 -0
- package/dist/lib/output.js +46 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/pipeline-config.d.ts +20 -0
- package/dist/lib/pipeline-config.js +208 -0
- package/dist/lib/pipeline-config.js.map +1 -0
- package/dist/lib/pipeline-map.d.ts +68 -0
- package/dist/lib/pipeline-map.js +256 -0
- package/dist/lib/pipeline-map.js.map +1 -0
- package/dist/lib/pipeline-schema.d.ts +48 -0
- package/dist/lib/pipeline-schema.js +22 -0
- package/dist/lib/pipeline-schema.js.map +1 -0
- package/dist/lib/placement.d.ts +6 -0
- package/dist/lib/placement.js +40 -0
- package/dist/lib/placement.js.map +1 -0
- package/dist/lib/precedent-search.d.ts +18 -0
- package/dist/lib/precedent-search.js +160 -0
- package/dist/lib/precedent-search.js.map +1 -0
- package/dist/lib/predicate-registry.d.ts +6 -0
- package/dist/lib/predicate-registry.js +40 -0
- package/dist/lib/predicate-registry.js.map +1 -0
- package/dist/lib/recall-cases.d.ts +15 -0
- package/dist/lib/recall-cases.js +39 -0
- package/dist/lib/recall-cases.js.map +1 -0
- package/dist/lib/repo.d.ts +20 -0
- package/dist/lib/repo.js +82 -0
- package/dist/lib/repo.js.map +1 -0
- package/dist/lib/requirements.d.ts +91 -0
- package/dist/lib/requirements.js +179 -0
- package/dist/lib/requirements.js.map +1 -0
- package/dist/lib/rollup.d.ts +24 -0
- package/dist/lib/rollup.js +34 -0
- package/dist/lib/rollup.js.map +1 -0
- package/dist/lib/scorecard-run.d.ts +6 -0
- package/dist/lib/scorecard-run.js +48 -0
- package/dist/lib/scorecard-run.js.map +1 -0
- package/dist/lib/scorecard.d.ts +69 -0
- package/dist/lib/scorecard.js +113 -0
- package/dist/lib/scorecard.js.map +1 -0
- package/dist/lib/semantic-search.d.ts +36 -0
- package/dist/lib/semantic-search.js +56 -0
- package/dist/lib/semantic-search.js.map +1 -0
- package/dist/lib/session-state.d.ts +8 -0
- package/dist/lib/session-state.js +25 -0
- package/dist/lib/session-state.js.map +1 -0
- package/dist/lib/spec-debt.d.ts +8 -0
- package/dist/lib/spec-debt.js +17 -0
- package/dist/lib/spec-debt.js.map +1 -0
- package/dist/lib/spec-pure.d.ts +59 -0
- package/dist/lib/spec-pure.js +215 -0
- package/dist/lib/spec-pure.js.map +1 -0
- package/dist/lib/specs.d.ts +148 -0
- package/dist/lib/specs.js +373 -0
- package/dist/lib/specs.js.map +1 -0
- package/dist/lib/test-generator.d.ts +5 -0
- package/dist/lib/test-generator.js +72 -0
- package/dist/lib/test-generator.js.map +1 -0
- package/dist/lib/user-stories.d.ts +6 -0
- package/dist/lib/user-stories.js +56 -0
- package/dist/lib/user-stories.js.map +1 -0
- package/dist/lib/verifies-scan.d.ts +9 -0
- package/dist/lib/verifies-scan.js +73 -0
- package/dist/lib/verifies-scan.js.map +1 -0
- package/dist/lib/verify-core.d.ts +23 -0
- package/dist/lib/verify-core.js +48 -0
- package/dist/lib/verify-core.js.map +1 -0
- package/dist/lib/with-event-log.d.ts +12 -0
- package/dist/lib/with-event-log.js +65 -0
- package/dist/lib/with-event-log.js.map +1 -0
- package/dist/lib/wizard-specs.d.ts +17 -0
- package/dist/lib/wizard-specs.js +34 -0
- package/dist/lib/wizard-specs.js.map +1 -0
- package/dist/pure.d.ts +19 -0
- package/dist/pure.js +31 -0
- package/dist/pure.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
/** The status values the audit's "AC Validated" gate accepts as closed
|
|
9
|
+
* (everything except `planned`). */
|
|
10
|
+
export const TERMINAL_AC_STATUSES = ['verified', 'hand_closed', 'deferred'];
|
|
11
|
+
export const AC_ID_RE = /^AC-\d+$/;
|
|
12
|
+
export const US_ID_RE = /^US-\d+$/;
|
|
13
|
+
export const FR_ID_RE = /^FR-\d+$/;
|
|
14
|
+
/** Scalar string -> `[string]`; array -> trimmed non-empty strings; anything else -> `[]`. */
|
|
15
|
+
export function coerceStringArray(v) {
|
|
16
|
+
if (typeof v === 'string') {
|
|
17
|
+
const t = v.trim();
|
|
18
|
+
return t === '' ? [] : [t];
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(v)) {
|
|
21
|
+
return v.filter((x) => typeof x === 'string' && x.trim() !== '').map((x) => x.trim());
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
function toStatus(v) {
|
|
26
|
+
if (v === 'verified' || v === 'hand_closed' || v === 'deferred')
|
|
27
|
+
return v;
|
|
28
|
+
return 'planned';
|
|
29
|
+
}
|
|
30
|
+
function isValidStatus(v) {
|
|
31
|
+
return v === 'planned' || v === 'verified' || v === 'hand_closed' || v === 'deferred';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes a raw YAML frontmatter `acceptance_criteria` value into typed
|
|
35
|
+
* `AcceptanceCriterion` objects, collecting `ShapeIssue`s for any structural
|
|
36
|
+
* problems found. An entry with an unrecognized status is still included with
|
|
37
|
+
* `status` defaulted to `'planned'` plus a ShapeIssue — callers must check
|
|
38
|
+
* `issues` before trusting `status`.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeAcceptanceCriteria(raw) {
|
|
41
|
+
const issues = [];
|
|
42
|
+
if (raw === undefined)
|
|
43
|
+
return { acs: [], issues };
|
|
44
|
+
if (!Array.isArray(raw)) {
|
|
45
|
+
return { acs: [], issues: [{ field: 'acceptance_criteria', message: 'must be a list of acceptance criteria' }] };
|
|
46
|
+
}
|
|
47
|
+
const acs = [];
|
|
48
|
+
raw.forEach((entry, i) => {
|
|
49
|
+
if (!entry || typeof entry !== 'object') {
|
|
50
|
+
issues.push({ field: `acceptance_criteria[${i}]`, message: 'entry is not a mapping' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const rec = entry;
|
|
54
|
+
const id = typeof rec.id === 'string' ? rec.id : undefined;
|
|
55
|
+
if (!id) {
|
|
56
|
+
issues.push({ field: `acceptance_criteria[${i}]`, message: 'missing string id' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const statementOnly = rec.statement_only === true;
|
|
60
|
+
const given = coerceStringArray(rec.given);
|
|
61
|
+
const when = coerceStringArray(rec.when);
|
|
62
|
+
const then = coerceStringArray(rec.then);
|
|
63
|
+
if (!statementOnly && (given.length === 0 || when.length === 0 || then.length === 0)) {
|
|
64
|
+
const missing = [
|
|
65
|
+
given.length === 0 ? 'given' : null,
|
|
66
|
+
when.length === 0 ? 'when' : null,
|
|
67
|
+
then.length === 0 ? 'then' : null,
|
|
68
|
+
].filter((x) => x !== null);
|
|
69
|
+
issues.push({
|
|
70
|
+
id,
|
|
71
|
+
field: 'given/when/then',
|
|
72
|
+
message: `AC ${id} is missing non-empty: ${missing.join(', ')} (or set statement_only: true)`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (rec.status !== undefined && !isValidStatus(rec.status)) {
|
|
76
|
+
issues.push({
|
|
77
|
+
id,
|
|
78
|
+
field: 'status',
|
|
79
|
+
message: `AC ${id} has invalid status "${String(rec.status)}" (planned|verified|hand_closed|deferred)`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
acs.push({
|
|
83
|
+
id,
|
|
84
|
+
userStory: typeof rec.user_story === 'string' ? rec.user_story : undefined,
|
|
85
|
+
status: toStatus(rec.status),
|
|
86
|
+
given,
|
|
87
|
+
when,
|
|
88
|
+
then,
|
|
89
|
+
statementOnly,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
return { acs, issues };
|
|
93
|
+
}
|
|
94
|
+
export function normalizeUserStories(raw) {
|
|
95
|
+
const issues = [];
|
|
96
|
+
if (raw === undefined)
|
|
97
|
+
return { uss: [], issues };
|
|
98
|
+
if (!Array.isArray(raw))
|
|
99
|
+
return { uss: [], issues: [{ field: 'user_stories', message: 'must be a list' }] };
|
|
100
|
+
const uss = [];
|
|
101
|
+
raw.forEach((entry, i) => {
|
|
102
|
+
if (!entry || typeof entry !== 'object') {
|
|
103
|
+
issues.push({ field: `user_stories[${i}]`, message: 'entry is not a mapping' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const rec = entry;
|
|
107
|
+
const id = typeof rec.id === 'string' ? rec.id : undefined;
|
|
108
|
+
if (!id) {
|
|
109
|
+
issues.push({ field: `user_stories[${i}]`, message: 'missing string id' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const text = typeof rec.text === 'string' ? rec.text.trim() : '';
|
|
113
|
+
if (!text)
|
|
114
|
+
issues.push({ id, field: 'text', message: `US ${id} is missing text` });
|
|
115
|
+
uss.push({ id, text });
|
|
116
|
+
});
|
|
117
|
+
return { uss, issues };
|
|
118
|
+
}
|
|
119
|
+
export function normalizeRequirements(raw) {
|
|
120
|
+
const issues = [];
|
|
121
|
+
if (raw === undefined)
|
|
122
|
+
return { frs: [], issues };
|
|
123
|
+
if (!Array.isArray(raw))
|
|
124
|
+
return { frs: [], issues: [{ field: 'requirements', message: 'must be a list' }] };
|
|
125
|
+
const frs = [];
|
|
126
|
+
raw.forEach((entry, i) => {
|
|
127
|
+
if (!entry || typeof entry !== 'object') {
|
|
128
|
+
issues.push({ field: `requirements[${i}]`, message: 'entry is not a mapping' });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const rec = entry;
|
|
132
|
+
const id = typeof rec.id === 'string' ? rec.id : undefined;
|
|
133
|
+
if (!id) {
|
|
134
|
+
issues.push({ field: `requirements[${i}]`, message: 'missing string id' });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const text = typeof rec.text === 'string' ? rec.text.trim() : '';
|
|
138
|
+
if (!text)
|
|
139
|
+
issues.push({ id, field: 'text', message: `FR ${id} is missing text` });
|
|
140
|
+
frs.push({ id, text, status: toStatus(rec.status) });
|
|
141
|
+
});
|
|
142
|
+
return { frs, issues };
|
|
143
|
+
}
|
|
144
|
+
/** Per-AC status tally for the pipeline status reader (consumed by the P3 audit).
|
|
145
|
+
* `hand_closed` and `deferred` are terminal-but-not-`planned` statuses the audit
|
|
146
|
+
* treats as closed (issue #174); they are counted separately so the audit can
|
|
147
|
+
* still apply the Verification-backlink guard to `hand_closed`. */
|
|
148
|
+
export function acStatusCounts(raw) {
|
|
149
|
+
const { acs } = normalizeAcceptanceCriteria(raw);
|
|
150
|
+
const counts = { planned: 0, verified: 0, handClosed: 0, deferred: 0 };
|
|
151
|
+
for (const ac of acs) {
|
|
152
|
+
if (ac.status === 'verified')
|
|
153
|
+
counts.verified += 1;
|
|
154
|
+
else if (ac.status === 'hand_closed')
|
|
155
|
+
counts.handClosed += 1;
|
|
156
|
+
else if (ac.status === 'deferred')
|
|
157
|
+
counts.deferred += 1;
|
|
158
|
+
else
|
|
159
|
+
counts.planned += 1;
|
|
160
|
+
}
|
|
161
|
+
return counts;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* True when a spec has ≥1 acceptance criterion and EVERY (well-formed) AC is in a
|
|
165
|
+
* terminal status (`verified` | `hand_closed` | `deferred`) — i.e. none remain
|
|
166
|
+
* `planned`. This is the typed, ADR-0015-aware replacement for the legacy
|
|
167
|
+
* `status === 'verified'` check `specs.ts` used to hand-roll: a `hand_closed`
|
|
168
|
+
* (proven by a CI suite the src-lane prover can't reach) or `deferred` AC
|
|
169
|
+
* terminally resolves the generate-tests gate exactly as a `verified` one does
|
|
170
|
+
* (issue #174). Malformed (id-less) AC entries are dropped by
|
|
171
|
+
* `normalizeAcceptanceCriteria` — and `validate` flags them — so they are not
|
|
172
|
+
* counted here; an empty / non-array value is `false`.
|
|
173
|
+
*/
|
|
174
|
+
export function allAcceptanceCriteriaTerminal(raw) {
|
|
175
|
+
const c = acStatusCounts(raw);
|
|
176
|
+
const total = c.planned + c.verified + c.handClosed + c.deferred;
|
|
177
|
+
return total > 0 && c.planned === 0;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=requirements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirements.js","sourceRoot":"","sources":["../../src/lib/requirements.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ScorecardReport } from './scorecard.js';
|
|
2
|
+
export type RollupCoreRow = {
|
|
3
|
+
core: string;
|
|
4
|
+
total: number;
|
|
5
|
+
authored: number;
|
|
6
|
+
claimsComplete: number;
|
|
7
|
+
claimedCompleteButUnverified: number;
|
|
8
|
+
withVerifiedRequirements: number;
|
|
9
|
+
verifiedPct: number;
|
|
10
|
+
authoredPct: number;
|
|
11
|
+
};
|
|
12
|
+
export type RollupReport = {
|
|
13
|
+
cores: string[];
|
|
14
|
+
totals: {
|
|
15
|
+
specs: number;
|
|
16
|
+
claimsComplete: number;
|
|
17
|
+
claimedCompleteButUnverified: number;
|
|
18
|
+
withVerifiedRequirements: number;
|
|
19
|
+
verifiedPct: number;
|
|
20
|
+
};
|
|
21
|
+
rows: RollupCoreRow[];
|
|
22
|
+
};
|
|
23
|
+
/** Aggregate per-core scorecards into a ranked platform readiness view (most-ready first). */
|
|
24
|
+
export declare function computeRollup(perCore: ScorecardReport[]): RollupReport;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const pct = (n, d) => (d > 0 ? Math.round((n / d) * 1000) / 10 : 0);
|
|
2
|
+
/** Aggregate per-core scorecards into a ranked platform readiness view (most-ready first). */
|
|
3
|
+
export function computeRollup(perCore) {
|
|
4
|
+
const rows = perCore.map((r) => {
|
|
5
|
+
const authored = r.byStage.tasks_generated ?? 0;
|
|
6
|
+
return {
|
|
7
|
+
core: r.core,
|
|
8
|
+
total: r.total,
|
|
9
|
+
authored,
|
|
10
|
+
claimsComplete: r.claimsComplete,
|
|
11
|
+
claimedCompleteButUnverified: r.claimedCompleteButUnverified,
|
|
12
|
+
withVerifiedRequirements: r.withVerifiedRequirements,
|
|
13
|
+
verifiedPct: pct(r.withVerifiedRequirements, r.total),
|
|
14
|
+
authoredPct: pct(authored, r.total),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
rows.sort((a, b) => b.verifiedPct - a.verifiedPct || b.authoredPct - a.authoredPct || a.core.localeCompare(b.core));
|
|
18
|
+
const specs = rows.reduce((s, r) => s + r.total, 0);
|
|
19
|
+
const claimsComplete = rows.reduce((s, r) => s + r.claimsComplete, 0);
|
|
20
|
+
const claimedCompleteButUnverified = rows.reduce((s, r) => s + r.claimedCompleteButUnverified, 0);
|
|
21
|
+
const withVerifiedRequirements = rows.reduce((s, r) => s + r.withVerifiedRequirements, 0);
|
|
22
|
+
return {
|
|
23
|
+
cores: perCore.map((r) => r.core),
|
|
24
|
+
totals: {
|
|
25
|
+
specs,
|
|
26
|
+
claimsComplete,
|
|
27
|
+
claimedCompleteButUnverified,
|
|
28
|
+
withVerifiedRequirements,
|
|
29
|
+
verifiedPct: pct(withVerifiedRequirements, specs),
|
|
30
|
+
},
|
|
31
|
+
rows,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=rollup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollup.js","sourceRoot":"","sources":["../../src/lib/rollup.ts"],"names":[],"mappings":"AAyBA,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5F,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,OAA0B;IACtD,MAAM,IAAI,GAAoB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9C,MAAM,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC;QAChD,OAAO;YACL,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ;YACR,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,4BAA4B,EAAE,CAAC,CAAC,4BAA4B;YAC5D,wBAAwB,EAAE,CAAC,CAAC,wBAAwB;YACpD,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,wBAAwB,EAAE,CAAC,CAAC,KAAK,CAAC;YACrD,WAAW,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC;SACpC,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEpH,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,4BAA4B,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC;IAClG,MAAM,wBAAwB,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC;IAE1F,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACjC,MAAM,EAAE;YACN,KAAK;YACL,cAAc;YACd,4BAA4B;YAC5B,wBAAwB;YACxB,WAAW,EAAE,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC;SAClD;QACD,IAAI;KACL,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ScorecardReport } from './scorecard.js';
|
|
2
|
+
/**
|
|
3
|
+
* Gather per-spec inputs for one core and compute its scorecard. `verifiedBySpec` is passed in so the
|
|
4
|
+
* caller can run scanVerifies(repoRoot) ONCE and reuse it across cores (the rollup does this).
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildScorecardForCore(repoRoot: string, core: string | undefined, verifiedBySpec: Map<string, Set<string>>): ScorecardReport;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readFrontmatter } from './frontmatter.js';
|
|
4
|
+
import { collectRequirementIds, computeScorecard, countRequirements, } from './scorecard.js';
|
|
5
|
+
import { listSpecs } from './specs.js';
|
|
6
|
+
/** True if dir exists and contains at least one entry matching `pred`. */
|
|
7
|
+
function dirHas(dir, pred) {
|
|
8
|
+
try {
|
|
9
|
+
return readdirSync(dir).some(pred);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Core-level Definition-of-Done signals: a DocShot lane and hand-written E2E (generated scaffolds excluded). */
|
|
16
|
+
function coreSignalsFor(repoRoot, core) {
|
|
17
|
+
if (!core)
|
|
18
|
+
return { hasDocshots: false, hasE2E: false };
|
|
19
|
+
const imagesDir = join(repoRoot, 'docs/images', core);
|
|
20
|
+
const e2eDir = join(repoRoot, 'tests/e2e', core);
|
|
21
|
+
const hasDocshots = existsSync(imagesDir) && dirHas(imagesDir, (n) => /\.(png|jpe?g|webp)$/i.test(n));
|
|
22
|
+
const hasE2E = dirHas(e2eDir, (n) => /\.(spec|test)\.tsx?$/.test(n));
|
|
23
|
+
return { hasDocshots, hasE2E };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gather per-spec inputs for one core and compute its scorecard. `verifiedBySpec` is passed in so the
|
|
27
|
+
* caller can run scanVerifies(repoRoot) ONCE and reuse it across cores (the rollup does this).
|
|
28
|
+
*/
|
|
29
|
+
export function buildScorecardForCore(repoRoot, core, verifiedBySpec) {
|
|
30
|
+
const specs = listSpecs(repoRoot, core);
|
|
31
|
+
const rows = specs.map((spec) => {
|
|
32
|
+
const text = readFileSync(spec.path, 'utf8');
|
|
33
|
+
const fm = readFrontmatter(text);
|
|
34
|
+
const claimedStatus = (typeof fm.status === 'string' ? fm.status : undefined) ?? spec.status;
|
|
35
|
+
const { acTotal, usTotal, frTotal } = countRequirements(text);
|
|
36
|
+
return {
|
|
37
|
+
spec,
|
|
38
|
+
claimedStatus,
|
|
39
|
+
acTotal,
|
|
40
|
+
usTotal,
|
|
41
|
+
frTotal,
|
|
42
|
+
declaredIds: collectRequirementIds(text),
|
|
43
|
+
verifiedIds: verifiedBySpec.get(spec.id) ?? new Set(),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return computeScorecard({ core: core ?? 'all', rows, coreSignals: coreSignalsFor(repoRoot, core) });
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=scorecard-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorecard-run.js","sourceRoot":"","sources":["../../src/lib/scorecard-run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,GAGlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,0EAA0E;AAC1E,SAAS,MAAM,CAAC,GAAW,EAAE,IAA+B;IAC1D,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,iHAAiH;AACjH,SAAS,cAAc,CAAC,QAAgB,EAAE,IAAwB;IAChE,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACxD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,UAAU,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,IAAwB,EACxB,cAAwC;IAExC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAwB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACnD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC;QAC7F,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC9D,OAAO;YACL,IAAI;YACJ,aAAa;YACb,OAAO;YACP,OAAO;YACP,OAAO;YACP,WAAW,EAAE,qBAAqB,CAAC,IAAI,CAAC;YACxC,WAAW,EAAE,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,EAAU;SAC9D,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;AACtG,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SpecFile } from './specs.js';
|
|
2
|
+
export type ScorecardRowInput = {
|
|
3
|
+
spec: SpecFile;
|
|
4
|
+
/** Claimed status — frontmatter `status` or the body `**Status:**` banner (raw). */
|
|
5
|
+
claimedStatus?: string;
|
|
6
|
+
acTotal: number;
|
|
7
|
+
usTotal: number;
|
|
8
|
+
/** Functional requirements (body `**FR-N**`) — first-class verifiable. */
|
|
9
|
+
frTotal?: number;
|
|
10
|
+
/** AC/US/FR ids backlinked for this spec via @verifies. */
|
|
11
|
+
verifiedIds: Set<string>;
|
|
12
|
+
/** Requirement ids the spec DECLARES (AC/US/FR). When provided, verifiedIds is intersected
|
|
13
|
+
* with this set; backlinks to undeclared ids count as warnings, not evidence. */
|
|
14
|
+
declaredIds?: Set<string>;
|
|
15
|
+
};
|
|
16
|
+
export type ScorecardRow = {
|
|
17
|
+
id: string;
|
|
18
|
+
core: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
stage: string;
|
|
21
|
+
claimedStatus?: string;
|
|
22
|
+
claimsComplete: boolean;
|
|
23
|
+
requirementsTotal: number;
|
|
24
|
+
requirementsVerified: number;
|
|
25
|
+
/** @verifies backlinks pointing at ids the spec never declared (typo'd AC, stale fixture). */
|
|
26
|
+
verifiedUndeclared: number;
|
|
27
|
+
frontmatterAccurate: 'match' | 'overstated' | 'understated';
|
|
28
|
+
claimedCompleteButUnverified: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type ScorecardReport = {
|
|
31
|
+
core: string;
|
|
32
|
+
total: number;
|
|
33
|
+
byStage: Record<string, number>;
|
|
34
|
+
claimsComplete: number;
|
|
35
|
+
claimedCompleteButUnverified: number;
|
|
36
|
+
withVerifiedRequirements: number;
|
|
37
|
+
/** Total backlinked-but-undeclared ids across the core — a data-quality warning, not evidence. */
|
|
38
|
+
verifiedUndeclaredTotal: number;
|
|
39
|
+
/** Core has a DocShot lane (docs/images/<core>/ with screenshots). */
|
|
40
|
+
hasDocshots: boolean;
|
|
41
|
+
/** Core has hand-written E2E (tests/e2e/<core>/). Generated scaffolds don't count. */
|
|
42
|
+
hasE2E: boolean;
|
|
43
|
+
rows: ScorecardRow[];
|
|
44
|
+
};
|
|
45
|
+
/** Count acceptance criteria (frontmatter), user stories, and functional requirements (body) declared by a spec.
|
|
46
|
+
* FR is first-class verifiable (Definition of Done). The `**` anchor means `**NFR-misc-N**` never counts as FR. */
|
|
47
|
+
export declare function countRequirements(specText: string): {
|
|
48
|
+
acTotal: number;
|
|
49
|
+
usTotal: number;
|
|
50
|
+
frTotal: number;
|
|
51
|
+
};
|
|
52
|
+
/** Collect the requirement ids a spec DECLARES: frontmatter acceptance_criteria / user_stories
|
|
53
|
+
* entry ids plus body `- **US-n**` / `- **FR-n**` items. Used to intersect @verifies backlinks —
|
|
54
|
+
* a backlink to an undeclared id is a warning, not verification evidence. */
|
|
55
|
+
export declare function collectRequirementIds(specText: string): Set<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Deterministic readiness scorecard. Per spec it records the computed authoring stage, whether the
|
|
58
|
+
* spec *claims* completion, how many requirements (ACs + user stories) are `@verifies`-backed, and a
|
|
59
|
+
* frontmatter-accuracy verdict. The rollup's headline — `claimedCompleteButUnverified` — is the exact
|
|
60
|
+
* column every completion audit hand-derives: "says complete, zero AC-linked tests".
|
|
61
|
+
*/
|
|
62
|
+
export declare function computeScorecard(input: {
|
|
63
|
+
core: string;
|
|
64
|
+
rows: ScorecardRowInput[];
|
|
65
|
+
coreSignals?: {
|
|
66
|
+
hasDocshots: boolean;
|
|
67
|
+
hasE2E: boolean;
|
|
68
|
+
};
|
|
69
|
+
}): ScorecardReport;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFrontmatter } from './frontmatter.js';
|
|
2
|
+
import { computeStage } from './pipeline-map.js';
|
|
3
|
+
const COMPLETE_RE = /\b(complete|completed|done|shipped)\b|✅/i;
|
|
4
|
+
/** Funnel's terminal authoring stage. */
|
|
5
|
+
const TERMINAL_STAGE = 'tasks_generated';
|
|
6
|
+
/** Count acceptance criteria (frontmatter), user stories, and functional requirements (body) declared by a spec.
|
|
7
|
+
* FR is first-class verifiable (Definition of Done). The `**` anchor means `**NFR-misc-N**` never counts as FR. */
|
|
8
|
+
export function countRequirements(specText) {
|
|
9
|
+
const fm = readFrontmatter(specText);
|
|
10
|
+
const acTotal = Array.isArray(fm.acceptance_criteria) ? fm.acceptance_criteria.length : 0;
|
|
11
|
+
const fmUs = Array.isArray(fm.user_stories) ? fm.user_stories.length : 0;
|
|
12
|
+
const bodyUs = new Set((specText.match(/(?:^|\n)\s*-\s+\*{0,2}US-\d+/g) ?? []).map((s) => /US-\d+/.exec(s)?.[0] ?? ''));
|
|
13
|
+
bodyUs.delete('');
|
|
14
|
+
const bodyFr = new Set((specText.match(/(?:^|\n)\s*-\s+\*{0,2}FR-\d+/g) ?? []).map((s) => /FR-\d+/.exec(s)?.[0] ?? ''));
|
|
15
|
+
bodyFr.delete('');
|
|
16
|
+
return { acTotal, usTotal: Math.max(fmUs, bodyUs.size), frTotal: bodyFr.size };
|
|
17
|
+
}
|
|
18
|
+
/** Collect the requirement ids a spec DECLARES: frontmatter acceptance_criteria / user_stories
|
|
19
|
+
* entry ids plus body `- **US-n**` / `- **FR-n**` items. Used to intersect @verifies backlinks —
|
|
20
|
+
* a backlink to an undeclared id is a warning, not verification evidence. */
|
|
21
|
+
export function collectRequirementIds(specText) {
|
|
22
|
+
const fm = readFrontmatter(specText);
|
|
23
|
+
const ids = new Set();
|
|
24
|
+
const addFromEntries = (v) => {
|
|
25
|
+
if (!Array.isArray(v))
|
|
26
|
+
return;
|
|
27
|
+
for (const entry of v) {
|
|
28
|
+
if (typeof entry === 'string') {
|
|
29
|
+
const id = /^(?:AC|US|FR)-\d+/.exec(entry.trim())?.[0];
|
|
30
|
+
if (id)
|
|
31
|
+
ids.add(id);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const id = entry?.id;
|
|
35
|
+
if (typeof id === 'string' && /^(?:AC|US|FR)-\d+$/.test(id))
|
|
36
|
+
ids.add(id);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
addFromEntries(fm.acceptance_criteria);
|
|
40
|
+
addFromEntries(fm.user_stories);
|
|
41
|
+
for (const m of specText.match(/(?:^|\n)\s*-\s+\*{0,2}(?:US|FR)-\d+/g) ?? []) {
|
|
42
|
+
const id = /(?:US|FR)-\d+/.exec(m)?.[0];
|
|
43
|
+
if (id)
|
|
44
|
+
ids.add(id);
|
|
45
|
+
}
|
|
46
|
+
return ids;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic readiness scorecard. Per spec it records the computed authoring stage, whether the
|
|
50
|
+
* spec *claims* completion, how many requirements (ACs + user stories) are `@verifies`-backed, and a
|
|
51
|
+
* frontmatter-accuracy verdict. The rollup's headline — `claimedCompleteButUnverified` — is the exact
|
|
52
|
+
* column every completion audit hand-derives: "says complete, zero AC-linked tests".
|
|
53
|
+
*/
|
|
54
|
+
export function computeScorecard(input) {
|
|
55
|
+
const byStage = {};
|
|
56
|
+
let claimsCompleteCount = 0;
|
|
57
|
+
let claimedCompleteButUnverified = 0;
|
|
58
|
+
let withVerifiedRequirements = 0;
|
|
59
|
+
let verifiedUndeclaredTotal = 0;
|
|
60
|
+
const rows = input.rows.map((r) => {
|
|
61
|
+
const stage = computeStage(r.spec);
|
|
62
|
+
byStage[stage] = (byStage[stage] ?? 0) + 1;
|
|
63
|
+
const claimsComplete = !!r.claimedStatus && COMPLETE_RE.test(r.claimedStatus);
|
|
64
|
+
const requirementsTotal = r.acTotal + r.usTotal + (r.frTotal ?? 0);
|
|
65
|
+
// Only backlinks to ids the spec actually declares count as evidence; the rest are warnings.
|
|
66
|
+
const declared = r.declaredIds;
|
|
67
|
+
const requirementsVerified = declared
|
|
68
|
+
? [...r.verifiedIds].filter((id) => declared.has(id)).length
|
|
69
|
+
: r.verifiedIds.size;
|
|
70
|
+
const verifiedUndeclared = r.verifiedIds.size - requirementsVerified;
|
|
71
|
+
verifiedUndeclaredTotal += verifiedUndeclared;
|
|
72
|
+
if (claimsComplete)
|
|
73
|
+
claimsCompleteCount++;
|
|
74
|
+
if (requirementsVerified > 0)
|
|
75
|
+
withVerifiedRequirements++;
|
|
76
|
+
const unverifiedComplete = claimsComplete && requirementsVerified === 0;
|
|
77
|
+
if (unverifiedComplete)
|
|
78
|
+
claimedCompleteButUnverified++;
|
|
79
|
+
let frontmatterAccurate = 'match';
|
|
80
|
+
if (claimsComplete && stage !== TERMINAL_STAGE && stage !== 'planned') {
|
|
81
|
+
frontmatterAccurate = 'overstated';
|
|
82
|
+
}
|
|
83
|
+
else if (!claimsComplete && stage === TERMINAL_STAGE) {
|
|
84
|
+
frontmatterAccurate = 'understated';
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
id: r.spec.id,
|
|
88
|
+
core: r.spec.core,
|
|
89
|
+
title: r.spec.title,
|
|
90
|
+
stage,
|
|
91
|
+
claimedStatus: r.claimedStatus,
|
|
92
|
+
claimsComplete,
|
|
93
|
+
requirementsTotal,
|
|
94
|
+
requirementsVerified,
|
|
95
|
+
verifiedUndeclared,
|
|
96
|
+
frontmatterAccurate,
|
|
97
|
+
claimedCompleteButUnverified: unverifiedComplete,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
core: input.core,
|
|
102
|
+
total: rows.length,
|
|
103
|
+
hasDocshots: input.coreSignals?.hasDocshots ?? false,
|
|
104
|
+
hasE2E: input.coreSignals?.hasE2E ?? false,
|
|
105
|
+
byStage,
|
|
106
|
+
claimsComplete: claimsCompleteCount,
|
|
107
|
+
claimedCompleteButUnverified,
|
|
108
|
+
withVerifiedRequirements,
|
|
109
|
+
verifiedUndeclaredTotal,
|
|
110
|
+
rows,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=scorecard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorecard.js","sourceRoot":"","sources":["../../src/lib/scorecard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type EmbeddingsSource } from './embeddings-source.js';
|
|
2
|
+
import { type PrecedentHit } from './precedent-search.js';
|
|
3
|
+
/** Async query embedder: returns a normalized vector, or null when unavailable. */
|
|
4
|
+
export type QueryEmbedder = (query: string) => Promise<Float32Array | null>;
|
|
5
|
+
/**
|
|
6
|
+
* Semantic precedent search over the committed embedding index. Returns ranked
|
|
7
|
+
* hits, or null when the index is absent OR the embedder can't produce a query
|
|
8
|
+
* vector — callers then fall back to the always-on lexical {@link searchSpecs}.
|
|
9
|
+
*
|
|
10
|
+
* Validated: qwen3-embedding:0.6b + the query instruction prefix lifts paraphrase
|
|
11
|
+
* recall to 7/8 (lexical 4/8) while holding exact@5 at 8/8. The lexical layer
|
|
12
|
+
* stays the zero-dependency baseline (CI / no-Ollama environments).
|
|
13
|
+
*/
|
|
14
|
+
export declare function searchSpecsSemantic(topic: string, opts: {
|
|
15
|
+
repoRoot: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
embedQuery: QueryEmbedder;
|
|
18
|
+
includeArchived?: boolean;
|
|
19
|
+
/** Inject a non-default embedding backend (tests / future pgvector source). */
|
|
20
|
+
source?: EmbeddingsSource;
|
|
21
|
+
}): Promise<PrecedentHit[] | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Best-available precedent search: semantic when the index + embedder resolve,
|
|
24
|
+
* else lexical. Single entry point for callers that want the strongest ranking
|
|
25
|
+
* without branching on availability themselves.
|
|
26
|
+
*/
|
|
27
|
+
export declare function searchSpecsBest(topic: string, opts: {
|
|
28
|
+
repoRoot: string;
|
|
29
|
+
core?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
embedQuery?: QueryEmbedder;
|
|
32
|
+
source?: EmbeddingsSource;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
hits: PrecedentHit[];
|
|
35
|
+
mode: 'semantic' | 'lexical';
|
|
36
|
+
}>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { cosineRank } from './embedding-store.js';
|
|
2
|
+
import { embeddingsSourceFromConfig } from './embeddings-source.js';
|
|
3
|
+
import { searchSpecs } from './precedent-search.js';
|
|
4
|
+
/**
|
|
5
|
+
* Semantic precedent search over the committed embedding index. Returns ranked
|
|
6
|
+
* hits, or null when the index is absent OR the embedder can't produce a query
|
|
7
|
+
* vector — callers then fall back to the always-on lexical {@link searchSpecs}.
|
|
8
|
+
*
|
|
9
|
+
* Validated: qwen3-embedding:0.6b + the query instruction prefix lifts paraphrase
|
|
10
|
+
* recall to 7/8 (lexical 4/8) while holding exact@5 at 8/8. The lexical layer
|
|
11
|
+
* stays the zero-dependency baseline (CI / no-Ollama environments).
|
|
12
|
+
*/
|
|
13
|
+
export async function searchSpecsSemantic(topic, opts) {
|
|
14
|
+
const index = (opts.source ?? embeddingsSourceFromConfig(opts.repoRoot)).loadIndex();
|
|
15
|
+
if (!index)
|
|
16
|
+
return null;
|
|
17
|
+
const qv = await opts.embedQuery(topic);
|
|
18
|
+
if (!qv)
|
|
19
|
+
return null;
|
|
20
|
+
const ranked = cosineRank(qv, index);
|
|
21
|
+
const hits = ranked.map((h) => ({
|
|
22
|
+
id: h.id,
|
|
23
|
+
title: h.title,
|
|
24
|
+
file: h.file,
|
|
25
|
+
core: h.id.split('-')[0]?.toLowerCase() ?? '',
|
|
26
|
+
status: 'unknown',
|
|
27
|
+
score: h.score,
|
|
28
|
+
matchedFields: ['semantic'],
|
|
29
|
+
}));
|
|
30
|
+
return typeof opts.limit === 'number' ? hits.slice(0, opts.limit) : hits;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Best-available precedent search: semantic when the index + embedder resolve,
|
|
34
|
+
* else lexical. Single entry point for callers that want the strongest ranking
|
|
35
|
+
* without branching on availability themselves.
|
|
36
|
+
*/
|
|
37
|
+
export async function searchSpecsBest(topic, opts) {
|
|
38
|
+
if (opts.embedQuery) {
|
|
39
|
+
const semantic = await searchSpecsSemantic(topic, {
|
|
40
|
+
repoRoot: opts.repoRoot,
|
|
41
|
+
limit: opts.limit,
|
|
42
|
+
embedQuery: opts.embedQuery,
|
|
43
|
+
source: opts.source,
|
|
44
|
+
});
|
|
45
|
+
// Core filtering on semantic hits (id-prefix based) when a core is requested.
|
|
46
|
+
if (semantic) {
|
|
47
|
+
const filtered = opts.core ? semantic.filter((h) => h.core === opts.core.toLowerCase()) : semantic;
|
|
48
|
+
return { hits: filtered, mode: 'semantic' };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
hits: searchSpecs(topic, { repoRoot: opts.repoRoot, core: opts.core, limit: opts.limit }),
|
|
53
|
+
mode: 'lexical',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=semantic-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-search.js","sourceRoot":"","sources":["../../src/lib/semantic-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAoB,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAyB,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAqB,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKvE;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,IAOC;IAED,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,0BAA0B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IACrF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAErB,MAAM,MAAM,GAAkB,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACpD,MAAM,IAAI,GAAmB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9C,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;QAC7C,MAAM,EAAE,SAAS;QACjB,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,aAAa,EAAE,CAAC,UAAU,CAAC;KAC5B,CAAC,CAAC,CAAC;IACJ,OAAO,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAa,EACb,IAAgH;IAEhH,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE;YAChD,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,8EAA8E;QAC9E,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YACpG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACzF,IAAI,EAAE,SAAS;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type SessionState = {
|
|
2
|
+
specId: string;
|
|
3
|
+
currentTask: string;
|
|
4
|
+
};
|
|
5
|
+
/** Parse STATE.md text into a session pointer, or null if no in-progress task. Never throws. */
|
|
6
|
+
export declare function parseSessionState(text: string): SessionState | null;
|
|
7
|
+
/** Read `.cursor/session/STATE.md` (gitignored, optional). Returns null if absent/unparseable. */
|
|
8
|
+
export declare function readSessionState(repoRoot: string): SessionState | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const SPEC_RE = /\*\*Spec(?:\s*ID)?:\*\*\s*([A-Z]{2,3}-\d+(?:[A-Z0-9-]*)?)/i;
|
|
4
|
+
const TASK_RE = /\*\*Current task:\*\*\s*(T\d+)\b/i;
|
|
5
|
+
/** Parse STATE.md text into a session pointer, or null if no in-progress task. Never throws. */
|
|
6
|
+
export function parseSessionState(text) {
|
|
7
|
+
const spec = SPEC_RE.exec(text)?.[1];
|
|
8
|
+
const task = TASK_RE.exec(text)?.[1];
|
|
9
|
+
if (!spec || !task)
|
|
10
|
+
return null;
|
|
11
|
+
return { specId: spec.toUpperCase(), currentTask: task.toUpperCase() };
|
|
12
|
+
}
|
|
13
|
+
/** Read `.cursor/session/STATE.md` (gitignored, optional). Returns null if absent/unparseable. */
|
|
14
|
+
export function readSessionState(repoRoot) {
|
|
15
|
+
const path = join(repoRoot, '.cursor', 'session', 'STATE.md');
|
|
16
|
+
if (!existsSync(path))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return parseSessionState(readFileSync(path, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=session-state.js.map
|