@devflow-tools/context-engine 0.17.7 → 0.18.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.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/code-evidence-producer.d.ts +29 -0
- package/dist/code-evidence-producer.d.ts.map +1 -0
- package/dist/code-evidence-producer.js +522 -0
- package/dist/code-evidence-producer.js.map +1 -0
- package/dist/code-evidence-types.d.ts +26 -0
- package/dist/code-evidence-types.d.ts.map +1 -0
- package/dist/code-evidence-types.js +2 -0
- package/dist/code-evidence-types.js.map +1 -0
- package/dist/compressor.d.ts.map +1 -1
- package/dist/compressor.js +2 -3
- package/dist/compressor.js.map +1 -1
- package/dist/context-budget-allocator.d.ts +5 -1
- package/dist/context-budget-allocator.d.ts.map +1 -1
- package/dist/context-budget-allocator.js +25 -3
- package/dist/context-budget-allocator.js.map +1 -1
- package/dist/context-engine.d.ts +19 -43
- package/dist/context-engine.d.ts.map +1 -1
- package/dist/context-engine.js +203 -1702
- package/dist/context-engine.js.map +1 -1
- package/dist/create-engines.d.ts +15 -19
- package/dist/create-engines.d.ts.map +1 -1
- package/dist/create-engines.js +39 -106
- package/dist/create-engines.js.map +1 -1
- package/dist/index.d.ts +7 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -15
- package/dist/index.js.map +1 -1
- package/dist/symbol-embedder.d.ts +1 -1
- package/dist/symbol-embedder.d.ts.map +1 -1
- package/dist/symbol-embedder.js +17 -12
- package/dist/symbol-embedder.js.map +1 -1
- package/dist/symbol-embedding-document.d.ts +4 -0
- package/dist/symbol-embedding-document.d.ts.map +1 -0
- package/dist/symbol-embedding-document.js +39 -0
- package/dist/symbol-embedding-document.js.map +1 -0
- package/dist/symbol-vector-store.d.ts +13 -1
- package/dist/symbol-vector-store.d.ts.map +1 -1
- package/dist/symbol-vector-store.js +45 -16
- package/dist/symbol-vector-store.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -9
- package/dist/artifact-inventory.d.ts +0 -39
- package/dist/artifact-inventory.d.ts.map +0 -1
- package/dist/artifact-inventory.js +0 -224
- package/dist/artifact-inventory.js.map +0 -1
- package/dist/channel-query-planner.d.ts +0 -9
- package/dist/channel-query-planner.d.ts.map +0 -1
- package/dist/channel-query-planner.js +0 -92
- package/dist/channel-query-planner.js.map +0 -1
- package/dist/intent-parser.d.ts +0 -15
- package/dist/intent-parser.d.ts.map +0 -1
- package/dist/intent-parser.js +0 -388
- package/dist/intent-parser.js.map +0 -1
- package/dist/retrieval-gap-detector.d.ts +0 -10
- package/dist/retrieval-gap-detector.d.ts.map +0 -1
- package/dist/retrieval-gap-detector.js +0 -85
- package/dist/retrieval-gap-detector.js.map +0 -1
- package/dist/retrieval-handlers.d.ts +0 -73
- package/dist/retrieval-handlers.d.ts.map +0 -1
- package/dist/retrieval-handlers.js +0 -289
- package/dist/retrieval-handlers.js.map +0 -1
- package/dist/retrieval-ledger.d.ts +0 -3
- package/dist/retrieval-ledger.d.ts.map +0 -1
- package/dist/retrieval-ledger.js +0 -156
- package/dist/retrieval-ledger.js.map +0 -1
- package/dist/retrieval-session-controller.d.ts +0 -6
- package/dist/retrieval-session-controller.d.ts.map +0 -1
- package/dist/retrieval-session-controller.js +0 -372
- package/dist/retrieval-session-controller.js.map +0 -1
- package/dist/retrieval-task-plan.d.ts +0 -28
- package/dist/retrieval-task-plan.d.ts.map +0 -1
- package/dist/retrieval-task-plan.js +0 -232
- package/dist/retrieval-task-plan.js.map +0 -1
- package/dist/semantic-authority-merger.d.ts +0 -11
- package/dist/semantic-authority-merger.d.ts.map +0 -1
- package/dist/semantic-authority-merger.js +0 -176
- package/dist/semantic-authority-merger.js.map +0 -1
- package/dist/semantic-resolver.d.ts +0 -19
- package/dist/semantic-resolver.d.ts.map +0 -1
- package/dist/semantic-resolver.js +0 -49
- package/dist/semantic-resolver.js.map +0 -1
- package/dist/semantic-route-catalog.d.ts +0 -22
- package/dist/semantic-route-catalog.d.ts.map +0 -1
- package/dist/semantic-route-catalog.js +0 -53
- package/dist/semantic-route-catalog.js.map +0 -1
- package/dist/semantic-router.d.ts +0 -39
- package/dist/semantic-router.d.ts.map +0 -1
- package/dist/semantic-router.js +0 -153
- package/dist/semantic-router.js.map +0 -1
- package/dist/semantic-structural-evidence.d.ts +0 -20
- package/dist/semantic-structural-evidence.d.ts.map +0 -1
- package/dist/semantic-structural-evidence.js +0 -84
- package/dist/semantic-structural-evidence.js.map +0 -1
- package/dist/task-retrieval-orchestrator.d.ts +0 -46
- package/dist/task-retrieval-orchestrator.d.ts.map +0 -1
- package/dist/task-retrieval-orchestrator.js +0 -418
- package/dist/task-retrieval-orchestrator.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shilongfeicool
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @devflow-tools/context-engine
|
|
2
2
|
|
|
3
|
-
> 组合代码图、知识、记忆和语义路由,构建有预算约束的任务上下文。(v0.17.
|
|
3
|
+
> 组合代码图、知识、记忆和语义路由,构建有预算约束的任务上下文。(v0.17.8)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@devflow-tools/context-engine)
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ context-engine 是组合层;SDK 仅提供 embedding contract,具体 embeddin
|
|
|
31
31
|
|
|
32
32
|
## 版本与兼容性
|
|
33
33
|
|
|
34
|
-
- 当前包版本:`0.17.
|
|
34
|
+
- 当前包版本:`0.17.8`
|
|
35
35
|
- Node.js:未在 package manifest 声明更高版本时,使用 Node.js 18+;原生 SQLite/Playwright 能力请按对应依赖安装
|
|
36
36
|
- workspace 内部包应使用同一 DevFlow minor/patch 版本,避免类型和数据库 schema 不一致
|
|
37
37
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CodeGraph } from '@colbymchenry/codegraph';
|
|
2
|
+
import { type CodeEvidenceProducerPort, type EvidenceProducerResult, type RetrievalRequestSignatureV1, type SemanticExecutionContext } from '@devflow-tools/sdk';
|
|
3
|
+
import type { IndexerAdapter } from './indexer-adapter.js';
|
|
4
|
+
import type { SymbolVectorStore } from './symbol-vector-store.js';
|
|
5
|
+
import type { EmbeddingProviderContract } from '@devflow-tools/sdk';
|
|
6
|
+
import { type CodeEvidenceSearchInput, type CodeEvidenceSearchOutput } from './code-evidence-types.js';
|
|
7
|
+
export declare class CodeEvidenceRuntime {
|
|
8
|
+
private readonly dependencies;
|
|
9
|
+
constructor(dependencies: {
|
|
10
|
+
codegraph: CodeGraph;
|
|
11
|
+
indexer: IndexerAdapter;
|
|
12
|
+
embedder?: EmbeddingProviderContract;
|
|
13
|
+
symbolStore?: SymbolVectorStore;
|
|
14
|
+
});
|
|
15
|
+
search(input: CodeEvidenceSearchInput): Promise<CodeEvidenceSearchOutput>;
|
|
16
|
+
private addSegmentCandidates;
|
|
17
|
+
private addVectorCandidates;
|
|
18
|
+
}
|
|
19
|
+
export declare class CodeEvidenceProducer implements CodeEvidenceProducerPort {
|
|
20
|
+
private readonly runtime;
|
|
21
|
+
private lastVectorCandidateRefs;
|
|
22
|
+
constructor(runtime: CodeEvidenceRuntime);
|
|
23
|
+
getLastVectorCandidateRefs(): Array<{
|
|
24
|
+
ref: string;
|
|
25
|
+
rank: number;
|
|
26
|
+
}>;
|
|
27
|
+
search(context: SemanticExecutionContext, request: RetrievalRequestSignatureV1): Promise<EvidenceProducerResult>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=code-evidence-producer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-evidence-producer.d.ts","sourceRoot":"","sources":["../src/code-evidence-producer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAGL,KAAK,wBAAwB,EAI7B,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;AA0ClC,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAAZ,YAAY,EAAE;QACzC,SAAS,EAAE,SAAS,CAAC;QACrB,OAAO,EAAE,cAAc,CAAC;QACxB,QAAQ,CAAC,EAAE,yBAAyB,CAAC;QACrC,WAAW,CAAC,EAAE,iBAAiB,CAAC;KACjC;IAEK,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAmM/E,OAAO,CAAC,oBAAoB;YA2Dd,mBAAmB;CAwClC;AAED,qBAAa,oBAAqB,YAAW,wBAAwB;IAGvD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,uBAAuB,CAA4C;gBAE9C,OAAO,EAAE,mBAAmB;IAEzD,0BAA0B,IAAI,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAI5D,MAAM,CACV,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC;CA+CnC"}
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { codeEvidenceStableRef, stableRuntimeHash, } from '@devflow-tools/sdk';
|
|
2
|
+
import { sanitizeCodeGraphEdges } from './graph-edge-quality.js';
|
|
3
|
+
import { requestValidatedEmbedding } from '@devflow-tools/provider-runtime';
|
|
4
|
+
import { CODE_EVIDENCE_PRODUCER_VERSION, } from './code-evidence-types.js';
|
|
5
|
+
const SEGMENT_CANDIDATE_LIMIT = 20;
|
|
6
|
+
const PROSE_STOPWORDS = new Set([
|
|
7
|
+
'about', 'above', 'actually', 'after', 'again', 'against', 'almost', 'along', 'also', 'always',
|
|
8
|
+
'another', 'anything', 'around', 'away', 'back', 'because', 'been', 'before', 'behind', 'being',
|
|
9
|
+
'below', 'best', 'better', 'between', 'both', 'cannot', 'come', 'could', 'does', 'doing', 'done',
|
|
10
|
+
'down', 'each', 'either', 'else', 'even', 'ever', 'every', 'everything', 'fine', 'first', 'from',
|
|
11
|
+
'getting', 'give', 'goes', 'going', 'gone', 'good', 'great', 'have', 'having', 'help', 'here',
|
|
12
|
+
'inside', 'instead', 'into', 'just', 'keep', 'know', 'last', 'least', 'less', 'like', 'likely',
|
|
13
|
+
'little', 'look', 'looking', 'made', 'make', 'making', 'many', 'maybe', 'mind', 'more', 'most',
|
|
14
|
+
'much', 'must', 'need', 'needs', 'never', 'next', 'nice', 'none', 'nothing', 'okay', 'only',
|
|
15
|
+
'onto', 'other', 'otherwise', 'over', 'please', 'pretty', 'probably', 'quite', 'rather', 'really',
|
|
16
|
+
'right', 'same', 'seem', 'seems', 'should', 'show', 'since', 'some', 'someone', 'something',
|
|
17
|
+
'somewhere', 'soon', 'still', 'such', 'sure', 'take', 'than', 'thank', 'thanks', 'that', 'their',
|
|
18
|
+
'them', 'then', 'there', 'these', 'they', 'thing', 'things', 'think', 'this', 'those', 'though',
|
|
19
|
+
'tried', 'tries', 'trying', 'under', 'until', 'upon', 'very', 'want', 'wants', 'well', 'went',
|
|
20
|
+
'were', 'what', 'when', 'which', 'while', 'will', 'wish', 'with', 'within', 'without', 'would',
|
|
21
|
+
'wrong', 'your', 'yours', 'change', 'changes', 'check', 'class', 'classes', 'code', 'detail',
|
|
22
|
+
'details', 'directory', 'error', 'errors', 'example', 'examples', 'file', 'files', 'folder',
|
|
23
|
+
'function', 'functions', 'issue', 'issues', 'line', 'lines', 'method', 'methods', 'name', 'names',
|
|
24
|
+
'problem', 'problems', 'project', 'question', 'questions', 'rename', 'test', 'tests', 'type',
|
|
25
|
+
'types', 'update', 'value', 'values', 'warning', 'warnings', 'work', 'working', 'write', 'writing',
|
|
26
|
+
]);
|
|
27
|
+
export class CodeEvidenceRuntime {
|
|
28
|
+
dependencies;
|
|
29
|
+
constructor(dependencies) {
|
|
30
|
+
this.dependencies = dependencies;
|
|
31
|
+
}
|
|
32
|
+
async search(input) {
|
|
33
|
+
const stats = this.dependencies.codegraph.getStats();
|
|
34
|
+
if (stats.nodeCount === 0 || stats.lastUpdated === 0) {
|
|
35
|
+
return {
|
|
36
|
+
candidates: [], rejectedRefs: [], indexReady: false,
|
|
37
|
+
gaps: [gap('unavailable_channel', 'code_index_not_ready', 'code')],
|
|
38
|
+
producerLegs: unavailableProducerLegs(input.query.query, 'code_index_not_ready', Boolean(input.profileHash)),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const query = input.query.query.slice(0, 2_000);
|
|
42
|
+
const searchLimit = Math.min(30, Math.max(5, input.query.symbols.length * 3));
|
|
43
|
+
let result;
|
|
44
|
+
try {
|
|
45
|
+
result = await this.dependencies.codegraph.findRelevantContext(query, {
|
|
46
|
+
searchLimit,
|
|
47
|
+
traversalDepth: Math.max(0, Math.min(3, input.query.maxDepth)),
|
|
48
|
+
maxNodes: Math.min(60, Math.max(10, Math.floor(input.tokenBudget / 128))),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const reason = error instanceof Error && error.message.toLowerCase().includes('timeout')
|
|
53
|
+
? 'code_search_timeout' : 'code_search_unavailable';
|
|
54
|
+
return {
|
|
55
|
+
candidates: [], rejectedRefs: [], indexReady: false,
|
|
56
|
+
gaps: [gap('unavailable_channel', reason, 'code')],
|
|
57
|
+
producerLegs: unavailableProducerLegs(query, reason, Boolean(input.profileHash), reason.endsWith('timeout') ? 'timeout' : 'unavailable'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const ranked = new Map();
|
|
61
|
+
for (const [index, rootId] of result.roots.entries()) {
|
|
62
|
+
const node = result.nodes.get(rootId);
|
|
63
|
+
if (node)
|
|
64
|
+
ranked.set(node.id, { node, lexicalRank: index + 1, root: true });
|
|
65
|
+
}
|
|
66
|
+
for (const node of result.nodes.values()) {
|
|
67
|
+
if (!ranked.has(node.id))
|
|
68
|
+
ranked.set(node.id, { node, root: false });
|
|
69
|
+
}
|
|
70
|
+
const segment = this.addSegmentCandidates(ranked, query, Math.max(0, Math.min(3, input.query.maxDepth)));
|
|
71
|
+
const vector = await this.addVectorCandidates(ranked, query, input.projectId, input.profileHash);
|
|
72
|
+
const rawEdges = result.edges.map(edge => ({
|
|
73
|
+
...edge,
|
|
74
|
+
kind: typeof edge.kind === 'string'
|
|
75
|
+
? edge.kind : 'depends_on',
|
|
76
|
+
}));
|
|
77
|
+
const sanitized = sanitizeCodeGraphEdges(this.dependencies.codegraph, rawEdges);
|
|
78
|
+
const graphRanks = graphEvidenceRanks(result.roots, sanitized.accepted);
|
|
79
|
+
const rejectedRefs = sanitized.rejected.map(edge => ({
|
|
80
|
+
ref: `code-relation:${edge.source}:${edge.target}:${edge.kind}`,
|
|
81
|
+
reason: edge.reason,
|
|
82
|
+
}));
|
|
83
|
+
const retrievalProfile = this.dependencies.symbolStore?.getProfile();
|
|
84
|
+
const gaps = [];
|
|
85
|
+
const candidates = [];
|
|
86
|
+
const bodyScores = new Map();
|
|
87
|
+
for (const item of ranked.values()) {
|
|
88
|
+
const freshness = this.dependencies.indexer.getFileFreshness(item.node.filePath);
|
|
89
|
+
const targetMatch = matchesTarget(item.node.filePath, input.query.targetAnchors);
|
|
90
|
+
const symbolMatch = input.query.symbols.some(symbol => normalizeSymbol(symbol) === normalizeSymbol(item.node.name));
|
|
91
|
+
const graphRank = graphRanks.get(item.node.id);
|
|
92
|
+
const boundStructure = graphRank !== undefined && sanitized.accepted.some(edge => edge.source === item.node.id || edge.target === item.node.id);
|
|
93
|
+
const segmentEvidence = item.segmentRank !== undefined || item.segmentGraphRank !== undefined;
|
|
94
|
+
const strength = freshness.current && (targetMatch || symbolMatch)
|
|
95
|
+
? 'strong' : freshness.current && (boundStructure || segmentEvidence) ? 'supporting' : 'weak';
|
|
96
|
+
const stableRef = codeStableRef(input.projectId, item.node);
|
|
97
|
+
const hasEvidenceLeg = item.lexicalRank !== undefined || item.vectorRank !== undefined
|
|
98
|
+
|| segmentEvidence || targetMatch || symbolMatch || boundStructure;
|
|
99
|
+
if (!hasEvidenceLeg) {
|
|
100
|
+
rejectedRefs.push({ ref: stableRef, reason: 'unbound_graph_node' });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!freshness.current) {
|
|
104
|
+
rejectedRefs.push({ ref: stableRef, reason: `stale:${freshness.reason ?? 'unknown'}` });
|
|
105
|
+
gaps.push({
|
|
106
|
+
kind: 'runtime_evidence', target: item.node.filePath,
|
|
107
|
+
reason: `code_source_stale:${freshness.reason ?? 'unknown'}`,
|
|
108
|
+
evidenceRefs: [stableRef], resolver: 'code', priority: 1,
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const excerpt = await this.dependencies.codegraph.getCode(item.node.id);
|
|
113
|
+
if (!excerpt?.trim()) {
|
|
114
|
+
rejectedRefs.push({ ref: stableRef, reason: 'source_excerpt_unavailable' });
|
|
115
|
+
gaps.push({
|
|
116
|
+
kind: 'runtime_evidence', target: item.node.filePath,
|
|
117
|
+
reason: 'code_source_excerpt_unavailable', evidenceRefs: [stableRef],
|
|
118
|
+
resolver: 'code', priority: 1,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const bodyScore = sourceBodyMatchScore(query, excerpt);
|
|
123
|
+
if (bodyScore > 0)
|
|
124
|
+
bodyScores.set(stableRef, bodyScore);
|
|
125
|
+
const legs = [];
|
|
126
|
+
if (item.lexicalRank)
|
|
127
|
+
legs.push({ leg: 'code:bm25', rank: item.lexicalRank });
|
|
128
|
+
if (item.vectorRank)
|
|
129
|
+
legs.push({ leg: 'code:vector', rank: item.vectorRank });
|
|
130
|
+
if (item.segmentRank)
|
|
131
|
+
legs.push({ leg: 'code:segment', rank: item.segmentRank });
|
|
132
|
+
if (item.segmentGraphRank) {
|
|
133
|
+
legs.push({ leg: 'code:segment-graph', rank: item.segmentGraphRank });
|
|
134
|
+
}
|
|
135
|
+
if (boundStructure)
|
|
136
|
+
legs.push({ leg: 'code:graph', rank: graphRank });
|
|
137
|
+
if (targetMatch)
|
|
138
|
+
legs.push({ leg: 'code:target', rank: 1 });
|
|
139
|
+
if (symbolMatch)
|
|
140
|
+
legs.push({ leg: 'code:symbol', rank: 1 });
|
|
141
|
+
candidates.push({
|
|
142
|
+
stableRef, channel: 'code', strength,
|
|
143
|
+
rank: Math.min(...legs.map(leg => leg.rank), item.lexicalRank ?? 999),
|
|
144
|
+
...(retrievalProfile ? {
|
|
145
|
+
retrievalProfile: retrievalProfile.profileHash,
|
|
146
|
+
generation: retrievalProfile.generation,
|
|
147
|
+
} : {}),
|
|
148
|
+
evidence: {
|
|
149
|
+
ref: stableRef,
|
|
150
|
+
sourceId: `${item.node.filePath}:${item.node.id}`,
|
|
151
|
+
producer: 'context-engine', producerVersion: CODE_EVIDENCE_PRODUCER_VERSION,
|
|
152
|
+
...(freshness.currentHash ?? freshness.indexedHash
|
|
153
|
+
? { sourceRevision: freshness.currentHash ?? freshness.indexedHash }
|
|
154
|
+
: {}),
|
|
155
|
+
...(freshness.currentHash ?? freshness.indexedHash
|
|
156
|
+
? { contentHash: freshness.currentHash ?? freshness.indexedHash }
|
|
157
|
+
: {}),
|
|
158
|
+
parentRefs: [
|
|
159
|
+
...(boundStructure ? relationRefs(item.node.id, sanitized.accepted) : []),
|
|
160
|
+
...(item.segmentParentRefs ?? []),
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
payload: {
|
|
164
|
+
path: item.node.filePath, symbolId: item.node.id,
|
|
165
|
+
symbol: item.node.name, kind: item.node.kind,
|
|
166
|
+
excerpt: excerpt.slice(0, 2_000),
|
|
167
|
+
...(item.node.startLine !== undefined ? { startLine: item.node.startLine } : {}),
|
|
168
|
+
...(item.node.endLine !== undefined ? { endLine: item.node.endLine } : {}),
|
|
169
|
+
relationKinds: input.query.relationKinds,
|
|
170
|
+
},
|
|
171
|
+
freshness: { current: true, checkedAt: input.now },
|
|
172
|
+
contributingLegs: legs.length > 0 ? legs : [{ leg: 'code:lexical', rank: item.lexicalRank ?? 999 }],
|
|
173
|
+
eligibility: {
|
|
174
|
+
scope: { kind: 'project', id: input.projectId }, permission: 'allowed', sourceReady: true,
|
|
175
|
+
...(item.vectorRank && input.profileHash ? { profileHash: input.profileHash } : {}),
|
|
176
|
+
},
|
|
177
|
+
estimatedTokens: estimateTokens(item.node),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const bodyRanks = new Map([...bodyScores.entries()]
|
|
181
|
+
.sort(([leftRef, leftScore], [rightRef, rightScore]) => rightScore - leftScore
|
|
182
|
+
|| leftRef.localeCompare(rightRef))
|
|
183
|
+
.map(([stableRef], index) => [stableRef, index + 1]));
|
|
184
|
+
for (const candidate of candidates) {
|
|
185
|
+
const bodyRank = bodyRanks.get(candidate.stableRef);
|
|
186
|
+
if (bodyRank === undefined)
|
|
187
|
+
continue;
|
|
188
|
+
candidate.contributingLegs = [
|
|
189
|
+
...candidate.contributingLegs,
|
|
190
|
+
{ leg: 'code:body', rank: bodyRank },
|
|
191
|
+
];
|
|
192
|
+
candidate.rank = Math.min(candidate.rank, bodyRank);
|
|
193
|
+
}
|
|
194
|
+
if (input.query.targetAnchors.length > 0 && !candidates.some(candidate => candidate.strength === 'strong')) {
|
|
195
|
+
gaps.push({
|
|
196
|
+
kind: 'target', reason: 'explicit_target_not_resolved_to_current_strong_evidence',
|
|
197
|
+
target: input.query.targetAnchors.join(','), evidenceRefs: [], resolver: 'code', priority: 1,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (input.query.symbols.length > 0 && !candidates.some(candidate => candidate.strength === 'strong')) {
|
|
201
|
+
gaps.push({
|
|
202
|
+
kind: 'symbol_ambiguity', reason: 'requested_symbol_not_resolved_to_unique_current_evidence',
|
|
203
|
+
target: input.query.symbols.join(','), evidenceRefs: [], resolver: 'code', priority: 1,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const legPrefix = producerLegPrefix(query);
|
|
207
|
+
return {
|
|
208
|
+
candidates,
|
|
209
|
+
rejectedRefs,
|
|
210
|
+
gaps: dedupeGaps(gaps),
|
|
211
|
+
indexReady: true,
|
|
212
|
+
producerLegs: [
|
|
213
|
+
{ schemaVersion: 'evidence-producer-leg-receipt.v1', legId: `${legPrefix}:lexical`, parentReceiptIds: [], kind: 'lexical', required: true, status: result.roots.length > 0 ? 'ready' : 'empty_valid', candidateCount: result.roots.length },
|
|
214
|
+
{ schemaVersion: 'evidence-producer-leg-receipt.v1', legId: `${legPrefix}:segment`, parentReceiptIds: [], kind: 'graph', required: false, status: segment.status, candidateCount: segment.candidateCount, ...(segment.reasonCode ? { reasonCode: segment.reasonCode } : {}) },
|
|
215
|
+
{ schemaVersion: 'evidence-producer-leg-receipt.v1', legId: `${legPrefix}:body`, parentReceiptIds: [], kind: 'lexical', required: false, status: bodyRanks.size > 0 ? 'ready' : 'empty_valid', candidateCount: bodyRanks.size },
|
|
216
|
+
{ schemaVersion: 'evidence-producer-leg-receipt.v1', legId: `${legPrefix}:vector`, parentReceiptIds: [], kind: 'vector', required: Boolean(input.profileHash), ...vector },
|
|
217
|
+
{ schemaVersion: 'evidence-producer-leg-receipt.v1', legId: `${legPrefix}:graph`, parentReceiptIds: [], kind: 'graph', required: false, status: sanitized.accepted.length > 0 ? 'ready' : 'empty_valid', candidateCount: graphRanks.size },
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
addSegmentCandidates(ranked, query, maxDepth) {
|
|
222
|
+
try {
|
|
223
|
+
const words = latinProseWords(query);
|
|
224
|
+
if (words.length === 0) {
|
|
225
|
+
return { status: 'empty_valid', candidateCount: 0 };
|
|
226
|
+
}
|
|
227
|
+
const matches = this.dependencies.codegraph.getSegmentMatches(words, SEGMENT_CANDIDATE_LIMIT);
|
|
228
|
+
const additions = new Map();
|
|
229
|
+
for (const [index, match] of matches.entries()) {
|
|
230
|
+
const node = resolveExactSegmentNode(this.dependencies.codegraph, match);
|
|
231
|
+
if (!node)
|
|
232
|
+
continue;
|
|
233
|
+
mergeSegmentNode(additions, node, 'segmentRank', index + 1);
|
|
234
|
+
if (!isCallerExpandable(node) || maxDepth === 0)
|
|
235
|
+
continue;
|
|
236
|
+
const callers = this.dependencies.codegraph.getCallers(node.id, maxDepth)
|
|
237
|
+
.map(item => ({ ...item, current: resolveExactNode(this.dependencies.codegraph, item.node) }));
|
|
238
|
+
const callerIds = new Set([node.id, ...callers.flatMap(item => item.current ? [item.current.id] : [])]);
|
|
239
|
+
for (const [callerIndex, caller] of callers.entries()) {
|
|
240
|
+
const current = caller.current;
|
|
241
|
+
if (!current || caller.edge.kind !== 'calls'
|
|
242
|
+
|| caller.edge.source !== current.id || !callerIds.has(caller.edge.target))
|
|
243
|
+
continue;
|
|
244
|
+
const relationRef = `code-relation:${caller.edge.source}:${caller.edge.target}:${caller.edge.kind}`;
|
|
245
|
+
mergeSegmentNode(additions, current, 'segmentGraphRank', callerIndex + 1, [relationRef]);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const item of additions.values()) {
|
|
249
|
+
const existing = ranked.get(item.node.id);
|
|
250
|
+
ranked.set(item.node.id, {
|
|
251
|
+
...(existing ?? item),
|
|
252
|
+
...(item.segmentRank ? { segmentRank: item.segmentRank } : {}),
|
|
253
|
+
...(item.segmentGraphRank ? { segmentGraphRank: item.segmentGraphRank } : {}),
|
|
254
|
+
...((existing?.segmentParentRefs?.length || item.segmentParentRefs?.length) ? {
|
|
255
|
+
segmentParentRefs: [...new Set([
|
|
256
|
+
...(existing?.segmentParentRefs ?? []), ...(item.segmentParentRefs ?? []),
|
|
257
|
+
])],
|
|
258
|
+
} : {}),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
status: additions.size > 0 ? 'ready' : 'empty_valid',
|
|
263
|
+
candidateCount: additions.size,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return {
|
|
268
|
+
status: 'unavailable', candidateCount: 0,
|
|
269
|
+
reasonCode: 'segment_query_unavailable',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async addVectorCandidates(ranked, query, projectId, expectedProfileHash) {
|
|
274
|
+
if (!this.dependencies.embedder || !this.dependencies.symbolStore || !expectedProfileHash) {
|
|
275
|
+
return { status: 'unavailable', candidateCount: 0, reasonCode: 'vector_provider_not_configured', candidateRefs: [] };
|
|
276
|
+
}
|
|
277
|
+
const storeProfile = this.dependencies.symbolStore.getProfile?.();
|
|
278
|
+
if (storeProfile && storeProfile.profileHash !== expectedProfileHash) {
|
|
279
|
+
return { status: 'invalid', candidateCount: 0, reasonCode: 'vector_profile_mismatch', candidateRefs: [] };
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const embedding = await requestValidatedEmbedding({
|
|
283
|
+
provider: this.dependencies.embedder,
|
|
284
|
+
purpose: 'code_retrieval',
|
|
285
|
+
text: query,
|
|
286
|
+
});
|
|
287
|
+
let vectorRank = 0;
|
|
288
|
+
const candidateRefs = [];
|
|
289
|
+
for (const result of this.dependencies.symbolStore.search(embedding, 30)) {
|
|
290
|
+
vectorRank += 1;
|
|
291
|
+
const node = this.dependencies.codegraph.getNode(result.id);
|
|
292
|
+
if (!node)
|
|
293
|
+
continue;
|
|
294
|
+
candidateRefs.push({ ref: codeStableRef(projectId, node), rank: vectorRank });
|
|
295
|
+
const existing = ranked.get(node.id);
|
|
296
|
+
ranked.set(node.id, existing ? { ...existing, vectorRank } : { node, vectorRank, root: false });
|
|
297
|
+
}
|
|
298
|
+
return { status: vectorRank > 0 ? 'ready' : 'empty_valid', candidateCount: vectorRank, candidateRefs };
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
const message = error instanceof Error ? error.message.toLowerCase() : '';
|
|
302
|
+
return {
|
|
303
|
+
status: message.includes('timeout') ? 'timeout' : 'unavailable',
|
|
304
|
+
candidateCount: 0,
|
|
305
|
+
candidateRefs: [],
|
|
306
|
+
reasonCode: message.includes('timeout') ? 'vector_query_timeout' : 'vector_query_unavailable',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
export class CodeEvidenceProducer {
|
|
312
|
+
runtime;
|
|
313
|
+
lastVectorCandidateRefs = [];
|
|
314
|
+
constructor(runtime) {
|
|
315
|
+
this.runtime = runtime;
|
|
316
|
+
}
|
|
317
|
+
getLastVectorCandidateRefs() {
|
|
318
|
+
return this.lastVectorCandidateRefs.map(item => ({ ...item }));
|
|
319
|
+
}
|
|
320
|
+
async search(context, request) {
|
|
321
|
+
this.lastVectorCandidateRefs = [];
|
|
322
|
+
if (request.channel !== 'code'
|
|
323
|
+
|| request.factsHash !== context.facts.factsHash
|
|
324
|
+
|| request.planHash !== context.plan.planHash
|
|
325
|
+
|| request.activationSnapshotHash !== context.activationSnapshotHash
|
|
326
|
+
|| stableRuntimeHash(request.identity) !== stableRuntimeHash(context.identity)) {
|
|
327
|
+
throw new Error('CODE_EVIDENCE_REQUEST_CONTEXT_MISMATCH');
|
|
328
|
+
}
|
|
329
|
+
if (context.plan.code.requirement === 'prohibited') {
|
|
330
|
+
throw new Error('CODE_EVIDENCE_CHANNEL_PROHIBITED');
|
|
331
|
+
}
|
|
332
|
+
const query = { ...context.plan.code.typedQuery, query: request.queryVariant.query };
|
|
333
|
+
const outputs = await Promise.all([this.runtime.search({
|
|
334
|
+
query,
|
|
335
|
+
projectId: context.scopeBindings.project.id,
|
|
336
|
+
contextHash: context.contextHash,
|
|
337
|
+
profileHash: request.filters.embeddingProfileHash,
|
|
338
|
+
tokenBudget: 8_000,
|
|
339
|
+
now: context.createdAt,
|
|
340
|
+
})]);
|
|
341
|
+
const candidates = dedupeCandidates(outputs.flatMap(output => output.candidates));
|
|
342
|
+
this.lastVectorCandidateRefs = outputs.flatMap(output => output.vectorCandidateRefs ?? []);
|
|
343
|
+
const gaps = dedupeGaps(outputs.flatMap(output => output.gaps));
|
|
344
|
+
const rejected = outputs.flatMap(output => output.rejectedRefs);
|
|
345
|
+
const producerLegs = outputs.flatMap(output => output.producerLegs)
|
|
346
|
+
.map(leg => ({ ...leg, parentReceiptIds: [request.receiptId] }));
|
|
347
|
+
const requiredLegFailure = producerLegs.find(leg => leg.required
|
|
348
|
+
&& leg.status !== 'ready' && leg.status !== 'empty_valid');
|
|
349
|
+
const unavailableGaps = requiredLegFailure ? dedupeGaps([...gaps, gap('unavailable_channel', requiredLegFailure.reasonCode ?? `${requiredLegFailure.kind}_unavailable`, 'code')]) : gaps;
|
|
350
|
+
const availability = outputs.every(output => !output.indexReady) || requiredLegFailure
|
|
351
|
+
? 'unavailable' : candidates.length === 0 ? 'empty_valid' : 'available';
|
|
352
|
+
return receipt(context, request, availability === 'unavailable' ? [] : candidates, rejected, unavailableGaps, availability, producerLegs, outputs.flatMap(output => output.vectorCandidateRefs ?? []));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function receipt(context, request, candidates, rejectedRefs, gaps, availability, producerLegs, vectorCandidateRefs) {
|
|
356
|
+
const contributingLegs = {};
|
|
357
|
+
for (const candidate of candidates) {
|
|
358
|
+
for (const leg of candidate.contributingLegs) {
|
|
359
|
+
(contributingLegs[leg.leg] ??= []).push({
|
|
360
|
+
ref: candidate.stableRef,
|
|
361
|
+
rank: leg.rank,
|
|
362
|
+
...(leg.score !== undefined ? { score: leg.score } : {}),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (vectorCandidateRefs.length > 0)
|
|
367
|
+
contributingLegs['code:vector'] = vectorCandidateRefs;
|
|
368
|
+
const body = {
|
|
369
|
+
schemaVersion: 'evidence-receipt.v1',
|
|
370
|
+
receiptId: `code:${stableRuntimeHash({ context: context.contextHash, ...(request.queryVariant ? { queryVariant: request.queryVariant } : {}), refs: candidates.map(item => item.stableRef), gaps }).slice(0, 32)}`,
|
|
371
|
+
parentReceiptIds: [request.receiptId],
|
|
372
|
+
taskContextHash: context.contextHash,
|
|
373
|
+
channel: 'code',
|
|
374
|
+
availability,
|
|
375
|
+
factsHash: context.facts.factsHash,
|
|
376
|
+
planHash: context.plan.planHash,
|
|
377
|
+
activationSnapshotHash: context.activationSnapshotHash,
|
|
378
|
+
selectedRefs: [],
|
|
379
|
+
legId: request.signatureHash,
|
|
380
|
+
rejectedRefs,
|
|
381
|
+
gaps,
|
|
382
|
+
contributingLegs,
|
|
383
|
+
producerLegs,
|
|
384
|
+
createdAt: context.createdAt,
|
|
385
|
+
};
|
|
386
|
+
return { candidates, gaps, availability, receipt: { ...body, receiptHash: stableRuntimeHash(body) } };
|
|
387
|
+
}
|
|
388
|
+
function producerLegPrefix(query) {
|
|
389
|
+
return `code:${stableRuntimeHash(query).slice(0, 16)}`;
|
|
390
|
+
}
|
|
391
|
+
function unavailableProducerLegs(query, reasonCode, vectorRequired, status = 'unavailable') {
|
|
392
|
+
const prefix = producerLegPrefix(query);
|
|
393
|
+
return ['lexical', 'vector', 'graph'].map(kind => ({
|
|
394
|
+
schemaVersion: 'evidence-producer-leg-receipt.v1',
|
|
395
|
+
legId: `${prefix}:${kind}`,
|
|
396
|
+
parentReceiptIds: [],
|
|
397
|
+
kind,
|
|
398
|
+
required: kind === 'lexical' || (kind === 'vector' && vectorRequired),
|
|
399
|
+
status,
|
|
400
|
+
candidateCount: 0,
|
|
401
|
+
reasonCode,
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
function codeStableRef(projectId, node) {
|
|
405
|
+
return codeEvidenceStableRef({
|
|
406
|
+
projectId,
|
|
407
|
+
normalizedPath: stableEvidencePath(node.filePath),
|
|
408
|
+
symbolId: node.id,
|
|
409
|
+
range: [node.startLine ?? 0, node.endLine ?? 0],
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function stableEvidencePath(value) {
|
|
413
|
+
return value.normalize('NFKC').replaceAll('\\', '/').replace(/^\.\//u, '');
|
|
414
|
+
}
|
|
415
|
+
function latinProseWords(query) {
|
|
416
|
+
const unique = new Set();
|
|
417
|
+
for (const word of query.match(/[\p{L}\p{N}]+/gu) ?? []) {
|
|
418
|
+
const normalized = word.normalize('NFD').replace(/\p{M}+/gu, '').toLocaleLowerCase('en-US');
|
|
419
|
+
if (normalized.length < 4 || normalized.length > 24
|
|
420
|
+
|| /^\p{N}+$/u.test(normalized) || PROSE_STOPWORDS.has(normalized))
|
|
421
|
+
continue;
|
|
422
|
+
unique.add(normalized);
|
|
423
|
+
if (unique.size === 16)
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
return [...unique];
|
|
427
|
+
}
|
|
428
|
+
function sourceBodyMatchScore(query, excerpt) {
|
|
429
|
+
const body = excerpt.normalize('NFKC').toLocaleLowerCase('en-US');
|
|
430
|
+
return new Set(latinProseWords(query).filter(word => body.includes(word))).size;
|
|
431
|
+
}
|
|
432
|
+
function resolveExactSegmentNode(codegraph, match) {
|
|
433
|
+
const matches = codegraph.getNodesByName(match.name).filter(node => node.name === match.name
|
|
434
|
+
&& stableEvidencePath(node.filePath) === stableEvidencePath(match.filePath)
|
|
435
|
+
&& node.kind === match.kind
|
|
436
|
+
&& node.startLine === match.startLine);
|
|
437
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
438
|
+
}
|
|
439
|
+
function resolveExactNode(codegraph, candidate) {
|
|
440
|
+
const current = codegraph.getNodesByName(candidate.name).filter(node => node.id === candidate.id
|
|
441
|
+
&& stableEvidencePath(node.filePath) === stableEvidencePath(candidate.filePath)
|
|
442
|
+
&& node.kind === candidate.kind
|
|
443
|
+
&& node.startLine === candidate.startLine);
|
|
444
|
+
return current.length === 1 ? current[0] : undefined;
|
|
445
|
+
}
|
|
446
|
+
function isCallerExpandable(node) {
|
|
447
|
+
return node.kind === 'function' || node.kind === 'method';
|
|
448
|
+
}
|
|
449
|
+
function mergeSegmentNode(ranked, node, rankKey, rank, parentRefs = []) {
|
|
450
|
+
const existing = ranked.get(node.id);
|
|
451
|
+
ranked.set(node.id, {
|
|
452
|
+
...(existing ?? { node, root: false }),
|
|
453
|
+
[rankKey]: Math.min(existing?.[rankKey] ?? Number.MAX_SAFE_INTEGER, rank),
|
|
454
|
+
...(parentRefs.length > 0 ? {
|
|
455
|
+
segmentParentRefs: [...new Set([...(existing?.segmentParentRefs ?? []), ...parentRefs])],
|
|
456
|
+
} : {}),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function graphEvidenceRanks(roots, edges) {
|
|
460
|
+
const adjacency = new Map();
|
|
461
|
+
for (const edge of edges) {
|
|
462
|
+
adjacency.set(edge.source, [...(adjacency.get(edge.source) ?? []), edge.target]);
|
|
463
|
+
adjacency.set(edge.target, [...(adjacency.get(edge.target) ?? []), edge.source]);
|
|
464
|
+
}
|
|
465
|
+
const ranks = new Map();
|
|
466
|
+
const queue = [];
|
|
467
|
+
for (const root of roots) {
|
|
468
|
+
if (ranks.has(root))
|
|
469
|
+
continue;
|
|
470
|
+
ranks.set(root, ranks.size + 1);
|
|
471
|
+
queue.push(root);
|
|
472
|
+
}
|
|
473
|
+
while (queue.length > 0) {
|
|
474
|
+
const current = queue.shift();
|
|
475
|
+
for (const next of adjacency.get(current) ?? []) {
|
|
476
|
+
if (ranks.has(next))
|
|
477
|
+
continue;
|
|
478
|
+
ranks.set(next, ranks.size + 1);
|
|
479
|
+
queue.push(next);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return ranks;
|
|
483
|
+
}
|
|
484
|
+
function relationRefs(id, edges) {
|
|
485
|
+
return edges.filter(edge => edge.source === id || edge.target === id)
|
|
486
|
+
.map(edge => `code-relation:${edge.source}:${edge.target}:${edge.kind}`);
|
|
487
|
+
}
|
|
488
|
+
function matchesTarget(path, targets) {
|
|
489
|
+
const normalized = normalizePath(path);
|
|
490
|
+
return targets.some(target => {
|
|
491
|
+
const anchor = normalizePath(target);
|
|
492
|
+
return normalized === anchor || normalized.endsWith(`/${anchor}`);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function normalizePath(value) {
|
|
496
|
+
return value.normalize('NFKC').replaceAll('\\', '/').replace(/^\.\//u, '').toLocaleLowerCase('en-US');
|
|
497
|
+
}
|
|
498
|
+
function normalizeSymbol(value) {
|
|
499
|
+
return value.normalize('NFKC').toLocaleLowerCase('en-US').replace(/[^\p{L}\p{N}_$]+/gu, '');
|
|
500
|
+
}
|
|
501
|
+
function estimateTokens(node) {
|
|
502
|
+
return Math.max(24, Math.ceil(`${node.name} ${node.signature ?? ''} ${node.filePath}`.length / 4));
|
|
503
|
+
}
|
|
504
|
+
function gap(kind, reason, resolver) {
|
|
505
|
+
return { kind, reason, evidenceRefs: [], resolver, priority: 1 };
|
|
506
|
+
}
|
|
507
|
+
function dedupeGaps(gaps) {
|
|
508
|
+
return [...new Map(gaps.map(item => [stableRuntimeHash(item), item])).values()];
|
|
509
|
+
}
|
|
510
|
+
function dedupeCandidates(candidates) {
|
|
511
|
+
const byRef = new Map();
|
|
512
|
+
for (const candidate of candidates) {
|
|
513
|
+
const existing = byRef.get(candidate.stableRef);
|
|
514
|
+
if (!existing || strengthRank(candidate.strength) > strengthRank(existing.strength))
|
|
515
|
+
byRef.set(candidate.stableRef, candidate);
|
|
516
|
+
}
|
|
517
|
+
return [...byRef.values()];
|
|
518
|
+
}
|
|
519
|
+
function strengthRank(value) {
|
|
520
|
+
return value === 'strong' ? 3 : value === 'supporting' ? 2 : 1;
|
|
521
|
+
}
|
|
522
|
+
//# sourceMappingURL=code-evidence-producer.js.map
|