@specship/specship-win32-arm64 0.23.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.
Files changed (143) hide show
  1. package/lib/dist/activation/steering.d.ts +4 -4
  2. package/lib/dist/activation/steering.d.ts.map +1 -1
  3. package/lib/dist/activation/steering.js +6 -6
  4. package/lib/dist/activation/steering.js.map +1 -1
  5. package/lib/dist/bin/specship.js +277 -40
  6. package/lib/dist/bin/specship.js.map +1 -1
  7. package/lib/dist/commands/specship/learn.md +5 -3
  8. package/lib/dist/commands/specship/spec.md +73 -15
  9. package/lib/dist/db/spec-queries.d.ts +73 -3
  10. package/lib/dist/db/spec-queries.d.ts.map +1 -1
  11. package/lib/dist/db/spec-queries.js +139 -5
  12. package/lib/dist/db/spec-queries.js.map +1 -1
  13. package/lib/dist/diagram/flow-diagram.d.ts +86 -0
  14. package/lib/dist/diagram/flow-diagram.d.ts.map +1 -0
  15. package/lib/dist/diagram/flow-diagram.js +0 -0
  16. package/lib/dist/diagram/flow-diagram.js.map +1 -0
  17. package/lib/dist/enforce/changed-files.d.ts +28 -0
  18. package/lib/dist/enforce/changed-files.d.ts.map +1 -0
  19. package/lib/dist/enforce/changed-files.js +72 -0
  20. package/lib/dist/enforce/changed-files.js.map +1 -0
  21. package/lib/dist/enforce/enforce.d.ts +28 -1
  22. package/lib/dist/enforce/enforce.d.ts.map +1 -1
  23. package/lib/dist/enforce/enforce.js +37 -5
  24. package/lib/dist/enforce/enforce.js.map +1 -1
  25. package/lib/dist/extraction/grammars.d.ts +10 -0
  26. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  27. package/lib/dist/extraction/grammars.js +32 -3
  28. package/lib/dist/extraction/grammars.js.map +1 -1
  29. package/lib/dist/extraction/index.d.ts +7 -0
  30. package/lib/dist/extraction/index.d.ts.map +1 -1
  31. package/lib/dist/extraction/index.js.map +1 -1
  32. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts +7 -0
  33. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  34. package/lib/dist/extraction/specs/markdown-spec-extractor.js +81 -8
  35. package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  36. package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm.gz +0 -0
  37. package/lib/dist/extraction/wasm/tree-sitter-scala.wasm.gz +0 -0
  38. package/lib/dist/graph/spec-coverage.d.ts +86 -0
  39. package/lib/dist/graph/spec-coverage.d.ts.map +1 -0
  40. package/lib/dist/graph/spec-coverage.js +152 -0
  41. package/lib/dist/graph/spec-coverage.js.map +1 -0
  42. package/lib/dist/index.d.ts +81 -6
  43. package/lib/dist/index.d.ts.map +1 -1
  44. package/lib/dist/index.js +178 -21
  45. package/lib/dist/index.js.map +1 -1
  46. package/lib/dist/installer/index.d.ts +33 -8
  47. package/lib/dist/installer/index.d.ts.map +1 -1
  48. package/lib/dist/installer/index.js +50 -50
  49. package/lib/dist/installer/index.js.map +1 -1
  50. package/lib/dist/installer/init-offer.d.ts +14 -9
  51. package/lib/dist/installer/init-offer.d.ts.map +1 -1
  52. package/lib/dist/installer/init-offer.js +13 -9
  53. package/lib/dist/installer/init-offer.js.map +1 -1
  54. package/lib/dist/installer/install-defaults.d.ts +62 -0
  55. package/lib/dist/installer/install-defaults.d.ts.map +1 -0
  56. package/lib/dist/installer/install-defaults.js +67 -0
  57. package/lib/dist/installer/install-defaults.js.map +1 -0
  58. package/lib/dist/installer/targets/claude.d.ts +14 -0
  59. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  60. package/lib/dist/installer/targets/claude.js +64 -0
  61. package/lib/dist/installer/targets/claude.js.map +1 -1
  62. package/lib/dist/installer/targets/shared.js +4 -4
  63. package/lib/dist/installer/targets/shared.js.map +1 -1
  64. package/lib/dist/mcp/fitness-tool.d.ts +5 -6
  65. package/lib/dist/mcp/fitness-tool.d.ts.map +1 -1
  66. package/lib/dist/mcp/fitness-tool.js +0 -16
  67. package/lib/dist/mcp/fitness-tool.js.map +1 -1
  68. package/lib/dist/mcp/health-tool.d.ts +26 -0
  69. package/lib/dist/mcp/health-tool.d.ts.map +1 -0
  70. package/lib/dist/mcp/health-tool.js +50 -0
  71. package/lib/dist/mcp/health-tool.js.map +1 -0
  72. package/lib/dist/mcp/maintainability-tool.d.ts +5 -7
  73. package/lib/dist/mcp/maintainability-tool.d.ts.map +1 -1
  74. package/lib/dist/mcp/maintainability-tool.js +0 -16
  75. package/lib/dist/mcp/maintainability-tool.js.map +1 -1
  76. package/lib/dist/mcp/model-context.d.ts +13 -2
  77. package/lib/dist/mcp/model-context.d.ts.map +1 -1
  78. package/lib/dist/mcp/model-context.js +39 -12
  79. package/lib/dist/mcp/model-context.js.map +1 -1
  80. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  81. package/lib/dist/mcp/proxy.js +4 -1
  82. package/lib/dist/mcp/proxy.js.map +1 -1
  83. package/lib/dist/mcp/server-instructions.d.ts +1 -1
  84. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  85. package/lib/dist/mcp/server-instructions.js +5 -4
  86. package/lib/dist/mcp/server-instructions.js.map +1 -1
  87. package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
  88. package/lib/dist/mcp/spec-tools.js +213 -28
  89. package/lib/dist/mcp/spec-tools.js.map +1 -1
  90. package/lib/dist/mcp/tools.d.ts +12 -8
  91. package/lib/dist/mcp/tools.d.ts.map +1 -1
  92. package/lib/dist/mcp/tools.js +89 -48
  93. package/lib/dist/mcp/tools.js.map +1 -1
  94. package/lib/dist/resolution/spec-link-resolver.d.ts +44 -2
  95. package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
  96. package/lib/dist/resolution/spec-link-resolver.js +132 -0
  97. package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
  98. package/lib/dist/resolution/test-title-links.d.ts +62 -0
  99. package/lib/dist/resolution/test-title-links.d.ts.map +1 -0
  100. package/lib/dist/resolution/test-title-links.js +108 -0
  101. package/lib/dist/resolution/test-title-links.js.map +1 -0
  102. package/lib/dist/search/query-parser.d.ts.map +1 -1
  103. package/lib/dist/search/query-parser.js +6 -1
  104. package/lib/dist/search/query-parser.js.map +1 -1
  105. package/lib/dist/server/project-registry.js +1 -1
  106. package/lib/dist/server/routes/claude.js +9 -36
  107. package/lib/dist/server/routes/graph.js +53 -7
  108. package/lib/dist/server/routes/mcp.js +4 -3
  109. package/lib/dist/server/routes/projects.js +4 -4
  110. package/lib/dist/server/routes/spec.js +49 -7
  111. package/lib/dist/server/server.js +29 -17
  112. package/lib/dist/skills/spec-author/SKILL.md +164 -0
  113. package/lib/dist/skills/spec-author/references/format.md +210 -0
  114. package/lib/dist/skills/spec-author/references/gap-questions.md +152 -0
  115. package/lib/dist/skills/spec-author/references/quality-rubric.md +125 -0
  116. package/lib/dist/skills/spec-author/references/review-checklist.md +175 -0
  117. package/lib/dist/spec-lint/lint.d.ts +82 -0
  118. package/lib/dist/spec-lint/lint.d.ts.map +1 -0
  119. package/lib/dist/spec-lint/lint.js +430 -0
  120. package/lib/dist/spec-lint/lint.js.map +1 -0
  121. package/lib/dist/statusline/index.js.map +1 -1
  122. package/lib/dist/statusline/render.d.ts +1 -1
  123. package/lib/dist/statusline/render.d.ts.map +1 -1
  124. package/lib/dist/statusline/render.js +3 -1
  125. package/lib/dist/statusline/render.js.map +1 -1
  126. package/lib/dist/ui/assets/index-BzuUWUAE.js +50 -0
  127. package/lib/dist/ui/index.html +1 -1
  128. package/lib/dist/verify/report-ingest.d.ts +88 -0
  129. package/lib/dist/verify/report-ingest.d.ts.map +1 -0
  130. package/lib/dist/verify/report-ingest.js +269 -0
  131. package/lib/dist/verify/report-ingest.js.map +1 -0
  132. package/lib/dist/workflows/defaults/spec-fix.yaml +103 -12
  133. package/lib/dist/workflows/defaults/spec-implement-mixed.yaml +264 -21
  134. package/lib/dist/workflows/defaults/spec-implement.yaml +260 -19
  135. package/lib/dist/workflows/defaults/spec-verify.yaml +60 -9
  136. package/lib/node_modules/.package-lock.json +1 -1
  137. package/lib/package.json +7 -3
  138. package/package.json +1 -1
  139. package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  140. package/lib/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  141. package/lib/dist/server/ingest/impact-query.js +0 -343
  142. package/lib/dist/server/routes/events.js +0 -152
  143. package/lib/dist/ui/assets/index-CjjaftDw.js +0 -50
@@ -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-CjjaftDw.js"></script>
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. After fixing, call specship_link_verify({result: "pass"})
18
- on each affected link before reporting done.
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
- npm test
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 test
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 -m pytest
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; skipping"
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: [verify]
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
- For each link that was drifted/broken on $INPUT.SPEC_ID, now that
101
- the fix has been applied and tests pass, call specship_link_verify
102
- with result="pass" (or "fail" if a test failed for that link).
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.