@specship/specship-win32-arm64 0.24.0 → 0.25.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/lib/dist/activation/steering.d.ts +4 -4
- package/lib/dist/activation/steering.d.ts.map +1 -1
- package/lib/dist/activation/steering.js +6 -6
- package/lib/dist/activation/steering.js.map +1 -1
- package/lib/dist/bin/specship.js +232 -40
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/commands/specship/learn.md +5 -3
- package/lib/dist/commands/specship/spec.md +73 -15
- package/lib/dist/db/spec-queries.d.ts +73 -3
- package/lib/dist/db/spec-queries.d.ts.map +1 -1
- package/lib/dist/db/spec-queries.js +139 -5
- package/lib/dist/db/spec-queries.js.map +1 -1
- package/lib/dist/enforce/changed-files.d.ts +28 -0
- package/lib/dist/enforce/changed-files.d.ts.map +1 -0
- package/lib/dist/enforce/changed-files.js +72 -0
- package/lib/dist/enforce/changed-files.js.map +1 -0
- package/lib/dist/enforce/enforce.d.ts +28 -1
- package/lib/dist/enforce/enforce.d.ts.map +1 -1
- package/lib/dist/enforce/enforce.js +37 -5
- package/lib/dist/enforce/enforce.js.map +1 -1
- package/lib/dist/extraction/grammars.d.ts +10 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +32 -3
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +7 -0
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts +7 -0
- package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/specs/markdown-spec-extractor.js +81 -8
- package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm.gz +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-scala.wasm.gz +0 -0
- package/lib/dist/graph/spec-coverage.d.ts +86 -0
- package/lib/dist/graph/spec-coverage.d.ts.map +1 -0
- package/lib/dist/graph/spec-coverage.js +152 -0
- package/lib/dist/graph/spec-coverage.js.map +1 -0
- package/lib/dist/index.d.ts +69 -6
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +133 -21
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts +33 -8
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +50 -50
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/init-offer.d.ts +14 -9
- package/lib/dist/installer/init-offer.d.ts.map +1 -1
- package/lib/dist/installer/init-offer.js +13 -9
- package/lib/dist/installer/init-offer.js.map +1 -1
- package/lib/dist/installer/install-defaults.d.ts +62 -0
- package/lib/dist/installer/install-defaults.d.ts.map +1 -0
- package/lib/dist/installer/install-defaults.js +67 -0
- package/lib/dist/installer/install-defaults.js.map +1 -0
- package/lib/dist/installer/targets/claude.d.ts +14 -0
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +64 -0
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/shared.js +4 -4
- package/lib/dist/installer/targets/shared.js.map +1 -1
- package/lib/dist/mcp/fitness-tool.d.ts +5 -6
- package/lib/dist/mcp/fitness-tool.d.ts.map +1 -1
- package/lib/dist/mcp/fitness-tool.js +0 -16
- package/lib/dist/mcp/fitness-tool.js.map +1 -1
- package/lib/dist/mcp/health-tool.d.ts +26 -0
- package/lib/dist/mcp/health-tool.d.ts.map +1 -0
- package/lib/dist/mcp/health-tool.js +50 -0
- package/lib/dist/mcp/health-tool.js.map +1 -0
- package/lib/dist/mcp/maintainability-tool.d.ts +5 -7
- package/lib/dist/mcp/maintainability-tool.d.ts.map +1 -1
- package/lib/dist/mcp/maintainability-tool.js +0 -16
- package/lib/dist/mcp/maintainability-tool.js.map +1 -1
- package/lib/dist/mcp/model-context.d.ts +13 -2
- package/lib/dist/mcp/model-context.d.ts.map +1 -1
- package/lib/dist/mcp/model-context.js +39 -12
- package/lib/dist/mcp/model-context.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +4 -1
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +5 -4
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
- package/lib/dist/mcp/spec-tools.js +213 -28
- package/lib/dist/mcp/spec-tools.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +12 -8
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +89 -48
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +44 -2
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +132 -0
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/dist/resolution/test-title-links.d.ts +62 -0
- package/lib/dist/resolution/test-title-links.d.ts.map +1 -0
- package/lib/dist/resolution/test-title-links.js +108 -0
- package/lib/dist/resolution/test-title-links.js.map +1 -0
- package/lib/dist/search/query-parser.d.ts.map +1 -1
- package/lib/dist/search/query-parser.js +6 -1
- package/lib/dist/search/query-parser.js.map +1 -1
- package/lib/dist/server/project-registry.js +1 -1
- package/lib/dist/server/routes/claude.js +9 -36
- package/lib/dist/server/routes/graph.js +53 -7
- package/lib/dist/server/routes/mcp.js +4 -3
- package/lib/dist/server/routes/projects.js +4 -4
- package/lib/dist/server/routes/spec.js +49 -7
- package/lib/dist/server/server.js +29 -17
- package/lib/dist/skills/spec-author/SKILL.md +164 -0
- package/lib/dist/skills/spec-author/references/format.md +210 -0
- package/lib/dist/skills/spec-author/references/gap-questions.md +152 -0
- package/lib/dist/skills/spec-author/references/quality-rubric.md +125 -0
- package/lib/dist/skills/spec-author/references/review-checklist.md +175 -0
- package/lib/dist/spec-lint/lint.d.ts +82 -0
- package/lib/dist/spec-lint/lint.d.ts.map +1 -0
- package/lib/dist/spec-lint/lint.js +430 -0
- package/lib/dist/spec-lint/lint.js.map +1 -0
- package/lib/dist/statusline/index.js.map +1 -1
- package/lib/dist/statusline/render.d.ts +1 -1
- package/lib/dist/statusline/render.d.ts.map +1 -1
- package/lib/dist/statusline/render.js +3 -1
- package/lib/dist/statusline/render.js.map +1 -1
- package/lib/dist/ui/assets/index-BzuUWUAE.js +50 -0
- package/lib/dist/ui/index.html +1 -1
- package/lib/dist/verify/report-ingest.d.ts +88 -0
- package/lib/dist/verify/report-ingest.d.ts.map +1 -0
- package/lib/dist/verify/report-ingest.js +269 -0
- package/lib/dist/verify/report-ingest.js.map +1 -0
- package/lib/dist/workflows/defaults/spec-fix.yaml +103 -12
- package/lib/dist/workflows/defaults/spec-implement-mixed.yaml +264 -21
- package/lib/dist/workflows/defaults/spec-implement.yaml +260 -19
- package/lib/dist/workflows/defaults/spec-verify.yaml +60 -9
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +7 -3
- package/package.json +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/lib/dist/server/ingest/impact-query.js +0 -343
- package/lib/dist/server/routes/events.js +0 -152
- package/lib/dist/ui/assets/index-CjjaftDw.js +0 -50
package/lib/dist/ui/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
|
9
9
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
|
|
10
10
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicon-180.png" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-BzuUWUAE.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-ezwX2EfG.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-report ingestion (REQ-VSTATE-004).
|
|
3
|
+
*
|
|
4
|
+
* `verified` used to mean "an LLM read a test log and said it passed". This
|
|
5
|
+
* module replaces that with a deterministic mapping: parse a real test report,
|
|
6
|
+
* match each reported case to the `tests` links that claim it, and move those
|
|
7
|
+
* links — passed → `verified`, failed → `broken` — recording the run itself as
|
|
8
|
+
* the evidence.
|
|
9
|
+
*
|
|
10
|
+
* Parsing is total before any write happens, so a malformed report exits with a
|
|
11
|
+
* parse error having changed no state (A4).
|
|
12
|
+
*/
|
|
13
|
+
import type { SpecLink } from '../types';
|
|
14
|
+
import type { SpecQueries } from '../db/spec-queries';
|
|
15
|
+
/** Thrown for an unreadable / unparseable / shapeless report (REQ-VSTATE-004.A4). */
|
|
16
|
+
export declare class TestReportParseError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** One test case as reported by the runner. */
|
|
20
|
+
export interface ReportedTestCase {
|
|
21
|
+
/** Project-relative POSIX path of the test file. */
|
|
22
|
+
file: string;
|
|
23
|
+
/** The `it()` title, without ancestor describe titles. */
|
|
24
|
+
title: string;
|
|
25
|
+
/** Ancestors + title joined, as the runner reported it. */
|
|
26
|
+
fullName: string;
|
|
27
|
+
status: 'passed' | 'failed' | 'skipped';
|
|
28
|
+
failureMessage?: string;
|
|
29
|
+
}
|
|
30
|
+
/** What one link's state move was, for reporting. */
|
|
31
|
+
export interface LinkOutcome {
|
|
32
|
+
linkId: number;
|
|
33
|
+
specId: string;
|
|
34
|
+
file: string;
|
|
35
|
+
/** The matched test titles the verdict rests on. */
|
|
36
|
+
titles: string[];
|
|
37
|
+
from: SpecLink['state'];
|
|
38
|
+
to: SpecLink['state'];
|
|
39
|
+
}
|
|
40
|
+
export interface VerifyReportResult {
|
|
41
|
+
runId: string;
|
|
42
|
+
reportPath: string;
|
|
43
|
+
/** Total cases read out of the report. */
|
|
44
|
+
cases: number;
|
|
45
|
+
promoted: LinkOutcome[];
|
|
46
|
+
demoted: LinkOutcome[];
|
|
47
|
+
/** Links matched whose state didn't move (already there, or only skips). */
|
|
48
|
+
unchanged: number;
|
|
49
|
+
/** Reported cases that matched no `tests` link at all (A3). */
|
|
50
|
+
unmatched: number;
|
|
51
|
+
/** A bounded sample of the unmatched cases, for the operator (A3). */
|
|
52
|
+
unmatchedSample: string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse a vitest / jest JSON report (`vitest run --reporter=json`).
|
|
56
|
+
*
|
|
57
|
+
* Shape: `{ testResults: [{ name, assertionResults: [{ title, fullName,
|
|
58
|
+
* ancestorTitles, status, failureMessages }] }] }`. Anything else — invalid
|
|
59
|
+
* JSON, a non-object, a missing/!array `testResults` — is a parse error rather
|
|
60
|
+
* than an empty result, so a report written by a crashed runner can never read
|
|
61
|
+
* as "nothing to verify" (A4).
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseVitestJsonReport(raw: string, projectRoot: string): ReportedTestCase[];
|
|
64
|
+
/**
|
|
65
|
+
* Does `testCase` prove `link`? Matching is by FILE first — a link's evidence
|
|
66
|
+
* must live in the file it points at — then by title, in descending precision:
|
|
67
|
+
*
|
|
68
|
+
* 1. the link's recorded `metadata.testTitles` (written by the test-title pass,
|
|
69
|
+
* REQ-VSTATE-002) contains the case's title or full name;
|
|
70
|
+
* 2. the case's full name mentions the link's spec id (the `(REQ-X.A1)` title
|
|
71
|
+
* convention, for links that predate the recorded titles);
|
|
72
|
+
* 3. the case's full name mentions the link's target symbol (a `verifies:`
|
|
73
|
+
* block naming a hoisted test function).
|
|
74
|
+
*/
|
|
75
|
+
export declare function caseMatchesLink(testCase: ReportedTestCase, link: SpecLink): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Read a report and apply it to the DB (REQ-VSTATE-004).
|
|
78
|
+
*
|
|
79
|
+
* Per matched link: any failed case → `broken`; otherwise at least one passed
|
|
80
|
+
* case → `verified`; only-skipped → left alone (a skipped test proves nothing
|
|
81
|
+
* either way). The verification record persisted on the link carries the run id,
|
|
82
|
+
* the report path, the ISO timestamp and the exact test names the verdict rests
|
|
83
|
+
* on, so `verified` is auditable back to a specific run.
|
|
84
|
+
*/
|
|
85
|
+
export declare function ingestTestReport(sq: SpecQueries, projectRoot: string, reportPath: string, options?: {
|
|
86
|
+
runId?: string;
|
|
87
|
+
}): VerifyReportResult;
|
|
88
|
+
//# sourceMappingURL=report-ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-ingest.d.ts","sourceRoot":"","sources":["../../src/verify/report-ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,qFAAqF;AACrF,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACxB,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAiE1F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAkBnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,WAAW,EACf,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,kBAAkB,CAqFpB"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test-report ingestion (REQ-VSTATE-004).
|
|
4
|
+
*
|
|
5
|
+
* `verified` used to mean "an LLM read a test log and said it passed". This
|
|
6
|
+
* module replaces that with a deterministic mapping: parse a real test report,
|
|
7
|
+
* match each reported case to the `tests` links that claim it, and move those
|
|
8
|
+
* links — passed → `verified`, failed → `broken` — recording the run itself as
|
|
9
|
+
* the evidence.
|
|
10
|
+
*
|
|
11
|
+
* Parsing is total before any write happens, so a malformed report exits with a
|
|
12
|
+
* parse error having changed no state (A4).
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.TestReportParseError = void 0;
|
|
49
|
+
exports.parseVitestJsonReport = parseVitestJsonReport;
|
|
50
|
+
exports.caseMatchesLink = caseMatchesLink;
|
|
51
|
+
exports.ingestTestReport = ingestTestReport;
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
/** Thrown for an unreadable / unparseable / shapeless report (REQ-VSTATE-004.A4). */
|
|
55
|
+
class TestReportParseError extends Error {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = 'TestReportParseError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.TestReportParseError = TestReportParseError;
|
|
62
|
+
/** Normalize an absolute or relative path to a project-relative POSIX path. */
|
|
63
|
+
function toProjectRelative(projectRoot, filePath) {
|
|
64
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
65
|
+
const abs = path.isAbsolute(normalized)
|
|
66
|
+
? normalized
|
|
67
|
+
: path.resolve(projectRoot, normalized);
|
|
68
|
+
const rel = path.relative(projectRoot, abs).replace(/\\/g, '/');
|
|
69
|
+
return rel.startsWith('..') ? normalized.replace(/^\.\//, '') : rel;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse a vitest / jest JSON report (`vitest run --reporter=json`).
|
|
73
|
+
*
|
|
74
|
+
* Shape: `{ testResults: [{ name, assertionResults: [{ title, fullName,
|
|
75
|
+
* ancestorTitles, status, failureMessages }] }] }`. Anything else — invalid
|
|
76
|
+
* JSON, a non-object, a missing/!array `testResults` — is a parse error rather
|
|
77
|
+
* than an empty result, so a report written by a crashed runner can never read
|
|
78
|
+
* as "nothing to verify" (A4).
|
|
79
|
+
*/
|
|
80
|
+
function parseVitestJsonReport(raw, projectRoot) {
|
|
81
|
+
const trimmed = raw.trim();
|
|
82
|
+
if (trimmed.length === 0) {
|
|
83
|
+
throw new TestReportParseError('report file is empty');
|
|
84
|
+
}
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(trimmed);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
throw new TestReportParseError(`report is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
91
|
+
}
|
|
92
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
93
|
+
throw new TestReportParseError('report is not a JSON object');
|
|
94
|
+
}
|
|
95
|
+
const results = parsed.testResults;
|
|
96
|
+
if (!Array.isArray(results)) {
|
|
97
|
+
throw new TestReportParseError('report has no `testResults` array — expected `vitest run --reporter=json` output');
|
|
98
|
+
}
|
|
99
|
+
const cases = [];
|
|
100
|
+
for (const entry of results) {
|
|
101
|
+
if (typeof entry !== 'object' || entry === null)
|
|
102
|
+
continue;
|
|
103
|
+
const fileResult = entry;
|
|
104
|
+
const name = typeof fileResult.name === 'string' ? fileResult.name : '';
|
|
105
|
+
if (!name)
|
|
106
|
+
continue;
|
|
107
|
+
const file = toProjectRelative(projectRoot, name);
|
|
108
|
+
const assertions = Array.isArray(fileResult.assertionResults)
|
|
109
|
+
? fileResult.assertionResults
|
|
110
|
+
: [];
|
|
111
|
+
for (const a of assertions) {
|
|
112
|
+
if (typeof a !== 'object' || a === null)
|
|
113
|
+
continue;
|
|
114
|
+
const ar = a;
|
|
115
|
+
const title = typeof ar.title === 'string' ? ar.title : '';
|
|
116
|
+
if (!title)
|
|
117
|
+
continue;
|
|
118
|
+
const ancestors = Array.isArray(ar.ancestorTitles)
|
|
119
|
+
? ar.ancestorTitles.filter((t) => typeof t === 'string')
|
|
120
|
+
: [];
|
|
121
|
+
const fullName = typeof ar.fullName === 'string' && ar.fullName.length > 0
|
|
122
|
+
? ar.fullName
|
|
123
|
+
: [...ancestors, title].join(' > ');
|
|
124
|
+
const rawStatus = typeof ar.status === 'string' ? ar.status : 'skipped';
|
|
125
|
+
const status = rawStatus === 'passed'
|
|
126
|
+
? 'passed'
|
|
127
|
+
: rawStatus === 'failed'
|
|
128
|
+
? 'failed'
|
|
129
|
+
: 'skipped';
|
|
130
|
+
const failures = Array.isArray(ar.failureMessages)
|
|
131
|
+
? ar.failureMessages.filter((m) => typeof m === 'string')
|
|
132
|
+
: [];
|
|
133
|
+
cases.push({
|
|
134
|
+
file,
|
|
135
|
+
title,
|
|
136
|
+
fullName,
|
|
137
|
+
status,
|
|
138
|
+
failureMessage: failures[0],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return cases;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Does `testCase` prove `link`? Matching is by FILE first — a link's evidence
|
|
146
|
+
* must live in the file it points at — then by title, in descending precision:
|
|
147
|
+
*
|
|
148
|
+
* 1. the link's recorded `metadata.testTitles` (written by the test-title pass,
|
|
149
|
+
* REQ-VSTATE-002) contains the case's title or full name;
|
|
150
|
+
* 2. the case's full name mentions the link's spec id (the `(REQ-X.A1)` title
|
|
151
|
+
* convention, for links that predate the recorded titles);
|
|
152
|
+
* 3. the case's full name mentions the link's target symbol (a `verifies:`
|
|
153
|
+
* block naming a hoisted test function).
|
|
154
|
+
*/
|
|
155
|
+
function caseMatchesLink(testCase, link) {
|
|
156
|
+
const linkFile = link.targetFilePath.replace(/\\/g, '/');
|
|
157
|
+
if (linkFile !== testCase.file)
|
|
158
|
+
return false;
|
|
159
|
+
const recorded = link.metadata?.testTitles;
|
|
160
|
+
if (Array.isArray(recorded)) {
|
|
161
|
+
for (const t of recorded) {
|
|
162
|
+
if (typeof t !== 'string')
|
|
163
|
+
continue;
|
|
164
|
+
if (t === testCase.title || t === testCase.fullName)
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (testCase.fullName.includes(link.specId))
|
|
169
|
+
return true;
|
|
170
|
+
// A file-targeted link (the test-title pass) has no symbol to match on — its
|
|
171
|
+
// qualified name IS the path, whose extension would match almost any title.
|
|
172
|
+
if (link.targetNodeKind === 'file')
|
|
173
|
+
return false;
|
|
174
|
+
const symbol = link.targetQualifiedName.split('.').pop() ?? '';
|
|
175
|
+
if (symbol.length >= 3 && testCase.fullName.includes(symbol))
|
|
176
|
+
return true;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Read a report and apply it to the DB (REQ-VSTATE-004).
|
|
181
|
+
*
|
|
182
|
+
* Per matched link: any failed case → `broken`; otherwise at least one passed
|
|
183
|
+
* case → `verified`; only-skipped → left alone (a skipped test proves nothing
|
|
184
|
+
* either way). The verification record persisted on the link carries the run id,
|
|
185
|
+
* the report path, the ISO timestamp and the exact test names the verdict rests
|
|
186
|
+
* on, so `verified` is auditable back to a specific run.
|
|
187
|
+
*/
|
|
188
|
+
function ingestTestReport(sq, projectRoot, reportPath, options = {}) {
|
|
189
|
+
let raw;
|
|
190
|
+
try {
|
|
191
|
+
raw = fs.readFileSync(path.resolve(projectRoot, reportPath), 'utf-8');
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
throw new TestReportParseError(`cannot read report ${reportPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
}
|
|
196
|
+
// Parse EVERYTHING before the first write (A4).
|
|
197
|
+
const cases = parseVitestJsonReport(raw, projectRoot);
|
|
198
|
+
const runId = options.runId ?? `run-${Date.now()}`;
|
|
199
|
+
const verifiedAt = new Date().toISOString();
|
|
200
|
+
const testsLinks = sq.getAllLinks().filter((l) => l.kind === 'tests');
|
|
201
|
+
// Bucket the cases per link, and track which cases matched something.
|
|
202
|
+
const perLink = new Map();
|
|
203
|
+
const matchedCases = new Set();
|
|
204
|
+
for (const link of testsLinks) {
|
|
205
|
+
for (const c of cases) {
|
|
206
|
+
if (!caseMatchesLink(c, link))
|
|
207
|
+
continue;
|
|
208
|
+
const bucket = perLink.get(link.id) ?? { link, cases: [] };
|
|
209
|
+
bucket.cases.push(c);
|
|
210
|
+
perLink.set(link.id, bucket);
|
|
211
|
+
matchedCases.add(c);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const promoted = [];
|
|
215
|
+
const demoted = [];
|
|
216
|
+
let unchanged = 0;
|
|
217
|
+
for (const { link, cases: linkCases } of perLink.values()) {
|
|
218
|
+
const failed = linkCases.filter((c) => c.status === 'failed');
|
|
219
|
+
const passed = linkCases.filter((c) => c.status === 'passed');
|
|
220
|
+
if (failed.length === 0 && passed.length === 0) {
|
|
221
|
+
unchanged++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const pass = failed.length === 0;
|
|
225
|
+
const relevant = pass ? passed : failed;
|
|
226
|
+
const titles = relevant.map((c) => c.fullName);
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
const metadata = {
|
|
229
|
+
...(link.metadata ?? {}),
|
|
230
|
+
verification: {
|
|
231
|
+
result: pass ? 'pass' : 'fail',
|
|
232
|
+
verifiedAt,
|
|
233
|
+
reason: pass
|
|
234
|
+
? `${passed.length} test(s) passed in ${path.basename(reportPath)}`
|
|
235
|
+
: (failed[0]?.failureMessage?.split('\n')[0] ??
|
|
236
|
+
`${failed.length} test(s) failed in ${path.basename(reportPath)}`),
|
|
237
|
+
evidence: relevant.map((c) => `${c.file}:${c.fullName}`),
|
|
238
|
+
run: { id: runId, report: reportPath, source: 'vitest-json', ranAt: verifiedAt },
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
const to = pass ? 'verified' : 'broken';
|
|
242
|
+
sq.updateSpecLinkState(link.id, to, null, now);
|
|
243
|
+
sq.updateSpecLinkMetadata(link.id, metadata, now);
|
|
244
|
+
const outcome = {
|
|
245
|
+
linkId: link.id,
|
|
246
|
+
specId: link.specId,
|
|
247
|
+
file: link.targetFilePath,
|
|
248
|
+
titles,
|
|
249
|
+
from: link.state,
|
|
250
|
+
to,
|
|
251
|
+
};
|
|
252
|
+
if (pass)
|
|
253
|
+
promoted.push(outcome);
|
|
254
|
+
else
|
|
255
|
+
demoted.push(outcome);
|
|
256
|
+
}
|
|
257
|
+
const unmatchedCases = cases.filter((c) => !matchedCases.has(c));
|
|
258
|
+
return {
|
|
259
|
+
runId,
|
|
260
|
+
reportPath,
|
|
261
|
+
cases: cases.length,
|
|
262
|
+
promoted,
|
|
263
|
+
demoted,
|
|
264
|
+
unchanged,
|
|
265
|
+
unmatched: unmatchedCases.length,
|
|
266
|
+
unmatchedSample: unmatchedCases.slice(0, 10).map((c) => `${c.file}:${c.fullName}`),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=report-ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-ingest.js","sourceRoot":"","sources":["../../src/verify/report-ingest.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,8 +14,10 @@ inputs:
|
|
|
14
14
|
systemPromptAppend: |
|
|
15
15
|
You are fixing code so a spec's link returns to "verified" state.
|
|
16
16
|
Call specship_spec($INPUT.SPEC_ID) first to see the current state and
|
|
17
|
-
drift_axis.
|
|
18
|
-
|
|
17
|
+
drift_axis. Link state changes only in the link_verify node, only after
|
|
18
|
+
the reviewer's approval, and only as the run's VERIFY_RESULT marker
|
|
19
|
+
allows — a verify leg that was skipped or never got past install/build
|
|
20
|
+
proves nothing and promotes nothing (REQ-REVINT-006).
|
|
19
21
|
|
|
20
22
|
nodes:
|
|
21
23
|
- id: diagnose
|
|
@@ -78,33 +80,122 @@ nodes:
|
|
|
78
80
|
output_type: test_results
|
|
79
81
|
timeout: 600000
|
|
80
82
|
bash: |
|
|
83
|
+
# Same VERIFY_RESULT marker contract as spec-implement
|
|
84
|
+
# (REQ-REVINT-006.A2): the link_verify node routes on the marker, so a
|
|
85
|
+
# skipped run can never be mistaken for a passing one, and an
|
|
86
|
+
# environment or build failure is never attributed to the tests.
|
|
81
87
|
if [ -f package.json ]; then
|
|
82
|
-
|
|
88
|
+
# A fresh worktree has no node_modules/ or build output.
|
|
89
|
+
if [ ! -d node_modules ]; then
|
|
90
|
+
if ! { npm ci --no-audit --no-fund || npm install --no-audit --no-fund; }; then
|
|
91
|
+
echo "VERIFY_RESULT=env-failed"; exit 1
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
if ! npm run build --if-present; then echo "VERIFY_RESULT=build-failed"; exit 1; fi
|
|
95
|
+
if npm test; then echo "VERIFY_RESULT=ran-and-passed"; else echo "VERIFY_RESULT=ran-and-failed"; exit 1; fi
|
|
83
96
|
elif [ -f Cargo.toml ]; then
|
|
84
|
-
cargo
|
|
97
|
+
if ! cargo build; then echo "VERIFY_RESULT=build-failed"; exit 1; fi
|
|
98
|
+
if cargo test; then echo "VERIFY_RESULT=ran-and-passed"; else echo "VERIFY_RESULT=ran-and-failed"; exit 1; fi
|
|
85
99
|
elif [ -f pyproject.toml ] || [ -f requirements.txt ]; then
|
|
86
|
-
python -
|
|
100
|
+
if ! python -c 'import pytest' 2>/dev/null; then echo "VERIFY_RESULT=env-failed"; exit 1; fi
|
|
101
|
+
if python -m pytest; then echo "VERIFY_RESULT=ran-and-passed"; else echo "VERIFY_RESULT=ran-and-failed"; exit 1; fi
|
|
87
102
|
else
|
|
88
|
-
echo "no recognised test framework;
|
|
103
|
+
echo "no recognised test framework; tests did NOT run"
|
|
104
|
+
echo "VERIFY_RESULT=skipped"
|
|
89
105
|
fi
|
|
90
106
|
|
|
107
|
+
- id: route_verify
|
|
108
|
+
kind: bash
|
|
109
|
+
depends_on: [verify]
|
|
110
|
+
output_type: verify_route
|
|
111
|
+
bash: |
|
|
112
|
+
# Deterministic verify routing (REQ-VSTATE-005.A1): code parses the
|
|
113
|
+
# VERIFY_RESULT marker and emits the LINK_ACTION the link node obeys, so
|
|
114
|
+
# the decision that governs link promotion isn't a prompt's reading of a
|
|
115
|
+
# test log. env-failed / build-failed halt here, before the approval gate
|
|
116
|
+
# and before any link mutation, naming the cause (REQ-VSTATE-005.A3).
|
|
117
|
+
VERIFY_OUT=$verify.output
|
|
118
|
+
MARKER=$(printf '%s' "$VERIFY_OUT" | grep -oE 'VERIFY_RESULT=[a-z-]+' | tail -n 1 | cut -d= -f2)
|
|
119
|
+
[ -n "$MARKER" ] || MARKER=missing
|
|
120
|
+
echo "VERIFY_RESULT=$MARKER"
|
|
121
|
+
case "$MARKER" in
|
|
122
|
+
ran-and-passed)
|
|
123
|
+
echo "LINK_ACTION=verify-pass" ;;
|
|
124
|
+
ran-and-failed)
|
|
125
|
+
echo "LINK_ACTION=verify-fail" ;;
|
|
126
|
+
skipped)
|
|
127
|
+
echo "LINK_ACTION=no-verify" ;;
|
|
128
|
+
env-failed)
|
|
129
|
+
echo "HALT_CAUSE=environment setup failed (dependency install) — nothing is known about the tests"
|
|
130
|
+
exit 1 ;;
|
|
131
|
+
build-failed)
|
|
132
|
+
echo "HALT_CAUSE=build failed — nothing is known about the tests"
|
|
133
|
+
exit 1 ;;
|
|
134
|
+
*)
|
|
135
|
+
echo "HALT_CAUSE=no VERIFY_RESULT marker found in the verify output — an unknown result is never a pass"
|
|
136
|
+
exit 1 ;;
|
|
137
|
+
esac
|
|
138
|
+
|
|
139
|
+
- id: approve_links
|
|
140
|
+
kind: approval
|
|
141
|
+
depends_on: [route_verify]
|
|
142
|
+
message: |
|
|
143
|
+
Fix applied for $INPUT.SPEC_ID — approve before any link changes state
|
|
144
|
+
(REQ-REVINT-006.A4).
|
|
145
|
+
|
|
146
|
+
Files changed:
|
|
147
|
+
$apply.output
|
|
148
|
+
|
|
149
|
+
Verify routing (computed in code, REQ-VSTATE-005.A1 — `no-verify`
|
|
150
|
+
means no test ran, so nothing here can reach `verified`):
|
|
151
|
+
$route_verify.output
|
|
152
|
+
|
|
153
|
+
Test results:
|
|
154
|
+
$verify.output
|
|
155
|
+
|
|
156
|
+
Approve to let the link states be updated, or reject to leave every
|
|
157
|
+
link exactly as it is.
|
|
158
|
+
capture_response: true
|
|
159
|
+
|
|
91
160
|
- id: link_verify
|
|
92
161
|
kind: prompt
|
|
93
|
-
depends_on: [
|
|
162
|
+
depends_on: [approve_links]
|
|
94
163
|
output_type: link_summary
|
|
95
164
|
allowed_tools:
|
|
96
165
|
- mcp__specship__specship_spec
|
|
97
166
|
- mcp__specship__specship_link_assert
|
|
98
167
|
- mcp__specship__specship_link_verify
|
|
99
168
|
prompt: |
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
169
|
+
The reviewer has approved updating link state for $INPUT.SPEC_ID.
|
|
170
|
+
|
|
171
|
+
Act on LINK_ACTION — a bash node already derived it from the
|
|
172
|
+
VERIFY_RESULT marker (REQ-VSTATE-005.A1). Do NOT re-derive it from the
|
|
173
|
+
test log; read it here:
|
|
174
|
+
|
|
175
|
+
$route_verify.output
|
|
176
|
+
|
|
177
|
+
- LINK_ACTION=verify-pass → for each link that was drifted/broken, call
|
|
178
|
+
specship_link_verify with result="pass" (or "fail" if a test failed
|
|
179
|
+
for that link specifically).
|
|
180
|
+
- LINK_ACTION=verify-fail → call specship_link_verify with
|
|
181
|
+
result="fail" for the affected links. Never "pass".
|
|
182
|
+
- LINK_ACTION=no-verify → no test ran, so nothing is proven. Do NOT
|
|
183
|
+
call specship_link_verify at all; no link may reach `verified`
|
|
184
|
+
(REQ-REVINT-006.A3). Report that verification was skipped.
|
|
185
|
+
|
|
186
|
+
A run whose environment or build broke never reaches this node — the
|
|
187
|
+
routing step halts it first (REQ-VSTATE-005.A3).
|
|
103
188
|
|
|
104
189
|
If a link is orphaned because the symbol moved, call
|
|
105
|
-
specship_link_assert at the new location.
|
|
190
|
+
specship_link_assert at the new location. Never pass kind: 'tests' —
|
|
191
|
+
test evidence is file-derived (`verifies:` block or an `@verifies`
|
|
192
|
+
comment), never minted by this workflow (REQ-REVINT-005.A1).
|
|
193
|
+
|
|
194
|
+
Reviewer feedback (if any):
|
|
195
|
+
$approve_links.output
|
|
106
196
|
|
|
107
197
|
Test results:
|
|
108
198
|
$verify.output
|
|
109
199
|
|
|
110
|
-
Output: one line per link with the new state
|
|
200
|
+
Output: one line per link with the new state, ending with one line
|
|
201
|
+
stating whether verification ran, failed, or was skipped.
|