@holmes-lab/holmes-kit 0.2.1 → 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/CHANGELOG.md +55 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/assoc/assoc-arm.d.ts +94 -0
- package/dist/holmes/assoc/assoc-arm.js +187 -0
- package/dist/holmes/assoc/explore.d.ts +21 -0
- package/dist/holmes/assoc/explore.js +160 -0
- package/dist/holmes/assoc/impact-baseline.d.ts +13 -0
- package/dist/holmes/assoc/impact-baseline.js +48 -0
- package/dist/holmes/assoc/ppr.d.ts +64 -0
- package/dist/holmes/assoc/ppr.js +110 -0
- package/dist/holmes/cli/agents.d.ts +24 -11
- package/dist/holmes/cli/agents.js +93 -21
- package/dist/holmes/cli/doctor.d.ts +56 -0
- package/dist/holmes/cli/doctor.js +311 -5
- package/dist/holmes/cli/index.js +13 -1
- package/dist/holmes/cli/semantic-key.d.ts +19 -0
- package/dist/holmes/cli/semantic-key.js +93 -0
- package/dist/holmes/config/config.d.ts +9 -0
- package/dist/holmes/config/config.js +8 -1
- package/dist/holmes/cpg/language-capability.d.ts +65 -0
- package/dist/holmes/cpg/language-capability.js +145 -0
- package/dist/holmes/cpg/language-parser-walk.js +179 -34
- package/dist/holmes/cpg/language-parser.d.ts +1 -1
- package/dist/holmes/governance/ledger-rechain.d.ts +12 -0
- package/dist/holmes/governance/ledger-rechain.js +17 -2
- package/dist/holmes/governance/provenance-ledger.js +21 -0
- package/dist/holmes/guardrail/blind-spots.js +12 -1
- package/dist/holmes/guardrail/impact-gate.d.ts +77 -0
- package/dist/holmes/guardrail/impact-gate.js +263 -0
- package/dist/holmes/guardrail/write-target.d.ts +38 -1
- package/dist/holmes/guardrail/write-target.js +41 -4
- package/dist/holmes/hooks/adapters/antigravity.js +12 -1
- package/dist/holmes/hooks/corrupt-state-run.d.ts +33 -0
- package/dist/holmes/hooks/corrupt-state-run.js +16 -0
- package/dist/holmes/hooks/pre-tool-use.d.ts +26 -0
- package/dist/holmes/hooks/pre-tool-use.js +203 -11
- package/dist/holmes/hooks/stop.d.ts +65 -0
- package/dist/holmes/hooks/stop.js +200 -2
- package/dist/holmes/mcp/elicit-approval.d.ts +32 -2
- package/dist/holmes/mcp/elicit-approval.js +30 -2
- package/dist/holmes/mcp/handlers.d.ts +88 -0
- package/dist/holmes/mcp/handlers.js +636 -35
- package/dist/holmes/mcp/maintenance-analyze.d.ts +435 -0
- package/dist/holmes/mcp/maintenance-analyze.js +994 -0
- package/dist/holmes/mcp/maintenance-evidence.d.ts +140 -0
- package/dist/holmes/mcp/maintenance-evidence.js +253 -0
- package/dist/holmes/mcp/server.js +13 -10
- package/dist/holmes/mcp/tool-schemas.js +71 -0
- package/dist/holmes/project/root.js +3 -1
- package/dist/holmes/review/baseline-arm.d.ts +37 -0
- package/dist/holmes/review/baseline-arm.js +51 -0
- package/dist/holmes/review/captured-stdin-guard.d.ts +8 -0
- package/dist/holmes/review/captured-stdin-guard.js +48 -0
- package/dist/holmes/review/coherence-verify.d.ts +31 -0
- package/dist/holmes/review/coherence-verify.js +144 -0
- package/dist/holmes/review/commit-text.d.ts +50 -0
- package/dist/holmes/review/commit-text.js +76 -0
- package/dist/holmes/review/confidence-calibration.d.ts +39 -0
- package/dist/holmes/review/confidence-calibration.js +39 -0
- package/dist/holmes/review/content-baseline.d.ts +38 -0
- package/dist/holmes/review/content-baseline.js +103 -0
- package/dist/holmes/review/content-verify.d.ts +20 -0
- package/dist/holmes/review/content-verify.js +73 -0
- package/dist/holmes/review/dense-retrieval.d.ts +66 -0
- package/dist/holmes/review/dense-retrieval.js +97 -0
- package/dist/holmes/review/edge-quality.d.ts +44 -0
- package/dist/holmes/review/edge-quality.js +117 -0
- package/dist/holmes/review/evaluation-metrics.d.ts +138 -0
- package/dist/holmes/review/evaluation-metrics.js +175 -0
- package/dist/holmes/review/graph-verifier.d.ts +34 -0
- package/dist/holmes/review/graph-verifier.js +62 -0
- package/dist/holmes/review/hop-ablation.d.ts +100 -0
- package/dist/holmes/review/hop-ablation.js +89 -0
- package/dist/holmes/review/manual-baseline.d.ts +209 -0
- package/dist/holmes/review/manual-baseline.js +2846 -0
- package/dist/holmes/review/oracle-gap.d.ts +32 -0
- package/dist/holmes/review/oracle-gap.js +102 -0
- package/dist/holmes/review/point-in-time-replay.d.ts +41 -0
- package/dist/holmes/review/point-in-time-replay.js +161 -0
- package/dist/holmes/review/rank-diagnosis.d.ts +43 -0
- package/dist/holmes/review/rank-diagnosis.js +163 -0
- package/dist/holmes/review/replay-calibration.d.ts +62 -0
- package/dist/holmes/review/replay-calibration.js +83 -0
- package/dist/holmes/review/replay-corpus.d.ts +135 -0
- package/dist/holmes/review/replay-corpus.js +210 -0
- package/dist/holmes/review/run-replay.d.ts +260 -0
- package/dist/holmes/review/run-replay.js +729 -0
- package/dist/holmes/review/semantic-arm.d.ts +271 -0
- package/dist/holmes/review/semantic-arm.js +717 -0
- package/dist/holmes/review/semantic-retrieval.d.ts +55 -0
- package/dist/holmes/review/semantic-retrieval.js +156 -0
- package/dist/holmes/review/spec-layer-stats.d.ts +38 -0
- package/dist/holmes/review/spec-layer-stats.js +52 -0
- package/dist/holmes/review/temporal-prior.d.ts +33 -0
- package/dist/holmes/review/temporal-prior.js +53 -0
- package/dist/holmes/review/test-runner.d.ts +15 -0
- package/dist/holmes/review/test-runner.js +41 -4
- package/dist/holmes/review/test-selection-breadth.d.ts +75 -0
- package/dist/holmes/review/test-selection-breadth.js +57 -0
- package/dist/holmes/review/traceability-benchmark.d.ts +81 -0
- package/dist/holmes/review/traceability-benchmark.js +135 -0
- package/dist/holmes/review/union-verify.d.ts +12 -0
- package/dist/holmes/review/union-verify.js +70 -0
- package/dist/holmes/rtm/graph-store.d.ts +51 -0
- package/dist/holmes/rtm/graph-store.js +122 -0
- package/dist/holmes/rtm/incremental.d.ts +25 -1
- package/dist/holmes/rtm/incremental.js +18 -1
- package/dist/holmes/rtm/localize.d.ts +28 -0
- package/dist/holmes/rtm/localize.js +272 -14
- package/dist/holmes/rtm/rtm-builder.d.ts +118 -3
- package/dist/holmes/rtm/rtm-builder.js +265 -28
- package/dist/holmes/rtm/rtm-graph.d.ts +117 -2
- package/dist/holmes/rtm/rtm-graph.js +194 -31
- package/dist/holmes/rtm/taint-benchmark.d.ts +97 -0
- package/dist/holmes/rtm/taint-benchmark.js +141 -0
- package/dist/holmes/rtm/test-scope.js +24 -1
- package/dist/holmes/semantic/credentials.d.ts +15 -0
- package/dist/holmes/semantic/credentials.js +134 -0
- package/dist/holmes/semantic/embedder.d.ts +44 -0
- package/dist/holmes/semantic/embedder.js +185 -0
- package/dist/holmes/semantic/hit-rerank.d.ts +4 -0
- package/dist/holmes/semantic/hit-rerank.js +38 -0
- package/dist/holmes/semantic/tier.d.ts +37 -0
- package/dist/holmes/semantic/tier.js +54 -0
- package/dist/holmes/semantic/vector-cache.d.ts +11 -0
- package/dist/holmes/semantic/vector-cache.js +91 -0
- package/dist/holmes/spec/acceptance-quality.d.ts +81 -0
- package/dist/holmes/spec/acceptance-quality.js +169 -0
- package/dist/holmes/spec/validator.js +33 -1
- package/dist/holmes/spec/yaml-scalar.d.ts +1 -0
- package/dist/holmes/spec/yaml-scalar.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Spec } from './spec-parser';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a requirement states what "satisfied" means.
|
|
4
|
+
*
|
|
5
|
+
* The validator checks that a Success Criteria section EXISTS; it never asked whether the section
|
|
6
|
+
* says anything checkable. Measured 2026-08-28 over this repository's 218 requirements: 29 have no
|
|
7
|
+
* section at all and 29 carry a single scaffold sentence, so **58 (27%) cannot answer "did we
|
|
8
|
+
* satisfy this?"** — and the scaffold sentence is what `spec_slice_init` itself writes, so the tool
|
|
9
|
+
* has been manufacturing unanswerable criteria and saying nothing about it.
|
|
10
|
+
*
|
|
11
|
+
* This module DETECTS. It does not repair specs and does not tighten approval: making the validator
|
|
12
|
+
* reject these would invalidate already-approved work, which is a governance decision and not a
|
|
13
|
+
* detector's to take.
|
|
14
|
+
*/
|
|
15
|
+
export type AcceptanceQuality = 'absent' | 'boilerplate' | 'stated';
|
|
16
|
+
/**
|
|
17
|
+
* Phrases that look like a criterion and commit to nothing.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately short and evidence-based: measured across the corpus, exactly ONE single-item
|
|
20
|
+
* boilerplate exists. Speculative additions would be patterns nobody has written, and a pattern that
|
|
21
|
+
* has never matched cannot be shown to work.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ACCEPTANCE_BOILERPLATE: readonly RegExp[];
|
|
24
|
+
export declare function acceptanceQuality(spec: Spec): AcceptanceQuality;
|
|
25
|
+
/** Requirements whose criteria cannot be checked, in corpus order, with the reason. */
|
|
26
|
+
export declare function unactionableRequirements(specs: readonly Spec[]): Array<{
|
|
27
|
+
req: string;
|
|
28
|
+
quality: Exclude<AcceptanceQuality, 'stated'>;
|
|
29
|
+
}>;
|
|
30
|
+
/** The four quadrants a T-SPEC's `coverage` frontmatter makes a claim about. */
|
|
31
|
+
export declare const QUADRANTS: readonly ["normal", "corner", "negative", "boundary"];
|
|
32
|
+
export type Quadrant = typeof QUADRANTS[number];
|
|
33
|
+
/**
|
|
34
|
+
* What a quadrant actually contains.
|
|
35
|
+
*
|
|
36
|
+
* `absent` and `scaffold` are different facts and are kept apart: nothing written, versus a template
|
|
37
|
+
* left untouched while the frontmatter declares the quadrant covered. The second is worse — it
|
|
38
|
+
* passes structural validation and makes a positive claim.
|
|
39
|
+
*/
|
|
40
|
+
export declare function quadrantQuality(spec: Spec, quadrant: Quadrant): 'stated' | 'scaffold' | 'absent';
|
|
41
|
+
/**
|
|
42
|
+
* Quadrants a T-SPEC declares covered without substantiating them. Not claiming coverage is not the
|
|
43
|
+
* same as claiming it falsely, so a `false` in the frontmatter is never reported here.
|
|
44
|
+
*/
|
|
45
|
+
export declare function unsubstantiatedCoverage(specs: readonly Spec[]): Array<{
|
|
46
|
+
spec: string;
|
|
47
|
+
quadrant: Quadrant;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* What an H-SPEC's `## Non-Functional` actually contains — the third judgement of this shape, beside
|
|
51
|
+
* `acceptanceQuality` and `quadrantQuality`, and in the same file so the fourth has an obvious home.
|
|
52
|
+
*/
|
|
53
|
+
export declare function nonFunctionalQuality(spec: Spec): 'stated' | 'scaffold' | 'absent';
|
|
54
|
+
/** H-SPECs whose non-functional section says nothing, with which of the two ways it says nothing. */
|
|
55
|
+
export declare function unstatedNonFunctional(specs: readonly Spec[]): Array<{
|
|
56
|
+
spec: string;
|
|
57
|
+
quality: 'scaffold' | 'absent';
|
|
58
|
+
}>;
|
|
59
|
+
export interface SpecQualityReport {
|
|
60
|
+
/** REQs whose success criteria cannot be checked (A-SPEC-295). */
|
|
61
|
+
unactionableRequirements: ReturnType<typeof unactionableRequirements>;
|
|
62
|
+
/** T-SPEC quadrants declared covered and left as scaffold (A-SPEC-321). */
|
|
63
|
+
unsubstantiatedCoverage: ReturnType<typeof unsubstantiatedCoverage>;
|
|
64
|
+
/** H-SPECs whose non-functional section says nothing (A-SPEC-324). */
|
|
65
|
+
unstatedNonFunctional: ReturnType<typeof unstatedNonFunctional>;
|
|
66
|
+
/** How many findings in total. A COUNT, not a score — see below. */
|
|
67
|
+
total: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The three spec-quality judgements, together.
|
|
71
|
+
*
|
|
72
|
+
* Each was built and measured on its own, and nothing looked at all three — the same shape
|
|
73
|
+
* A-SPEC-312 found on the file axis, where six tuning verdicts had been reached against half the
|
|
74
|
+
* picture. This adds no judgement of its own; a fourth implementation would be a fourth truth.
|
|
75
|
+
*
|
|
76
|
+
* There is deliberately NO aggregate score. The three findings ask for three different actions —
|
|
77
|
+
* rewrite a criterion, write a test case, state an obligation — and one number would say none of
|
|
78
|
+
* them. `total` reports SCALE; the three lists report what to do. Four times in this work an
|
|
79
|
+
* undecomposed number sent the reasoning somewhere wrong before someone looked at the parts.
|
|
80
|
+
*/
|
|
81
|
+
export declare function specQualityReport(specs: readonly Spec[]): SpecQualityReport;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QUADRANTS = exports.ACCEPTANCE_BOILERPLATE = void 0;
|
|
4
|
+
exports.acceptanceQuality = acceptanceQuality;
|
|
5
|
+
exports.unactionableRequirements = unactionableRequirements;
|
|
6
|
+
exports.quadrantQuality = quadrantQuality;
|
|
7
|
+
exports.unsubstantiatedCoverage = unsubstantiatedCoverage;
|
|
8
|
+
exports.nonFunctionalQuality = nonFunctionalQuality;
|
|
9
|
+
exports.unstatedNonFunctional = unstatedNonFunctional;
|
|
10
|
+
exports.specQualityReport = specQualityReport;
|
|
11
|
+
/**
|
|
12
|
+
* Phrases that look like a criterion and commit to nothing.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately short and evidence-based: measured across the corpus, exactly ONE single-item
|
|
15
|
+
* boilerplate exists. Speculative additions would be patterns nobody has written, and a pattern that
|
|
16
|
+
* has never matched cannot be shown to work.
|
|
17
|
+
*/
|
|
18
|
+
exports.ACCEPTANCE_BOILERPLATE = [
|
|
19
|
+
/^implementation completed and verified by tests\.?$/i,
|
|
20
|
+
];
|
|
21
|
+
function criteriaItems(spec) {
|
|
22
|
+
const body = spec.sections?.['Success Criteria'];
|
|
23
|
+
if (typeof body !== 'string')
|
|
24
|
+
return [];
|
|
25
|
+
return body.split('\n').map((line) => line.replace(/^\s*[-*]\s*/, '').trim()).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
function acceptanceQuality(spec) {
|
|
28
|
+
const items = criteriaItems(spec);
|
|
29
|
+
if (items.length === 0)
|
|
30
|
+
return 'absent';
|
|
31
|
+
// One real criterion is enough: a scaffold line sitting next to a measurable one is noise, not a
|
|
32
|
+
// defect. Only a section that is ENTIRELY boilerplate says nothing.
|
|
33
|
+
const anyReal = items.some((item) => !exports.ACCEPTANCE_BOILERPLATE.some((p) => p.test(item)));
|
|
34
|
+
return anyReal ? 'stated' : 'boilerplate';
|
|
35
|
+
}
|
|
36
|
+
/** Requirements whose criteria cannot be checked, in corpus order, with the reason. */
|
|
37
|
+
function unactionableRequirements(specs) {
|
|
38
|
+
return specs
|
|
39
|
+
.filter((spec) => spec.type === 'REQ')
|
|
40
|
+
.map((spec) => ({ req: spec.id, quality: acceptanceQuality(spec) }))
|
|
41
|
+
.filter((row) => row.quality !== 'stated');
|
|
42
|
+
}
|
|
43
|
+
// @implements A-SPEC-321
|
|
44
|
+
/** The four quadrants a T-SPEC's `coverage` frontmatter makes a claim about. */
|
|
45
|
+
exports.QUADRANTS = ['normal', 'corner', 'negative', 'boundary'];
|
|
46
|
+
const QUADRANT_SECTION = {
|
|
47
|
+
normal: 'Normal Cases',
|
|
48
|
+
corner: 'Corner Cases',
|
|
49
|
+
negative: 'Negative Cases',
|
|
50
|
+
boundary: 'Boundary Cases',
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The scaffold `spec_slice_init` writes. Evidence-based, exactly like ACCEPTANCE_BOILERPLATE above:
|
|
54
|
+
* these are the lines actually present in this corpus, not a guess at what filler might look like.
|
|
55
|
+
* Measured 2026-08-28 — 31 of 230 T-SPECs are approved, declare all four quadrants covered, and
|
|
56
|
+
* contain nothing but these.
|
|
57
|
+
*/
|
|
58
|
+
const QUADRANT_SCAFFOLD = [
|
|
59
|
+
/^\s*-?\s*Given valid inputs\s*$/i,
|
|
60
|
+
/^\s*-?\s*Given edge cases\s*$/i,
|
|
61
|
+
/^\s*-?\s*Given invalid inputs\s*$/i,
|
|
62
|
+
/^\s*-?\s*Given boundary conditions\s*$/i,
|
|
63
|
+
/^\s*When operation is performed\s*$/i,
|
|
64
|
+
/^\s*Then correct behavior is observed\s*$/i,
|
|
65
|
+
/^\s*Then system handles gracefully\s*$/i,
|
|
66
|
+
/^\s*Then appropriate error is returned\s*$/i,
|
|
67
|
+
/^\s*Then boundary limits are respected\s*$/i,
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* What a quadrant actually contains.
|
|
71
|
+
*
|
|
72
|
+
* `absent` and `scaffold` are different facts and are kept apart: nothing written, versus a template
|
|
73
|
+
* left untouched while the frontmatter declares the quadrant covered. The second is worse — it
|
|
74
|
+
* passes structural validation and makes a positive claim.
|
|
75
|
+
*/
|
|
76
|
+
function quadrantQuality(spec, quadrant) {
|
|
77
|
+
const body = spec.sections?.[QUADRANT_SECTION[quadrant]] ?? '';
|
|
78
|
+
const lines = body.split('\n').map((line) => line.trim()).filter((line) => line !== '');
|
|
79
|
+
if (lines.length === 0)
|
|
80
|
+
return 'absent';
|
|
81
|
+
// One real line is enough, exactly as one real acceptance criterion is enough above.
|
|
82
|
+
const substantive = lines.some((line) => !QUADRANT_SCAFFOLD.some((pattern) => pattern.test(line)));
|
|
83
|
+
return substantive ? 'stated' : 'scaffold';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Quadrants a T-SPEC declares covered without substantiating them. Not claiming coverage is not the
|
|
87
|
+
* same as claiming it falsely, so a `false` in the frontmatter is never reported here.
|
|
88
|
+
*/
|
|
89
|
+
function unsubstantiatedCoverage(specs) {
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const spec of specs) {
|
|
92
|
+
if (spec.type !== 'T-SPEC')
|
|
93
|
+
continue;
|
|
94
|
+
const coverage = spec.frontmatter?.coverage;
|
|
95
|
+
if (!coverage)
|
|
96
|
+
continue;
|
|
97
|
+
for (const quadrant of exports.QUADRANTS) {
|
|
98
|
+
if (coverage[quadrant] !== true)
|
|
99
|
+
continue;
|
|
100
|
+
if (quadrantQuality(spec, quadrant) !== 'stated')
|
|
101
|
+
out.push({ spec: spec.id, quadrant });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
// @implements A-SPEC-324
|
|
107
|
+
/**
|
|
108
|
+
* The scaffold `spec_slice_init` writes into `## Non-Functional`. Kept deliberately SHORT.
|
|
109
|
+
*
|
|
110
|
+
* This list is the whole protection for prose. Measured 2026-08-28: 180 of 187 approved H-SPECs
|
|
111
|
+
* produce no parseable tagged obligation, and it would be easy to read that as a defect — but
|
|
112
|
+
* `nonfunctional.ts` states in its own documentation that untagged prose is out of scope on purpose,
|
|
113
|
+
* because "those are human judgements, and simulating them would recreate the documented-but-
|
|
114
|
+
* unenforced state this slice exists to remove". Widening these patterns would erase that judgement
|
|
115
|
+
* and call a person's sentence filler. 31 of the 180 carry this exact generated line; those are the
|
|
116
|
+
* ones nobody wrote.
|
|
117
|
+
*/
|
|
118
|
+
const NONFUNCTIONAL_SCAFFOLD = [
|
|
119
|
+
/^Performance and stability maintained\.?$/i,
|
|
120
|
+
];
|
|
121
|
+
/**
|
|
122
|
+
* What an H-SPEC's `## Non-Functional` actually contains — the third judgement of this shape, beside
|
|
123
|
+
* `acceptanceQuality` and `quadrantQuality`, and in the same file so the fourth has an obvious home.
|
|
124
|
+
*/
|
|
125
|
+
function nonFunctionalQuality(spec) {
|
|
126
|
+
const body = spec.sections?.['Non-Functional'] ?? '';
|
|
127
|
+
const lines = body.split('\n')
|
|
128
|
+
.map((line) => line.trim().replace(/^[-*]\s*/, '').trim())
|
|
129
|
+
.filter((line) => line !== '');
|
|
130
|
+
if (lines.length === 0)
|
|
131
|
+
return 'absent';
|
|
132
|
+
const substantive = lines.some((line) => !NONFUNCTIONAL_SCAFFOLD.some((pattern) => pattern.test(line)));
|
|
133
|
+
return substantive ? 'stated' : 'scaffold';
|
|
134
|
+
}
|
|
135
|
+
/** H-SPECs whose non-functional section says nothing, with which of the two ways it says nothing. */
|
|
136
|
+
function unstatedNonFunctional(specs) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const spec of specs) {
|
|
139
|
+
if (spec.type !== 'H-SPEC')
|
|
140
|
+
continue;
|
|
141
|
+
const quality = nonFunctionalQuality(spec);
|
|
142
|
+
if (quality !== 'stated')
|
|
143
|
+
out.push({ spec: spec.id, quality });
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The three spec-quality judgements, together.
|
|
149
|
+
*
|
|
150
|
+
* Each was built and measured on its own, and nothing looked at all three — the same shape
|
|
151
|
+
* A-SPEC-312 found on the file axis, where six tuning verdicts had been reached against half the
|
|
152
|
+
* picture. This adds no judgement of its own; a fourth implementation would be a fourth truth.
|
|
153
|
+
*
|
|
154
|
+
* There is deliberately NO aggregate score. The three findings ask for three different actions —
|
|
155
|
+
* rewrite a criterion, write a test case, state an obligation — and one number would say none of
|
|
156
|
+
* them. `total` reports SCALE; the three lists report what to do. Four times in this work an
|
|
157
|
+
* undecomposed number sent the reasoning somewhere wrong before someone looked at the parts.
|
|
158
|
+
*/
|
|
159
|
+
function specQualityReport(specs) {
|
|
160
|
+
const requirements = unactionableRequirements(specs);
|
|
161
|
+
const coverage = unsubstantiatedCoverage(specs);
|
|
162
|
+
const nonFunctional = unstatedNonFunctional(specs);
|
|
163
|
+
return {
|
|
164
|
+
unactionableRequirements: requirements,
|
|
165
|
+
unsubstantiatedCoverage: coverage,
|
|
166
|
+
unstatedNonFunctional: nonFunctional,
|
|
167
|
+
total: requirements.length + coverage.length + nonFunctional.length,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -9,6 +9,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
9
9
|
const spec_types_1 = require("./spec-types");
|
|
10
10
|
const spec_digest_1 = require("./spec-digest");
|
|
11
11
|
const legacy_format_1 = require("./legacy-format");
|
|
12
|
+
const acceptance_quality_1 = require("./acceptance-quality");
|
|
12
13
|
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
13
14
|
/**
|
|
14
15
|
* The canonical `rev` value for a citation: a content digest of the source as it was read.
|
|
@@ -272,8 +273,39 @@ function validateSpec(spec, resolve) {
|
|
|
272
273
|
// between them (review F5 — the old unordered `given && when && then` passed bare keyword prose
|
|
273
274
|
// like "given when then"). A trailing given/when clause still needs its consequent `then`.
|
|
274
275
|
const hasGwt = /\bgiven\b.{2,}?\bwhen\b.{2,}?\bthen\b/is.test(section);
|
|
275
|
-
if (!hasGwt)
|
|
276
|
+
if (!hasGwt) {
|
|
276
277
|
err('empty-quadrant', `coverage.${q}=true but '## ${q[0].toUpperCase()}${q.slice(1)} Cases' has no Given/When/Then scenario`);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
// @implements A-SPEC-322
|
|
281
|
+
// The check above is STRUCTURAL, and the scaffold `spec_slice_init` writes has the structure:
|
|
282
|
+
// "Given valid inputs / When operation is performed / Then correct behavior is observed" is a
|
|
283
|
+
// Given/When/Then scenario and says nothing. Measured 2026-08-28: 31 of 230 T-SPECs were
|
|
284
|
+
// approved carrying nothing but that template while declaring all four quadrants covered.
|
|
285
|
+
//
|
|
286
|
+
// A DIFFERENT code from `empty-quadrant` on purpose — "you wrote nothing" and "you left the
|
|
287
|
+
// template" ask the author for different things.
|
|
288
|
+
//
|
|
289
|
+
// Not retroactive: validation runs on an approval ATTEMPT, and nothing re-validates a sealed
|
|
290
|
+
// document, so the existing 31 stay valid. New ones cannot join them.
|
|
291
|
+
if ((0, acceptance_quality_1.quadrantQuality)(spec, q) === 'scaffold') {
|
|
292
|
+
// A WARNING, not an error, and the downgrade is itself a measured decision. As an error it
|
|
293
|
+
// broke two things at once:
|
|
294
|
+
// - `constitution.ts` validates the WHOLE corpus, so the 31 already-approved scaffolds
|
|
295
|
+
// became violations and the A-SPEC-146 guard ("the enforcement must not brick the
|
|
296
|
+
// harness") fired — the premise that sealed documents are never re-validated was wrong;
|
|
297
|
+
// - T-SPEC-203 pins `spec_slice_init` -> `spec_slice_approve` as a working sequence, and
|
|
298
|
+
// a scaffold error makes that sequence impossible without editing the T-SPEC first.
|
|
299
|
+
//
|
|
300
|
+
// The second is a genuine conflict between two approved specs, not a bug to route around, so
|
|
301
|
+
// it is reported rather than decided here. A warning still travels with every approval
|
|
302
|
+
// result, which is what turns "31 silently" into "31 and you were told".
|
|
303
|
+
f.push({
|
|
304
|
+
level: 'warn',
|
|
305
|
+
code: 'scaffold-quadrant',
|
|
306
|
+
message: `coverage.${q}=true but '## ${q[0].toUpperCase()}${q.slice(1)} Cases' still holds the generated scaffold — replace it with a real case`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
277
309
|
}
|
|
278
310
|
}
|
|
279
311
|
return { ok: f.filter((x) => x.level === 'error').length === 0, findings: f };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function yamlScalar(value: string, indent?: number): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.yamlScalar = yamlScalar;
|
|
4
|
+
// @implements A-SPEC-298
|
|
5
|
+
/**
|
|
6
|
+
* Render a string so it can be pasted into YAML frontmatter and read back unchanged.
|
|
7
|
+
*
|
|
8
|
+
* This exists because `spec_slice_init` interpolated user text straight into frontmatter. A title or
|
|
9
|
+
* objective containing `": "` — unremarkable in ordinary prose — produced a document the spec store
|
|
10
|
+
* could not parse, so the freshly created requirement vanished from the store entirely.
|
|
11
|
+
*
|
|
12
|
+
* The safe set is an ALLOW-list. Enumerating dangerous characters means the one you forget is the
|
|
13
|
+
* next defect; requiring a value to prove it is boring is the direction that fails safe. An
|
|
14
|
+
* adversarial probe of the first version of this file found four holes it had missed, which is why
|
|
15
|
+
* that probe now lives beside it as a test.
|
|
16
|
+
*/
|
|
17
|
+
const PLAIN_SAFE = /^[A-Za-z0-9가-힣][A-Za-z0-9가-힣 _().,\/-]*$/u;
|
|
18
|
+
/**
|
|
19
|
+
* Values YAML would read as something other than a string. `123` comes back as a number and `null`
|
|
20
|
+
* as null, so a title of "123" would reach callers as a number and break the first `.startsWith`
|
|
21
|
+
* it met. Type coercion is a silent corruption, not a parse error, which makes it worse.
|
|
22
|
+
*/
|
|
23
|
+
const YAML_TYPED = /^(?:[-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?|0[xob][0-9a-fA-F]+|true|false|yes|no|on|off|null|~|)$/i;
|
|
24
|
+
function yamlScalar(value, indent = 0) {
|
|
25
|
+
// A carriage return or other control character is normalised away by plain and single-quoted
|
|
26
|
+
// scalars — the value would come back CHANGED rather than failing. Only a double-quoted scalar
|
|
27
|
+
// preserves it, and JSON's escaping is a valid YAML double-quoted scalar.
|
|
28
|
+
if (/[\u0000-\u0008\u000B-\u001F\u007F\r]/.test(value))
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
if (value.includes('\n')) {
|
|
31
|
+
// A block scalar keeps the line breaks readable in the file instead of turning them into \n.
|
|
32
|
+
// A line with trailing whitespace cannot survive one, so those fall through to double quotes.
|
|
33
|
+
if (value.split('\n').some((line) => line !== line.replace(/\s+$/, '')))
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
const pad = ' '.repeat(indent + 2);
|
|
36
|
+
return `|-\n${value.split('\n').map((line) => `${pad}${line}`).join('\n')}`;
|
|
37
|
+
}
|
|
38
|
+
if (value !== '' && value === value.trim() && PLAIN_SAFE.test(value)
|
|
39
|
+
&& !value.includes(': ') && !YAML_TYPED.test(value)) {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@implements A-SPEC-209",
|
|
3
3
|
"name": "@holmes-lab/holmes-kit",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
|
|
6
6
|
"main": "dist/holmes/mcp/server.js",
|
|
7
7
|
"types": "dist/holmes/mcp/server.d.ts",
|