@holmes-lab/holmes-kit 0.19.6 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +9 -2
  3. package/dist/.build-id +1 -1
  4. package/dist/holmes/cli/approve-status.d.ts +24 -0
  5. package/dist/holmes/cli/approve-status.js +166 -0
  6. package/dist/holmes/cli/approve-watch.d.ts +6 -0
  7. package/dist/holmes/cli/approve-watch.js +6 -0
  8. package/dist/holmes/cli/approve.d.ts +27 -0
  9. package/dist/holmes/cli/approve.js +68 -7
  10. package/dist/holmes/cli/cli-execution.d.ts +2 -0
  11. package/dist/holmes/cli/cli-execution.js +24 -0
  12. package/dist/holmes/cli/doctor-check.d.ts +15 -0
  13. package/dist/holmes/cli/doctor-check.js +2 -0
  14. package/dist/holmes/cli/doctor.d.ts +2 -8
  15. package/dist/holmes/cli/doctor.js +12 -3
  16. package/dist/holmes/cli/gitignore-merge.d.ts +0 -5
  17. package/dist/holmes/cli/gitignore-merge.js +6 -2
  18. package/dist/holmes/cli/index.js +176 -139
  19. package/dist/holmes/cli/npx-cache-check.d.ts +16 -0
  20. package/dist/holmes/cli/npx-cache-check.js +130 -0
  21. package/dist/holmes/cli/playbook-skills.d.ts +2 -22
  22. package/dist/holmes/cli/playbook-skills.js +10 -54
  23. package/dist/holmes/cli/probe-process.d.ts +17 -2
  24. package/dist/holmes/cli/probe-process.js +44 -11
  25. package/dist/holmes/governance/approval-grants.d.ts +22 -0
  26. package/dist/holmes/governance/approval-grants.js +55 -1
  27. package/dist/holmes/governance/approval-queue.d.ts +43 -4
  28. package/dist/holmes/governance/approval-queue.js +91 -13
  29. package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
  30. package/dist/holmes/governance/display-id-aliases.js +86 -0
  31. package/dist/holmes/governance/ledger-store.d.ts +42 -14
  32. package/dist/holmes/governance/ledger-store.js +49 -3
  33. package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
  34. package/dist/holmes/governance/ledger-timeline.js +11 -1
  35. package/dist/holmes/governance/provenance-chain.d.ts +2 -0
  36. package/dist/holmes/governance/provenance-chain.js +13 -2
  37. package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
  38. package/dist/holmes/governance/provenance-ledger.js +69 -16
  39. package/dist/holmes/governance/session-context.d.ts +3 -0
  40. package/dist/holmes/governance/session-context.js +26 -8
  41. package/dist/holmes/guardrail/risk-classifier.js +2 -2
  42. package/dist/holmes/guardrail/write-target.js +2 -1
  43. package/dist/holmes/hooks/pre-tool-use.js +12 -1
  44. package/dist/holmes/hooks/stop.js +14 -3
  45. package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
  46. package/dist/holmes/mcp/handlers/adoption.js +186 -0
  47. package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
  48. package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
  49. package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
  50. package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
  51. package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
  52. package/dist/holmes/mcp/handlers/entity-store.js +212 -0
  53. package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
  54. package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
  55. package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
  56. package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
  57. package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
  58. package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
  59. package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
  60. package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
  61. package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
  62. package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
  63. package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
  64. package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
  65. package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
  66. package/dist/holmes/mcp/handlers/review-queries.js +79 -0
  67. package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
  68. package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
  69. package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +72 -0
  70. package/dist/holmes/mcp/handlers/slice-orchestration.js +373 -0
  71. package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
  72. package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
  73. package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
  74. package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
  75. package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
  76. package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
  77. package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
  78. package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
  79. package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
  80. package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
  81. package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
  82. package/dist/holmes/mcp/handlers/test-execution.js +210 -0
  83. package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
  84. package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
  85. package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
  86. package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
  87. package/dist/holmes/mcp/handlers.d.ts +661 -424
  88. package/dist/holmes/mcp/handlers.js +270 -3251
  89. package/dist/holmes/mcp/server-specs-dir.d.ts +2 -0
  90. package/dist/holmes/mcp/server-specs-dir.js +71 -0
  91. package/dist/holmes/mcp/server.js +12 -2
  92. package/dist/holmes/mcp/tool-schemas.js +43 -2
  93. package/dist/holmes/project/execution-context.d.ts +17 -0
  94. package/dist/holmes/project/execution-context.js +121 -0
  95. package/dist/holmes/project/installer-markers.d.ts +34 -0
  96. package/dist/holmes/project/installer-markers.js +65 -0
  97. package/dist/holmes/project/root.d.ts +12 -1
  98. package/dist/holmes/project/root.js +22 -3
  99. package/dist/holmes/project/workspace-identity.d.ts +29 -0
  100. package/dist/holmes/project/workspace-identity.js +181 -0
  101. package/dist/holmes/review/test-outcomes.d.ts +6 -1
  102. package/dist/holmes/review/test-outcomes.js +23 -5
  103. package/dist/holmes/review/test-runner.d.ts +18 -0
  104. package/dist/holmes/review/test-runner.js +136 -5
  105. package/dist/holmes/rtm/graph-store.d.ts +50 -1
  106. package/dist/holmes/rtm/graph-store.js +244 -3
  107. package/dist/holmes/rtm/incremental.d.ts +1 -0
  108. package/dist/holmes/rtm/incremental.js +12 -3
  109. package/dist/holmes/rtm/localize.js +7 -0
  110. package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
  111. package/dist/holmes/rtm/rtm-graph.js +13 -0
  112. package/dist/holmes/rtm/test-scope.js +4 -1
  113. package/dist/holmes/semantic/vector-cache.d.ts +16 -1
  114. package/dist/holmes/semantic/vector-cache.js +155 -19
  115. package/dist/holmes/spec/approval-status.d.ts +10 -0
  116. package/dist/holmes/spec/approval-status.js +7 -3
  117. package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
  118. package/dist/holmes/spec/entity-git-snapshot.js +276 -0
  119. package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
  120. package/dist/holmes/spec/entity-integration-discharge.js +233 -0
  121. package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
  122. package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
  123. package/dist/holmes/spec/entity-integration.d.ts +216 -0
  124. package/dist/holmes/spec/entity-integration.js +760 -0
  125. package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
  126. package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
  127. package/dist/holmes/spec/entity-renumber.d.ts +32 -0
  128. package/dist/holmes/spec/entity-renumber.js +156 -0
  129. package/dist/holmes/spec/entity-store.d.ts +135 -0
  130. package/dist/holmes/spec/entity-store.js +1053 -0
  131. package/dist/holmes/spec/entity-transaction.d.ts +105 -0
  132. package/dist/holmes/spec/entity-transaction.js +741 -0
  133. package/dist/holmes/spec/renumber.d.ts +58 -0
  134. package/dist/holmes/spec/renumber.js +200 -1
  135. package/dist/holmes/spec/spec-store.d.ts +3 -2
  136. package/dist/holmes/spec/spec-store.js +23 -2
  137. package/dist/holmes/spec/transition-policy.d.ts +36 -0
  138. package/dist/holmes/spec/transition-policy.js +135 -0
  139. package/package.json +5 -2
@@ -0,0 +1,183 @@
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.createTaintScreeningHandlers = createTaintScreeningHandlers;
37
+ // @implements A-SPEC-611, A-SPEC-100.2, A-SPEC-128, A-SPEC-138, A-SPEC-140.2, A-SPEC-189, A-SPEC-461, A-SPEC-512.2, A-SPEC-513.1, A-SPEC-524.1
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const rtm_graph_1 = require("../../rtm/rtm-graph");
41
+ const rtm_builder_1 = require("../../rtm/rtm-builder");
42
+ const taint_1 = require("../../rtm/taint");
43
+ function createTaintScreeningHandlers(context) {
44
+ return {
45
+ /**
46
+ * @implements A-SPEC-138
47
+ * Call-graph taint REACHABILITY screen: source-named functions that reach sink-named functions
48
+ * through call edges. A SCREENING signal that routes a security review, NOT a data-flow proof —
49
+ * the result always carries the honesty envelope (kind + limits) and phrases pairs as "reaches",
50
+ * never "vulnerable".
51
+ */
52
+ async taint_scan(a) {
53
+ // @implements A-SPEC-461 — same §15 defence as rtm_check: refuse a root that is not this
54
+ // server's project rather than scanning it against the bound store's specs.
55
+ {
56
+ const foreign = context.foreignRootReason(a.root);
57
+ if (foreign)
58
+ return { ok: false, reason: foreign };
59
+ }
60
+ const root = context.projectRootOf(a.root);
61
+ const scanned = context.cachedScan(root, root);
62
+ const g = new rtm_graph_1.RtmGraph();
63
+ try {
64
+ (0, rtm_builder_1.buildRtm)(await context.listSpecs(), scanned, g);
65
+ const cfg = taint_1.DEFAULT_TAINT_CONFIG;
66
+ const { pairs, truncated } = (0, taint_1.taintReachability)(g, cfg);
67
+ const base = { kind: 'call-reachability', limits: [...taint_1.TAINT_LIMITS], maxPaths: cfg.maxPaths, truncated, pairs };
68
+ if (a.dataFlow !== true)
69
+ return base;
70
+ // @implements A-SPEC-512.2
71
+ // OPT-IN def-use lane. Measured 2026-09-02: extracting data-flow facts over this
72
+ // repository costs 7.6s on top of the scan, which is why this follows the `cpg_scan
73
+ // lints:true` precedent instead of running by default. Before this slice the engine
74
+ // (A-SPEC-140.1, with its own 13-case benchmark) had NO caller at all — capability
75
+ // present, unreachable. Findings are filtered by reaching definitions: inter-procedural
76
+ // arrival is preserved, and only what the DDG positively refutes is dropped.
77
+ const { TreeSitterTsParser } = require('../../cpg/language-parser');
78
+ const { taintDataFlow, DATAFLOW_LIMITS } = require('../../rtm/dataflow-taint');
79
+ const { filterFindingsForFile } = require('../../rtm/reaching-def-filter');
80
+ // @implements A-SPEC-513.1 — vocabulary is SELECTED per file language, never unioned:
81
+ // Python's `input(` must not taint a TypeScript `input`, and `req.body` means nothing in
82
+ // Python. Measured on jarvis: the shipped (JS) list missed `subprocess.run`, `os.environ`
83
+ // and `input(` entirely, so a Python repository could only ever report zero.
84
+ const { vocabularyFor } = require('../../rtm/taint-vocabulary');
85
+ const parser = new TreeSitterTsParser();
86
+ const factSet = [];
87
+ // @implements A-SPEC-524.1 — every non-TS language with a CFG takes the flow-sensitive
88
+ // lane, each judged with ITS OWN vocabulary. The TS family keeps the facts+reaching-defs
89
+ // lane (its interprocedural propagation lives there).
90
+ const FLOW_EXT = [
91
+ [/\.py$/i, 'python'], [/\.go$/i, 'go'], [/\.rs$/i, 'rust'],
92
+ [/\.java$/i, 'java'], [/\.cs$/i, 'csharp'], [/\.(cpp|cc|cxx|hpp|h)$/i, 'cpp'],
93
+ ];
94
+ const flowLangFiles = new Map();
95
+ for (const f of scanned) {
96
+ const vocab = vocabularyFor(f.sourcePath);
97
+ if (!vocab)
98
+ continue;
99
+ const flowLang = FLOW_EXT.find(([re]) => re.test(f.sourcePath))?.[1];
100
+ if (flowLang !== undefined) {
101
+ flowLangFiles.set(flowLang, [...(flowLangFiles.get(flowLang) ?? []), f.sourcePath]);
102
+ continue;
103
+ }
104
+ let src;
105
+ try {
106
+ src = fs.readFileSync(path.join(root, f.sourcePath), 'utf8');
107
+ }
108
+ catch {
109
+ continue;
110
+ }
111
+ const facts = parser.extractDataFlow(src, f.sourcePath.endsWith('x') ? 'tsx' : 'typescript');
112
+ if (facts)
113
+ factSet.push({ file: f.sourcePath, facts });
114
+ }
115
+ const tsVocab = vocabularyFor('x.ts');
116
+ const raw = taintDataFlow(factSet, tsVocab);
117
+ const byFile = new Map();
118
+ for (const fnd of raw.findings)
119
+ byFile.set(fnd.file, [...(byFile.get(fnd.file) ?? []), fnd]);
120
+ const kept = [];
121
+ let refuted = 0;
122
+ for (const [rel, fs2] of byFile) {
123
+ const out = await filterFindingsForFile(root, rel, fs2, tsVocab);
124
+ kept.push(...out.kept.map((f) => ({ ...f, lane: 'facts+reaching-defs' })));
125
+ refuted += out.removed.length;
126
+ }
127
+ // @implements A-SPEC-513.1 / A-SPEC-524.1 — the flow-sensitive lane, generalized from
128
+ // Python-only to every language whose CFG/DDG landed in P2. The judgement engine is
129
+ // language-agnostic; only the vocabulary is selected per language.
130
+ const flowLangsAnalysed = [];
131
+ if (flowLangFiles.size > 0) {
132
+ const { parseAst } = require('../../cpg/foundation/ast-store');
133
+ const { cfgOf, functionsIn } = require('../../cpg/foundation/cfg');
134
+ const { ddgOf } = require('../../cpg/foundation/ddg');
135
+ const { flowSensitiveTaint } = require('../../rtm/flow-sensitive-taint');
136
+ for (const [flowLang, files] of flowLangFiles) {
137
+ flowLangsAnalysed.push(flowLang);
138
+ for (const rel of files) {
139
+ let src;
140
+ try {
141
+ src = fs.readFileSync(path.join(root, rel), 'utf8');
142
+ }
143
+ catch {
144
+ continue;
145
+ }
146
+ const vocab = vocabularyFor(rel);
147
+ const ast = await parseAst(src, rel);
148
+ if (!ast || ast.errorCount > 0)
149
+ continue;
150
+ for (const fn of functionsIn(ast)) {
151
+ const fcfg = cfgOf(ast, fn, src);
152
+ if ('unsupported' in fcfg)
153
+ continue;
154
+ const fddg = ddgOf(ast, fcfg, fn, src);
155
+ if ('unsupported' in fddg)
156
+ continue;
157
+ const r = flowSensitiveTaint({ ast, cfg: fcfg, ddg: fddg, source: src, fnName: rel, config: vocab });
158
+ for (const f of r.findings)
159
+ kept.push({ ...f, file: rel, lane: 'flow-sensitive' });
160
+ }
161
+ }
162
+ }
163
+ }
164
+ return {
165
+ ...base,
166
+ dataFlow: {
167
+ findings: kept,
168
+ /** Findings the reaching-definition evidence positively refuted (dead or fully sanitized). */
169
+ refutedByReachingDefs: refuted,
170
+ limits: [...DATAFLOW_LIMITS],
171
+ converged: raw.converged,
172
+ truncated: raw.truncated,
173
+ /** Which languages this run could actually judge, so a zero is readable. */
174
+ languagesAnalysed: [...new Set([factSet.length > 0 ? 'typescript' : null, ...flowLangsAnalysed].filter(Boolean))],
175
+ },
176
+ };
177
+ }
178
+ finally {
179
+ g.close();
180
+ }
181
+ },
182
+ };
183
+ }
@@ -0,0 +1,63 @@
1
+ import { Spec } from '../../spec/spec-parser';
2
+ import { ScannedFile } from '../../cpg/cpg-scanner';
3
+ import { ChangeSourceInfo } from '../../project/change-source';
4
+ export interface TestExecutionContext {
5
+ defaultBaseline: string;
6
+ deriveChangedContext(root: string, args: {
7
+ base?: string;
8
+ head?: string;
9
+ since?: string;
10
+ }, tool: string): Promise<{
11
+ root: string;
12
+ specs: Spec[];
13
+ scanned: ScannedFile[];
14
+ changedFiles: string[];
15
+ changedSymbols: string[];
16
+ changeSource: ChangeSourceInfo;
17
+ scopeFallback?: 'full';
18
+ anchorImpactedSpecs: string[];
19
+ changedTestFiles: string[];
20
+ unresolvedFiles: string[];
21
+ }>;
22
+ }
23
+ export declare function createTestExecutionHandlers(context: TestExecutionContext): {
24
+ test_run(a: {
25
+ root: string;
26
+ base?: string;
27
+ head?: string;
28
+ since?: string;
29
+ mark?: string;
30
+ mutate?: string;
31
+ }): Promise<{
32
+ mutate: {
33
+ tspec: string;
34
+ aspec: string | undefined;
35
+ coveringFiles: string[];
36
+ results: ({
37
+ mutation: import("../../spec/kills").Mutation;
38
+ applied: boolean;
39
+ reason: string;
40
+ } | {
41
+ applied: boolean;
42
+ verdict?: "killed" | "survived";
43
+ mutation: import("../../spec/kills").Mutation;
44
+ reason?: undefined;
45
+ })[];
46
+ survivors: import("../../spec/kills").Mutation[];
47
+ };
48
+ } | {
49
+ calibrationClosed?: string[] | undefined;
50
+ baselineRecorded?: string | undefined;
51
+ scopeFallback?: "full" | undefined;
52
+ tier: import("../../rtm/test-scope").RegressionTier;
53
+ mode: "full" | "noop" | "scoped";
54
+ passed: boolean;
55
+ skipped: boolean;
56
+ ranFiles: string[];
57
+ executedByAspec: Record<string, number>;
58
+ tail: string;
59
+ unresolvedFiles: string[];
60
+ changeSource: ChangeSourceInfo;
61
+ mutate?: undefined;
62
+ }>;
63
+ };
@@ -0,0 +1,210 @@
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.createTestExecutionHandlers = createTestExecutionHandlers;
37
+ // @implements A-SPEC-616, A-SPEC-100.2, A-SPEC-102.1, A-SPEC-121.2, A-SPEC-121.3, A-SPEC-128, A-SPEC-130, A-SPEC-189, A-SPEC-233, A-SPEC-419, A-SPEC-433, A-SPEC-534.5, A-SPEC-534.8, A-SPEC-578.4, A-SPEC-614
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const node_child_process_1 = require("node:child_process");
41
+ const change_source_1 = require("../../project/change-source");
42
+ const root_1 = require("../../project/root");
43
+ const ignore_1 = require("../../project/ignore");
44
+ const baseline_1 = require("../../project/baseline");
45
+ const rtm_graph_1 = require("../../rtm/rtm-graph");
46
+ const rtm_builder_1 = require("../../rtm/rtm-builder");
47
+ const test_scope_1 = require("../../rtm/test-scope");
48
+ const scope_1 = require("../../review/scope");
49
+ const test_runner_1 = require("../../review/test-runner");
50
+ const kills_1 = require("../../spec/kills");
51
+ const mutate_1 = require("../../review/mutate");
52
+ const test_evidence_1 = require("../../review/test-evidence");
53
+ const test_outcomes_1 = require("../../review/test-outcomes");
54
+ const maintenance_evidence_1 = require("../maintenance-evidence");
55
+ // @implements A-SPEC-534.8
56
+ // The governed source file a `kills` mutation targets: one that @implements the A-SPEC AND contains
57
+ // the `where` literal. Bounded walk, skips vendored/test/hidden dirs. null when none qualifies.
58
+ function sourceFileWithMutation(root, aspecId, where) {
59
+ if (!where)
60
+ return null;
61
+ const SOURCE = /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rs|java|kt|cs|rb|php|swift)$/;
62
+ let found = null;
63
+ const walk = (d) => {
64
+ if (found)
65
+ return;
66
+ let entries;
67
+ try {
68
+ entries = fs.readdirSync(d, { withFileTypes: true });
69
+ }
70
+ catch {
71
+ return;
72
+ }
73
+ for (const e of entries) {
74
+ if (found)
75
+ return;
76
+ if (e.name === 'node_modules' || e.name === 'dist' || e.name === 'reference'
77
+ || e.name === 'vendor' || e.name === 'third_party' || e.name.startsWith('.'))
78
+ continue;
79
+ const p = path.join(d, e.name);
80
+ if (e.isDirectory()) {
81
+ walk(p);
82
+ continue;
83
+ }
84
+ if (!SOURCE.test(e.name) || /\.test\./.test(e.name))
85
+ continue;
86
+ let text;
87
+ try {
88
+ text = fs.readFileSync(p, 'utf8');
89
+ }
90
+ catch {
91
+ continue;
92
+ }
93
+ if (text.includes(`@implements ${aspecId}`) && text.includes(where)) {
94
+ found = p;
95
+ return;
96
+ }
97
+ }
98
+ };
99
+ walk(root);
100
+ return found;
101
+ }
102
+ function createTestExecutionHandlers(context) {
103
+ return {
104
+ async test_run(a) {
105
+ // Closes the decision->execution loop: scope -> run -> durable per-A-SPEC EXECUTION evidence
106
+ // (what the constitution's ART-4 prefers over the syntactic count).
107
+ const { root, specs, scanned, changedFiles, changedSymbols, changeSource, scopeFallback, anchorImpactedSpecs, changedTestFiles, unresolvedFiles } = await context.deriveChangedContext(a.root, a, 'test_run');
108
+ // @implements A-SPEC-534.8 — `mutate`: run a T-SPEC's declared `kills` mutations against the
109
+ // A-SPEC's source and report which SURVIVED (a discriminating-power gap). Selective, opt-in via
110
+ // the argument; absent → the ordinary run below is untouched.
111
+ if (a.mutate) {
112
+ const tspec = specs.find((s) => s.id === a.mutate && s.type === 'T-SPEC');
113
+ const kills = tspec ? (0, kills_1.parseKills)(tspec.frontmatter) : [];
114
+ const aspecId = tspec?.dependsOn[0];
115
+ const anchors = (0, test_scope_1.scanTestAnchors)(root);
116
+ const coveringFiles = aspecId
117
+ ? Object.entries(anchors).filter(([, ids]) => ids.includes(aspecId)).map(([f]) => f) : [];
118
+ const results = kills.map((m) => {
119
+ const src = aspecId ? sourceFileWithMutation(root, aspecId, m.where) : null;
120
+ if (!src)
121
+ return { mutation: m, applied: false, reason: 'no governed source anchors this A-SPEC and contains `where`' };
122
+ const r = (0, mutate_1.runKillsOnFile)(src, m, coveringFiles, (files) => (0, test_runner_1.runJestOutcomes)(files, root));
123
+ return { mutation: m, ...r };
124
+ });
125
+ const survivors = results.filter((r) => r.verdict === 'survived').map((r) => r.mutation);
126
+ return { mutate: { tspec: a.mutate, aspec: aspecId, coveringFiles, results, survivors } };
127
+ }
128
+ const g = new rtm_graph_1.RtmGraph();
129
+ let testScope;
130
+ try {
131
+ (0, rtm_builder_1.buildRtm)(specs, scanned, g);
132
+ const scope = (0, scope_1.computeReviewScope)(g, specs, changedSymbols);
133
+ // @implements A-SPEC-130
134
+ // Lane 2 widens the impacted set with file-anchor edges the symbol walk cannot see; lanes 3
135
+ // and the unresolved remainder ride in through extras. A nonempty change set can end in a
136
+ // scoped run or a full run — never in "nothing to do".
137
+ const widenedImpacted = [...new Set([...scope.impactedSpecs, ...anchorImpactedSpecs])];
138
+ // @implements A-SPEC-233 — the architecture axis of `assessRisk` already calls these changes
139
+ // `hard-hitl`; until now nothing on the test side consumed that verdict, so the most
140
+ // dangerous change could receive the narrowest run (measured 2026-08-22: tier `unit`, one
141
+ // test file, against `hard-hitl`).
142
+ testScope = (0, test_scope_1.computeTestScope)(widenedImpacted, (0, test_scope_1.scanTestAnchors)(root), specs, scope.coverageGaps, undefined, { changedTestFiles, unresolvedFiles, architectureTouched: (0, test_scope_1.architectureGoverningFiles)(changedFiles), obligationGaps: scope.obligationGaps });
143
+ // @implements A-SPEC-128
144
+ // No change set means no basis for narrowing. Widening is the same fail-safe direction
145
+ // computeTestScope already takes when no anchored test resolves — never a silently narrow run.
146
+ if (scopeFallback === 'full') {
147
+ testScope = { ...testScope, tier: 'full', reason: `${changeSource.reason ?? 'no change set'} — cannot narrow safely, full regression` };
148
+ }
149
+ }
150
+ finally {
151
+ g.close();
152
+ }
153
+ const result = (0, test_runner_1.runTestScope)(testScope, root);
154
+ const anchors = (0, test_scope_1.scanTestAnchors)(root);
155
+ const executedByAspec = (0, test_evidence_1.computeExecutedByAspec)(result.executedByFile ?? {}, anchors);
156
+ let head = '';
157
+ try {
158
+ head = (0, node_child_process_1.execFileSync)('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8', env: (0, root_1.cleanSubprocessEnv)(), stdio: ['ignore', 'pipe', 'pipe'] }).trim();
159
+ }
160
+ catch { /* non-git */ }
161
+ // Record ONLY an actually-executed, GREEN run (review C4/C7): a red suite must not stand as
162
+ // coverage evidence at the moment the code is broken, and a no-op run must not overwrite a real
163
+ // record with a dishonest `passed: true` over an empty result.
164
+ // @implements A-SPEC-578.4 — read BEFORE the write below replaces it: the calibration window
165
+ // is "since the previous evidence run", and this line is the only moment that value exists.
166
+ const previousEvidenceTs = (0, test_evidence_1.readTestEvidence)(root)?.ts;
167
+ const verified = (0, baseline_1.shouldRecordBaseline)(result);
168
+ if (verified) {
169
+ (0, test_evidence_1.writeTestEvidence)(root, { ts: new Date().toISOString(), head, tier: testScope.tier, passed: true, executedByAspec });
170
+ }
171
+ // @implements A-SPEC-534.5 — RED-first outcome evidence for ART-8. Recorded UNCONDITIONALLY,
172
+ // unlike the green-only baseline above: a red-assertion recorded before the code is exactly what
173
+ // the red→green sequence needs. Append-only, one record per (A-SPEC, outcome) at this HEAD.
174
+ if (result.outcomeByFile) {
175
+ (0, test_outcomes_1.appendOutcomes)(root, (0, test_outcomes_1.buildOutcomeRecords)(result.outcomeByFile, anchors, head, new Date().toISOString()));
176
+ }
177
+ // @implements A-SPEC-128
178
+ // The baseline is written under EXACTLY the condition that already gates evidence: a run that
179
+ // actually executed and passed. A red or skipped run must never become the reference point for
180
+ // "since the last verified state" — that would silently narrow every later scope against a
181
+ // state nobody verified.
182
+ // @implements A-SPEC-578.4 — close the calibration loop on real work.
183
+ // The window is the PREVIOUS evidence run's timestamp, read before this run overwrote it —
184
+ // an analysis older than that belonged to a previous slice, and attributing today's changes
185
+ // to it would be contamination rather than measurement. Its own try: an observation that
186
+ // could change `passed` would be a gate wearing another name.
187
+ let calibrationClosed;
188
+ try {
189
+ const closed = (0, maintenance_evidence_1.closeOpenArtifacts)(path.join(root, maintenance_evidence_1.EVIDENCE_DIR), { files: changedFiles }, new Date().toISOString(), previousEvidenceTs).closed;
190
+ if (closed.length > 0)
191
+ calibrationClosed = closed;
192
+ }
193
+ catch { /* the loop is observation; it never touches the verdict */ }
194
+ // @implements A-SPEC-128
195
+ let baseline;
196
+ if (verified) {
197
+ baseline = a.mark ?? context.defaultBaseline;
198
+ // A-SPEC-256.2 round-4 — surfaced to the report below
199
+ const ig = (0, ignore_1.loadIgnore)(root); // parse .gitignore ONCE, not per file (round-10)
200
+ (0, baseline_1.writeBaseline)(root, baseline, (0, change_source_1.hashTree)(root, { isIgnored: (p) => ig.isIgnored(p) }));
201
+ }
202
+ return { tier: testScope.tier, mode: result.mode, passed: result.passed, skipped: result.skipped,
203
+ ranFiles: result.ranFiles, executedByAspec, tail: result.tail,
204
+ // @implements A-SPEC-130 — the remediation rides in the answer: these are the files to anchor.
205
+ unresolvedFiles: testScope.unresolvedFiles,
206
+ changeSource, ...(scopeFallback ? { scopeFallback } : {}), ...(baseline ? { baselineRecorded: baseline } : {}),
207
+ ...(calibrationClosed ? { calibrationClosed } : {}) };
208
+ },
209
+ };
210
+ }
@@ -0,0 +1,30 @@
1
+ import { IdentityOperation } from '../../project/workspace-identity';
2
+ export declare function createWorkspaceIdentityHandlers(context: {
3
+ foreignRootReason(root?: string): string | null;
4
+ storeRoot?: string;
5
+ }): {
6
+ workspace_identity(a: {
7
+ root: string;
8
+ operation?: IdentityOperation;
9
+ }): Promise<{
10
+ ok: true;
11
+ state: "legacy";
12
+ } | {
13
+ ok: true;
14
+ state: "initialized";
15
+ workspaceId: string;
16
+ } | {
17
+ ok: true;
18
+ state: "registered";
19
+ workspaceId: string;
20
+ replicaId: string;
21
+ } | {
22
+ ok: boolean;
23
+ reason: string;
24
+ code?: undefined;
25
+ } | {
26
+ ok: boolean;
27
+ code: string;
28
+ reason: string;
29
+ }>;
30
+ };
@@ -0,0 +1,68 @@
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.createWorkspaceIdentityHandlers = createWorkspaceIdentityHandlers;
37
+ // @implements A-SPEC-623, A-SPEC-189
38
+ const workspace_identity_1 = require("../../project/workspace-identity");
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const root_1 = require("../../project/root");
42
+ function createWorkspaceIdentityHandlers(context) {
43
+ return {
44
+ async workspace_identity(a) {
45
+ const foreign = context.foreignRootReason(a.root);
46
+ if (foreign)
47
+ return { ok: false, reason: foreign };
48
+ // A new/custom store directory may not exist yet. Resolve its existing ancestor rather than
49
+ // treating failure to resolve that directory as permission to initialize any supplied root.
50
+ if (context.storeRoot !== undefined) {
51
+ let ancestor = path.resolve(context.storeRoot);
52
+ while (!fs.existsSync(ancestor) && path.dirname(ancestor) !== ancestor)
53
+ ancestor = path.dirname(ancestor);
54
+ const bound = (0, root_1.resolveProjectRoot)(ancestor), asked = (0, root_1.resolveProjectRoot)(a.root);
55
+ if (bound.marker !== '.ax' || bound.root !== asked.root)
56
+ return { ok: false, code: 'foreign-workspace', reason: 'The configured spec store must belong to the requested Holmes workspace before identity operations.' };
57
+ }
58
+ try {
59
+ return (0, workspace_identity_1.workspaceIdentity)(a.root, a.operation);
60
+ }
61
+ catch (e) {
62
+ if (e instanceof workspace_identity_1.WorkspaceIdentityError)
63
+ return { ok: false, code: e.code, reason: e.message };
64
+ throw e;
65
+ }
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,77 @@
1
+ import { Spec } from '../../spec/spec-parser';
2
+ import { ScannedFile } from '../../cpg/cpg-scanner';
3
+ export interface WorkspaceQueryContext {
4
+ listSpecs(): Promise<Spec[]>;
5
+ foreignRootReason(root?: string): string | null;
6
+ projectRootOf(root: string): string;
7
+ cachedScan(root: string, repoRoot?: string): ScannedFile[];
8
+ cachedScanWithReport(root: string, repoRoot?: string): {
9
+ scanned: ScannedFile[];
10
+ skipped: Array<{
11
+ file: string;
12
+ reason: string;
13
+ }>;
14
+ };
15
+ assertStoreReachable(tool: string, root: unknown): void;
16
+ }
17
+ export declare function createWorkspaceQueryHandlers(context: WorkspaceQueryContext): {
18
+ rtm_check(a?: {
19
+ root?: string;
20
+ }): Promise<{
21
+ ok: boolean;
22
+ reason: string;
23
+ issues?: undefined;
24
+ gaps?: undefined;
25
+ breakingChangeDistribution?: undefined;
26
+ } | {
27
+ issues: import("../../rtm/rtm-check").RtmIssue[];
28
+ gaps: import("../../rtm/gap-analyzer").ConvergenceGaps;
29
+ breakingChangeDistribution: Record<string, number>;
30
+ ok?: undefined;
31
+ reason?: undefined;
32
+ } | {
33
+ issues: import("../../rtm/rtm-check").RtmIssue[];
34
+ breakingChangeDistribution: Record<string, number>;
35
+ ok?: undefined;
36
+ reason?: undefined;
37
+ gaps?: undefined;
38
+ }>;
39
+ cpg_scan(a: {
40
+ root: string;
41
+ lints?: boolean;
42
+ }): Promise<{
43
+ files: number;
44
+ symbols: number;
45
+ skipped: {
46
+ file: string;
47
+ reason: string;
48
+ }[];
49
+ skippedCount: number;
50
+ } | {
51
+ lints: {
52
+ dynamicRef: {
53
+ file: string;
54
+ name: string;
55
+ line: number;
56
+ }[];
57
+ asyncBlocking: {
58
+ file: string;
59
+ pattern: string;
60
+ line: number;
61
+ }[];
62
+ limits: string[];
63
+ };
64
+ files: number;
65
+ symbols: number;
66
+ skipped: {
67
+ file: string;
68
+ reason: string;
69
+ }[];
70
+ skippedCount: number;
71
+ }>;
72
+ issue_localize(a: {
73
+ root: string;
74
+ issue: string;
75
+ topN?: number;
76
+ }): Promise<import("../../rtm/localize").LocalizationReport>;
77
+ };