@holmes-lab/holmes-kit 0.2.1 → 0.3.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 +55 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/assoc/assoc-arm.d.ts +94 -0
- package/dist/holmes/assoc/assoc-arm.js +187 -0
- package/dist/holmes/assoc/explore.d.ts +21 -0
- package/dist/holmes/assoc/explore.js +160 -0
- package/dist/holmes/assoc/impact-baseline.d.ts +13 -0
- package/dist/holmes/assoc/impact-baseline.js +48 -0
- package/dist/holmes/assoc/ppr.d.ts +64 -0
- package/dist/holmes/assoc/ppr.js +110 -0
- package/dist/holmes/cli/agents.d.ts +24 -11
- package/dist/holmes/cli/agents.js +93 -21
- package/dist/holmes/cli/doctor.d.ts +56 -0
- package/dist/holmes/cli/doctor.js +311 -5
- package/dist/holmes/cli/index.js +13 -1
- package/dist/holmes/cli/semantic-key.d.ts +19 -0
- package/dist/holmes/cli/semantic-key.js +93 -0
- package/dist/holmes/config/config.d.ts +9 -0
- package/dist/holmes/config/config.js +8 -1
- package/dist/holmes/cpg/language-capability.d.ts +65 -0
- package/dist/holmes/cpg/language-capability.js +145 -0
- package/dist/holmes/cpg/language-parser-walk.js +179 -34
- package/dist/holmes/cpg/language-parser.d.ts +1 -1
- package/dist/holmes/governance/ledger-rechain.d.ts +12 -0
- package/dist/holmes/governance/ledger-rechain.js +17 -2
- package/dist/holmes/governance/provenance-ledger.js +21 -0
- package/dist/holmes/guardrail/blind-spots.js +12 -1
- package/dist/holmes/guardrail/impact-gate.d.ts +77 -0
- package/dist/holmes/guardrail/impact-gate.js +263 -0
- package/dist/holmes/guardrail/write-target.d.ts +38 -1
- package/dist/holmes/guardrail/write-target.js +41 -4
- package/dist/holmes/hooks/adapters/antigravity.js +12 -1
- package/dist/holmes/hooks/corrupt-state-run.d.ts +33 -0
- package/dist/holmes/hooks/corrupt-state-run.js +16 -0
- package/dist/holmes/hooks/pre-tool-use.d.ts +26 -0
- package/dist/holmes/hooks/pre-tool-use.js +203 -11
- package/dist/holmes/hooks/stop.d.ts +65 -0
- package/dist/holmes/hooks/stop.js +200 -2
- package/dist/holmes/mcp/elicit-approval.d.ts +32 -2
- package/dist/holmes/mcp/elicit-approval.js +30 -2
- package/dist/holmes/mcp/handlers.d.ts +88 -0
- package/dist/holmes/mcp/handlers.js +636 -35
- package/dist/holmes/mcp/maintenance-analyze.d.ts +435 -0
- package/dist/holmes/mcp/maintenance-analyze.js +994 -0
- package/dist/holmes/mcp/maintenance-evidence.d.ts +140 -0
- package/dist/holmes/mcp/maintenance-evidence.js +253 -0
- package/dist/holmes/mcp/server.js +13 -10
- package/dist/holmes/mcp/tool-schemas.js +71 -0
- package/dist/holmes/project/root.js +3 -1
- package/dist/holmes/review/baseline-arm.d.ts +37 -0
- package/dist/holmes/review/baseline-arm.js +51 -0
- package/dist/holmes/review/captured-stdin-guard.d.ts +8 -0
- package/dist/holmes/review/captured-stdin-guard.js +48 -0
- package/dist/holmes/review/coherence-verify.d.ts +31 -0
- package/dist/holmes/review/coherence-verify.js +144 -0
- package/dist/holmes/review/commit-text.d.ts +50 -0
- package/dist/holmes/review/commit-text.js +76 -0
- package/dist/holmes/review/confidence-calibration.d.ts +39 -0
- package/dist/holmes/review/confidence-calibration.js +39 -0
- package/dist/holmes/review/content-baseline.d.ts +38 -0
- package/dist/holmes/review/content-baseline.js +103 -0
- package/dist/holmes/review/content-verify.d.ts +20 -0
- package/dist/holmes/review/content-verify.js +73 -0
- package/dist/holmes/review/dense-retrieval.d.ts +66 -0
- package/dist/holmes/review/dense-retrieval.js +97 -0
- package/dist/holmes/review/edge-quality.d.ts +44 -0
- package/dist/holmes/review/edge-quality.js +117 -0
- package/dist/holmes/review/evaluation-metrics.d.ts +138 -0
- package/dist/holmes/review/evaluation-metrics.js +175 -0
- package/dist/holmes/review/graph-verifier.d.ts +34 -0
- package/dist/holmes/review/graph-verifier.js +62 -0
- package/dist/holmes/review/hop-ablation.d.ts +100 -0
- package/dist/holmes/review/hop-ablation.js +89 -0
- package/dist/holmes/review/manual-baseline.d.ts +209 -0
- package/dist/holmes/review/manual-baseline.js +2846 -0
- package/dist/holmes/review/oracle-gap.d.ts +32 -0
- package/dist/holmes/review/oracle-gap.js +102 -0
- package/dist/holmes/review/point-in-time-replay.d.ts +41 -0
- package/dist/holmes/review/point-in-time-replay.js +161 -0
- package/dist/holmes/review/rank-diagnosis.d.ts +43 -0
- package/dist/holmes/review/rank-diagnosis.js +163 -0
- package/dist/holmes/review/replay-calibration.d.ts +62 -0
- package/dist/holmes/review/replay-calibration.js +83 -0
- package/dist/holmes/review/replay-corpus.d.ts +135 -0
- package/dist/holmes/review/replay-corpus.js +210 -0
- package/dist/holmes/review/run-replay.d.ts +260 -0
- package/dist/holmes/review/run-replay.js +729 -0
- package/dist/holmes/review/semantic-arm.d.ts +271 -0
- package/dist/holmes/review/semantic-arm.js +717 -0
- package/dist/holmes/review/semantic-retrieval.d.ts +55 -0
- package/dist/holmes/review/semantic-retrieval.js +156 -0
- package/dist/holmes/review/spec-layer-stats.d.ts +38 -0
- package/dist/holmes/review/spec-layer-stats.js +52 -0
- package/dist/holmes/review/temporal-prior.d.ts +33 -0
- package/dist/holmes/review/temporal-prior.js +53 -0
- package/dist/holmes/review/test-runner.d.ts +15 -0
- package/dist/holmes/review/test-runner.js +41 -4
- package/dist/holmes/review/test-selection-breadth.d.ts +75 -0
- package/dist/holmes/review/test-selection-breadth.js +57 -0
- package/dist/holmes/review/traceability-benchmark.d.ts +81 -0
- package/dist/holmes/review/traceability-benchmark.js +135 -0
- package/dist/holmes/review/union-verify.d.ts +12 -0
- package/dist/holmes/review/union-verify.js +70 -0
- package/dist/holmes/rtm/graph-store.d.ts +51 -0
- package/dist/holmes/rtm/graph-store.js +122 -0
- package/dist/holmes/rtm/incremental.d.ts +25 -1
- package/dist/holmes/rtm/incremental.js +18 -1
- package/dist/holmes/rtm/localize.d.ts +28 -0
- package/dist/holmes/rtm/localize.js +272 -14
- package/dist/holmes/rtm/rtm-builder.d.ts +118 -3
- package/dist/holmes/rtm/rtm-builder.js +265 -28
- package/dist/holmes/rtm/rtm-graph.d.ts +117 -2
- package/dist/holmes/rtm/rtm-graph.js +194 -31
- package/dist/holmes/rtm/taint-benchmark.d.ts +97 -0
- package/dist/holmes/rtm/taint-benchmark.js +141 -0
- package/dist/holmes/rtm/test-scope.js +24 -1
- package/dist/holmes/semantic/credentials.d.ts +15 -0
- package/dist/holmes/semantic/credentials.js +134 -0
- package/dist/holmes/semantic/embedder.d.ts +44 -0
- package/dist/holmes/semantic/embedder.js +185 -0
- package/dist/holmes/semantic/hit-rerank.d.ts +4 -0
- package/dist/holmes/semantic/hit-rerank.js +38 -0
- package/dist/holmes/semantic/tier.d.ts +37 -0
- package/dist/holmes/semantic/tier.js +54 -0
- package/dist/holmes/semantic/vector-cache.d.ts +11 -0
- package/dist/holmes/semantic/vector-cache.js +91 -0
- package/dist/holmes/spec/acceptance-quality.d.ts +81 -0
- package/dist/holmes/spec/acceptance-quality.js +169 -0
- package/dist/holmes/spec/validator.js +33 -1
- package/dist/holmes/spec/yaml-scalar.d.ts +1 -0
- package/dist/holmes/spec/yaml-scalar.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
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.resolveSemanticKey = resolveSemanticKey;
|
|
37
|
+
exports.storeSemanticKey = storeSemanticKey;
|
|
38
|
+
exports.removeSemanticKey = removeSemanticKey;
|
|
39
|
+
// @implements A-SPEC-477
|
|
40
|
+
/**
|
|
41
|
+
* The cloud tier's credential: WHERE the consent lives, and in what order it is looked up.
|
|
42
|
+
*
|
|
43
|
+
* Consent model: the key IS the opt-in for egress, so only a human may mint it. The gate blocks
|
|
44
|
+
* an agent assigning the env names (self-granted egress, the HOLMES_ROLE class); this module owns
|
|
45
|
+
* the storage the human's CLI act writes to. A project-tree file is NEVER a source — a tree file
|
|
46
|
+
* is a commit-accident surface and readable by every in-session tool, which is exactly what the
|
|
47
|
+
* owner's ".env 는 임시 조치" call-out named.
|
|
48
|
+
*
|
|
49
|
+
* Resolution chain, the order being the contract:
|
|
50
|
+
* 1. HOLMES_SEMANTIC_API_KEY — dedicated name, CI/headless.
|
|
51
|
+
* 2. GEMINI_API_KEY — ecosystem-compatible name.
|
|
52
|
+
* 3. macOS keychain — `security` via injected exec (absent elsewhere; win32 is a
|
|
53
|
+
* recorded Windows-agent follow-up).
|
|
54
|
+
* 4. ~/.holmes/credentials.json — 0600 under 0700, the everywhere-fallback.
|
|
55
|
+
*
|
|
56
|
+
* The VALUE never appears in argv (ps/history surfaces), logs, reports, or error messages.
|
|
57
|
+
* Keychain writes feed the secret over STDIN (`security -i`) for the same reason.
|
|
58
|
+
*/
|
|
59
|
+
const fs = __importStar(require("node:fs"));
|
|
60
|
+
const path = __importStar(require("node:path"));
|
|
61
|
+
const os = __importStar(require("node:os"));
|
|
62
|
+
const node_child_process_1 = require("node:child_process");
|
|
63
|
+
const SERVICE = 'holmes-kit';
|
|
64
|
+
const ACCOUNT = 'semantic';
|
|
65
|
+
const defaultExec = (cmd, args, stdin) => (0, node_child_process_1.execFileSync)(cmd, args, { encoding: 'utf8', input: stdin, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
66
|
+
const credFile = (home) => path.join(home, '.holmes', 'credentials.json');
|
|
67
|
+
const nonBlank = (v) => typeof v === 'string' && v.trim() !== '' ? v.trim() : null;
|
|
68
|
+
function resolveSemanticKey(opts = {}) {
|
|
69
|
+
const env = opts.env ?? process.env;
|
|
70
|
+
const home = opts.home ?? os.homedir();
|
|
71
|
+
const platform = opts.platform ?? process.platform;
|
|
72
|
+
const exec = opts.exec ?? defaultExec;
|
|
73
|
+
const dedicated = nonBlank(env.HOLMES_SEMANTIC_API_KEY);
|
|
74
|
+
if (dedicated !== null)
|
|
75
|
+
return { key: dedicated, source: 'env' };
|
|
76
|
+
// GOOGLE_API_KEY is Google's own SDK-compatible spelling; both count as the same consent.
|
|
77
|
+
const compat = nonBlank(env.GEMINI_API_KEY) ?? nonBlank(env.GOOGLE_API_KEY);
|
|
78
|
+
if (compat !== null)
|
|
79
|
+
return { key: compat, source: 'env-compat' };
|
|
80
|
+
if (platform === 'darwin') {
|
|
81
|
+
try {
|
|
82
|
+
const out = nonBlank(exec('security', ['find-generic-password', '-s', SERVICE, '-a', ACCOUNT, '-w']));
|
|
83
|
+
if (out !== null)
|
|
84
|
+
return { key: out, source: 'keychain' };
|
|
85
|
+
}
|
|
86
|
+
catch { /* no keychain entry, or no keychain — the chain continues */ }
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(fs.readFileSync(credFile(home), 'utf8'));
|
|
90
|
+
const k = nonBlank(parsed.semantic?.key);
|
|
91
|
+
if (k !== null)
|
|
92
|
+
return { key: k, source: 'file' };
|
|
93
|
+
}
|
|
94
|
+
catch { /* absent or corrupted — a broken store must not take the tier down */ }
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/** Store via the human's CLI act. Returns where it landed. */
|
|
98
|
+
function storeSemanticKey(key, opts = {}) {
|
|
99
|
+
const home = opts.home ?? os.homedir();
|
|
100
|
+
const platform = opts.platform ?? process.platform;
|
|
101
|
+
const exec = opts.exec ?? defaultExec;
|
|
102
|
+
if (platform === 'darwin') {
|
|
103
|
+
try {
|
|
104
|
+
// `security -i` reads commands from stdin, so the secret never rides argv where ps and
|
|
105
|
+
// shell history could see it. -U updates an existing entry in place.
|
|
106
|
+
exec('security', ['-i'], `add-generic-password -U -s ${SERVICE} -a ${ACCOUNT} -w "${key.replace(/"/g, '\\"')}"\n`);
|
|
107
|
+
return 'keychain';
|
|
108
|
+
}
|
|
109
|
+
catch { /* fall through to the file */ }
|
|
110
|
+
}
|
|
111
|
+
const file = credFile(home);
|
|
112
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
113
|
+
fs.chmodSync(path.dirname(file), 0o700);
|
|
114
|
+
const tmp = file + '.tmp';
|
|
115
|
+
fs.writeFileSync(tmp, JSON.stringify({ semantic: { provider: 'gemini', key } }), { mode: 0o600 });
|
|
116
|
+
fs.renameSync(tmp, file);
|
|
117
|
+
fs.chmodSync(file, 0o600);
|
|
118
|
+
return 'file';
|
|
119
|
+
}
|
|
120
|
+
function removeSemanticKey(opts = {}) {
|
|
121
|
+
const home = opts.home ?? os.homedir();
|
|
122
|
+
const platform = opts.platform ?? process.platform;
|
|
123
|
+
const exec = opts.exec ?? defaultExec;
|
|
124
|
+
if (platform === 'darwin') {
|
|
125
|
+
try {
|
|
126
|
+
exec('security', ['delete-generic-password', '-s', SERVICE, '-a', ACCOUNT]);
|
|
127
|
+
}
|
|
128
|
+
catch { /* absent is fine */ }
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
fs.rmSync(credFile(home));
|
|
132
|
+
}
|
|
133
|
+
catch { /* absent is fine */ }
|
|
134
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tier adapters — the productized form of exactly what the measurement scripts proved:
|
|
3
|
+
*
|
|
4
|
+
* local = @xenova/transformers with the FROZEN pooling table (bge-m3 → CLS; mean-pooling it
|
|
5
|
+
* was the S-491 instrument accident), quantized, lazy pipeline.
|
|
6
|
+
* cloud = gemini batchEmbedContents with the MEASURED pacing (≤25 per batch, backoff on 429 —
|
|
7
|
+
* the free-tier and TPM ceilings were both hit live). The key never appears in any
|
|
8
|
+
* log or error string.
|
|
9
|
+
*
|
|
10
|
+
* Both flow through the VectorCache (content hash + model tag), which is what makes warming
|
|
11
|
+
* idempotent, replays offline-reproducible, and cloud model drift a cache miss instead of a
|
|
12
|
+
* silent corruption.
|
|
13
|
+
*
|
|
14
|
+
* Division of labor with the HOT PATH: `warm()` is the only bulk computation and belongs to
|
|
15
|
+
* explicit heavy operations (rtm_reindex). The ranking path only calls `cachedDocVector`
|
|
16
|
+
* (a lookup) and `embedQuery` (one text, awaited by the HANDLER before the sync ranking runs).
|
|
17
|
+
*/
|
|
18
|
+
import { type SemanticTier } from './tier';
|
|
19
|
+
import { VectorCache } from './vector-cache';
|
|
20
|
+
export interface SemanticRuntime {
|
|
21
|
+
label: string;
|
|
22
|
+
/** One text, awaited by the handler BEFORE the sync ranking; null on any failure. */
|
|
23
|
+
embedQuery(text: string): Promise<number[] | null>;
|
|
24
|
+
/** Cache lookup only — the hot path never computes a document embedding. */
|
|
25
|
+
cachedDocVector(text: string): number[] | null;
|
|
26
|
+
/** The bulk path, for explicit heavy operations. Idempotent through the cache. */
|
|
27
|
+
warm(texts: string[]): Promise<{
|
|
28
|
+
computed: number;
|
|
29
|
+
cached: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface EmbedderOpts {
|
|
33
|
+
pipelineLoader?: (model: string) => Promise<(text: string, o: {
|
|
34
|
+
pooling: string;
|
|
35
|
+
normalize: boolean;
|
|
36
|
+
}) => Promise<{
|
|
37
|
+
data: Float32Array;
|
|
38
|
+
}>>;
|
|
39
|
+
fetchImpl?: typeof fetch;
|
|
40
|
+
key?: string;
|
|
41
|
+
sleep?: (ms: number) => Promise<void>;
|
|
42
|
+
log?: (line: string) => void;
|
|
43
|
+
}
|
|
44
|
+
export declare function makeTierEmbedder(tier: SemanticTier, cache: VectorCache, opts?: EmbedderOpts): SemanticRuntime | null;
|
|
@@ -0,0 +1,185 @@
|
|
|
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.makeTierEmbedder = makeTierEmbedder;
|
|
37
|
+
// @implements A-SPEC-478
|
|
38
|
+
/**
|
|
39
|
+
* The tier adapters — the productized form of exactly what the measurement scripts proved:
|
|
40
|
+
*
|
|
41
|
+
* local = @xenova/transformers with the FROZEN pooling table (bge-m3 → CLS; mean-pooling it
|
|
42
|
+
* was the S-491 instrument accident), quantized, lazy pipeline.
|
|
43
|
+
* cloud = gemini batchEmbedContents with the MEASURED pacing (≤25 per batch, backoff on 429 —
|
|
44
|
+
* the free-tier and TPM ceilings were both hit live). The key never appears in any
|
|
45
|
+
* log or error string.
|
|
46
|
+
*
|
|
47
|
+
* Both flow through the VectorCache (content hash + model tag), which is what makes warming
|
|
48
|
+
* idempotent, replays offline-reproducible, and cloud model drift a cache miss instead of a
|
|
49
|
+
* silent corruption.
|
|
50
|
+
*
|
|
51
|
+
* Division of labor with the HOT PATH: `warm()` is the only bulk computation and belongs to
|
|
52
|
+
* explicit heavy operations (rtm_reindex). The ranking path only calls `cachedDocVector`
|
|
53
|
+
* (a lookup) and `embedQuery` (one text, awaited by the HANDLER before the sync ranking runs).
|
|
54
|
+
*/
|
|
55
|
+
const tier_1 = require("./tier");
|
|
56
|
+
const credentials_1 = require("./credentials");
|
|
57
|
+
const CLOUD_BATCH = 25;
|
|
58
|
+
const CLOUD_BACKOFF_MS = 30_000;
|
|
59
|
+
function makeTierEmbedder(tier, cache, opts = {}) {
|
|
60
|
+
if (tier.tier === 'none' || tier.model === undefined)
|
|
61
|
+
return null;
|
|
62
|
+
const model = tier.model;
|
|
63
|
+
if (tier.tier === 'local') {
|
|
64
|
+
const tag = `${model}@q8-${(0, tier_1.POOLING_OF)(model)}`;
|
|
65
|
+
let pipe = null;
|
|
66
|
+
const load = () => {
|
|
67
|
+
if (pipe === null) {
|
|
68
|
+
pipe = (opts.pipelineLoader ?? (async (m) => {
|
|
69
|
+
const specifier = '@xenova/transformers';
|
|
70
|
+
const mod = await Promise.resolve(`${specifier}`).then(s => __importStar(require(s)));
|
|
71
|
+
return mod.pipeline('feature-extraction', m, { quantized: true });
|
|
72
|
+
}))(model);
|
|
73
|
+
}
|
|
74
|
+
return pipe;
|
|
75
|
+
};
|
|
76
|
+
const embed = async (text) => {
|
|
77
|
+
try {
|
|
78
|
+
const p = await load();
|
|
79
|
+
return Array.from((await p(text, { pooling: (0, tier_1.POOLING_OF)(model), normalize: true })).data);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
label: `local:${model}`,
|
|
87
|
+
embedQuery: async (text) => {
|
|
88
|
+
const hit = cache.get(text, tag, 'query');
|
|
89
|
+
if (hit)
|
|
90
|
+
return hit;
|
|
91
|
+
const v = await embed(text);
|
|
92
|
+
// In-memory only: maintenance analysis is READ-ONLY (a pinned contract — a cold project
|
|
93
|
+
// must stay cache-free), so the hot path never flushes. Disk persistence belongs to
|
|
94
|
+
// warm(), which only runs inside explicit write-path operations (rtm_reindex).
|
|
95
|
+
if (v)
|
|
96
|
+
cache.put(text, tag, 'query', v);
|
|
97
|
+
return v;
|
|
98
|
+
},
|
|
99
|
+
cachedDocVector: (text) => cache.get(text, tag, 'doc'),
|
|
100
|
+
warm: async (texts) => {
|
|
101
|
+
let computed = 0, cached = 0;
|
|
102
|
+
for (const t of texts) {
|
|
103
|
+
if (cache.get(t, tag, 'doc')) {
|
|
104
|
+
cached++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const v = await embed(t);
|
|
108
|
+
if (v) {
|
|
109
|
+
cache.put(t, tag, 'doc', v);
|
|
110
|
+
computed++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
cache.flush();
|
|
114
|
+
return { computed, cached };
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// cloud
|
|
119
|
+
const tag = `${model}@v1`;
|
|
120
|
+
const key = opts.key ?? (0, credentials_1.resolveSemanticKey)()?.key ?? null;
|
|
121
|
+
if (key === null)
|
|
122
|
+
return null;
|
|
123
|
+
const f = opts.fetchImpl ?? fetch;
|
|
124
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
125
|
+
const callBatch = async (texts, taskType) => {
|
|
126
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:batchEmbedContents?key=${key}`;
|
|
127
|
+
const body = JSON.stringify({ requests: texts.map((t) => ({ model: `models/${model}`, content: { parts: [{ text: t }] }, taskType })) });
|
|
128
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
129
|
+
try {
|
|
130
|
+
const res = await f(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body });
|
|
131
|
+
if (res.status === 429 || res.status >= 500) {
|
|
132
|
+
opts.log?.(`cloud embed backoff (HTTP ${res.status})`);
|
|
133
|
+
await sleep(CLOUD_BACKOFF_MS);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
opts.log?.(`cloud embed failed (HTTP ${res.status})`);
|
|
138
|
+
return null;
|
|
139
|
+
} // never the key, never the body
|
|
140
|
+
const j = await res.json();
|
|
141
|
+
return j.embeddings.map((e) => e.values);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
opts.log?.('cloud embed network error');
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
opts.log?.('cloud embed rate-limited after retries');
|
|
149
|
+
return null;
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
label: `cloud:${model}`,
|
|
153
|
+
embedQuery: async (text) => {
|
|
154
|
+
const hit = cache.get(text, tag, 'query');
|
|
155
|
+
if (hit)
|
|
156
|
+
return hit;
|
|
157
|
+
const vs = await callBatch([text], 'RETRIEVAL_QUERY');
|
|
158
|
+
if (vs === null)
|
|
159
|
+
return null;
|
|
160
|
+
cache.put(text, tag, 'query', vs[0]); // in-memory only — see the local adapter's note
|
|
161
|
+
return vs[0];
|
|
162
|
+
},
|
|
163
|
+
cachedDocVector: (text) => cache.get(text, tag, 'doc'),
|
|
164
|
+
warm: async (texts) => {
|
|
165
|
+
let computed = 0, cached = 0;
|
|
166
|
+
const missing = [];
|
|
167
|
+
for (const t of texts) {
|
|
168
|
+
if (cache.get(t, tag, 'doc'))
|
|
169
|
+
cached++;
|
|
170
|
+
else
|
|
171
|
+
missing.push(t);
|
|
172
|
+
}
|
|
173
|
+
for (let i = 0; i < missing.length; i += CLOUD_BATCH) {
|
|
174
|
+
const slice = missing.slice(i, i + CLOUD_BATCH);
|
|
175
|
+
const vs = await callBatch(slice, 'RETRIEVAL_DOCUMENT');
|
|
176
|
+
if (vs === null)
|
|
177
|
+
break; // partial warm is fine — the cache keeps what landed
|
|
178
|
+
slice.forEach((t, n) => cache.put(t, tag, 'doc', vs[n]));
|
|
179
|
+
computed += slice.length;
|
|
180
|
+
cache.flush();
|
|
181
|
+
}
|
|
182
|
+
return { computed, cached };
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rerankHitsBySemantic = rerankHitsBySemantic;
|
|
4
|
+
// @implements A-SPEC-496
|
|
5
|
+
/**
|
|
6
|
+
* The uncited semantic head rerank, extracted PURE — exactly the A-SPEC-478 math that ships in
|
|
7
|
+
* maintenance_analyze, so the direct localization path (issue_localize) provably applies the
|
|
8
|
+
* same policy: only hits with a CACHED vector move (stable order otherwise), the set never
|
|
9
|
+
* changes, coverage lands as a why-line, and any missing signal is a complete no-op.
|
|
10
|
+
*
|
|
11
|
+
* The gate (the request cited no spec) stays with the CALLER — it needs the spec store, and
|
|
12
|
+
* keeping it there mirrors how A-SPEC-478 divides the same responsibility.
|
|
13
|
+
*/
|
|
14
|
+
const dense_retrieval_1 = require("../review/dense-retrieval");
|
|
15
|
+
function rerankHitsBySemantic(hits, queryVec, docVecOf, label) {
|
|
16
|
+
if (queryVec === null || hits.length <= 1)
|
|
17
|
+
return;
|
|
18
|
+
const sim = new Map();
|
|
19
|
+
let covered = 0;
|
|
20
|
+
for (const h of hits) {
|
|
21
|
+
const dv = docVecOf(h.file);
|
|
22
|
+
if (dv !== null) {
|
|
23
|
+
sim.set(h.file, (0, dense_retrieval_1.cosine)(queryVec, dv));
|
|
24
|
+
covered++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (covered === 0)
|
|
28
|
+
return;
|
|
29
|
+
hits.sort((a, b) => {
|
|
30
|
+
const sa = sim.get(a.file) ?? -Infinity;
|
|
31
|
+
const sb = sim.get(b.file) ?? -Infinity;
|
|
32
|
+
return sb === sa ? 0 : sb - sa;
|
|
33
|
+
});
|
|
34
|
+
for (const h of hits) {
|
|
35
|
+
if (sim.has(h.file))
|
|
36
|
+
h.why.push(`semantic-rerank(${label}, coverage ${covered}/${hits.length})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The semantic layer's tier structure — the owner's two-lever design made a contract:
|
|
3
|
+
* maximize what the harness can do WITHOUT egress (a local model), and let an explicit opt-in
|
|
4
|
+
* maximize further (a cloud model). The default ships NEITHER: tier 'none', zero egress.
|
|
5
|
+
*
|
|
6
|
+
* The measured parameters this encodes (S-491, four-arm traceability, 305 cases):
|
|
7
|
+
* lexical 0.486 < local bge-m3 (CLS) 0.667 < cloud gemini-embedding-001 0.887 recall,
|
|
8
|
+
* and on the lexical-zero segment (16.4%, growing): 0.000 / 0.517 / 0.920.
|
|
9
|
+
*
|
|
10
|
+
* Consent model: setting GEMINI_API_KEY IS the opt-in. The tier resolver never probes the
|
|
11
|
+
* network; presence signals only, so doctor can call it offline.
|
|
12
|
+
*/
|
|
13
|
+
import { type CredentialOpts, type KeySource } from './credentials';
|
|
14
|
+
/** The local tier's model. CLS-pooled — see POOLING_OF. */
|
|
15
|
+
export declare const LOCAL_MODEL = "Xenova/bge-m3";
|
|
16
|
+
/** The opt-in cloud tier's model (S-491 four-arm winner). */
|
|
17
|
+
export declare const CLOUD_MODEL = "gemini-embedding-001";
|
|
18
|
+
export interface SemanticTier {
|
|
19
|
+
tier: 'cloud' | 'local' | 'none';
|
|
20
|
+
model?: string;
|
|
21
|
+
/** Whether using this tier sends repository-derived text off the machine. */
|
|
22
|
+
egress: boolean;
|
|
23
|
+
/** Where the cloud consent came from — shown by doctor/status, never the value itself. */
|
|
24
|
+
keySource?: KeySource;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Which pooling a model's dense head expects. bge-m3 is CLS-pooled: mean-pooling it was the
|
|
28
|
+
* S-491 instrument accident — a 4.4x quality collapse (0.150 vs 0.667 recall) that read as a
|
|
29
|
+
* verdict until the "a stronger model losing to a weaker one means the instrument is broken"
|
|
30
|
+
* heuristic caught it. The table exists so that lesson is code, not tribal knowledge.
|
|
31
|
+
*/
|
|
32
|
+
export declare function POOLING_OF(model: string): 'cls' | 'mean';
|
|
33
|
+
export declare function resolveSemanticTier(opts?: {
|
|
34
|
+
env?: NodeJS.ProcessEnv;
|
|
35
|
+
hasLocalModule?: boolean;
|
|
36
|
+
credentials?: CredentialOpts;
|
|
37
|
+
}): SemanticTier;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @implements A-SPEC-476
|
|
3
|
+
/**
|
|
4
|
+
* The semantic layer's tier structure — the owner's two-lever design made a contract:
|
|
5
|
+
* maximize what the harness can do WITHOUT egress (a local model), and let an explicit opt-in
|
|
6
|
+
* maximize further (a cloud model). The default ships NEITHER: tier 'none', zero egress.
|
|
7
|
+
*
|
|
8
|
+
* The measured parameters this encodes (S-491, four-arm traceability, 305 cases):
|
|
9
|
+
* lexical 0.486 < local bge-m3 (CLS) 0.667 < cloud gemini-embedding-001 0.887 recall,
|
|
10
|
+
* and on the lexical-zero segment (16.4%, growing): 0.000 / 0.517 / 0.920.
|
|
11
|
+
*
|
|
12
|
+
* Consent model: setting GEMINI_API_KEY IS the opt-in. The tier resolver never probes the
|
|
13
|
+
* network; presence signals only, so doctor can call it offline.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CLOUD_MODEL = exports.LOCAL_MODEL = void 0;
|
|
17
|
+
exports.POOLING_OF = POOLING_OF;
|
|
18
|
+
exports.resolveSemanticTier = resolveSemanticTier;
|
|
19
|
+
const credentials_1 = require("./credentials");
|
|
20
|
+
/** The local tier's model. CLS-pooled — see POOLING_OF. */
|
|
21
|
+
exports.LOCAL_MODEL = 'Xenova/bge-m3';
|
|
22
|
+
/** The opt-in cloud tier's model (S-491 four-arm winner). */
|
|
23
|
+
exports.CLOUD_MODEL = 'gemini-embedding-001';
|
|
24
|
+
/**
|
|
25
|
+
* Which pooling a model's dense head expects. bge-m3 is CLS-pooled: mean-pooling it was the
|
|
26
|
+
* S-491 instrument accident — a 4.4x quality collapse (0.150 vs 0.667 recall) that read as a
|
|
27
|
+
* verdict until the "a stronger model losing to a weaker one means the instrument is broken"
|
|
28
|
+
* heuristic caught it. The table exists so that lesson is code, not tribal knowledge.
|
|
29
|
+
*/
|
|
30
|
+
function POOLING_OF(model) {
|
|
31
|
+
return model.includes('bge-m3') ? 'cls' : 'mean';
|
|
32
|
+
}
|
|
33
|
+
function localModulePresent() {
|
|
34
|
+
try {
|
|
35
|
+
// Same optional-dependency discipline as dense-retrieval: resolved, never imported here.
|
|
36
|
+
require.resolve('@xenova/transformers');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function resolveSemanticTier(opts = {}) {
|
|
44
|
+
// @implements A-SPEC-477 — the cloud consent is resolved through the credential CHAIN
|
|
45
|
+
// (dedicated env → compat env → keychain → 0600 user file), never a project-tree file.
|
|
46
|
+
const found = (0, credentials_1.resolveSemanticKey)({ env: opts.env ?? process.env, ...opts.credentials });
|
|
47
|
+
if (found !== null) {
|
|
48
|
+
return { tier: 'cloud', model: exports.CLOUD_MODEL, egress: true, keySource: found.source };
|
|
49
|
+
}
|
|
50
|
+
const hasLocal = opts.hasLocalModule ?? localModulePresent();
|
|
51
|
+
if (hasLocal)
|
|
52
|
+
return { tier: 'local', model: exports.LOCAL_MODEL, egress: false };
|
|
53
|
+
return { tier: 'none', egress: false };
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class VectorCache {
|
|
2
|
+
private readonly file;
|
|
3
|
+
private store;
|
|
4
|
+
constructor(root: string);
|
|
5
|
+
private load;
|
|
6
|
+
private key;
|
|
7
|
+
get(text: string, modelTag: string, kind: 'query' | 'doc'): number[] | null;
|
|
8
|
+
put(text: string, modelTag: string, kind: 'query' | 'doc', vector: number[]): void;
|
|
9
|
+
/** Atomic persist: tmp + rename, so a crash mid-write cannot corrupt the previous cache. */
|
|
10
|
+
flush(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
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.VectorCache = void 0;
|
|
37
|
+
// @implements A-SPEC-476
|
|
38
|
+
/**
|
|
39
|
+
* Content-addressed vector cache: never compute the same embedding twice, never serve a stale
|
|
40
|
+
* vector across a model update.
|
|
41
|
+
*
|
|
42
|
+
* The key is `hash(text) : modelTag : kind`. The model TAG (id plus a caller-supplied version
|
|
43
|
+
* label) is load-bearing for the cloud tier — an API model can be re-deployed silently, and
|
|
44
|
+
* "same text, different vector" across that boundary would corrupt every similarity the product
|
|
45
|
+
* computes. A tag change is a cache miss by construction.
|
|
46
|
+
*
|
|
47
|
+
* The file lives under `.ax/cpg_cache/` — an existing protected directory, so no gate surface
|
|
48
|
+
* moves. A cache must be LOSABLE: a corrupted file restarts empty instead of throwing (measured
|
|
49
|
+
* economics: 37MB for a three-corpus measurement day; recomputing is cost, not damage).
|
|
50
|
+
*/
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
53
|
+
const node_crypto_1 = require("node:crypto");
|
|
54
|
+
const FILE = path.join('.ax', 'cpg_cache', 'semantic-vectors.json');
|
|
55
|
+
class VectorCache {
|
|
56
|
+
file;
|
|
57
|
+
store = null;
|
|
58
|
+
constructor(root) {
|
|
59
|
+
this.file = path.join(root, FILE);
|
|
60
|
+
}
|
|
61
|
+
load() {
|
|
62
|
+
if (this.store !== null)
|
|
63
|
+
return this.store;
|
|
64
|
+
try {
|
|
65
|
+
this.store = JSON.parse(fs.readFileSync(this.file, 'utf8'));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
this.store = {};
|
|
69
|
+
}
|
|
70
|
+
return this.store;
|
|
71
|
+
}
|
|
72
|
+
key(text, modelTag, kind) {
|
|
73
|
+
return `${(0, node_crypto_1.createHash)('sha256').update(text).digest('hex').slice(0, 24)}:${modelTag}:${kind}`;
|
|
74
|
+
}
|
|
75
|
+
get(text, modelTag, kind) {
|
|
76
|
+
return this.load()[this.key(text, modelTag, kind)] ?? null;
|
|
77
|
+
}
|
|
78
|
+
put(text, modelTag, kind, vector) {
|
|
79
|
+
this.load()[this.key(text, modelTag, kind)] = vector;
|
|
80
|
+
}
|
|
81
|
+
/** Atomic persist: tmp + rename, so a crash mid-write cannot corrupt the previous cache. */
|
|
82
|
+
flush() {
|
|
83
|
+
if (this.store === null)
|
|
84
|
+
return;
|
|
85
|
+
const tmp = this.file + '.tmp';
|
|
86
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
87
|
+
fs.writeFileSync(tmp, JSON.stringify(this.store));
|
|
88
|
+
fs.renameSync(tmp, this.file);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.VectorCache = VectorCache;
|