@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.
- package/CHANGELOG.md +143 -0
- package/README.md +9 -2
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/approve-status.d.ts +24 -0
- package/dist/holmes/cli/approve-status.js +166 -0
- package/dist/holmes/cli/approve-watch.d.ts +6 -0
- package/dist/holmes/cli/approve-watch.js +6 -0
- package/dist/holmes/cli/approve.d.ts +27 -0
- package/dist/holmes/cli/approve.js +68 -7
- package/dist/holmes/cli/cli-execution.d.ts +2 -0
- package/dist/holmes/cli/cli-execution.js +24 -0
- package/dist/holmes/cli/doctor-check.d.ts +15 -0
- package/dist/holmes/cli/doctor-check.js +2 -0
- package/dist/holmes/cli/doctor.d.ts +2 -8
- package/dist/holmes/cli/doctor.js +12 -3
- package/dist/holmes/cli/gitignore-merge.d.ts +0 -5
- package/dist/holmes/cli/gitignore-merge.js +6 -2
- package/dist/holmes/cli/index.js +176 -139
- package/dist/holmes/cli/npx-cache-check.d.ts +16 -0
- package/dist/holmes/cli/npx-cache-check.js +130 -0
- package/dist/holmes/cli/playbook-skills.d.ts +2 -22
- package/dist/holmes/cli/playbook-skills.js +10 -54
- package/dist/holmes/cli/probe-process.d.ts +17 -2
- package/dist/holmes/cli/probe-process.js +44 -11
- package/dist/holmes/governance/approval-grants.d.ts +22 -0
- package/dist/holmes/governance/approval-grants.js +55 -1
- package/dist/holmes/governance/approval-queue.d.ts +43 -4
- package/dist/holmes/governance/approval-queue.js +91 -13
- package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
- package/dist/holmes/governance/display-id-aliases.js +86 -0
- package/dist/holmes/governance/ledger-store.d.ts +42 -14
- package/dist/holmes/governance/ledger-store.js +49 -3
- package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
- package/dist/holmes/governance/ledger-timeline.js +11 -1
- package/dist/holmes/governance/provenance-chain.d.ts +2 -0
- package/dist/holmes/governance/provenance-chain.js +13 -2
- package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
- package/dist/holmes/governance/provenance-ledger.js +69 -16
- package/dist/holmes/governance/session-context.d.ts +3 -0
- package/dist/holmes/governance/session-context.js +26 -8
- package/dist/holmes/guardrail/risk-classifier.js +2 -2
- package/dist/holmes/guardrail/write-target.js +2 -1
- package/dist/holmes/hooks/pre-tool-use.js +12 -1
- package/dist/holmes/hooks/stop.js +14 -3
- package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
- package/dist/holmes/mcp/handlers/adoption.js +186 -0
- package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
- package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
- package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
- package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
- package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
- package/dist/holmes/mcp/handlers/entity-store.js +212 -0
- package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
- package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
- package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
- package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
- package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
- package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
- package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
- package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
- package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
- package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
- package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
- package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
- package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
- package/dist/holmes/mcp/handlers/review-queries.js +79 -0
- package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
- package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
- package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +72 -0
- package/dist/holmes/mcp/handlers/slice-orchestration.js +373 -0
- package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
- package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
- package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
- package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
- package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
- package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
- package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
- package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
- package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
- package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
- package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
- package/dist/holmes/mcp/handlers/test-execution.js +210 -0
- package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
- package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
- package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
- package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
- package/dist/holmes/mcp/handlers.d.ts +661 -424
- package/dist/holmes/mcp/handlers.js +270 -3251
- package/dist/holmes/mcp/server-specs-dir.d.ts +2 -0
- package/dist/holmes/mcp/server-specs-dir.js +71 -0
- package/dist/holmes/mcp/server.js +12 -2
- package/dist/holmes/mcp/tool-schemas.js +43 -2
- package/dist/holmes/project/execution-context.d.ts +17 -0
- package/dist/holmes/project/execution-context.js +121 -0
- package/dist/holmes/project/installer-markers.d.ts +34 -0
- package/dist/holmes/project/installer-markers.js +65 -0
- package/dist/holmes/project/root.d.ts +12 -1
- package/dist/holmes/project/root.js +22 -3
- package/dist/holmes/project/workspace-identity.d.ts +29 -0
- package/dist/holmes/project/workspace-identity.js +181 -0
- package/dist/holmes/review/test-outcomes.d.ts +6 -1
- package/dist/holmes/review/test-outcomes.js +23 -5
- package/dist/holmes/review/test-runner.d.ts +18 -0
- package/dist/holmes/review/test-runner.js +136 -5
- package/dist/holmes/rtm/graph-store.d.ts +50 -1
- package/dist/holmes/rtm/graph-store.js +244 -3
- package/dist/holmes/rtm/incremental.d.ts +1 -0
- package/dist/holmes/rtm/incremental.js +12 -3
- package/dist/holmes/rtm/localize.js +7 -0
- package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
- package/dist/holmes/rtm/rtm-graph.js +13 -0
- package/dist/holmes/rtm/test-scope.js +4 -1
- package/dist/holmes/semantic/vector-cache.d.ts +16 -1
- package/dist/holmes/semantic/vector-cache.js +155 -19
- package/dist/holmes/spec/approval-status.d.ts +10 -0
- package/dist/holmes/spec/approval-status.js +7 -3
- package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
- package/dist/holmes/spec/entity-git-snapshot.js +276 -0
- package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
- package/dist/holmes/spec/entity-integration-discharge.js +233 -0
- package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
- package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
- package/dist/holmes/spec/entity-integration.d.ts +216 -0
- package/dist/holmes/spec/entity-integration.js +760 -0
- package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
- package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
- package/dist/holmes/spec/entity-renumber.d.ts +32 -0
- package/dist/holmes/spec/entity-renumber.js +156 -0
- package/dist/holmes/spec/entity-store.d.ts +135 -0
- package/dist/holmes/spec/entity-store.js +1053 -0
- package/dist/holmes/spec/entity-transaction.d.ts +105 -0
- package/dist/holmes/spec/entity-transaction.js +741 -0
- package/dist/holmes/spec/renumber.d.ts +58 -0
- package/dist/holmes/spec/renumber.js +200 -1
- package/dist/holmes/spec/spec-store.d.ts +3 -2
- package/dist/holmes/spec/spec-store.js +23 -2
- package/dist/holmes/spec/transition-policy.d.ts +36 -0
- package/dist/holmes/spec/transition-policy.js +135 -0
- package/package.json +5 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Spec } from '../../spec/spec-parser';
|
|
2
|
+
import { ScannedFile } from '../../cpg/cpg-scanner';
|
|
3
|
+
import { ChangeSet, ChangeSourceInfo } from '../../project/change-source';
|
|
4
|
+
import { type GraphBasis } from '../../rtm/graph-store';
|
|
5
|
+
export interface GraphOperationsContext {
|
|
6
|
+
listSpecs(): Promise<Spec[]>;
|
|
7
|
+
assertStoreReachable(tool: string, root: unknown): void;
|
|
8
|
+
foreignRootReason(root?: string): string | null;
|
|
9
|
+
refusal(reason: string): Error;
|
|
10
|
+
projectRootOf(root: string): string;
|
|
11
|
+
cachedScan(root: string): ScannedFile[];
|
|
12
|
+
cacheDirFor(root: string): string;
|
|
13
|
+
deriveChangedContext(root: string, args: {
|
|
14
|
+
base?: string;
|
|
15
|
+
head?: string;
|
|
16
|
+
since?: string;
|
|
17
|
+
}, tool: string): Promise<{
|
|
18
|
+
root: string;
|
|
19
|
+
changes: ChangeSet;
|
|
20
|
+
specs: Spec[];
|
|
21
|
+
scanned: ScannedFile[];
|
|
22
|
+
bySourcePath: Map<string, ScannedFile>;
|
|
23
|
+
changeSource: ChangeSourceInfo;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/** Publish the complete graph for `specs` over `scanned` at the live path (A-SPEC-631 primitive). */
|
|
27
|
+
export declare function publishCurrentGraph(root: string, specs: Spec[], scanned: ScannedFile[]): {
|
|
28
|
+
basis: GraphBasis;
|
|
29
|
+
nodes: number;
|
|
30
|
+
edges: number;
|
|
31
|
+
};
|
|
32
|
+
export type SemanticWarmOutcome = {
|
|
33
|
+
status: 'discharged';
|
|
34
|
+
tier: string;
|
|
35
|
+
computed: number;
|
|
36
|
+
cached: number;
|
|
37
|
+
} | {
|
|
38
|
+
status: 'not-applicable';
|
|
39
|
+
tier: 'none';
|
|
40
|
+
} | {
|
|
41
|
+
status: 'unavailable';
|
|
42
|
+
tier: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* @implements A-SPEC-478 — the semantic cache WARMING lives in explicit heavy operations, so the
|
|
46
|
+
* ranking hot path only ever LOOKS UP vectors. Idempotent through the cache. The outcome says
|
|
47
|
+
* whether there was nothing to do (tier none), whether it happened, or whether a configured tier
|
|
48
|
+
* could not embed — a discharge must not record the last one as done.
|
|
49
|
+
*/
|
|
50
|
+
export declare function warmSemanticCache(root: string, scanned: readonly ScannedFile[]): Promise<SemanticWarmOutcome>;
|
|
51
|
+
export declare function createGraphOperationsHandlers(context: GraphOperationsContext): {
|
|
52
|
+
rtm_impact(a: {
|
|
53
|
+
root: string;
|
|
54
|
+
changed: string[];
|
|
55
|
+
}): Promise<{
|
|
56
|
+
breadthWarning?: string | undefined;
|
|
57
|
+
rankedImpact: {
|
|
58
|
+
file: string;
|
|
59
|
+
score: number;
|
|
60
|
+
}[];
|
|
61
|
+
reachedByDepth: number[];
|
|
62
|
+
bounded: {
|
|
63
|
+
id: string;
|
|
64
|
+
reason: "hub" | "depth";
|
|
65
|
+
inDegree?: number;
|
|
66
|
+
}[] | undefined;
|
|
67
|
+
summariesOmitted?: number | undefined;
|
|
68
|
+
impacted: string[];
|
|
69
|
+
impactedSummaries: {
|
|
70
|
+
id: string;
|
|
71
|
+
summary: string | null;
|
|
72
|
+
}[];
|
|
73
|
+
}>;
|
|
74
|
+
rtm_reindex(a: {
|
|
75
|
+
root: string;
|
|
76
|
+
base?: string;
|
|
77
|
+
head?: string;
|
|
78
|
+
since?: string;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
semanticWarm?: {
|
|
81
|
+
tier: string;
|
|
82
|
+
computed: number;
|
|
83
|
+
cached: number;
|
|
84
|
+
} | undefined;
|
|
85
|
+
changed: number;
|
|
86
|
+
nodes: number;
|
|
87
|
+
edges: number;
|
|
88
|
+
changeSource: ChangeSourceInfo;
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
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.publishCurrentGraph = publishCurrentGraph;
|
|
37
|
+
exports.warmSemanticCache = warmSemanticCache;
|
|
38
|
+
exports.createGraphOperationsHandlers = createGraphOperationsHandlers;
|
|
39
|
+
// @implements A-SPEC-614, A-SPEC-613, A-SPEC-100.2, A-SPEC-121.2, A-SPEC-128, A-SPEC-139, A-SPEC-189, A-SPEC-280, A-SPEC-282, A-SPEC-283, A-SPEC-419, A-SPEC-433, A-SPEC-469, A-SPEC-478, A-SPEC-568.1, A-SPEC-568.2, A-SPEC-569.1, A-SPEC-569.2, A-SPEC-569.3, A-SPEC-589
|
|
40
|
+
// @implements A-SPEC-632
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const hash_cache_1 = require("../../cpg/hash-cache");
|
|
44
|
+
const root_1 = require("../../project/root");
|
|
45
|
+
const rtm_builder_1 = require("../../rtm/rtm-builder");
|
|
46
|
+
const graph_store_1 = require("../../rtm/graph-store");
|
|
47
|
+
const assoc_arm_1 = require("../../assoc/assoc-arm");
|
|
48
|
+
// @implements A-SPEC-478 — the semantic tier runtime stays at the async reindex edge.
|
|
49
|
+
const tier_1 = require("../../semantic/tier");
|
|
50
|
+
const vector_cache_1 = require("../../semantic/vector-cache");
|
|
51
|
+
const embedder_1 = require("../../semantic/embedder");
|
|
52
|
+
// @implements A-SPEC-283 — bumped whenever the graph's node/edge shape changes, so a store written
|
|
53
|
+
// by an older build is rebuilt rather than read with new assumptions.
|
|
54
|
+
// @implements A-SPEC-568.1 — /3: nodes gained the intent `summary` column.
|
|
55
|
+
const RTM_GRAPH_SCHEMA = 'rtm-graph/3';
|
|
56
|
+
// @implements A-SPEC-569.3 — how many impacted specs get their intent sentence attached. A prose
|
|
57
|
+
// constant, never a verdict input: the impacted list itself is never truncated.
|
|
58
|
+
const SUMMARY_CAP = 40;
|
|
59
|
+
// @implements A-SPEC-569.1 (revision) — /2: pre-fix 0.16.0 builds could persist forged structural
|
|
60
|
+
// characters in the summary column, and every other basis field would still match after upgrading.
|
|
61
|
+
// A-SPEC-283's own rule applies to us too: an older build's artifact is rebuilt, never read with
|
|
62
|
+
// new assumptions.
|
|
63
|
+
const RTM_EXTRACTOR_VERSION = 'holmes-rtm/2';
|
|
64
|
+
// @implements A-SPEC-632 — the two rebuild paths a completed integration's discharge runs are the
|
|
65
|
+
// SAME functions explicit reindex runs, so a discharged graph and a reindexed graph are one artifact.
|
|
66
|
+
/** Publish the complete graph for `specs` over `scanned` at the live path (A-SPEC-631 primitive). */
|
|
67
|
+
function publishCurrentGraph(root, specs, scanned) {
|
|
68
|
+
// The publication basis: the same five fields rtm_impact expects (A-SPEC-283), so what this
|
|
69
|
+
// publishes is exactly what the next query reuses.
|
|
70
|
+
const basis = {
|
|
71
|
+
graphSchema: RTM_GRAPH_SCHEMA,
|
|
72
|
+
extractorVersion: RTM_EXTRACTOR_VERSION,
|
|
73
|
+
sourceCommit: (0, graph_store_1.headCommitOf)(root, (0, root_1.cleanSubprocessEnv)()) ?? '',
|
|
74
|
+
specFingerprint: (0, graph_store_1.specFingerprint)(specs),
|
|
75
|
+
scanDigest: (0, graph_store_1.scanDigest)(scanned),
|
|
76
|
+
};
|
|
77
|
+
const { nodes, edges } = (0, graph_store_1.publishGraph)(path.join(root, '.ax', 'rtm.sqlite'), basis, (g) => (0, rtm_builder_1.buildRtm)(specs, scanned, g));
|
|
78
|
+
return { basis, nodes, edges };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* @implements A-SPEC-478 — the semantic cache WARMING lives in explicit heavy operations, so the
|
|
82
|
+
* ranking hot path only ever LOOKS UP vectors. Idempotent through the cache. The outcome says
|
|
83
|
+
* whether there was nothing to do (tier none), whether it happened, or whether a configured tier
|
|
84
|
+
* could not embed — a discharge must not record the last one as done.
|
|
85
|
+
*/
|
|
86
|
+
async function warmSemanticCache(root, scanned) {
|
|
87
|
+
const tier = (0, tier_1.resolveSemanticTier)();
|
|
88
|
+
if (tier.tier === 'none')
|
|
89
|
+
return { status: 'not-applicable', tier: 'none' };
|
|
90
|
+
const configured = tier.model ? `${tier.tier}:${tier.model}` : tier.tier;
|
|
91
|
+
const rt = (0, embedder_1.makeTierEmbedder)(tier, new vector_cache_1.VectorCache(root));
|
|
92
|
+
if (rt === null)
|
|
93
|
+
return { status: 'unavailable', tier: configured };
|
|
94
|
+
const docTexts = scanned.map((f) => (f.sourcePath + ' ' + f.symbols.map((sy) => sy.qualifiedName).join(' ')).slice(0, 2000));
|
|
95
|
+
const w = await rt.warm(docTexts);
|
|
96
|
+
if (docTexts.length > 0 && w.computed + w.cached === 0)
|
|
97
|
+
return { status: 'unavailable', tier: rt.label };
|
|
98
|
+
return { status: 'discharged', tier: rt.label, computed: w.computed, cached: w.cached };
|
|
99
|
+
}
|
|
100
|
+
function createGraphOperationsHandlers(context) {
|
|
101
|
+
return {
|
|
102
|
+
async rtm_impact(a) {
|
|
103
|
+
context.assertStoreReachable('rtm_impact', a.root); // @implements A-SPEC-433
|
|
104
|
+
// @implements A-SPEC-614 — do not scan another workspace with this bound spec store.
|
|
105
|
+
const foreign = context.foreignRootReason(a.root);
|
|
106
|
+
if (foreign)
|
|
107
|
+
throw context.refusal(foreign);
|
|
108
|
+
// @implements A-SPEC-189 §14 (round 13) — `context.projectRootOf(a.root)` was called as a bare
|
|
109
|
+
// statement: its only effect was to throw on a bad path, and the advertised contract ("a
|
|
110
|
+
// subdirectory resolves up to it") was dropped on the floor. `taint_scan` one function away
|
|
111
|
+
// honours it, so the same argument answered about two different trees depending on which tool
|
|
112
|
+
// was asked. Bind the derivation and use it.
|
|
113
|
+
const root = context.projectRootOf(a.root);
|
|
114
|
+
const scanned = context.cachedScan(root);
|
|
115
|
+
// @implements A-SPEC-569.2 — the impact/advisory graph is APPROVED-ONLY. A draft needs no
|
|
116
|
+
// approval to exist, and the 0.16.0 adversarial round showed one reaching the agent-visible
|
|
117
|
+
// channels (impacted closure, advisory anchor summaries) — the trust boundary for those
|
|
118
|
+
// channels is the act of approval. NOT filterGoverned: that predicate passes drafts (it only
|
|
119
|
+
// drops outdated/legacy), which is exactly what let this in. Diagnosis (rtm_check) and
|
|
120
|
+
// matching (issue_localize / maintenance_analyze) keep their own populations — the replay
|
|
121
|
+
// pins were measured on them.
|
|
122
|
+
const specs = (await context.listSpecs()).filter((s) => s.status === 'approved');
|
|
123
|
+
// @implements A-SPEC-283
|
|
124
|
+
// Reuse the persisted graph when its basis still holds. Measured: on the warm path the graph
|
|
125
|
+
// build is ~81% of the cost and reopening is ~0ms. `scanDigest` is the field that makes this
|
|
126
|
+
// safe — an uncommitted edit never moves HEAD, so a commit-only basis would happily serve a
|
|
127
|
+
// graph describing content the working tree no longer has.
|
|
128
|
+
const expected = {
|
|
129
|
+
graphSchema: RTM_GRAPH_SCHEMA,
|
|
130
|
+
extractorVersion: RTM_EXTRACTOR_VERSION,
|
|
131
|
+
sourceCommit: (0, graph_store_1.headCommitOf)(root, (0, root_1.cleanSubprocessEnv)()) ?? '',
|
|
132
|
+
specFingerprint: (0, graph_store_1.specFingerprint)(specs),
|
|
133
|
+
scanDigest: (0, graph_store_1.scanDigest)(scanned),
|
|
134
|
+
};
|
|
135
|
+
// @implements A-SPEC-631 — the live file is NEVER cleared in place. `publishable`: reuse it
|
|
136
|
+
// while every basis field holds; otherwise build the complete graph in a private temporary
|
|
137
|
+
// database, answer from it, and publish it by one atomic rename only after the answer stands.
|
|
138
|
+
// Reproduced 2026-09-13: the previous in-place clear+rebuild (this is also the Stop hook's
|
|
139
|
+
// refresh path) let a concurrent approval read zero nodes as "no impact", and a refresher
|
|
140
|
+
// killed mid-way left an empty, basis-less file behind for every later reader.
|
|
141
|
+
const opened = (0, graph_store_1.openReusableGraph)(path.join(root, '.ax', 'rtm.sqlite'), expected, { publishable: true });
|
|
142
|
+
const g = opened.graph;
|
|
143
|
+
let answered = false;
|
|
144
|
+
try {
|
|
145
|
+
if (!opened.reusable) {
|
|
146
|
+
(0, rtm_builder_1.buildRtm)(specs, scanned, g);
|
|
147
|
+
opened.commitBasis();
|
|
148
|
+
}
|
|
149
|
+
// explainImpact, not impactedBy: the bounds and the breadth signal must reach the caller.
|
|
150
|
+
// An impact set is not just a list — a broad one means "review the contract", and a consumer
|
|
151
|
+
// that cannot tell the difference will try to bundle two hundred call sites.
|
|
152
|
+
const { specs: impactedRaw, reachedByDepth, stoppedAt, seedIsHub } = (0, rtm_builder_1.explainImpact)(g, a.changed);
|
|
153
|
+
// @implements A-SPEC-569.2 — the closure walks EDGES, and an implements edge is owned by
|
|
154
|
+
// the code file, so an anchor naming a draft (or a spec nobody wrote) still emits one —
|
|
155
|
+
// deliberately, for rtm_check's dangling diagnosis. The CHANNEL filter is node existence:
|
|
156
|
+
// approved-only specs were given nodes above, so only sealed intent reaches the caller.
|
|
157
|
+
const impacted = impactedRaw.filter((id) => g.hasNode(id));
|
|
158
|
+
// @implements A-SPEC-469 — the graded FILE surface beside the spec closure, same code path
|
|
159
|
+
// as the S-484 measurement (identity, not reimplementation). Seeds are the changed symbols'
|
|
160
|
+
// nodes; the files that own them are excluded — a prediction naming the change itself is
|
|
161
|
+
// vacuously true. The closure fields above stay exactly as they were: mass never enters a
|
|
162
|
+
// gate decision.
|
|
163
|
+
const riSeeds = new Map(a.changed.flatMap((qn) => g.codeNodeIds(qn)).map((id) => [id, 1]));
|
|
164
|
+
const riExclude = new Set([...riSeeds.keys()]
|
|
165
|
+
.map((id) => (id.includes('@') ? id.slice(id.lastIndexOf('@') + 1) : ''))
|
|
166
|
+
.filter((f) => f !== ''));
|
|
167
|
+
const rankedImpact = (0, assoc_arm_1.pprImpactRanked)((0, assoc_arm_1.graphViewOf)(g.dumpCanonical()), riSeeds, riExclude, assoc_arm_1.RANKED_IMPACT_K, assoc_arm_1.RANKED_IMPACT_CONFIG);
|
|
168
|
+
// @implements A-SPEC-568.2 — the intent sentence beside every impacted spec id, same order
|
|
169
|
+
// as `impacted` (which stays a bare id list for its existing consumers). Information only:
|
|
170
|
+
// nothing reads it back into the walk, the ranking or any gate.
|
|
171
|
+
// @implements A-SPEC-569.3 — capped: measured on this repository, uncapped summaries were
|
|
172
|
+
// 94% of a 104,706-byte response (a hub-grade impact of 339 specs). The omission is COUNTED,
|
|
173
|
+
// never silent, and `impacted` itself stays complete — only the annotation is bounded.
|
|
174
|
+
const shownSummaries = impacted.slice(0, SUMMARY_CAP);
|
|
175
|
+
const impactedSummaries = shownSummaries.map((id) => {
|
|
176
|
+
let summary = null;
|
|
177
|
+
try {
|
|
178
|
+
summary = g.summaryOf(id);
|
|
179
|
+
}
|
|
180
|
+
catch { /* summary stays null */ }
|
|
181
|
+
return { id, summary };
|
|
182
|
+
});
|
|
183
|
+
const summariesOmitted = impacted.length - shownSummaries.length;
|
|
184
|
+
answered = true;
|
|
185
|
+
return {
|
|
186
|
+
impacted,
|
|
187
|
+
impactedSummaries,
|
|
188
|
+
...(summariesOmitted > 0 ? { summariesOmitted } : {}),
|
|
189
|
+
rankedImpact,
|
|
190
|
+
reachedByDepth,
|
|
191
|
+
bounded: stoppedAt.length > 0 ? stoppedAt.slice(0, 20) : undefined,
|
|
192
|
+
...(seedIsHub ? {
|
|
193
|
+
breadthWarning: `a changed symbol has ${seedIsHub.callers} callers (hub threshold ${seedIsHub.threshold}) — this is a CONTRACT change; review the contract and its tests rather than bundling every impacted spec`,
|
|
194
|
+
} : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
// Release the native SQLite handle even if build/query throws. A failed answer discards a
|
|
199
|
+
// private rebuild; a successful one publishes it — and a publication failure never
|
|
200
|
+
// overturns the answer, it only means the cache was not refreshed this time.
|
|
201
|
+
if (!answered)
|
|
202
|
+
opened.discard();
|
|
203
|
+
else {
|
|
204
|
+
try {
|
|
205
|
+
opened.publish();
|
|
206
|
+
}
|
|
207
|
+
catch { /* answer stands; live graph unchanged */ }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
async rtm_reindex(a) {
|
|
212
|
+
context.assertStoreReachable('rtm_reindex', a.root); // @implements A-SPEC-419
|
|
213
|
+
// Removal/re-derivation of changedFiles/changedSymbols is not needed
|
|
214
|
+
// here (rtm_reindex works off changes/specs/scanned/bySourcePath
|
|
215
|
+
// directly), but the guard+diff+scan prologue is identical to
|
|
216
|
+
// review_scope/review_prepare — see deriveChangedContext.
|
|
217
|
+
const { root, changes, specs, scanned, changeSource } = await context.deriveChangedContext(a.root, a, 'rtm_reindex');
|
|
218
|
+
const changed = changes.added.length + changes.modified.length + changes.deleted.length + changes.renamed.length;
|
|
219
|
+
// The HashCache records every changed file's content hash (A-SPEC-128 — project-root cache,
|
|
220
|
+
// no subdir leak) so an incremental consumer can skip files whose content did not move. The
|
|
221
|
+
// reindex itself publishes the FULL build below and no longer applies the diff on top of it.
|
|
222
|
+
const cache = new hash_cache_1.HashCache(context.cacheDirFor(root)); // @implements A-SPEC-128
|
|
223
|
+
const record = (files) => {
|
|
224
|
+
for (const relPath of files) {
|
|
225
|
+
let content;
|
|
226
|
+
try {
|
|
227
|
+
content = fs.readFileSync(path.join(root, relPath), 'utf8');
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!cache.unchanged(relPath, content))
|
|
233
|
+
cache.put(relPath, content);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
record(changes.added);
|
|
237
|
+
record(changes.modified);
|
|
238
|
+
// @implements A-SPEC-631 — explicit reindex is a persistence operation: build the COMPLETE
|
|
239
|
+
// graph in a private temporary database, commit the matching basis, close it, and replace
|
|
240
|
+
// the live file by one atomic rename (A-SPEC-589's publication contract, now the same
|
|
241
|
+
// primitive rtm_impact uses). The post-build applyIncremental pass is gone: measured
|
|
242
|
+
// 2026-09-13, its per-file replacement rebuilt symbols but not the changed file's FILE node
|
|
243
|
+
// or imports edge, so the first reindex after a change published 4 nodes / 3 edges where the
|
|
244
|
+
// next run over the same tree published 5 / 4. The full build re-resolves every call edge
|
|
245
|
+
// against the whole scan, which is what A-SPEC-280 required (@implements A-SPEC-280).
|
|
246
|
+
const published = publishCurrentGraph(root, specs, scanned);
|
|
247
|
+
// @implements A-SPEC-478 — warming is best-effort here: reindex's own result is unaffected,
|
|
248
|
+
// and only an actual warm is reported (tier none or an unavailable tier adds no field).
|
|
249
|
+
let semanticWarm;
|
|
250
|
+
try {
|
|
251
|
+
const w = await warmSemanticCache(root, scanned);
|
|
252
|
+
if (w.status === 'discharged')
|
|
253
|
+
semanticWarm = { tier: w.tier, computed: w.computed, cached: w.cached };
|
|
254
|
+
}
|
|
255
|
+
catch { /* warming is best-effort; reindex's own result is unaffected */ }
|
|
256
|
+
return { changed, nodes: published.nodes, edges: published.edges, changeSource,
|
|
257
|
+
...(semanticWarm !== undefined ? { semanticWarm } : {}) };
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Spec } from '../../spec/spec-parser';
|
|
2
|
+
import { ScannedFile } from '../../cpg/cpg-scanner';
|
|
3
|
+
import { DecisionRecord, DecisionCitation } from '../../rtm/rtm-builder';
|
|
4
|
+
import { ContentSource } from '../../context/bundler';
|
|
5
|
+
import { MaintenanceGroundTruth, MaintenanceAnalysis } from '../maintenance-analyze';
|
|
6
|
+
import { Basis } from '../basis';
|
|
7
|
+
export interface MaintenanceAnalysisContext {
|
|
8
|
+
listSpecs(): Promise<Spec[]>;
|
|
9
|
+
foreignRootReason(root?: string): string | null;
|
|
10
|
+
refusal(reason: string): Error;
|
|
11
|
+
projectRootOf(root: string): string;
|
|
12
|
+
basisFor(root: string, withDisk: boolean): Basis;
|
|
13
|
+
collectDecisions(root: string, scanned: readonly ScannedFile[], specs: readonly Spec[]): {
|
|
14
|
+
decisions: DecisionRecord[];
|
|
15
|
+
citations: DecisionCitation[];
|
|
16
|
+
};
|
|
17
|
+
buildContentSource(specs: Spec[], scanned: ScannedFile[]): ContentSource;
|
|
18
|
+
specStoreBlindReason(root: string, count: number): string | null;
|
|
19
|
+
fileDigestOf(root: string, rel: string): string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function createMaintenanceAnalysisHandlers(context: MaintenanceAnalysisContext): {
|
|
22
|
+
maintenance_analyze(a: {
|
|
23
|
+
root: string;
|
|
24
|
+
request: string;
|
|
25
|
+
historyLimit?: number;
|
|
26
|
+
contextBudget?: number;
|
|
27
|
+
groundTruth?: MaintenanceGroundTruth;
|
|
28
|
+
persist?: boolean;
|
|
29
|
+
changedFiles?: string[];
|
|
30
|
+
}): Promise<MaintenanceAnalysis & {
|
|
31
|
+
persistedTo?: string;
|
|
32
|
+
bounded?: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
};
|