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.
@@ -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 = 5;
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
  /**
@@ -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 = 5;
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