@sun-asterisk/sungen 3.0.0-beta.77 → 3.0.0-beta.78
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/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +10 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/harness/audit.d.ts +2 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +15 -4
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +37 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -0
- package/dist/harness/spec-coverage.js +159 -0
- package/dist/harness/spec-coverage.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/commands/audit.ts +7 -0
- package/src/harness/audit.ts +17 -4
- package/src/harness/spec-coverage.ts +139 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyEpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmC3D"}
|
|
@@ -101,6 +101,16 @@ function render(r) {
|
|
|
101
101
|
L(` ⑥ Traceability — ${(r.trace.mappedRatio * 100).toFixed(0)}% scenarios linked to viewpoint-overview`);
|
|
102
102
|
L(` ${r.trace.note}`);
|
|
103
103
|
L('');
|
|
104
|
+
if (r.spec.hasSpec && (r.spec.frTotal > 0 || r.spec.triggerGaps.length > 0 || r.spec.verdict !== 'pass')) {
|
|
105
|
+
L(` ⑦ Spec coverage — FR ${r.spec.frCovered}/${r.spec.frTotal} covered [${r.spec.verdict.toUpperCase()}]`);
|
|
106
|
+
for (const g of r.spec.triggerGaps)
|
|
107
|
+
L(` ✗ TRIGGER-UNCOVERED: "${g.constraint}"${g.code ? ` (${g.code})` : ''} mandated on [${g.required.join(', ')}], only tested on [${g.found.join(', ') || 'none'}] → missing ${g.missing.join(', ')}`);
|
|
108
|
+
for (const u of r.spec.uncoveredMust.slice(0, 6))
|
|
109
|
+
L(` ✗ SPEC-UNCOVERED: ${u.id} (MUST) — "${u.text}"`);
|
|
110
|
+
if (!r.spec.triggerGaps.length && !r.spec.uncoveredMust.length)
|
|
111
|
+
L(' ✓ every MUST FR + per-constraint trigger covered');
|
|
112
|
+
L('');
|
|
113
|
+
}
|
|
104
114
|
L(' ── Findings (Repair targets) ──');
|
|
105
115
|
if (r.findings.length === 0)
|
|
106
116
|
L(' ✓ none — output passes the harness');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/harness/audit.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult } from './sensors';
|
|
2
2
|
import { IntentProfile } from './intent';
|
|
3
3
|
import { Provenance } from './provenance';
|
|
4
|
+
import { SpecCoverageResult } from './spec-coverage';
|
|
4
5
|
export interface AuditReport {
|
|
5
6
|
screen: string;
|
|
6
7
|
scenarioCount: number;
|
|
@@ -23,6 +24,7 @@ export interface AuditReport {
|
|
|
23
24
|
findings: string[];
|
|
24
25
|
intent: IntentProfile;
|
|
25
26
|
provenance: Provenance;
|
|
27
|
+
spec: SpecCoverageResult;
|
|
26
28
|
}
|
|
27
29
|
export declare function runAudit(screenDir: string, screenName: string): AuditReport;
|
|
28
30
|
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/harness/audit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/harness/audit.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EACvG,MAAM,WAAW,CAAC;AACnB,OAAO,EAAwC,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/E,OAAO,EAAiB,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAgB,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CA8F3E"}
|
package/dist/harness/audit.js
CHANGED
|
@@ -43,16 +43,21 @@ exports.runAudit = runAudit;
|
|
|
43
43
|
* docs/orchestration-spec.md §5 and reports/sungen_home_gherkin_viewpoint_coverage_review.md.
|
|
44
44
|
*/
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
46
47
|
const parse_1 = require("./parse");
|
|
47
48
|
const sensors_1 = require("./sensors");
|
|
48
49
|
const intent_1 = require("./intent");
|
|
49
50
|
const provenance_1 = require("./provenance");
|
|
51
|
+
const spec_coverage_1 = require("./spec-coverage");
|
|
50
52
|
function runAudit(screenDir, screenName) {
|
|
51
53
|
const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
|
|
52
54
|
const viewpointPath = path.join(screenDir, 'requirements', 'test-viewpoint.md');
|
|
55
|
+
const specPath = path.join(screenDir, 'requirements', 'spec.md');
|
|
56
|
+
const featureText = fs.existsSync(featurePath) ? fs.readFileSync(featurePath, 'utf-8') : '';
|
|
53
57
|
const scenarios = (0, parse_1.loadScenarios)(featurePath);
|
|
54
58
|
const viewpoints = (0, parse_1.parseViewpointOverview)(viewpointPath);
|
|
55
59
|
const catalog = (0, sensors_1.loadCatalog)();
|
|
60
|
+
const spec = (0, spec_coverage_1.specCoverage)(specPath, scenarios, featureText);
|
|
56
61
|
const gate = (0, sensors_1.viewpointGate)(scenarios, viewpoints, catalog);
|
|
57
62
|
// P3 — intent profile from qa/context.md drives the depth threshold (focus).
|
|
58
63
|
const intent = (0, intent_1.readIntent)((0, intent_1.projectRootFromScreenDir)(screenDir));
|
|
@@ -102,13 +107,19 @@ function runAudit(screenDir, screenName) {
|
|
|
102
107
|
if (gate.universalGaps.length) {
|
|
103
108
|
findings.push(`UNIVERSAL: missing theme(s): ${gate.universalGaps.join(', ')} (low priority reminder).`);
|
|
104
109
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
for (const g of spec.triggerGaps) {
|
|
111
|
+
findings.push(`TRIGGER-UNCOVERED: spec validates "${g.constraint}"${g.code ? ` (${g.code})` : ''} on [${g.required.join(', ')}] but scenarios only exercise it on [${g.found.join(', ') || 'none'}] → add a ${g.missing.join(', ')}-trigger scenario for this constraint (don't collapse the trigger × input matrix).`);
|
|
112
|
+
}
|
|
113
|
+
for (const u of spec.uncoveredMust) {
|
|
114
|
+
findings.push(`SPEC-UNCOVERED: ${u.id} (MUST) has no covering scenario — "${u.text}" → add a scenario or tag one @spec:${u.id}.`);
|
|
115
|
+
}
|
|
116
|
+
// Gate spans coverage (viewpoint themes), depth (data-correctness), claim-proof,
|
|
117
|
+
// AND spec-clause coverage (every MUST clause + every mandated validation trigger).
|
|
118
|
+
const gateStatus = gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' && spec.verdict !== 'fail' ? 'PASS' : 'FAIL';
|
|
108
119
|
return {
|
|
109
120
|
screen: screenName,
|
|
110
121
|
scenarioCount: scenarios.length,
|
|
111
|
-
gate, depth, claim, taxonomy, balance, duplicates, trace,
|
|
122
|
+
gate, depth, claim, taxonomy, balance, duplicates, trace, spec,
|
|
112
123
|
score: {
|
|
113
124
|
overall: Math.round(overall * 10) / 10,
|
|
114
125
|
coverage: Math.round(coverage * 100) / 100,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/harness/audit.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/harness/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ScenarioInfo } from './parse';
|
|
2
|
+
export type Modality = 'MUST' | 'SHOULD' | 'MAY';
|
|
3
|
+
export interface TriggerGap {
|
|
4
|
+
constraint: string;
|
|
5
|
+
code: string;
|
|
6
|
+
required: string[];
|
|
7
|
+
found: string[];
|
|
8
|
+
missing: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface SpecCoverageResult {
|
|
11
|
+
hasSpec: boolean;
|
|
12
|
+
frTotal: number;
|
|
13
|
+
frCovered: number;
|
|
14
|
+
uncoveredMust: {
|
|
15
|
+
id: string;
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
triggerGaps: TriggerGap[];
|
|
19
|
+
verdict: 'pass' | 'warn' | 'fail';
|
|
20
|
+
}
|
|
21
|
+
interface FrClause {
|
|
22
|
+
id: string;
|
|
23
|
+
text: string;
|
|
24
|
+
modality: Modality;
|
|
25
|
+
}
|
|
26
|
+
interface ValRow {
|
|
27
|
+
constraint: string;
|
|
28
|
+
code: string;
|
|
29
|
+
triggers: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function parseSpecClauses(specPath: string): {
|
|
32
|
+
frs: FrClause[];
|
|
33
|
+
valRows: ValRow[];
|
|
34
|
+
};
|
|
35
|
+
export declare function specCoverage(specPath: string, scenarios: ScenarioInfo[], featureText: string): SpecCoverageResult;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=spec-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-coverage.d.ts","sourceRoot":"","sources":["../../src/harness/spec-coverage.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjD,MAAM,WAAW,UAAU;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE;AAExH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,UAAU,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE;AACnE,UAAU,MAAM;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE;AA2BzE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAmCzF;AAOD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,CA0CjH"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseSpecClauses = parseSpecClauses;
|
|
37
|
+
exports.specCoverage = specCoverage;
|
|
38
|
+
/**
|
|
39
|
+
* Spec-clause coverage (harness G2) — the spec-layer faithfulness check.
|
|
40
|
+
*
|
|
41
|
+
* Parses the structured parts of a screen `spec.md` (Functional Requirements + the
|
|
42
|
+
* Validation Rules table) and verifies (a) every MUST FR is covered by a scenario and
|
|
43
|
+
* (b) for EACH validation constraint, every trigger the spec mandates is actually
|
|
44
|
+
* exercised. The trigger check is PER-CONSTRAINT — so "validate format on blur AND on
|
|
45
|
+
* submit" tested only on blur is caught even when other constraints do use submit.
|
|
46
|
+
* Generic: no project data.
|
|
47
|
+
*/
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
// Parsing the spec's Trigger CELL: loose word match ("blur, submit").
|
|
50
|
+
const SPEC_TRIGGER = [
|
|
51
|
+
{ trigger: 'blur', re: /\b(blur|on blur|focus[- ]?out)\b/i },
|
|
52
|
+
{ trigger: 'submit', re: /\b(submit|on submit)\b/i },
|
|
53
|
+
];
|
|
54
|
+
// Detecting the trigger ACTION in a scenario: must be an actual action, NOT the word
|
|
55
|
+
// "submit" in a "[Submit] button is disabled" assertion.
|
|
56
|
+
const ACTION_TRIGGER = [
|
|
57
|
+
{ trigger: 'blur', re: /\b(press tab|blur|focus[- ]?out|loses? focus|tab away|tab out)\b/i },
|
|
58
|
+
{ trigger: 'submit', re: /(click \[submit\]|press enter|\benter key\b|送信する)/i },
|
|
59
|
+
];
|
|
60
|
+
function modalityOf(text) {
|
|
61
|
+
if (/\bMUST\b/.test(text))
|
|
62
|
+
return 'MUST';
|
|
63
|
+
if (/\bSHOULD\b/.test(text))
|
|
64
|
+
return 'SHOULD';
|
|
65
|
+
return 'MAY';
|
|
66
|
+
}
|
|
67
|
+
function specTriggersIn(text) {
|
|
68
|
+
return SPEC_TRIGGER.filter((t) => t.re.test(text)).map((t) => t.trigger);
|
|
69
|
+
}
|
|
70
|
+
function actionTriggersIn(text) {
|
|
71
|
+
return ACTION_TRIGGER.filter((t) => t.re.test(text)).map((t) => t.trigger);
|
|
72
|
+
}
|
|
73
|
+
function parseSpecClauses(specPath) {
|
|
74
|
+
if (!fs.existsSync(specPath))
|
|
75
|
+
return { frs: [], valRows: [] };
|
|
76
|
+
const lines = fs.readFileSync(specPath, 'utf-8').split('\n');
|
|
77
|
+
const frs = [];
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const m = line.match(/\*\*FR-(\d+)\*\*\s*:\s*(.+)$/);
|
|
80
|
+
if (m)
|
|
81
|
+
frs.push({ id: `FR-${m[1]}`, text: m[2].replace(/\*\*/g, '').trim(), modality: modalityOf(m[2]) });
|
|
82
|
+
}
|
|
83
|
+
// Validation Rules table: a row carries a Constraint, a Trigger cell, and (often) a code.
|
|
84
|
+
const valRows = [];
|
|
85
|
+
let cTrigger = -1, cConstraint = -1, cCode = -1, inTable = false;
|
|
86
|
+
for (const raw of lines) {
|
|
87
|
+
const line = raw.trim();
|
|
88
|
+
if (line.startsWith('|') && /\btrigger\b/i.test(line) && cTrigger < 0) {
|
|
89
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
90
|
+
cTrigger = cells.findIndex((c) => /^trigger$/i.test(c));
|
|
91
|
+
cConstraint = cells.findIndex((c) => /constraint/i.test(c));
|
|
92
|
+
cCode = cells.findIndex((c) => /code/i.test(c));
|
|
93
|
+
inTable = cTrigger >= 0;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (inTable) {
|
|
97
|
+
if (!line.startsWith('|')) {
|
|
98
|
+
inTable = false;
|
|
99
|
+
cTrigger = -1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (/^\|[\s|:-]+\|?$/.test(line))
|
|
103
|
+
continue;
|
|
104
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
105
|
+
const triggers = [...new Set((cells[cTrigger] || '').split(/,|\band\b/i).flatMap((p) => specTriggersIn(p)))];
|
|
106
|
+
if (!triggers.length)
|
|
107
|
+
continue; // server-only rows etc.
|
|
108
|
+
const constraint = (cConstraint >= 0 ? cells[cConstraint] : '').replace(/`/g, '') || 'validation';
|
|
109
|
+
const codeM = (cCode >= 0 ? cells[cCode] : line).match(/M\d+/);
|
|
110
|
+
valRows.push({ constraint, code: codeM ? codeM[0] : '', triggers });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { frs, valRows };
|
|
114
|
+
}
|
|
115
|
+
/** Split a feature file into per-scenario blocks (tags + comments + steps), keyed by blank lines. */
|
|
116
|
+
function scenarioBlocks(featureText) {
|
|
117
|
+
return featureText.split(/\n\s*\n/).filter((b) => /\bScenario:/.test(b)).map((b) => b.toLowerCase());
|
|
118
|
+
}
|
|
119
|
+
function specCoverage(specPath, scenarios, featureText) {
|
|
120
|
+
const { frs, valRows } = parseSpecClauses(specPath);
|
|
121
|
+
if (!fs.existsSync(specPath) || (frs.length === 0 && valRows.length === 0)) {
|
|
122
|
+
return { hasSpec: fs.existsSync(specPath), frTotal: 0, frCovered: 0, uncoveredMust: [], triggerGaps: [], verdict: 'pass' };
|
|
123
|
+
}
|
|
124
|
+
const featLower = featureText.toLowerCase();
|
|
125
|
+
// FR coverage: explicit @spec:FR / literal FR-id citation, else keyword fallback.
|
|
126
|
+
const uncoveredMust = [];
|
|
127
|
+
let frCovered = 0;
|
|
128
|
+
for (const fr of frs) {
|
|
129
|
+
const idLower = fr.id.toLowerCase();
|
|
130
|
+
const cited = featLower.includes(idLower);
|
|
131
|
+
const words = [...new Set((fr.text.toLowerCase().match(/[a-z][a-z-]{4,}/g) || []))]
|
|
132
|
+
.filter((w) => !/must|should|system|screen|users?|value|input|field/.test(w));
|
|
133
|
+
const kwHit = words.length > 0 && scenarios.some((s) => words.filter((w) => s.haystack.includes(w)).length >= Math.min(2, words.length));
|
|
134
|
+
if (cited || kwHit)
|
|
135
|
+
frCovered++;
|
|
136
|
+
else if (fr.modality === 'MUST')
|
|
137
|
+
uncoveredMust.push({ id: fr.id, text: fr.text.slice(0, 90) });
|
|
138
|
+
}
|
|
139
|
+
// Per-constraint trigger coverage — the matrix-collapse catch.
|
|
140
|
+
// generic words that don't identify a constraint (would over-match happy-path scenarios)
|
|
141
|
+
const GENERIC = new Set(['valid', 'local', 'part', 'domain', 'email', 'address', 'must', 'exist', 'store', 'field', 'input', 'value', 'formed', 'well', 'character', 'characters']);
|
|
142
|
+
const blocks = scenarioBlocks(featureText);
|
|
143
|
+
const triggerGaps = [];
|
|
144
|
+
for (const row of valRows) {
|
|
145
|
+
const kw = (row.constraint.toLowerCase().match(/[a-z]{4,}/g) || []).filter((w) => !GENERIC.has(w));
|
|
146
|
+
// blocks that belong to this constraint: cite its msg code (primary), or mention a
|
|
147
|
+
// DISTINCTIVE constraint word (generic words filtered to avoid matching happy paths).
|
|
148
|
+
const own = blocks.filter((b) => (row.code && b.includes(row.code.toLowerCase())) || kw.some((w) => b.includes(w)));
|
|
149
|
+
if (!own.length)
|
|
150
|
+
continue; // constraint has no scenarios at all — FR/viewpoint gate covers that
|
|
151
|
+
const found = [...new Set(own.flatMap((b) => actionTriggersIn(b)))];
|
|
152
|
+
const missing = row.triggers.filter((t) => !found.includes(t));
|
|
153
|
+
if (missing.length)
|
|
154
|
+
triggerGaps.push({ constraint: row.constraint, code: row.code, required: row.triggers, found, missing });
|
|
155
|
+
}
|
|
156
|
+
const verdict = uncoveredMust.length > 0 || triggerGaps.length > 0 ? 'fail' : 'pass';
|
|
157
|
+
return { hasSpec: true, frTotal: frs.length, frCovered, uncoveredMust, triggerGaps, verdict };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=spec-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../src/harness/spec-coverage.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -58,6 +58,13 @@ function render(r: AuditReport): void {
|
|
|
58
58
|
L(` ⑥ Traceability — ${(r.trace.mappedRatio * 100).toFixed(0)}% scenarios linked to viewpoint-overview`);
|
|
59
59
|
L(` ${r.trace.note}`);
|
|
60
60
|
L('');
|
|
61
|
+
if (r.spec.hasSpec && (r.spec.frTotal > 0 || r.spec.triggerGaps.length > 0 || r.spec.verdict !== 'pass')) {
|
|
62
|
+
L(` ⑦ Spec coverage — FR ${r.spec.frCovered}/${r.spec.frTotal} covered [${r.spec.verdict.toUpperCase()}]`);
|
|
63
|
+
for (const g of r.spec.triggerGaps) L(` ✗ TRIGGER-UNCOVERED: "${g.constraint}"${g.code ? ` (${g.code})` : ''} mandated on [${g.required.join(', ')}], only tested on [${g.found.join(', ') || 'none'}] → missing ${g.missing.join(', ')}`);
|
|
64
|
+
for (const u of r.spec.uncoveredMust.slice(0, 6)) L(` ✗ SPEC-UNCOVERED: ${u.id} (MUST) — "${u.text}"`);
|
|
65
|
+
if (!r.spec.triggerGaps.length && !r.spec.uncoveredMust.length) L(' ✓ every MUST FR + per-constraint trigger covered');
|
|
66
|
+
L('');
|
|
67
|
+
}
|
|
61
68
|
L(' ── Findings (Repair targets) ──');
|
|
62
69
|
if (r.findings.length === 0) L(' ✓ none — output passes the harness');
|
|
63
70
|
for (const f of r.findings) L(` • ${f}`);
|
package/src/harness/audit.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* docs/orchestration-spec.md §5 and reports/sungen_home_gherkin_viewpoint_coverage_review.md.
|
|
8
8
|
*/
|
|
9
9
|
import * as path from 'path';
|
|
10
|
+
import * as fs from 'fs';
|
|
10
11
|
import { loadScenarios, parseViewpointOverview, ScenarioInfo, ViewpointEntry } from './parse';
|
|
11
12
|
import {
|
|
12
13
|
loadCatalog, viewpointGate, assertionDepth, dataThemesFor, coverageBalance, duplicateClusters, traceability, claimProof, taxonomyLint,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from './sensors';
|
|
15
16
|
import { readIntent, projectRootFromScreenDir, IntentProfile } from './intent';
|
|
16
17
|
import { getProvenance, Provenance } from './provenance';
|
|
18
|
+
import { specCoverage, SpecCoverageResult } from './spec-coverage';
|
|
17
19
|
|
|
18
20
|
export interface AuditReport {
|
|
19
21
|
screen: string;
|
|
@@ -37,15 +39,20 @@ export interface AuditReport {
|
|
|
37
39
|
findings: string[]; // human-actionable, what the Repair loop would target
|
|
38
40
|
intent: IntentProfile; // P3 — the intent profile that drove the thresholds
|
|
39
41
|
provenance: Provenance; // D1 — sungen version + catalog hash (diagnose cross-user score gaps)
|
|
42
|
+
spec: SpecCoverageResult; // G2 — spec-clause coverage (FR + validation-trigger matrix)
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
43
46
|
const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
|
|
44
47
|
const viewpointPath = path.join(screenDir, 'requirements', 'test-viewpoint.md');
|
|
45
48
|
|
|
49
|
+
const specPath = path.join(screenDir, 'requirements', 'spec.md');
|
|
50
|
+
const featureText = fs.existsSync(featurePath) ? fs.readFileSync(featurePath, 'utf-8') : '';
|
|
51
|
+
|
|
46
52
|
const scenarios: ScenarioInfo[] = loadScenarios(featurePath);
|
|
47
53
|
const viewpoints: ViewpointEntry[] = parseViewpointOverview(viewpointPath);
|
|
48
54
|
const catalog = loadCatalog();
|
|
55
|
+
const spec = specCoverage(specPath, scenarios, featureText);
|
|
49
56
|
|
|
50
57
|
const gate = viewpointGate(scenarios, viewpoints, catalog);
|
|
51
58
|
// P3 — intent profile from qa/context.md drives the depth threshold (focus).
|
|
@@ -100,16 +107,22 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
100
107
|
if (gate.universalGaps.length) {
|
|
101
108
|
findings.push(`UNIVERSAL: missing theme(s): ${gate.universalGaps.join(', ')} (low priority reminder).`);
|
|
102
109
|
}
|
|
110
|
+
for (const g of spec.triggerGaps) {
|
|
111
|
+
findings.push(`TRIGGER-UNCOVERED: spec validates "${g.constraint}"${g.code ? ` (${g.code})` : ''} on [${g.required.join(', ')}] but scenarios only exercise it on [${g.found.join(', ') || 'none'}] → add a ${g.missing.join(', ')}-trigger scenario for this constraint (don't collapse the trigger × input matrix).`);
|
|
112
|
+
}
|
|
113
|
+
for (const u of spec.uncoveredMust) {
|
|
114
|
+
findings.push(`SPEC-UNCOVERED: ${u.id} (MUST) has no covering scenario — "${u.text}" → add a scenario or tag one @spec:${u.id}.`);
|
|
115
|
+
}
|
|
103
116
|
|
|
104
|
-
// Gate
|
|
105
|
-
//
|
|
117
|
+
// Gate spans coverage (viewpoint themes), depth (data-correctness), claim-proof,
|
|
118
|
+
// AND spec-clause coverage (every MUST clause + every mandated validation trigger).
|
|
106
119
|
const gateStatus: 'PASS' | 'FAIL' =
|
|
107
|
-
gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' ? 'PASS' : 'FAIL';
|
|
120
|
+
gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' && spec.verdict !== 'fail' ? 'PASS' : 'FAIL';
|
|
108
121
|
|
|
109
122
|
return {
|
|
110
123
|
screen: screenName,
|
|
111
124
|
scenarioCount: scenarios.length,
|
|
112
|
-
gate, depth, claim, taxonomy, balance, duplicates, trace,
|
|
125
|
+
gate, depth, claim, taxonomy, balance, duplicates, trace, spec,
|
|
113
126
|
score: {
|
|
114
127
|
overall: Math.round(overall * 10) / 10,
|
|
115
128
|
coverage: Math.round(coverage * 100) / 100,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec-clause coverage (harness G2) — the spec-layer faithfulness check.
|
|
3
|
+
*
|
|
4
|
+
* Parses the structured parts of a screen `spec.md` (Functional Requirements + the
|
|
5
|
+
* Validation Rules table) and verifies (a) every MUST FR is covered by a scenario and
|
|
6
|
+
* (b) for EACH validation constraint, every trigger the spec mandates is actually
|
|
7
|
+
* exercised. The trigger check is PER-CONSTRAINT — so "validate format on blur AND on
|
|
8
|
+
* submit" tested only on blur is caught even when other constraints do use submit.
|
|
9
|
+
* Generic: no project data.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import { ScenarioInfo } from './parse';
|
|
13
|
+
|
|
14
|
+
export type Modality = 'MUST' | 'SHOULD' | 'MAY';
|
|
15
|
+
|
|
16
|
+
export interface TriggerGap { constraint: string; code: string; required: string[]; found: string[]; missing: string[] }
|
|
17
|
+
|
|
18
|
+
export interface SpecCoverageResult {
|
|
19
|
+
hasSpec: boolean;
|
|
20
|
+
frTotal: number;
|
|
21
|
+
frCovered: number;
|
|
22
|
+
uncoveredMust: { id: string; text: string }[];
|
|
23
|
+
triggerGaps: TriggerGap[]; // per-constraint trigger matrix gaps
|
|
24
|
+
verdict: 'pass' | 'warn' | 'fail';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FrClause { id: string; text: string; modality: Modality }
|
|
28
|
+
interface ValRow { constraint: string; code: string; triggers: string[] }
|
|
29
|
+
|
|
30
|
+
// Parsing the spec's Trigger CELL: loose word match ("blur, submit").
|
|
31
|
+
const SPEC_TRIGGER: { trigger: string; re: RegExp }[] = [
|
|
32
|
+
{ trigger: 'blur', re: /\b(blur|on blur|focus[- ]?out)\b/i },
|
|
33
|
+
{ trigger: 'submit', re: /\b(submit|on submit)\b/i },
|
|
34
|
+
];
|
|
35
|
+
// Detecting the trigger ACTION in a scenario: must be an actual action, NOT the word
|
|
36
|
+
// "submit" in a "[Submit] button is disabled" assertion.
|
|
37
|
+
const ACTION_TRIGGER: { trigger: string; re: RegExp }[] = [
|
|
38
|
+
{ trigger: 'blur', re: /\b(press tab|blur|focus[- ]?out|loses? focus|tab away|tab out)\b/i },
|
|
39
|
+
{ trigger: 'submit', re: /(click \[submit\]|press enter|\benter key\b|送信する)/i },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function modalityOf(text: string): Modality {
|
|
43
|
+
if (/\bMUST\b/.test(text)) return 'MUST';
|
|
44
|
+
if (/\bSHOULD\b/.test(text)) return 'SHOULD';
|
|
45
|
+
return 'MAY';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function specTriggersIn(text: string): string[] {
|
|
49
|
+
return SPEC_TRIGGER.filter((t) => t.re.test(text)).map((t) => t.trigger);
|
|
50
|
+
}
|
|
51
|
+
function actionTriggersIn(text: string): string[] {
|
|
52
|
+
return ACTION_TRIGGER.filter((t) => t.re.test(text)).map((t) => t.trigger);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseSpecClauses(specPath: string): { frs: FrClause[]; valRows: ValRow[] } {
|
|
56
|
+
if (!fs.existsSync(specPath)) return { frs: [], valRows: [] };
|
|
57
|
+
const lines = fs.readFileSync(specPath, 'utf-8').split('\n');
|
|
58
|
+
|
|
59
|
+
const frs: FrClause[] = [];
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const m = line.match(/\*\*FR-(\d+)\*\*\s*:\s*(.+)$/);
|
|
62
|
+
if (m) frs.push({ id: `FR-${m[1]}`, text: m[2].replace(/\*\*/g, '').trim(), modality: modalityOf(m[2]) });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validation Rules table: a row carries a Constraint, a Trigger cell, and (often) a code.
|
|
66
|
+
const valRows: ValRow[] = [];
|
|
67
|
+
let cTrigger = -1, cConstraint = -1, cCode = -1, inTable = false;
|
|
68
|
+
for (const raw of lines) {
|
|
69
|
+
const line = raw.trim();
|
|
70
|
+
if (line.startsWith('|') && /\btrigger\b/i.test(line) && cTrigger < 0) {
|
|
71
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
72
|
+
cTrigger = cells.findIndex((c) => /^trigger$/i.test(c));
|
|
73
|
+
cConstraint = cells.findIndex((c) => /constraint/i.test(c));
|
|
74
|
+
cCode = cells.findIndex((c) => /code/i.test(c));
|
|
75
|
+
inTable = cTrigger >= 0;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (inTable) {
|
|
79
|
+
if (!line.startsWith('|')) { inTable = false; cTrigger = -1; continue; }
|
|
80
|
+
if (/^\|[\s|:-]+\|?$/.test(line)) continue;
|
|
81
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
82
|
+
const triggers = [...new Set((cells[cTrigger] || '').split(/,|\band\b/i).flatMap((p) => specTriggersIn(p)))];
|
|
83
|
+
if (!triggers.length) continue; // server-only rows etc.
|
|
84
|
+
const constraint = (cConstraint >= 0 ? cells[cConstraint] : '').replace(/`/g, '') || 'validation';
|
|
85
|
+
const codeM = (cCode >= 0 ? cells[cCode] : line).match(/M\d+/);
|
|
86
|
+
valRows.push({ constraint, code: codeM ? codeM[0] : '', triggers });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { frs, valRows };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Split a feature file into per-scenario blocks (tags + comments + steps), keyed by blank lines. */
|
|
93
|
+
function scenarioBlocks(featureText: string): string[] {
|
|
94
|
+
return featureText.split(/\n\s*\n/).filter((b) => /\bScenario:/.test(b)).map((b) => b.toLowerCase());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function specCoverage(specPath: string, scenarios: ScenarioInfo[], featureText: string): SpecCoverageResult {
|
|
98
|
+
const { frs, valRows } = parseSpecClauses(specPath);
|
|
99
|
+
if (!fs.existsSync(specPath) || (frs.length === 0 && valRows.length === 0)) {
|
|
100
|
+
return { hasSpec: fs.existsSync(specPath), frTotal: 0, frCovered: 0, uncoveredMust: [], triggerGaps: [], verdict: 'pass' };
|
|
101
|
+
}
|
|
102
|
+
const featLower = featureText.toLowerCase();
|
|
103
|
+
|
|
104
|
+
// FR coverage: explicit @spec:FR / literal FR-id citation, else keyword fallback.
|
|
105
|
+
const uncoveredMust: { id: string; text: string }[] = [];
|
|
106
|
+
let frCovered = 0;
|
|
107
|
+
for (const fr of frs) {
|
|
108
|
+
const idLower = fr.id.toLowerCase();
|
|
109
|
+
const cited = featLower.includes(idLower);
|
|
110
|
+
const words = [...new Set((fr.text.toLowerCase().match(/[a-z][a-z-]{4,}/g) || []))]
|
|
111
|
+
.filter((w) => !/must|should|system|screen|users?|value|input|field/.test(w));
|
|
112
|
+
const kwHit = words.length > 0 && scenarios.some((s) => words.filter((w) => s.haystack.includes(w)).length >= Math.min(2, words.length));
|
|
113
|
+
if (cited || kwHit) frCovered++;
|
|
114
|
+
else if (fr.modality === 'MUST') uncoveredMust.push({ id: fr.id, text: fr.text.slice(0, 90) });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Per-constraint trigger coverage — the matrix-collapse catch.
|
|
118
|
+
// generic words that don't identify a constraint (would over-match happy-path scenarios)
|
|
119
|
+
const GENERIC = new Set(['valid', 'local', 'part', 'domain', 'email', 'address', 'must', 'exist', 'store', 'field', 'input', 'value', 'formed', 'well', 'character', 'characters']);
|
|
120
|
+
const blocks = scenarioBlocks(featureText);
|
|
121
|
+
const triggerGaps: TriggerGap[] = [];
|
|
122
|
+
for (const row of valRows) {
|
|
123
|
+
const kw = (row.constraint.toLowerCase().match(/[a-z]{4,}/g) || []).filter((w) => !GENERIC.has(w));
|
|
124
|
+
// blocks that belong to this constraint: cite its msg code (primary), or mention a
|
|
125
|
+
// DISTINCTIVE constraint word (generic words filtered to avoid matching happy paths).
|
|
126
|
+
const own = blocks.filter((b) =>
|
|
127
|
+
(row.code && b.includes(row.code.toLowerCase())) || kw.some((w) => b.includes(w)),
|
|
128
|
+
);
|
|
129
|
+
if (!own.length) continue; // constraint has no scenarios at all — FR/viewpoint gate covers that
|
|
130
|
+
const found = [...new Set(own.flatMap((b) => actionTriggersIn(b)))];
|
|
131
|
+
const missing = row.triggers.filter((t) => !found.includes(t));
|
|
132
|
+
if (missing.length) triggerGaps.push({ constraint: row.constraint, code: row.code, required: row.triggers, found, missing });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const verdict: SpecCoverageResult['verdict'] =
|
|
136
|
+
uncoveredMust.length > 0 || triggerGaps.length > 0 ? 'fail' : 'pass';
|
|
137
|
+
|
|
138
|
+
return { hasSpec: true, frTotal: frs.length, frCovered, uncoveredMust, triggerGaps, verdict };
|
|
139
|
+
}
|