homegraph 1.6.0 → 1.6.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 +9 -0
- package/README.md +64 -14
- package/dist/db/queries.d.ts +4 -0
- package/dist/db/queries.js +15 -0
- package/dist/graph/evidence-paths.d.ts +2 -0
- package/dist/graph/evidence-paths.js +20 -3
- package/dist/mcp/arkts-evidence-packs.d.ts +17 -0
- package/dist/mcp/arkts-evidence-packs.js +218 -10
- package/dist/mcp/evidence-audit.d.ts +3 -0
- package/dist/mcp/evidence-audit.js +40 -0
- package/dist/mcp/implementation-context.d.ts +35 -0
- package/dist/mcp/implementation-context.js +232 -0
- package/dist/mcp/query-cache.d.ts +1 -15
- package/dist/mcp/query-cache.js +20 -11
- package/dist/mcp/request-evidence.d.ts +69 -0
- package/dist/mcp/request-evidence.js +297 -0
- package/dist/mcp/server-instructions.d.ts +2 -2
- package/dist/mcp/server-instructions.js +8 -8
- package/dist/mcp/tools.d.ts +18 -10
- package/dist/mcp/tools.js +129 -72
- package/dist/search/literal-evidence.js +11 -3
- package/dist/search/query-plan-provider.js +13 -5
- package/dist/search/query-plan.d.ts +3 -1
- package/dist/search/query-plan.js +11 -4
- package/dist/search/request-contract.d.ts +29 -0
- package/dist/search/request-contract.js +76 -0
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordEvidencePack = recordEvidencePack;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
/** Opt-in local evidence only, never an answer cache. Logging cannot break retrieval. */
|
|
8
|
+
function recordEvidencePack(projectRoot, input, output) {
|
|
9
|
+
if (process.env.HOMEGRAPH_ARKTS_EVIDENCE_LOG !== '1')
|
|
10
|
+
return 'disabled';
|
|
11
|
+
let fd;
|
|
12
|
+
try {
|
|
13
|
+
const target = (0, utils_1.validatePathWithinRoot)(projectRoot, '.homegraph/evidence-packs.jsonl');
|
|
14
|
+
if (!target)
|
|
15
|
+
return 'unavailable';
|
|
16
|
+
const record = Buffer.from(JSON.stringify({ id: (0, node_crypto_1.randomUUID)(), time: new Date().toISOString(),
|
|
17
|
+
stage: 'arkts_pack_before_host_wrapping', version: 1, input, output }) + '\n');
|
|
18
|
+
fd = (0, node_fs_1.openSync)(target, node_fs_1.constants.O_WRONLY | node_fs_1.constants.O_CREAT | node_fs_1.constants.O_APPEND | node_fs_1.constants.O_NONBLOCK | (node_fs_1.constants.O_NOFOLLOW ?? 0), 0o600);
|
|
19
|
+
if (!(0, node_fs_1.fstatSync)(fd).isFile())
|
|
20
|
+
return 'unavailable';
|
|
21
|
+
if ((0, node_fs_1.fstatSync)(fd).size + record.length > 16 * 1024 * 1024)
|
|
22
|
+
return 'limit';
|
|
23
|
+
let at = 0;
|
|
24
|
+
while (at < record.length) {
|
|
25
|
+
const written = (0, node_fs_1.writeSync)(fd, record, at);
|
|
26
|
+
if (!written)
|
|
27
|
+
return 'unavailable';
|
|
28
|
+
at += written;
|
|
29
|
+
}
|
|
30
|
+
return 'written';
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return 'unavailable';
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
if (fd !== undefined)
|
|
37
|
+
(0, node_fs_1.closeSync)(fd);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=evidence-audit.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Edge, Node } from '../types';
|
|
2
|
+
export declare const implementationContextEnabled: () => boolean;
|
|
3
|
+
export declare const controlEvidenceEnabled: () => boolean;
|
|
4
|
+
export interface ImplementationGraph {
|
|
5
|
+
getNode(id: string): Node | null | undefined;
|
|
6
|
+
getNodesInFile(file: string): Node[];
|
|
7
|
+
getOutgoingEdges(id: string): Edge[];
|
|
8
|
+
getIncomingEdges(id: string): Edge[];
|
|
9
|
+
}
|
|
10
|
+
export interface ImplementationGroup {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
nodes: Node[];
|
|
14
|
+
edges: Edge[];
|
|
15
|
+
notes: string[];
|
|
16
|
+
missing: string[];
|
|
17
|
+
sdk?: {
|
|
18
|
+
module: string;
|
|
19
|
+
version: string;
|
|
20
|
+
nodes: Node[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface SdkModule {
|
|
24
|
+
version: string;
|
|
25
|
+
nodes: Node[];
|
|
26
|
+
}
|
|
27
|
+
/** No fuzzy symbol lookup: only stored edges and the target file of an actual import.
|
|
28
|
+
* Source must be hash-verified by the caller before its relationships are expanded.
|
|
29
|
+
* Limits bound work independently of repository size; gaps survive budget selection.
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectImplementationContext(graph: ImplementationGraph, seeds: Node[], source: (node: Node) => string | undefined, sdkModule?: (module: string) => SdkModule | undefined, moduleBinding?: (importer: string, module: string) => {
|
|
32
|
+
relativePath: string;
|
|
33
|
+
witnesses: Node[];
|
|
34
|
+
} | undefined): ImplementationGroup[];
|
|
35
|
+
//# sourceMappingURL=implementation-context.d.ts.map
|
|
@@ -0,0 +1,232 @@
|
|
|
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.controlEvidenceEnabled = exports.implementationContextEnabled = void 0;
|
|
37
|
+
exports.collectImplementationContext = collectImplementationContext;
|
|
38
|
+
const evidence_paths_1 = require("../graph/evidence-paths");
|
|
39
|
+
const request_evidence_1 = require("./request-evidence");
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const implementationContextEnabled = () => process.env.HOMEGRAPH_ARKTS_IMPLEMENTATION_CONTEXT !== '0';
|
|
42
|
+
exports.implementationContextEnabled = implementationContextEnabled;
|
|
43
|
+
const controlEvidenceEnabled = () => process.env.HOMEGRAPH_ARKTS_CONTROL_EVIDENCE !== '0';
|
|
44
|
+
exports.controlEvidenceEnabled = controlEvidenceEnabled;
|
|
45
|
+
const DECLARATIONS = new Set(['class', 'struct', 'component', 'interface', 'function', 'method', 'field', 'property', 'type_alias', 'enum', 'variable', 'constant']);
|
|
46
|
+
const TYPES = new Set(['type_of', 'returns', 'instantiates']);
|
|
47
|
+
const at = (n) => `${n.filePath}:${n.startLine}`;
|
|
48
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
49
|
+
const uses = (code, name) => /^[\w$]+$/.test(name) && new RegExp(`(?<![\\w$])${esc(name)}(?![\\w$])`).test(code);
|
|
50
|
+
/** No fuzzy symbol lookup: only stored edges and the target file of an actual import.
|
|
51
|
+
* Source must be hash-verified by the caller before its relationships are expanded.
|
|
52
|
+
* Limits bound work independently of repository size; gaps survive budget selection.
|
|
53
|
+
*/
|
|
54
|
+
function collectImplementationContext(graph, seeds, source, sdkModule, moduleBinding) {
|
|
55
|
+
const groups = [];
|
|
56
|
+
const files = new Map();
|
|
57
|
+
const sdkCache = new Map();
|
|
58
|
+
const inFile = (file) => {
|
|
59
|
+
if (!files.has(file))
|
|
60
|
+
files.set(file, graph.getNodesInFile(file));
|
|
61
|
+
return files.get(file);
|
|
62
|
+
};
|
|
63
|
+
// Import edges may be absent for ArkAnalyzer type-only imports. Resolve the exact
|
|
64
|
+
// relative module path, including barrel exports, without searching other modules.
|
|
65
|
+
let moduleReads = 0;
|
|
66
|
+
const relativeDefinition = (importer, module, name, depth = 0) => {
|
|
67
|
+
if (!module.startsWith('.') || depth > 2 || moduleReads >= 12)
|
|
68
|
+
return [];
|
|
69
|
+
const base = path.posix.normalize(path.posix.join(path.posix.dirname(importer), module));
|
|
70
|
+
if (base === '..' || base.startsWith('../') || path.posix.isAbsolute(base))
|
|
71
|
+
return [];
|
|
72
|
+
const paths = /\.(?:ets|ts)$/.test(base) ? [base] : [base + '.ets', base + '.ts', `${base}/Index.ets`, `${base}/index.ets`, `${base}/index.ts`];
|
|
73
|
+
const available = paths.map(file => ({ file, nodes: inFile(file) })).filter(x => x.nodes.length);
|
|
74
|
+
if (available.length !== 1)
|
|
75
|
+
return [];
|
|
76
|
+
moduleReads++;
|
|
77
|
+
const { file, nodes } = available[0];
|
|
78
|
+
const matches = [...new Map(nodes.filter(n => DECLARATIONS.has(n.kind) && n.name === name)
|
|
79
|
+
.map(n => [`${n.filePath}:${n.startLine}:${n.name}`, n])).values()];
|
|
80
|
+
if (matches.length === 1) {
|
|
81
|
+
const body = source(matches[0]);
|
|
82
|
+
if (body && new RegExp(`\\bexport\\s+(?:declare\\s+)?(?:class|struct|interface|enum|type|function|const|let|var)\\s+${esc(name)}\\b`).test((0, request_evidence_1.lexEvidenceSource)(body).code))
|
|
83
|
+
return [[matches[0]]];
|
|
84
|
+
}
|
|
85
|
+
const fileNode = { ...nodes[0], id: `context-file:${file}`, name: file, qualifiedName: file, kind: 'file', startLine: 1, endLine: 1 };
|
|
86
|
+
const body = source(fileNode);
|
|
87
|
+
if (!body)
|
|
88
|
+
return [];
|
|
89
|
+
const out = [];
|
|
90
|
+
const exportCode = (0, request_evidence_1.lexEvidenceSource)(body);
|
|
91
|
+
if (!exportCode.valid)
|
|
92
|
+
return [];
|
|
93
|
+
for (const m of body.matchAll(/\bexport\s+(\*|\{[^}]+\})\s+from\s*['"]([^'"\r\n]+)['"]/g)) {
|
|
94
|
+
if (!exportCode.code.slice(m.index).startsWith('export'))
|
|
95
|
+
continue;
|
|
96
|
+
let original = name;
|
|
97
|
+
if (m[1] !== '*') {
|
|
98
|
+
const binding = m[1].slice(1, -1).split(',').map(x => x.trim().split(/\s+as\s+/))
|
|
99
|
+
.find(parts => (parts[1] ?? parts[0]) === name);
|
|
100
|
+
if (!binding)
|
|
101
|
+
continue;
|
|
102
|
+
original = binding[0];
|
|
103
|
+
}
|
|
104
|
+
for (const chain of relativeDefinition(file, m[2], original, depth + 1))
|
|
105
|
+
out.push([fileNode, ...chain]);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
};
|
|
109
|
+
const queue = seeds.slice(0, 6).map(n => ({ node: n, depth: 0 }));
|
|
110
|
+
const visited = new Set();
|
|
111
|
+
const seenEdges = new Set();
|
|
112
|
+
const seenImports = new Set();
|
|
113
|
+
let reads = 0;
|
|
114
|
+
while (queue.length && visited.size < 24 && reads < 48 && groups.length < 24) {
|
|
115
|
+
const { node, depth } = queue.shift();
|
|
116
|
+
if (visited.has(node.id) || node.filePath.startsWith('ohos-sdk:'))
|
|
117
|
+
continue;
|
|
118
|
+
visited.add(node.id);
|
|
119
|
+
const body = source(node);
|
|
120
|
+
if (!body)
|
|
121
|
+
continue;
|
|
122
|
+
const parsed = (0, request_evidence_1.lexEvidenceSource)(body);
|
|
123
|
+
if (!parsed.valid)
|
|
124
|
+
continue;
|
|
125
|
+
const own = inFile(node.filePath);
|
|
126
|
+
// Component → build is containment, not an invented runtime call.
|
|
127
|
+
const nested = own.filter(n => n.id !== node.id && n.startLine >= node.startLine && n.endLine <= node.endLine
|
|
128
|
+
&& n.kind === 'method' && n.name === 'build').slice(0, 2);
|
|
129
|
+
if (depth < 2)
|
|
130
|
+
queue.push(...nested.map(n => ({ node: n, depth: depth + 1 })));
|
|
131
|
+
const adjacent = [...graph.getOutgoingEdges(node.id), ...graph.getIncomingEdges(node.id)];
|
|
132
|
+
reads += 2;
|
|
133
|
+
for (const edge of adjacent.slice(0, 80)) {
|
|
134
|
+
if (groups.length >= 24)
|
|
135
|
+
break;
|
|
136
|
+
const scope = (0, evidence_paths_1.isArkuiScopeEdge)(edge);
|
|
137
|
+
const outgoing = edge.source === node.id;
|
|
138
|
+
if (!scope && !(outgoing && TYPES.has(edge.kind)))
|
|
139
|
+
continue;
|
|
140
|
+
const key = JSON.stringify([edge.source, edge.target, edge.kind, edge.metadata]);
|
|
141
|
+
if (seenEdges.has(key))
|
|
142
|
+
continue;
|
|
143
|
+
seenEdges.add(key);
|
|
144
|
+
const other = graph.getNode(outgoing ? edge.target : edge.source);
|
|
145
|
+
if (!other || other.filePath.startsWith('ohos-sdk:'))
|
|
146
|
+
continue;
|
|
147
|
+
groups.push({ id: `impl-edge:${groups.length}`, label: `${scope ? 'render/navigation scope' : 'type dependency'}: ${node.name} ↔ ${other.name}`,
|
|
148
|
+
nodes: [node, other], edges: [edge], missing: [], notes: scope
|
|
149
|
+
? ['Static wiring only; enclosing conditions and other shared users remain relevant.'] : [] });
|
|
150
|
+
// Do not fan out from a shared caller into its unrelated siblings.
|
|
151
|
+
if (outgoing && depth < 2)
|
|
152
|
+
queue.push({ node: other, depth: depth + 1 });
|
|
153
|
+
}
|
|
154
|
+
if (adjacent.length > 80)
|
|
155
|
+
groups.push({ id: `impl-limit:${node.id}`, label: 'context expansion limit', nodes: [node], edges: [],
|
|
156
|
+
notes: [], missing: [`additional relationships at ${at(node)}`] });
|
|
157
|
+
for (const imp of own.filter(n => n.kind === 'import').slice(0, 40)) {
|
|
158
|
+
if (groups.length >= 24 || seenImports.has(imp.id) || !uses(parsed.code, imp.name))
|
|
159
|
+
continue;
|
|
160
|
+
seenImports.add(imp.id);
|
|
161
|
+
const importSource = source(imp);
|
|
162
|
+
if (!importSource)
|
|
163
|
+
continue;
|
|
164
|
+
const importCode = (0, request_evidence_1.lexEvidenceSource)(importSource);
|
|
165
|
+
const match = [...importSource.matchAll(/\bimport\s+([\s\S]*?)\s+from\s*['"]([^'"\r\n]+)['"]/g)]
|
|
166
|
+
.find(m => importCode.valid && importCode.code.slice(m.index).startsWith('import'));
|
|
167
|
+
if (!match)
|
|
168
|
+
continue;
|
|
169
|
+
const clause = match[1];
|
|
170
|
+
const module = match[2];
|
|
171
|
+
const aliases = [...clause.matchAll(/([\w$]+)\s+as\s+([\w$]+)/g)];
|
|
172
|
+
const name = aliases.find(a => a[2] === imp.name)?.[1] ?? imp.name;
|
|
173
|
+
const group = { id: `impl-import:${imp.id}`, label: `import ${imp.name} from ${module}`,
|
|
174
|
+
nodes: [node, imp], edges: [], notes: [], missing: [] };
|
|
175
|
+
const links = graph.getOutgoingEdges(imp.id).filter(e => e.kind === 'imports');
|
|
176
|
+
reads++;
|
|
177
|
+
for (const link of links.slice(0, 4)) {
|
|
178
|
+
const target = graph.getNode(link.target);
|
|
179
|
+
if (!target || target.filePath.startsWith('ohos-sdk:'))
|
|
180
|
+
continue;
|
|
181
|
+
const pool = target.kind === 'file' ? inFile(target.filePath) : [target];
|
|
182
|
+
const matches = pool.filter(n => DECLARATIONS.has(n.kind) && n.name === name);
|
|
183
|
+
const unique = [...new Map(matches.map(n => [`${n.filePath}:${n.startLine}:${n.name}`, n])).values()];
|
|
184
|
+
if (unique.length === 1) {
|
|
185
|
+
group.nodes.push(unique[0]);
|
|
186
|
+
group.edges.push(link);
|
|
187
|
+
}
|
|
188
|
+
// Export declarations are evidence of the public boundary, never proof that every file-local name is exported.
|
|
189
|
+
const exports = pool.filter(n => n.kind === 'export' && (n.name === name || uses(n.signature ?? '', name))).slice(0, 3);
|
|
190
|
+
group.nodes.push(...exports);
|
|
191
|
+
}
|
|
192
|
+
if (group.nodes.length === 2) {
|
|
193
|
+
const binding = module.startsWith('.') ? undefined : moduleBinding?.(imp.filePath, module);
|
|
194
|
+
const chains = relativeDefinition(imp.filePath, binding?.relativePath ?? module, name);
|
|
195
|
+
if (chains.length === 1)
|
|
196
|
+
group.nodes.push(...(binding?.witnesses ?? []), ...chains[0]);
|
|
197
|
+
else if (chains.length > 1)
|
|
198
|
+
group.missing.push(`ambiguous export ${name} from ${module}`);
|
|
199
|
+
}
|
|
200
|
+
if (group.nodes.length === 2 && /^@(?:kit|ohos|hms)\./.test(module) && sdkModule) {
|
|
201
|
+
if (!sdkCache.has(module))
|
|
202
|
+
sdkCache.set(module, sdkModule(module));
|
|
203
|
+
const sdk = sdkCache.get(module);
|
|
204
|
+
if (sdk) {
|
|
205
|
+
const memberNames = new Set([name]);
|
|
206
|
+
for (const m of parsed.code.matchAll(new RegExp(`\\b${esc(imp.name)}\\s*\\.\\s*([\\w$]+)`, 'g')))
|
|
207
|
+
memberNames.add(m[1]);
|
|
208
|
+
const sdkNodes = sdk.nodes.filter(n => n.signature && memberNames.has(n.name));
|
|
209
|
+
// Complete parameter objects are a group: include declared fields of referenced types.
|
|
210
|
+
const signature = sdkNodes.map(n => n.signature).join('\n');
|
|
211
|
+
const types = sdk.nodes.filter(n => ['interface', 'class', 'type_alias', 'enum'].includes(n.kind) && uses(signature, n.name));
|
|
212
|
+
const selected = [...new Map([...sdkNodes, ...types, ...sdk.nodes.filter(n => types.some(t => n.filePath === t.filePath && n.startLine >= t.startLine && n.endLine <= t.endLine))].map(n => [n.id, n])).values()];
|
|
213
|
+
if (selected.length && selected.length <= 32) {
|
|
214
|
+
group.sdk = { module, version: sdk.version, nodes: selected };
|
|
215
|
+
group.notes.push('Bounded SDK signature bundle; inherited/indirect types, API availability and runtime requirements remain unverified.');
|
|
216
|
+
}
|
|
217
|
+
else
|
|
218
|
+
group.missing.push(`SDK signature/type closure for ${module}.${name}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (group.nodes.length === 2 && !group.sdk)
|
|
222
|
+
group.missing.push(`resolved definition/export of ${imp.name} from ${module} (${at(imp)})`);
|
|
223
|
+
group.notes.push('Import/definition evidence does not prove module installation or public export availability.');
|
|
224
|
+
groups.push(group);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (queue.length)
|
|
228
|
+
groups.push({ id: 'impl-limit', label: 'context expansion limit', nodes: [], edges: [], notes: [],
|
|
229
|
+
missing: [at(queue[0].node)] });
|
|
230
|
+
return groups;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=implementation-context.js.map
|
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP query cache — memory key index + serialized tool responses in homegraph.db.
|
|
3
|
-
*
|
|
4
|
-
* A per-project in-memory Set of cache keys gives O(1) negative lookup so misses
|
|
5
|
-
* skip the SQLite SELECT. Payloads stay in homegraph.db; the Set is warmed from
|
|
6
|
-
* DB on first use after daemon restart when the index stamp is still valid.
|
|
7
|
-
*
|
|
8
|
-
* Invalidated wholesale when the index stamp or cache format version changes.
|
|
9
|
-
* Per-call staleness/worktree notices are applied AFTER cache lookup so hits
|
|
10
|
-
* stay fresh.
|
|
11
|
-
*
|
|
12
|
-
* The key Set lives on the daemon main thread only (cache read/write runs there
|
|
13
|
-
* before/after worker-pool explore dispatch), so no cross-thread locking is needed.
|
|
14
|
-
*/
|
|
15
1
|
import type { QueryBuilder } from '../db/queries';
|
|
16
2
|
import type { ToolResult } from './tools';
|
|
17
3
|
/** Bump when cache-key normalization or cached payload shape changes. */
|
|
18
|
-
export declare const QUERY_CACHE_FORMAT_VERSION =
|
|
4
|
+
export declare const QUERY_CACHE_FORMAT_VERSION = 7;
|
|
19
5
|
export declare function isMcpQueryCacheEnabled(): boolean;
|
|
20
6
|
export declare function isCacheableMcpTool(toolName: string): boolean;
|
|
21
7
|
/**
|
package/dist/mcp/query-cache.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.McpQueryCacheIndex = exports.QUERY_CACHE_FORMAT_VERSION = void 0;
|
|
4
|
+
exports.isMcpQueryCacheEnabled = isMcpQueryCacheEnabled;
|
|
5
|
+
exports.isCacheableMcpTool = isCacheableMcpTool;
|
|
6
|
+
exports.getMcpQueryCacheIndex = getMcpQueryCacheIndex;
|
|
7
|
+
exports.resetMcpQueryCacheIndices = resetMcpQueryCacheIndices;
|
|
8
|
+
exports.normalizeExploreQueryTerms = normalizeExploreQueryTerms;
|
|
9
|
+
exports.buildMcpQueryCacheFingerprint = buildMcpQueryCacheFingerprint;
|
|
10
|
+
exports.buildMcpQueryCacheKey = buildMcpQueryCacheKey;
|
|
11
|
+
const request_contract_1 = require("../search/request-contract");
|
|
2
12
|
/**
|
|
3
13
|
* MCP query cache — memory key index + serialized tool responses in homegraph.db.
|
|
4
14
|
*
|
|
@@ -13,15 +23,6 @@
|
|
|
13
23
|
* The key Set lives on the daemon main thread only (cache read/write runs there
|
|
14
24
|
* before/after worker-pool explore dispatch), so no cross-thread locking is needed.
|
|
15
25
|
*/
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.McpQueryCacheIndex = exports.QUERY_CACHE_FORMAT_VERSION = void 0;
|
|
18
|
-
exports.isMcpQueryCacheEnabled = isMcpQueryCacheEnabled;
|
|
19
|
-
exports.isCacheableMcpTool = isCacheableMcpTool;
|
|
20
|
-
exports.getMcpQueryCacheIndex = getMcpQueryCacheIndex;
|
|
21
|
-
exports.resetMcpQueryCacheIndices = resetMcpQueryCacheIndices;
|
|
22
|
-
exports.normalizeExploreQueryTerms = normalizeExploreQueryTerms;
|
|
23
|
-
exports.buildMcpQueryCacheFingerprint = buildMcpQueryCacheFingerprint;
|
|
24
|
-
exports.buildMcpQueryCacheKey = buildMcpQueryCacheKey;
|
|
25
26
|
const node_crypto_1 = require("node:crypto");
|
|
26
27
|
const query_plan_1 = require("../search/query-plan");
|
|
27
28
|
const PLAN_RELATIONS = new Set(query_plan_1.QUERY_RELATIONS);
|
|
@@ -35,7 +36,7 @@ function defaultExploreMaxFiles(fileCount) {
|
|
|
35
36
|
return 8;
|
|
36
37
|
}
|
|
37
38
|
/** Bump when cache-key normalization or cached payload shape changes. */
|
|
38
|
-
exports.QUERY_CACHE_FORMAT_VERSION =
|
|
39
|
+
exports.QUERY_CACHE_FORMAT_VERSION = 7;
|
|
39
40
|
const METADATA_INDEX_STAMP = 'query_cache_index_stamp';
|
|
40
41
|
const METADATA_FORMAT_VERSION = 'query_cache_format_version';
|
|
41
42
|
/** Tools that must stay live — never cached. */
|
|
@@ -146,7 +147,7 @@ function exploreEnvFingerprint() {
|
|
|
146
147
|
const fullSource = process.env.HOMEGRAPH_EXPLORE_FULL_SOURCE === '1' ? '1' : '0';
|
|
147
148
|
const evidencePacks = process.env.HOMEGRAPH_ARKTS_EVIDENCE_PACKS === '0' ? '0' : '1';
|
|
148
149
|
const queryPaths = process.env.HOMEGRAPH_ARKTS_QUERY_PATHS === '0' ? '0' : '1';
|
|
149
|
-
return `linums:${linums}|adaptive:${adaptive}|rankMultiterm:${rankMultiterm}|fullSource:${fullSource}|arktsEvidence:${evidencePacks}|arktsPaths:${queryPaths}`;
|
|
150
|
+
return `implementationContext:${process.env.HOMEGRAPH_ARKTS_IMPLEMENTATION_CONTEXT === '0' ? '0' : '1'}|controlEvidence:${process.env.HOMEGRAPH_ARKTS_CONTROL_EVIDENCE === '0' ? '0' : '1'}|linums:${linums}|adaptive:${adaptive}|rankMultiterm:${rankMultiterm}|fullSource:${fullSource}|arktsEvidence:${evidencePacks}|arktsPaths:${queryPaths}|accuracyTargets:${process.env.HOMEGRAPH_ACCURACY_TARGETS === '0' ? '0' : '1'}|accuracyCoverage:${process.env.HOMEGRAPH_ACCURACY_COVERAGE === '0' ? '0' : '1'}`;
|
|
150
151
|
}
|
|
151
152
|
function normalizeString(value) {
|
|
152
153
|
if (typeof value !== 'string')
|
|
@@ -213,7 +214,15 @@ function queryPlanFingerprint(value) {
|
|
|
213
214
|
if (!features || Object.keys(features).length > 64
|
|
214
215
|
|| !Object.values(features).every(v => typeof v === 'boolean'))
|
|
215
216
|
return undefined;
|
|
217
|
+
let requestContract;
|
|
218
|
+
try {
|
|
219
|
+
requestContract = (0, request_contract_1.validateRequestContract)(plan.requestContract, originalQuery + '\n' + (query(plan.taskContext) ?? ''));
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
216
224
|
return stableJson({
|
|
225
|
+
requestContract,
|
|
217
226
|
version: plan.version, originalQuery, canonicalQuery, intent, route,
|
|
218
227
|
taskContext: query(plan.taskContext),
|
|
219
228
|
anchors, searchTerms, steps, features,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { RequestContract, RequestTarget } from '../search/request-contract';
|
|
2
|
+
export interface RequestSource {
|
|
3
|
+
filePath: string;
|
|
4
|
+
start: number;
|
|
5
|
+
source: string;
|
|
6
|
+
}
|
|
7
|
+
interface StringToken {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
value: string;
|
|
11
|
+
}
|
|
12
|
+
interface Lexed {
|
|
13
|
+
code: string;
|
|
14
|
+
strings: StringToken[];
|
|
15
|
+
pairs: Map<number, number>;
|
|
16
|
+
valid: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface TargetEvidence {
|
|
19
|
+
id: string;
|
|
20
|
+
text: string;
|
|
21
|
+
presence: RequestTarget['presence'];
|
|
22
|
+
status: 'observed' | 'ambiguous' | 'not_observed' | 'requested_not_observed';
|
|
23
|
+
locations: string[];
|
|
24
|
+
note: string;
|
|
25
|
+
}
|
|
26
|
+
export interface BehaviorEvidence {
|
|
27
|
+
id: string;
|
|
28
|
+
text: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
status: 'binding_observed' | 'binding_not_observed' | 'unknown';
|
|
31
|
+
locations: string[];
|
|
32
|
+
note: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RequestEvidence {
|
|
35
|
+
scope: 'returned_source_only';
|
|
36
|
+
runtimeVerified: false;
|
|
37
|
+
targets: TargetEvidence[];
|
|
38
|
+
behaviors: BehaviorEvidence[];
|
|
39
|
+
}
|
|
40
|
+
/** Conservative lexical witness reader. It does not evaluate ArkTS or infer data flow.
|
|
41
|
+
* Mask comments, strings, templates and regular expressions before reading structure.
|
|
42
|
+
* Escaped/interpolated strings are deliberately not used as exact literal witnesses.
|
|
43
|
+
*/
|
|
44
|
+
export declare function lexEvidenceSource(source: string): Lexed;
|
|
45
|
+
/** Reuse one analysis per verified source unit; no disk reads or model calls. */
|
|
46
|
+
export declare function createRequestEvidenceInspector(contract: RequestContract): {
|
|
47
|
+
score: (units: RequestSource[]) => number;
|
|
48
|
+
inspect: (units: RequestSource[]) => RequestEvidence;
|
|
49
|
+
};
|
|
50
|
+
export declare function renderRequestEvidence(evidence: RequestEvidence, targets: boolean, behaviors: boolean): string[];
|
|
51
|
+
export interface ControlEvidence {
|
|
52
|
+
location: string;
|
|
53
|
+
control: string;
|
|
54
|
+
labels: string[];
|
|
55
|
+
enabled: 'binding_observed' | 'binding_not_observed' | 'unknown';
|
|
56
|
+
expression?: string;
|
|
57
|
+
click?: string;
|
|
58
|
+
state: Array<{
|
|
59
|
+
expression: string;
|
|
60
|
+
writeLocations: string[];
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
/** An inventory of returned controls, not a natural-language target match or a UI test.
|
|
64
|
+
* Resource names stay literal: an icon called "undo" is not translated into a task label.
|
|
65
|
+
*/
|
|
66
|
+
export declare function inspectControlEvidence(units: RequestSource[]): ControlEvidence[];
|
|
67
|
+
export declare function renderControlEvidence(rows: ControlEvidence[]): string[];
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=request-evidence.d.ts.map
|