@holmes-lab/holmes-kit 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Whether the semantic layer is doing anything at all.
3
+ *
4
+ * Measured on this repository 2026-09-19: the resolved tier was `cloud`, and of 602 scanned files
5
+ * ZERO had a cached document vector under that model — while 327 sat in the same cache under
6
+ * `bge-m3`, a model this project no longer resolves to. Every `cachedDocVector` returned null, so
7
+ * `rerankHitsBySemantic` reordered nothing and `semanticAlternates` emitted nothing. Neither said
8
+ * so, and `doctor` printed `PASS semantic tier` throughout — a statement about a key and a model
9
+ * name, not about whether one lookup would succeed.
10
+ *
11
+ * An inert layer and a layer that examined every candidate and agreed with the lexical ordering
12
+ * produce byte-identical output. That is the defect this module removes: it makes zero say zero.
13
+ *
14
+ * OBSERVATION ONLY. Nothing here reads into a ranking, a selection or any verdict.
15
+ */
16
+ /** The shape the scanner returns, narrowed to what the document text needs. */
17
+ export interface ScannedLike {
18
+ sourcePath: string;
19
+ symbols: ReadonlyArray<{
20
+ qualifiedName: string;
21
+ }>;
22
+ }
23
+ export type CoverageState = 'covered' | 'inert' | 'not-adopted' | 'unknown';
24
+ export interface CoverageInput {
25
+ /** The RESOLVED tier. `none` is the shipped default (zero egress) and means never opted in. */
26
+ tier: string;
27
+ /** The resolved model's cache tag. null when it cannot be determined. */
28
+ modelTag: string | null;
29
+ total: number;
30
+ covered: number;
31
+ }
32
+ export interface VectorCoverage {
33
+ state: CoverageState;
34
+ covered: number;
35
+ total: number;
36
+ /** null whenever a ratio would be invented — an unjudgeable count or an empty scan. */
37
+ ratio: number | null;
38
+ modelTag: string | null;
39
+ }
40
+ /** The maximum document text length; part of the cache key, so it is part of the contract. */
41
+ export declare const DOC_TEXT_LIMIT = 2000;
42
+ /**
43
+ * THE definition of the text whose hash keys a document vector.
44
+ *
45
+ * It existed as an inline expression in five places, four of them in `maintenance-analyze.ts` and
46
+ * one in `issue_localize`'s rerank — two of which are the surfaces this module's coverage claims to
47
+ * describe. A separate copy here would be free to drift, and a drifted instrument reports a
48
+ * coverage the product does not have. One definition, or the build fails.
49
+ */
50
+ export declare function docTextOf(file: ScannedLike): string;
51
+ /** The same text, indexed by source path — the form the three ranking passes consume. */
52
+ export declare function docTextIndex(scanned: ReadonlyArray<ScannedLike>): Map<string, string>;
53
+ /**
54
+ * Pure: the caller supplies the counts. Keeping the filesystem out means the judgement is testable
55
+ * without a cache on disk, and it cannot be wrong about ordering or locking.
56
+ */
57
+ export declare function vectorCoverage(input: CoverageInput): VectorCoverage;
58
+ /** Empty when there is nothing to say. Never silent when there is. */
59
+ export declare function coverageLine(v: VectorCoverage): string;
60
+ /**
61
+ * How many scanned files have a cached document vector, judged by a lookup the CALLER supplies.
62
+ *
63
+ * The lookup is injected so this module stays free of the cache's I/O and locking, and so the
64
+ * count is taken through the same public accessor the ranking surfaces use — a private key
65
+ * recomputed here could agree with the cache today and drift from it tomorrow.
66
+ */
67
+ export declare function countCovered(scanned: ReadonlyArray<ScannedLike>, isCached: (docText: string) => boolean): number;
68
+ export type WarmReason = 'warmed' | 'suppressed' | 'not-adopted';
69
+ export interface PersistedCoverage {
70
+ coverage: VectorCoverage;
71
+ reason: WarmReason;
72
+ at: string;
73
+ stored?: number;
74
+ head?: string;
75
+ }
76
+ export declare const COVERAGE_STATE_FILE = "semantic-coverage.json";
77
+ /** Best-effort: a verdict that cannot be written is not worth failing a refresh over. */
78
+ export declare function writeCoverageState(root: string, p: PersistedCoverage): void;
79
+ /** null means NOT RUN — missing, unreadable, or not shaped like a verdict. Never throws. */
80
+ export declare function readCoverageState(root: string): PersistedCoverage | null;
81
+ /**
82
+ * The turn-boundary line. Empty when there is nothing to say; NEVER empty when there is.
83
+ *
84
+ * `null` is the case this exists for. A workspace whose refresh never ran looks exactly like a
85
+ * healthy one if absence is allowed to be silent, and that silence is what hid zero coverage for
86
+ * twelve days.
87
+ */
88
+ export declare function coverageStopLine(p: PersistedCoverage | null, currentHead?: string): string;
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ // @implements A-SPEC-682
3
+ /**
4
+ * Whether the semantic layer is doing anything at all.
5
+ *
6
+ * Measured on this repository 2026-09-19: the resolved tier was `cloud`, and of 602 scanned files
7
+ * ZERO had a cached document vector under that model — while 327 sat in the same cache under
8
+ * `bge-m3`, a model this project no longer resolves to. Every `cachedDocVector` returned null, so
9
+ * `rerankHitsBySemantic` reordered nothing and `semanticAlternates` emitted nothing. Neither said
10
+ * so, and `doctor` printed `PASS semantic tier` throughout — a statement about a key and a model
11
+ * name, not about whether one lookup would succeed.
12
+ *
13
+ * An inert layer and a layer that examined every candidate and agreed with the lexical ordering
14
+ * produce byte-identical output. That is the defect this module removes: it makes zero say zero.
15
+ *
16
+ * OBSERVATION ONLY. Nothing here reads into a ranking, a selection or any verdict.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.COVERAGE_STATE_FILE = exports.DOC_TEXT_LIMIT = void 0;
53
+ exports.docTextOf = docTextOf;
54
+ exports.docTextIndex = docTextIndex;
55
+ exports.vectorCoverage = vectorCoverage;
56
+ exports.coverageLine = coverageLine;
57
+ exports.countCovered = countCovered;
58
+ exports.writeCoverageState = writeCoverageState;
59
+ exports.readCoverageState = readCoverageState;
60
+ exports.coverageStopLine = coverageStopLine;
61
+ const fs = __importStar(require("node:fs"));
62
+ const path = __importStar(require("node:path"));
63
+ /** The maximum document text length; part of the cache key, so it is part of the contract. */
64
+ exports.DOC_TEXT_LIMIT = 2000;
65
+ /**
66
+ * THE definition of the text whose hash keys a document vector.
67
+ *
68
+ * It existed as an inline expression in five places, four of them in `maintenance-analyze.ts` and
69
+ * one in `issue_localize`'s rerank — two of which are the surfaces this module's coverage claims to
70
+ * describe. A separate copy here would be free to drift, and a drifted instrument reports a
71
+ * coverage the product does not have. One definition, or the build fails.
72
+ */
73
+ function docTextOf(file) {
74
+ return (file.sourcePath + ' ' + file.symbols.map((sy) => sy.qualifiedName).join(' ')).slice(0, exports.DOC_TEXT_LIMIT);
75
+ }
76
+ /** The same text, indexed by source path — the form the three ranking passes consume. */
77
+ function docTextIndex(scanned) {
78
+ return new Map(scanned.map((f) => [f.sourcePath, docTextOf(f)]));
79
+ }
80
+ const isCount = (n) => typeof n === 'number' && Number.isFinite(n) && n >= 0;
81
+ /**
82
+ * Pure: the caller supplies the counts. Keeping the filesystem out means the judgement is testable
83
+ * without a cache on disk, and it cannot be wrong about ordering or locking.
84
+ */
85
+ function vectorCoverage(input) {
86
+ const total = isCount(input.total) ? Math.floor(input.total) : 0;
87
+ const covered = isCount(input.covered) ? Math.min(Math.floor(input.covered), total) : 0;
88
+ const modelTag = typeof input.modelTag === 'string' && input.modelTag !== '' ? input.modelTag : null;
89
+ const base = { covered, total, modelTag };
90
+ // A project at the shipped default never opted in. It is not failing at something it declined.
91
+ if (input.tier === 'none' || input.tier === '')
92
+ return { state: 'not-adopted', ratio: null, ...base };
93
+ // No model tag means no lookup key: the count could not be taken. Saying `covered: 0` here would
94
+ // report an inert layer that may well be working — the two must not share a shape.
95
+ if (modelTag === null)
96
+ return { state: 'unknown', ratio: null, ...base };
97
+ // Nothing was scanned, so nothing was missed. An empty scan is evidence about the scan, not the
98
+ // cache, and a ratio over zero files would be invented.
99
+ if (total === 0)
100
+ return { state: 'unknown', ratio: null, ...base };
101
+ if (covered === 0)
102
+ return { state: 'inert', ratio: 0, ...base };
103
+ return { state: 'covered', ratio: covered / total, ...base };
104
+ }
105
+ /** Empty when there is nothing to say. Never silent when there is. */
106
+ function coverageLine(v) {
107
+ if (v.state === 'not-adopted')
108
+ return '';
109
+ if (v.state === 'unknown') {
110
+ // An empty scan has nothing to report; an unjudgeable count with files present does.
111
+ if (v.total === 0)
112
+ return '';
113
+ return 'document-vector coverage could not be judged — the resolved model has no cache tag, '
114
+ + 'so whether a lookup would succeed is unknown';
115
+ }
116
+ if (v.state === 'inert') {
117
+ return `the semantic layer is INERT: 0 of ${v.total} scanned files have a cached document vector `
118
+ + `under ${v.modelTag ?? 'the resolved model'} — every lookup returns nothing, so reranking `
119
+ + 'reorders nothing and alternates emit nothing. This reads identically to agreement.';
120
+ }
121
+ const pct = v.ratio === null ? '' : ` (${(v.ratio * 100).toFixed(1)}%)`;
122
+ return `document-vector coverage ${v.covered} of ${v.total}${pct} under ${v.modelTag ?? 'the resolved model'}`;
123
+ }
124
+ /**
125
+ * How many scanned files have a cached document vector, judged by a lookup the CALLER supplies.
126
+ *
127
+ * The lookup is injected so this module stays free of the cache's I/O and locking, and so the
128
+ * count is taken through the same public accessor the ranking surfaces use — a private key
129
+ * recomputed here could agree with the cache today and drift from it tomorrow.
130
+ */
131
+ function countCovered(scanned, isCached) {
132
+ let n = 0;
133
+ for (const f of scanned) {
134
+ if (isCached(docTextOf(f)))
135
+ n++;
136
+ }
137
+ return n;
138
+ }
139
+ exports.COVERAGE_STATE_FILE = 'semantic-coverage.json';
140
+ const statePath = (root) => path.join(root, '.ax', 'state', exports.COVERAGE_STATE_FILE);
141
+ /** Best-effort: a verdict that cannot be written is not worth failing a refresh over. */
142
+ function writeCoverageState(root, p) {
143
+ try {
144
+ const file = statePath(root);
145
+ fs.mkdirSync(path.dirname(file), { recursive: true });
146
+ fs.writeFileSync(file, JSON.stringify(p, null, 2) + '\n');
147
+ }
148
+ catch { /* the refresh is fail-soft; so is recording it */ }
149
+ }
150
+ /** null means NOT RUN — missing, unreadable, or not shaped like a verdict. Never throws. */
151
+ function readCoverageState(root) {
152
+ let parsed;
153
+ try {
154
+ parsed = JSON.parse(fs.readFileSync(statePath(root), 'utf8'));
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ if (typeof parsed !== 'object' || parsed === null)
160
+ return null;
161
+ const o = parsed;
162
+ const c = o.coverage;
163
+ if (typeof c !== 'object' || c === null)
164
+ return null;
165
+ if (typeof c.state !== 'string' || typeof c.covered !== 'number' || typeof c.total !== 'number')
166
+ return null;
167
+ if (o.reason !== 'warmed' && o.reason !== 'suppressed' && o.reason !== 'not-adopted')
168
+ return null;
169
+ return { coverage: c, reason: o.reason, at: typeof o.at === 'string' ? o.at : '',
170
+ ...(typeof o.stored === 'number' ? { stored: o.stored } : {}),
171
+ ...(typeof o.head === 'string' && o.head !== '' ? { head: o.head } : {}) };
172
+ }
173
+ /**
174
+ * The turn-boundary line. Empty when there is nothing to say; NEVER empty when there is.
175
+ *
176
+ * `null` is the case this exists for. A workspace whose refresh never ran looks exactly like a
177
+ * healthy one if absence is allowed to be silent, and that silence is what hid zero coverage for
178
+ * twelve days.
179
+ */
180
+ function coverageStopLine(p, currentHead) {
181
+ if (p === null) {
182
+ return 'semantic vectors: not run — no refresh has recorded a coverage verdict for this '
183
+ + 'workspace, so whether the semantic layer contributes anything is unknown';
184
+ }
185
+ if (p.reason === 'not-adopted' || p.coverage.state === 'not-adopted')
186
+ return '';
187
+ // The verdict is machine-local; the CODE it describes arrives from other machines through Git.
188
+ // A pull brings in symbols this verdict never saw, and the document text those symbols key is
189
+ // what every vector lookup hashes — so a verdict from another commit describes a tree that no
190
+ // longer exists. Report it as needing re-measurement: neither healthy nor broken. Absence of a
191
+ // head on either side is not a mismatch; it is simply unjudgeable, and silence is correct there
192
+ // because the other states already speak for themselves.
193
+ if (typeof p.head === 'string' && p.head !== '' && typeof currentHead === 'string'
194
+ && currentHead !== '' && p.head !== currentHead) {
195
+ return `semantic vectors: the coverage verdict was recorded at another commit (${p.head.slice(0, 7)}, `
196
+ + `now ${currentHead.slice(0, 7)}) — work merged since then changes the symbols the vectors key on, `
197
+ + 'so coverage needs re-measuring';
198
+ }
199
+ if (p.reason === 'suppressed') {
200
+ // An owner who turned the transfer off must not be shown a fault. Report the number, name the
201
+ // switch, and say nothing that reads as breakage.
202
+ return `semantic vectors: automatic warming is switched off (HOLMES_NO_SEMANTIC_WARM); `
203
+ + `coverage stands at ${p.coverage.covered} of ${p.coverage.total} — run rtm_reindex to warm on demand`;
204
+ }
205
+ const line = coverageLine(p.coverage);
206
+ if (p.coverage.state === 'covered' && p.stored !== undefined && p.stored < p.coverage.covered) {
207
+ return `${line} — ${p.coverage.covered - p.stored} vector(s) were computed but never stored`;
208
+ }
209
+ return p.coverage.state === 'covered' ? '' : line;
210
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "@implements A-SPEC-209",
3
3
  "name": "@holmes-lab/holmes-kit",
4
- "version": "0.24.0",
4
+ "version": "0.25.0",
5
5
  "description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
6
6
  "main": "dist/holmes/mcp/server.js",
7
7
  "types": "dist/holmes/mcp/server.d.ts",