@encore-os/eos-spec 0.16.0 → 0.17.0
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/dist/commands/close-out.js +48 -2
- package/dist/commands/close-out.js.map +1 -1
- package/dist/commands/doctor.js +14 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/index-spec.js +22 -4
- package/dist/commands/index-spec.js.map +1 -1
- package/dist/commands/next.d.ts +6 -0
- package/dist/commands/next.js +11 -1
- package/dist/commands/next.js.map +1 -1
- package/dist/commands/reconcile.js +6 -2
- package/dist/commands/reconcile.js.map +1 -1
- package/dist/commands/scorecard.js +51 -0
- package/dist/commands/scorecard.js.map +1 -1
- package/dist/commands/signoff.js +12 -1
- package/dist/commands/signoff.js.map +1 -1
- package/dist/commands/verify-acs.d.ts +7 -0
- package/dist/commands/verify-acs.js +61 -8
- package/dist/commands/verify-acs.js.map +1 -1
- package/dist/lib/closeout-core.d.ts +15 -0
- package/dist/lib/closeout-core.js +42 -5
- package/dist/lib/closeout-core.js.map +1 -1
- package/dist/lib/config.d.ts +24 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/doctor-core.d.ts +17 -2
- package/dist/lib/doctor-core.js +50 -12
- package/dist/lib/doctor-core.js.map +1 -1
- package/dist/lib/frontmatter.js +26 -3
- package/dist/lib/frontmatter.js.map +1 -1
- package/dist/lib/reconcile-core.d.ts +1 -1
- package/dist/lib/reconcile-core.js +10 -2
- package/dist/lib/reconcile-core.js.map +1 -1
- package/dist/lib/rtm-scorecard-run.d.ts +38 -0
- package/dist/lib/rtm-scorecard-run.js +70 -0
- package/dist/lib/rtm-scorecard-run.js.map +1 -0
- package/dist/lib/rtm-scorecard.d.ts +151 -0
- package/dist/lib/rtm-scorecard.js +197 -0
- package/dist/lib/rtm-scorecard.js.map +1 -0
- package/dist/lib/spec-pure.d.ts +38 -0
- package/dist/lib/spec-pure.js +162 -17
- package/dist/lib/spec-pure.js.map +1 -1
- package/dist/lib/specs.d.ts +20 -0
- package/dist/lib/specs.js +37 -1
- package/dist/lib/specs.js.map +1 -1
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/pure.d.ts +1 -0
- package/dist/pure.js +3 -0
- package/dist/pure.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-core.js","sourceRoot":"","sources":["../../src/lib/reconcile-core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAuBvD;;oFAEoF;AACpF,MAAM,UAAU,QAAQ,CACtB,OAA2B,EAC3B,QAA4B,EAC5B,SAAsC,IAAI;IAE1C,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;AACvE,CAAC;AAUD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,IAAc,EACd,KAAc,EACd,QAA4B;
|
|
1
|
+
{"version":3,"file":"reconcile-core.js","sourceRoot":"","sources":["../../src/lib/reconcile-core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAuBvD;;oFAEoF;AACpF,MAAM,UAAU,QAAQ,CACtB,OAA2B,EAC3B,QAA4B,EAC5B,SAAsC,IAAI;IAE1C,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;AACvE,CAAC;AAUD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,IAAc,EACd,KAAc,EACd,QAA4B,EAC5B,gBAAyB;IAEzB,MAAM,OAAO,GAAG,QAAQ,EAAE,YAAY,IAAI,YAAY,CAAC;IACvD,MAAM,MAAM,GAAG,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC;IAC5C,iEAAiE;IACjE,yFAAyF;IACzF,iFAAiF;IACjF,qFAAqF;IACrF,0FAA0F;IAC1F,sFAAsF;IACtF,2EAA2E;IAC3E,MAAM,cAAc,GAAG,gBAAgB,KAAK,SAAS,IAAI,OAAO,KAAK,gBAAgB,CAAC;IACtF,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9E,IAAI,KAAK,IAAI,QAAQ,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,IAAI,GAAG,QAAQ,CAAC;QACpB,IAAI,SAAS,KAAK,IAAI;YAAE,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACjE,+EAA+E;QAC/E,6EAA6E;QAC7E,8EAA8E;QAC9E,+EAA+E;QAC/E,2EAA2E;QAC3E,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;YAC5F,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,EAAE,SAAS,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;YACpG,IAAI,IAAI;gBAAE,IAAI,GAAG,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,IAAI,KAAK,QAAQ;YAAE,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type RtmSpecRow = {
|
|
2
|
+
id: string;
|
|
3
|
+
core: string;
|
|
4
|
+
fr: {
|
|
5
|
+
total: number;
|
|
6
|
+
verified: number;
|
|
7
|
+
unverified: string[];
|
|
8
|
+
};
|
|
9
|
+
sc: {
|
|
10
|
+
total: number;
|
|
11
|
+
met: number;
|
|
12
|
+
unmet: string[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type RtmReport = {
|
|
16
|
+
core: string;
|
|
17
|
+
/** Specs scanned. */
|
|
18
|
+
total: number;
|
|
19
|
+
/** Specs carrying ≥1 FR or SC (the ones the scorecard actually scores). */
|
|
20
|
+
specsWithReqs: number;
|
|
21
|
+
fr: {
|
|
22
|
+
total: number;
|
|
23
|
+
verified: number;
|
|
24
|
+
};
|
|
25
|
+
sc: {
|
|
26
|
+
total: number;
|
|
27
|
+
met: number;
|
|
28
|
+
};
|
|
29
|
+
/** Fraction of FRs verified (0–1); 1 when there are no FRs (nothing to verify). */
|
|
30
|
+
frVerifiedPct: number;
|
|
31
|
+
/** Fraction of SCs met (0–1); 1 when there are no SCs. */
|
|
32
|
+
scMetPct: number;
|
|
33
|
+
/** Per-spec rows, only for specs that carry FR/SC requirements. */
|
|
34
|
+
rows: RtmSpecRow[];
|
|
35
|
+
};
|
|
36
|
+
/** Build the corpus RTM scorecard for `coreFilter` (or all cores). Deterministic (listSpecs
|
|
37
|
+
* order). Specs with no FR/SC requirements are counted in `total` but omitted from `rows`. */
|
|
38
|
+
export declare function buildRtmScorecard(repoRoot: string, coreFilter?: string): RtmReport;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fs-bound driver for the FR/SC RTM scorecard (#79): walk a repo's specs, read each spec's
|
|
3
|
+
* body + frontmatter ACs, and aggregate the pure {@link deriveScorecard} per spec into a corpus
|
|
4
|
+
* report. The verdict logic + parsing stay in the pure `rtm-scorecard.ts` (on the `/pure` graph);
|
|
5
|
+
* this only supplies the corpus walk + file IO, so it lives behind the package main entry.
|
|
6
|
+
*
|
|
7
|
+
* On-disk (proof-free) by design: "verified" reads from each AC's frontmatter `status` and each
|
|
8
|
+
* FR's body `[status]`, not from a live test run. A consumer that wants run-proven coverage feeds
|
|
9
|
+
* `deriveScorecard` its own `proof` set from `verify-acs`; this static readiness view mirrors the
|
|
10
|
+
* existing `scorecard` command.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { readFrontmatter, stripFrontmatter } from './frontmatter.js';
|
|
14
|
+
import { collectScorecardItems, deriveScorecard } from './rtm-scorecard.js';
|
|
15
|
+
import { listSpecs } from './specs.js';
|
|
16
|
+
/** Build the corpus RTM scorecard for `coreFilter` (or all cores). Deterministic (listSpecs
|
|
17
|
+
* order). Specs with no FR/SC requirements are counted in `total` but omitted from `rows`. */
|
|
18
|
+
export function buildRtmScorecard(repoRoot, coreFilter) {
|
|
19
|
+
const specs = listSpecs(repoRoot, coreFilter);
|
|
20
|
+
const rows = [];
|
|
21
|
+
let frTotal = 0;
|
|
22
|
+
let frVerified = 0;
|
|
23
|
+
let scTotal = 0;
|
|
24
|
+
let scMet = 0;
|
|
25
|
+
let specsWithReqs = 0;
|
|
26
|
+
for (const spec of specs) {
|
|
27
|
+
let text = '';
|
|
28
|
+
try {
|
|
29
|
+
text = readFileSync(spec.path, 'utf8');
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue; // unreadable — skip
|
|
33
|
+
}
|
|
34
|
+
const fm = readFrontmatter(text);
|
|
35
|
+
const items = collectScorecardItems(stripFrontmatter(text), fm.acceptance_criteria);
|
|
36
|
+
const card = deriveScorecard(items);
|
|
37
|
+
if (card.fr.total === 0 && card.sc.total === 0)
|
|
38
|
+
continue; // nothing to score
|
|
39
|
+
specsWithReqs++;
|
|
40
|
+
frTotal += card.fr.total;
|
|
41
|
+
frVerified += card.fr.verified;
|
|
42
|
+
scTotal += card.sc.total;
|
|
43
|
+
scMet += card.sc.met;
|
|
44
|
+
rows.push({
|
|
45
|
+
id: spec.id,
|
|
46
|
+
core: spec.core,
|
|
47
|
+
fr: {
|
|
48
|
+
total: card.fr.total,
|
|
49
|
+
verified: card.fr.verified,
|
|
50
|
+
unverified: card.fr.rows.filter((r) => !r.verified).map((r) => r.id),
|
|
51
|
+
},
|
|
52
|
+
sc: {
|
|
53
|
+
total: card.sc.total,
|
|
54
|
+
met: card.sc.met,
|
|
55
|
+
unmet: card.sc.rows.filter((r) => !r.met).map((r) => r.id),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
core: coreFilter ?? 'all',
|
|
61
|
+
total: specs.length,
|
|
62
|
+
specsWithReqs,
|
|
63
|
+
fr: { total: frTotal, verified: frVerified },
|
|
64
|
+
sc: { total: scTotal, met: scMet },
|
|
65
|
+
frVerifiedPct: frTotal === 0 ? 1 : frVerified / frTotal,
|
|
66
|
+
scMetPct: scTotal === 0 ? 1 : scMet / scTotal,
|
|
67
|
+
rows,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=rtm-scorecard-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rtm-scorecard-run.js","sourceRoot":"","sources":["../../src/lib/rtm-scorecard-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FR/SC Requirements-Traceability-Matrix (RTM) scorecard — a NON-VACUOUS coverage
|
|
3
|
+
* derivation folded into the engine from encoreos's repo-local `scripts/specs/verify-acs.mjs`
|
|
4
|
+
* (issue #79). It answers "how much of this spec's Definition of Done (its functional
|
|
5
|
+
* requirements FR-N and success criteria SC-N) is actually proven?" — where "proven" is
|
|
6
|
+
* defined so a coverage-free corpus cannot read 100% (the vacuity guard):
|
|
7
|
+
*
|
|
8
|
+
* - an **FR** is verified iff it has ≥1 covering AC and every covering AC is verified,
|
|
9
|
+
* OR it carries its own proof. An FR with zero covering ACs and no own-proof is NOT
|
|
10
|
+
* verified — that is the property the vacuity guard pins.
|
|
11
|
+
* - an **SC** is met iff it references ≥1 AC, at least one referenced AC is verified,
|
|
12
|
+
* AND every NFR it gates is green. An SC that references no AC is NOT met.
|
|
13
|
+
*
|
|
14
|
+
* PURE (fs-free): it reads a spec's body text + already-parsed `acceptance_criteria`, so it
|
|
15
|
+
* sits on the `/pure` graph and a browser bundle (Spec Studio) can compute the scorecard
|
|
16
|
+
* client-side. The corpus walk + file IO that feed it stay in the command layer. Trace edges
|
|
17
|
+
* (AC→FR via `fr_refs`, SC→AC/NFR via reference parens) mirror the engine's existing model.
|
|
18
|
+
*/
|
|
19
|
+
/** An AC block the scorecard reasons over: its id, terminal status, and the FR/NFR ids it
|
|
20
|
+
* covers (from frontmatter `fr_refs` / `nfr_refs`). Sourced from the parsed frontmatter. */
|
|
21
|
+
export type ScorecardAcBlock = {
|
|
22
|
+
id: string;
|
|
23
|
+
status?: string;
|
|
24
|
+
fr_refs?: string[];
|
|
25
|
+
nfr_refs?: string[];
|
|
26
|
+
};
|
|
27
|
+
/** A body requirement/NFR item (`- **FR-1** [status] …`). */
|
|
28
|
+
export type ScorecardRequirementItem = {
|
|
29
|
+
id: string;
|
|
30
|
+
status: string;
|
|
31
|
+
};
|
|
32
|
+
/** A success criterion parsed from the body, with the ACs/NFRs it traces to. */
|
|
33
|
+
export type SuccessCriterionItem = {
|
|
34
|
+
id: string;
|
|
35
|
+
status: string;
|
|
36
|
+
ac_refs: string[];
|
|
37
|
+
nfr_refs: string[];
|
|
38
|
+
};
|
|
39
|
+
/** Proof accumulated from a verification run: AC/FR ids proven by a passing backing test.
|
|
40
|
+
* Empty ⇒ the scorecard reflects on-disk statuses only (still non-vacuous). */
|
|
41
|
+
export type ScorecardProof = {
|
|
42
|
+
provenAcIds?: Set<string>;
|
|
43
|
+
provenFrIds?: Set<string>;
|
|
44
|
+
};
|
|
45
|
+
export type FrVerdict = {
|
|
46
|
+
verified: boolean;
|
|
47
|
+
reason: string;
|
|
48
|
+
coveringAcs: string[];
|
|
49
|
+
};
|
|
50
|
+
export type ScVerdictResult = {
|
|
51
|
+
met: boolean;
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
export type Scorecard = {
|
|
55
|
+
fr: {
|
|
56
|
+
total: number;
|
|
57
|
+
verified: number;
|
|
58
|
+
rows: Array<{
|
|
59
|
+
id: string;
|
|
60
|
+
} & FrVerdict>;
|
|
61
|
+
};
|
|
62
|
+
sc: {
|
|
63
|
+
total: number;
|
|
64
|
+
met: number;
|
|
65
|
+
rows: Array<{
|
|
66
|
+
id: string;
|
|
67
|
+
} & ScVerdictResult>;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/** Parse a single body line into `{ id, kind: 'US' | 'FR', status }`, or null. US and FR
|
|
71
|
+
* share one list format (`- **FR-1** [verified] …`); FR is first-class verifiable. */
|
|
72
|
+
export declare function matchBodyRequirement(line: string): {
|
|
73
|
+
id: string;
|
|
74
|
+
kind: 'US' | 'FR';
|
|
75
|
+
status: string;
|
|
76
|
+
} | null;
|
|
77
|
+
/** Parse one body line into a success criterion `{ id, status, ac_refs, nfr_refs }`, or null.
|
|
78
|
+
* SC lines carry trailing reference parens — `(US-3, AC-6)`, `(US-1, FR-1, NFR-1)` — from
|
|
79
|
+
* which the AC and NFR ids are scraped (dedup'd). */
|
|
80
|
+
export declare function matchSuccessCriterion(line: string): SuccessCriterionItem | null;
|
|
81
|
+
/** Match a body NFR item (`- **NFR-perf-1** [status] …`) — the gates an SC verdict checks. */
|
|
82
|
+
export declare function matchNfrItem(line: string): ScorecardRequirementItem | null;
|
|
83
|
+
/** Invert the AC→FR trace edges (each AC's `fr_refs`) into `FR id → [covering AC ids]`. */
|
|
84
|
+
export declare function coveringAcsByFr(acBlocks: ScorecardAcBlock[]): Map<string, string[]>;
|
|
85
|
+
/** True if an AC is verified — status `verified` on disk OR proven in this run. */
|
|
86
|
+
export declare function isAcVerified(acId: string, acStatusById: Map<string, string | undefined>, provenAcIds: Set<string>): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The NON-VACUOUS FR verdict. An FR counts verified iff (a) it has ≥1 covering AC and every
|
|
89
|
+
* covering AC is verified, or (b) the FR itself is proven. Zero covering ACs and no own-proof
|
|
90
|
+
* ⇒ NOT verified (the vacuity guard: a coverage-free corpus cannot read 100%).
|
|
91
|
+
*/
|
|
92
|
+
export declare function frVerdict(opts: {
|
|
93
|
+
coveringAcs?: string[];
|
|
94
|
+
acStatusById: Map<string, string | undefined>;
|
|
95
|
+
provenAcIds: Set<string>;
|
|
96
|
+
frProven?: boolean;
|
|
97
|
+
frVerifiedOnDisk?: boolean;
|
|
98
|
+
}): FrVerdict;
|
|
99
|
+
/**
|
|
100
|
+
* The NON-VACUOUS SC verdict. An SC counts met iff (a) it references ≥1 AC and at least one
|
|
101
|
+
* referenced AC is verified, and (b) every NFR it gates is green. A missing NFR (not in the
|
|
102
|
+
* spec) is ungreen — a dangling gate cannot be assumed satisfied. An SC referencing no AC is
|
|
103
|
+
* NOT met.
|
|
104
|
+
*/
|
|
105
|
+
export declare function scVerdict(sc: SuccessCriterionItem, opts: {
|
|
106
|
+
acStatusById: Map<string, string | undefined>;
|
|
107
|
+
provenAcIds: Set<string>;
|
|
108
|
+
nfrVerifiedById: Map<string, boolean>;
|
|
109
|
+
}): ScVerdictResult;
|
|
110
|
+
/** The items one spec's scorecard reasons over (body-parsed FR/SC/NFR + frontmatter AC blocks). */
|
|
111
|
+
export type ScorecardItems = {
|
|
112
|
+
frItems: ScorecardRequirementItem[];
|
|
113
|
+
acBlocks: ScorecardAcBlock[];
|
|
114
|
+
scItems: SuccessCriterionItem[];
|
|
115
|
+
nfrItems: ScorecardRequirementItem[];
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Collect the scorecard items for one spec from its BODY text (FR/US/NFR/SC list lines) and
|
|
119
|
+
* its already-parsed `acceptance_criteria` frontmatter (AC id/status/fr_refs/nfr_refs). Pure —
|
|
120
|
+
* the caller supplies the body (frontmatter-stripped) and the raw AC array. US items are not
|
|
121
|
+
* scored (they trace via ACs) so they are not returned.
|
|
122
|
+
*/
|
|
123
|
+
export declare function collectScorecardItems(bodyText: string, rawAcs: unknown): ScorecardItems;
|
|
124
|
+
/** Derive the whole FR/SC scorecard for one spec — the single non-vacuous entry point. */
|
|
125
|
+
export declare function deriveScorecard(spec: Partial<ScorecardItems>, proof?: ScorecardProof): Scorecard;
|
|
126
|
+
/** Advisory coverage ratchet floors (warn-level, NEVER fatal): the fraction of FRs that clear
|
|
127
|
+
* the non-vacuous bar and of SCs that are met. Conservative defaults; a consumer overrides via
|
|
128
|
+
* `rtm.ratchets` in `specs/config.yaml`. Kept advisory so a young/low-coverage corpus stays
|
|
129
|
+
* green while a real regression below the floor still surfaces. */
|
|
130
|
+
export declare const COVERAGE_RATCHET_FLOORS: Record<string, number>;
|
|
131
|
+
/** The resolved RTM config: whether the scorecard is enabled + the effective ratchet floors.
|
|
132
|
+
* `resolveRtm` duck-types the config (no fs-bound `SpecConfig` import) so this stays pure. */
|
|
133
|
+
export type RtmConfig = {
|
|
134
|
+
enabled: boolean;
|
|
135
|
+
ratchets: Record<string, number>;
|
|
136
|
+
};
|
|
137
|
+
/** Resolve the opt-in RTM scorecard config; defaults to disabled + the built-in floors, so a
|
|
138
|
+
* repo without an `rtm:` block (or with `enabled` falsy) gets no scorecard. */
|
|
139
|
+
export declare function resolveRtm(config: {
|
|
140
|
+
rtm?: {
|
|
141
|
+
enabled?: boolean;
|
|
142
|
+
ratchets?: Record<string, number>;
|
|
143
|
+
};
|
|
144
|
+
} | null | undefined): RtmConfig;
|
|
145
|
+
/** Compare measured coverage (`{ frVerifiedPct, scMetPct, … }`, each 0–1) against the advisory
|
|
146
|
+
* floors. Returns the breaches (empty ⇒ none). Pure — the caller decides whether to print/warn. */
|
|
147
|
+
export declare function checkCoverageRatchets(measured: Record<string, number>, floors?: Record<string, number>): Array<{
|
|
148
|
+
metric: string;
|
|
149
|
+
floor: number;
|
|
150
|
+
value: number;
|
|
151
|
+
}>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FR/SC Requirements-Traceability-Matrix (RTM) scorecard — a NON-VACUOUS coverage
|
|
3
|
+
* derivation folded into the engine from encoreos's repo-local `scripts/specs/verify-acs.mjs`
|
|
4
|
+
* (issue #79). It answers "how much of this spec's Definition of Done (its functional
|
|
5
|
+
* requirements FR-N and success criteria SC-N) is actually proven?" — where "proven" is
|
|
6
|
+
* defined so a coverage-free corpus cannot read 100% (the vacuity guard):
|
|
7
|
+
*
|
|
8
|
+
* - an **FR** is verified iff it has ≥1 covering AC and every covering AC is verified,
|
|
9
|
+
* OR it carries its own proof. An FR with zero covering ACs and no own-proof is NOT
|
|
10
|
+
* verified — that is the property the vacuity guard pins.
|
|
11
|
+
* - an **SC** is met iff it references ≥1 AC, at least one referenced AC is verified,
|
|
12
|
+
* AND every NFR it gates is green. An SC that references no AC is NOT met.
|
|
13
|
+
*
|
|
14
|
+
* PURE (fs-free): it reads a spec's body text + already-parsed `acceptance_criteria`, so it
|
|
15
|
+
* sits on the `/pure` graph and a browser bundle (Spec Studio) can compute the scorecard
|
|
16
|
+
* client-side. The corpus walk + file IO that feed it stay in the command layer. Trace edges
|
|
17
|
+
* (AC→FR via `fr_refs`, SC→AC/NFR via reference parens) mirror the engine's existing model.
|
|
18
|
+
*/
|
|
19
|
+
/** Parse a single body line into `{ id, kind: 'US' | 'FR', status }`, or null. US and FR
|
|
20
|
+
* share one list format (`- **FR-1** [verified] …`); FR is first-class verifiable. */
|
|
21
|
+
export function matchBodyRequirement(line) {
|
|
22
|
+
const m = /^\s*-\s+\*{0,2}((US|FR)-\d+)\*{0,2}\s+\[([a-z_]+)\]/.exec(line);
|
|
23
|
+
return m ? { id: m[1], kind: m[2], status: m[3] } : null;
|
|
24
|
+
}
|
|
25
|
+
/** Parse one body line into a success criterion `{ id, status, ac_refs, nfr_refs }`, or null.
|
|
26
|
+
* SC lines carry trailing reference parens — `(US-3, AC-6)`, `(US-1, FR-1, NFR-1)` — from
|
|
27
|
+
* which the AC and NFR ids are scraped (dedup'd). */
|
|
28
|
+
export function matchSuccessCriterion(line) {
|
|
29
|
+
const m = /^\s*-\s+\*\*(SC-\d+(?:\.\d+)*)\b[^*\n]*\*\*\s*(?:\[([a-z_]+)\])?\s*(.*)$/.exec(line);
|
|
30
|
+
if (!m)
|
|
31
|
+
return null;
|
|
32
|
+
const tail = m[3] ?? '';
|
|
33
|
+
return {
|
|
34
|
+
id: m[1],
|
|
35
|
+
status: m[2] ?? 'planned',
|
|
36
|
+
ac_refs: [...new Set(tail.match(/\bAC-\d+(?:\.\d+)*/g) ?? [])],
|
|
37
|
+
nfr_refs: [...new Set(tail.match(/\bNFR(?:-[a-z]+)?-\d+(?:\.\d+)*/g) ?? [])],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Match a body NFR item (`- **NFR-perf-1** [status] …`) — the gates an SC verdict checks. */
|
|
41
|
+
export function matchNfrItem(line) {
|
|
42
|
+
const m = /^\s*-\s+\*{0,2}(NFR(?:-[a-z]+)?-\d+(?:\.\d+)*)\*{0,2}(?:\s+\[([a-z_]+)\])?/.exec(line);
|
|
43
|
+
return m ? { id: m[1], status: m[2] ?? 'planned' } : null;
|
|
44
|
+
}
|
|
45
|
+
/** Invert the AC→FR trace edges (each AC's `fr_refs`) into `FR id → [covering AC ids]`. */
|
|
46
|
+
export function coveringAcsByFr(acBlocks) {
|
|
47
|
+
const map = new Map();
|
|
48
|
+
for (const ac of acBlocks) {
|
|
49
|
+
for (const fr of ac.fr_refs ?? []) {
|
|
50
|
+
if (!map.has(fr))
|
|
51
|
+
map.set(fr, []);
|
|
52
|
+
map.get(fr).push(ac.id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
57
|
+
/** True if an AC is verified — status `verified` on disk OR proven in this run. */
|
|
58
|
+
export function isAcVerified(acId, acStatusById, provenAcIds) {
|
|
59
|
+
return acStatusById.get(acId) === 'verified' || provenAcIds.has(acId);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The NON-VACUOUS FR verdict. An FR counts verified iff (a) it has ≥1 covering AC and every
|
|
63
|
+
* covering AC is verified, or (b) the FR itself is proven. Zero covering ACs and no own-proof
|
|
64
|
+
* ⇒ NOT verified (the vacuity guard: a coverage-free corpus cannot read 100%).
|
|
65
|
+
*/
|
|
66
|
+
export function frVerdict(opts) {
|
|
67
|
+
const { coveringAcs = [], acStatusById, provenAcIds, frProven = false, frVerifiedOnDisk = false } = opts;
|
|
68
|
+
if (frProven || frVerifiedOnDisk) {
|
|
69
|
+
return { verified: true, reason: 'own @verifies proof', coveringAcs };
|
|
70
|
+
}
|
|
71
|
+
if (coveringAcs.length === 0) {
|
|
72
|
+
return { verified: false, reason: 'no covering AC and no own proof', coveringAcs };
|
|
73
|
+
}
|
|
74
|
+
const unverified = coveringAcs.filter((ac) => !isAcVerified(ac, acStatusById, provenAcIds));
|
|
75
|
+
if (unverified.length === 0) {
|
|
76
|
+
return { verified: true, reason: `${coveringAcs.length} covering AC(s) all verified`, coveringAcs };
|
|
77
|
+
}
|
|
78
|
+
return { verified: false, reason: `covering AC(s) not yet verified: ${unverified.join(', ')}`, coveringAcs };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The NON-VACUOUS SC verdict. An SC counts met iff (a) it references ≥1 AC and at least one
|
|
82
|
+
* referenced AC is verified, and (b) every NFR it gates is green. A missing NFR (not in the
|
|
83
|
+
* spec) is ungreen — a dangling gate cannot be assumed satisfied. An SC referencing no AC is
|
|
84
|
+
* NOT met.
|
|
85
|
+
*/
|
|
86
|
+
export function scVerdict(sc, opts) {
|
|
87
|
+
const { acStatusById, provenAcIds, nfrVerifiedById } = opts;
|
|
88
|
+
const acRefs = sc.ac_refs ?? [];
|
|
89
|
+
const nfrRefs = sc.nfr_refs ?? [];
|
|
90
|
+
if (acRefs.length === 0) {
|
|
91
|
+
return { met: false, reason: 'references no AC (cannot be proven)' };
|
|
92
|
+
}
|
|
93
|
+
const verifiedAcs = acRefs.filter((ac) => isAcVerified(ac, acStatusById, provenAcIds));
|
|
94
|
+
if (verifiedAcs.length === 0) {
|
|
95
|
+
return { met: false, reason: `no referenced AC is verified (${acRefs.join(', ')})` };
|
|
96
|
+
}
|
|
97
|
+
const ungreenNfrs = nfrRefs.filter((nfr) => nfrVerifiedById.get(nfr) !== true);
|
|
98
|
+
if (ungreenNfrs.length > 0) {
|
|
99
|
+
return { met: false, reason: `NFR gate(s) not green: ${ungreenNfrs.join(', ')}` };
|
|
100
|
+
}
|
|
101
|
+
return { met: true, reason: `${verifiedAcs.length}/${acRefs.length} referenced AC(s) verified, NFR gates green` };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Collect the scorecard items for one spec from its BODY text (FR/US/NFR/SC list lines) and
|
|
105
|
+
* its already-parsed `acceptance_criteria` frontmatter (AC id/status/fr_refs/nfr_refs). Pure —
|
|
106
|
+
* the caller supplies the body (frontmatter-stripped) and the raw AC array. US items are not
|
|
107
|
+
* scored (they trace via ACs) so they are not returned.
|
|
108
|
+
*/
|
|
109
|
+
export function collectScorecardItems(bodyText, rawAcs) {
|
|
110
|
+
const acBlocks = Array.isArray(rawAcs)
|
|
111
|
+
? rawAcs
|
|
112
|
+
.filter((e) => !!e && typeof e === 'object')
|
|
113
|
+
.map((e) => ({
|
|
114
|
+
id: typeof e.id === 'string' ? e.id : '',
|
|
115
|
+
status: typeof e.status === 'string' ? e.status : undefined,
|
|
116
|
+
fr_refs: coerceIdList(e.fr_refs),
|
|
117
|
+
nfr_refs: coerceIdList(e.nfr_refs),
|
|
118
|
+
}))
|
|
119
|
+
.filter((a) => a.id !== '')
|
|
120
|
+
: [];
|
|
121
|
+
const frItems = [];
|
|
122
|
+
const nfrItems = [];
|
|
123
|
+
const scItems = [];
|
|
124
|
+
for (const line of bodyText.split('\n')) {
|
|
125
|
+
const req = matchBodyRequirement(line);
|
|
126
|
+
if (req) {
|
|
127
|
+
if (req.kind === 'FR')
|
|
128
|
+
frItems.push({ id: req.id, status: req.status });
|
|
129
|
+
continue; // US items are traced via ACs, not scored directly
|
|
130
|
+
}
|
|
131
|
+
const nfr = matchNfrItem(line);
|
|
132
|
+
if (nfr) {
|
|
133
|
+
nfrItems.push(nfr);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const sc = matchSuccessCriterion(line);
|
|
137
|
+
if (sc)
|
|
138
|
+
scItems.push(sc);
|
|
139
|
+
}
|
|
140
|
+
return { frItems, acBlocks, scItems, nfrItems };
|
|
141
|
+
}
|
|
142
|
+
/** Coerce a frontmatter ref field (`fr_refs`/`nfr_refs`) to a string id list — accepts a YAML
|
|
143
|
+
* array of strings or a single string; anything else ⇒ []. */
|
|
144
|
+
function coerceIdList(v) {
|
|
145
|
+
if (typeof v === 'string')
|
|
146
|
+
return v.trim() === '' ? [] : [v.trim()];
|
|
147
|
+
if (Array.isArray(v))
|
|
148
|
+
return v.filter((x) => typeof x === 'string' && x.trim() !== '').map((x) => x.trim());
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
/** Derive the whole FR/SC scorecard for one spec — the single non-vacuous entry point. */
|
|
152
|
+
export function deriveScorecard(spec, proof = {}) {
|
|
153
|
+
const { frItems = [], acBlocks = [], scItems = [], nfrItems = [] } = spec;
|
|
154
|
+
const provenAcIds = proof.provenAcIds ?? new Set();
|
|
155
|
+
const provenFrIds = proof.provenFrIds ?? new Set();
|
|
156
|
+
const acStatusById = new Map(acBlocks.map((a) => [a.id, a.status]));
|
|
157
|
+
const nfrVerifiedById = new Map(nfrItems.map((n) => [n.id, n.status === 'verified']));
|
|
158
|
+
const coveringByFr = coveringAcsByFr(acBlocks);
|
|
159
|
+
const frRows = frItems.map((fr) => ({
|
|
160
|
+
id: fr.id,
|
|
161
|
+
...frVerdict({
|
|
162
|
+
coveringAcs: coveringByFr.get(fr.id) ?? [],
|
|
163
|
+
acStatusById,
|
|
164
|
+
provenAcIds,
|
|
165
|
+
frProven: provenFrIds.has(fr.id),
|
|
166
|
+
frVerifiedOnDisk: fr.status === 'verified',
|
|
167
|
+
}),
|
|
168
|
+
}));
|
|
169
|
+
const scRows = scItems.map((sc) => ({ id: sc.id, ...scVerdict(sc, { acStatusById, provenAcIds, nfrVerifiedById }) }));
|
|
170
|
+
return {
|
|
171
|
+
fr: { total: frRows.length, verified: frRows.filter((r) => r.verified).length, rows: frRows },
|
|
172
|
+
sc: { total: scRows.length, met: scRows.filter((r) => r.met).length, rows: scRows },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Advisory coverage ratchet floors (warn-level, NEVER fatal): the fraction of FRs that clear
|
|
176
|
+
* the non-vacuous bar and of SCs that are met. Conservative defaults; a consumer overrides via
|
|
177
|
+
* `rtm.ratchets` in `specs/config.yaml`. Kept advisory so a young/low-coverage corpus stays
|
|
178
|
+
* green while a real regression below the floor still surfaces. */
|
|
179
|
+
export const COVERAGE_RATCHET_FLOORS = { frVerifiedPct: 0, scMetPct: 0 };
|
|
180
|
+
/** Resolve the opt-in RTM scorecard config; defaults to disabled + the built-in floors, so a
|
|
181
|
+
* repo without an `rtm:` block (or with `enabled` falsy) gets no scorecard. */
|
|
182
|
+
export function resolveRtm(config) {
|
|
183
|
+
const rtm = config?.rtm;
|
|
184
|
+
return { enabled: rtm?.enabled === true, ratchets: rtm?.ratchets ?? COVERAGE_RATCHET_FLOORS };
|
|
185
|
+
}
|
|
186
|
+
/** Compare measured coverage (`{ frVerifiedPct, scMetPct, … }`, each 0–1) against the advisory
|
|
187
|
+
* floors. Returns the breaches (empty ⇒ none). Pure — the caller decides whether to print/warn. */
|
|
188
|
+
export function checkCoverageRatchets(measured, floors = COVERAGE_RATCHET_FLOORS) {
|
|
189
|
+
const breaches = [];
|
|
190
|
+
for (const [metric, floor] of Object.entries(floors)) {
|
|
191
|
+
const value = measured[metric];
|
|
192
|
+
if (typeof value === 'number' && value < floor)
|
|
193
|
+
breaches.push({ metric, floor, value });
|
|
194
|
+
}
|
|
195
|
+
return breaches;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=rtm-scorecard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rtm-scorecard.js","sourceRoot":"","sources":["../../src/lib/rtm-scorecard.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/spec-pure.d.ts
CHANGED
|
@@ -8,6 +8,22 @@ export declare const SPEC_ID_GRAMMAR: {
|
|
|
8
8
|
readonly pattern: "<core>(-[A-Z]{1,4})?-<n>(.<n>)?(-(EN-<n>|E<n>|P<n>(.<n>)?(-EN-<n>)?))?";
|
|
9
9
|
readonly caseInsensitive: true;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Remove regions that legitimately QUOTE compliance-sounding words as examples,
|
|
13
|
+
* so the keyword heuristic in {@link detectSensitive} reads only load-bearing
|
|
14
|
+
* prose. Strips, per line: fenced code blocks (``` … ``` / ~~~ … ~~~), a
|
|
15
|
+
* `## Examples` heading section — an EXACT `Examples`/`Example` heading title only
|
|
16
|
+
* (so `## Examples of Regulated Data` is a normal heading, its body left as
|
|
17
|
+
* scannable prose) — down to the next same-or-higher heading, and Gherkin step
|
|
18
|
+
* lines. `Scenario:` / `Example(s):` / `Given` / `When` / `Then` openers strip
|
|
19
|
+
* unconditionally; `And` / `But` strip ONLY when the immediately preceding
|
|
20
|
+
* non-blank line (no intervening blank line) was itself a Gherkin step/opener —
|
|
21
|
+
* a standalone `And`/`But` prose line (e.g. a paragraph continuation) is left in
|
|
22
|
+
* place. Pure string transform — no fs, stays on the `/pure` graph. Callers apply
|
|
23
|
+
* it to the keyword-match INPUT only; opt-in/opt-out markers are still read from
|
|
24
|
+
* the raw text.
|
|
25
|
+
*/
|
|
26
|
+
export declare function stripExampleRegions(text: string): string;
|
|
11
27
|
/** True when a generated test source has ≥1 ACTIVE `test(` block (not `test.fixme`/
|
|
12
28
|
* `test.describe`) and is not an AC-less placeholder. Pure (operates on source text). */
|
|
13
29
|
export declare function hasActiveGeneratedTestSource(source: string): boolean;
|
|
@@ -88,6 +104,28 @@ export declare function declaredSpecId(declaredId: string | null | undefined, co
|
|
|
88
104
|
* Returns the resolved spec id, or null when the file is not a primary spec.
|
|
89
105
|
*/
|
|
90
106
|
export declare function resolveSpecFileId(core: string, filename: string, declaredId?: string | null): string | null;
|
|
107
|
+
/**
|
|
108
|
+
* True when an on-disk filename PARSES to a well-formed primary-spec id yet the corpus
|
|
109
|
+
* loader would silently DROP it ({@link resolveSpecFileId} → null) for a reason a
|
|
110
|
+
* frontmatter `id:` could rescue — a reserved lifecycle word (REVIEW / ANALYSIS /
|
|
111
|
+
* CLARIFICATION …) landed inside a real feature slug and no `id:` disambiguates it. This
|
|
112
|
+
* is the silent-authoring-drop class issue #76 surfaces: the file reads as spec X to a
|
|
113
|
+
* human but never reaches spec-index.json / navigator / reconcile / recall.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately EXCLUDES bona-fide sibling artifacts that legitimately carry a parent id
|
|
116
|
+
* and are SUPPOSED to be non-primary, so the surfacer stays quiet on a healthy corpus:
|
|
117
|
+
* - dedicated CONTEXT/CLARIFICATION/ENHANCEMENTS/EXPANSION files ({@link isDedicatedArtifactFile}),
|
|
118
|
+
* - `{ID}-PHASE…` lifecycle files ({@link isPhaseLifecycleFile}),
|
|
119
|
+
* - descriptor-suffix artifacts (`-TASKS`/`-PLAN`/`-RESEARCH`/… — {@link DERIVATIVE_DESCRIPTOR_RE}),
|
|
120
|
+
* - and a LONE terminal descriptor (`{ID}-REVIEW`, `{ID}-ANALYSIS`): only files with real
|
|
121
|
+
* slug content AFTER the id (≥2 slug words) are flagged, i.e. the reserved word is
|
|
122
|
+
* embedded in a feature name (`{ID}-compliance-review-workflow`), not the whole slug.
|
|
123
|
+
*
|
|
124
|
+
* The residue is exactly the case the mint-time guard (spec-id-guard → {@link resolveSpecFileId})
|
|
125
|
+
* blocks on `create`/`promote`, made visible for files already on disk. Pure (fs-free); the
|
|
126
|
+
* fs walk that feeds it lives in specs.ts `findSkippedSpecFiles`.
|
|
127
|
+
*/
|
|
128
|
+
export declare function isSilentlySkippedSpecFile(core: string, filename: string, declaredId?: string | null): boolean;
|
|
91
129
|
/** The skip rules `collectSpecsFromDirectory` applies to one filename. */
|
|
92
130
|
export declare function isPrimarySpecFile(filename: string): boolean;
|
|
93
131
|
/**
|