@jcode.labs/ragmir 4.2.2 → 5.0.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/README.md +71 -160
- package/dist/chunking.d.ts +1 -1
- package/dist/chunking.d.ts.map +1 -1
- package/dist/chunking.js +17 -1
- package/dist/chunking.js.map +1 -1
- package/dist/cli-entry.js +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/cli-fast.d.ts +1 -3
- package/dist/cli-fast.d.ts.map +1 -1
- package/dist/cli-fast.js +2 -104
- package/dist/cli-fast.js.map +1 -1
- package/dist/cli-options.d.ts +0 -37
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +0 -53
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +9 -701
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -36
- package/dist/client.js.map +1 -1
- package/dist/config-migration.d.ts +6 -0
- package/dist/config-migration.d.ts.map +1 -0
- package/dist/config-migration.js +31 -0
- package/dist/config-migration.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -65
- package/dist/config.js.map +1 -1
- package/dist/context-resources.d.ts.map +1 -1
- package/dist/context-resources.js +2 -14
- package/dist/context-resources.js.map +1 -1
- package/dist/defaults.d.ts +0 -3
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +0 -13
- package/dist/defaults.js.map +1 -1
- package/dist/destroy.d.ts.map +1 -1
- package/dist/destroy.js +0 -2
- package/dist/destroy.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +1 -9
- package/dist/doctor.js.map +1 -1
- package/dist/document-structure.js.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +9 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +0 -7
- package/dist/evaluate.js.map +1 -1
- package/dist/files.js.map +1 -1
- package/dist/generation-retention.js.map +1 -1
- package/dist/gitignore.js.map +1 -1
- package/dist/index-diagnostics.js +1 -1
- package/dist/index-diagnostics.js.map +1 -1
- package/dist/index-policy.d.ts.map +1 -1
- package/dist/index-policy.js +3 -4
- package/dist/index-policy.js.map +1 -1
- package/dist/index-write-lock.js.map +1 -1
- package/dist/index.d.ts +4 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/index.js.map +1 -1
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +3 -18
- package/dist/ingest.js.map +1 -1
- package/dist/ingestion-metrics.d.ts +1 -1
- package/dist/ingestion-metrics.d.ts.map +1 -1
- package/dist/ingestion-metrics.js +0 -1
- package/dist/ingestion-metrics.js.map +1 -1
- package/dist/ingestion-state.d.ts +1 -2
- package/dist/ingestion-state.d.ts.map +1 -1
- package/dist/ingestion-state.js +0 -2
- package/dist/ingestion-state.js.map +1 -1
- package/dist/lexical-scoring.d.ts +13 -0
- package/dist/lexical-scoring.d.ts.map +1 -0
- package/dist/lexical-scoring.js +35 -0
- package/dist/lexical-scoring.js.map +1 -0
- package/dist/mcp-output.d.ts +4 -31
- package/dist/mcp-output.d.ts.map +1 -1
- package/dist/mcp-output.js +4 -123
- package/dist/mcp-output.js.map +1 -1
- package/dist/mcp-summaries.d.ts +1 -12
- package/dist/mcp-summaries.d.ts.map +1 -1
- package/dist/mcp-summaries.js +0 -112
- package/dist/mcp-summaries.js.map +1 -1
- package/dist/mcp.d.ts +1 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +22 -329
- package/dist/mcp.js.map +1 -1
- package/dist/ocr.d.ts +0 -1
- package/dist/ocr.d.ts.map +1 -1
- package/dist/ocr.js +0 -4
- package/dist/ocr.js.map +1 -1
- package/dist/parsing.js +13 -0
- package/dist/parsing.js.map +1 -1
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +2 -5
- package/dist/preview.js.map +1 -1
- package/dist/query-sanitizer.d.ts +2 -1
- package/dist/query-sanitizer.d.ts.map +1 -1
- package/dist/query-sanitizer.js +9 -127
- package/dist/query-sanitizer.js.map +1 -1
- package/dist/query.d.ts +1 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +95 -71
- package/dist/query.js.map +1 -1
- package/dist/ranking.d.ts +1 -1
- package/dist/ranking.d.ts.map +1 -1
- package/dist/ranking.js +52 -41
- package/dist/ranking.js.map +1 -1
- package/dist/runtime-info.js.map +1 -1
- package/dist/search-output.d.ts +3 -0
- package/dist/search-output.d.ts.map +1 -0
- package/dist/search-output.js +26 -0
- package/dist/search-output.js.map +1 -0
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +5 -37
- package/dist/security.js.map +1 -1
- package/dist/setup-prompt.d.ts +1 -1
- package/dist/setup-prompt.d.ts.map +1 -1
- package/dist/setup-prompt.js +22 -25
- package/dist/setup-prompt.js.map +1 -1
- package/dist/setup.js +1 -1
- package/dist/setup.js.map +1 -1
- package/dist/skill.d.ts +1 -4
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +3 -71
- package/dist/skill.js.map +1 -1
- package/dist/storage-maintenance.js.map +1 -1
- package/dist/store.js +9 -1
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +19 -156
- package/dist/types.d.ts.map +1 -1
- package/dist/upgrade.d.ts +3 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +20 -2
- package/dist/upgrade.js.map +1 -1
- package/dist/vector-index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workload.js.map +1 -1
- package/examples/document-evidence-benchmark/.ragmir/config.json +0 -9
- package/examples/document-evidence-benchmark/README.md +5 -3
- package/examples/document-evidence-benchmark/raw/rfp/security-questionnaire.md +1 -1
- package/examples/library-api-demo/README.md +17 -8
- package/examples/library-api-demo/run.mjs +6 -6
- package/examples/sovereign-rag-demo/.ragmir/config.json +3 -10
- package/examples/sovereign-rag-demo/README.md +21 -12
- package/package.json +21 -30
- package/skills/ragmir/SKILL.md +67 -199
- package/dist/access-log.d.ts +0 -30
- package/dist/access-log.d.ts.map +0 -1
- package/dist/access-log.js +0 -415
- package/dist/access-log.js.map +0 -1
- package/dist/cli-stdin.d.ts +0 -3
- package/dist/cli-stdin.d.ts.map +0 -1
- package/dist/cli-stdin.js +0 -15
- package/dist/cli-stdin.js.map +0 -1
- package/dist/portable-entry.d.ts +0 -5
- package/dist/portable-entry.d.ts.map +0 -1
- package/dist/portable-entry.js +0 -337
- package/dist/portable-entry.js.map +0 -1
- package/dist/portable.d.ts +0 -89
- package/dist/portable.d.ts.map +0 -1
- package/dist/portable.js +0 -1290
- package/dist/portable.js.map +0 -1
- package/dist/prompt-routing.d.ts +0 -12
- package/dist/prompt-routing.d.ts.map +0 -1
- package/dist/prompt-routing.js +0 -150
- package/dist/prompt-routing.js.map +0 -1
- package/dist/redaction.d.ts +0 -12
- package/dist/redaction.d.ts.map +0 -1
- package/dist/redaction.js +0 -253
- package/dist/redaction.js.map +0 -1
- package/dist/research.d.ts +0 -21
- package/dist/research.d.ts.map +0 -1
- package/dist/research.js +0 -667
- package/dist/research.js.map +0 -1
- package/dist/team-diagnostics.d.ts +0 -119
- package/dist/team-diagnostics.d.ts.map +0 -1
- package/dist/team-diagnostics.js +0 -515
- package/dist/team-diagnostics.js.map +0 -1
- package/dist/team-sync.d.ts +0 -49
- package/dist/team-sync.d.ts.map +0 -1
- package/dist/team-sync.js +0 -436
- package/dist/team-sync.js.map +0 -1
- package/skills/ragmir-audio-summary/SKILL.md +0 -80
- package/skills/ragmir-audio-summary/forge-voice.sh +0 -150
- package/skills/ragmir-audio-summary/listening-style.md +0 -92
- package/skills/ragmir-audio-summary/narration-templates.md +0 -162
- package/skills/ragmir-audio-summary/split-lines.py +0 -14
- package/skills/ragmir-audio-summary/xtts-voice.py +0 -46
- package/skills/ragmir-legal-dossier/SKILL.md +0 -143
- package/skills/ragmir-markdown-report/SKILL.md +0 -110
package/dist/research.js
DELETED
|
@@ -1,667 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fg from "fast-glob";
|
|
4
|
-
import { recordAccess } from "./access-log.js";
|
|
5
|
-
import { loadConfig } from "./config.js";
|
|
6
|
-
import { RagmirError } from "./errors.js";
|
|
7
|
-
import { countSkippedByReason, DEFAULT_FAST_GLOB_IGNORES, isSensitiveFilePath } from "./files.js";
|
|
8
|
-
import { auditWithConfig } from "./ingest.js";
|
|
9
|
-
import { operationSignal, throwIfAborted } from "./operation.js";
|
|
10
|
-
import { searchWithConfig } from "./query.js";
|
|
11
|
-
import { redactText } from "./redaction.js";
|
|
12
|
-
import { securityAuditWithConfig } from "./security.js";
|
|
13
|
-
import { closeIndexReadSnapshot, loadIndexReadSnapshot } from "./store.js";
|
|
14
|
-
import { normalizeForMatch } from "./text.js";
|
|
15
|
-
const DEFAULT_RESEARCH_QUERY_LIMIT = 5;
|
|
16
|
-
const DEFAULT_CODE_EVIDENCE_LIMIT = 20;
|
|
17
|
-
const DEFAULT_CODE_SCAN_MAX_FILES = 1_000;
|
|
18
|
-
const DEFAULT_CODE_SCAN_MAX_BYTES = 32 * 1024 * 1024;
|
|
19
|
-
const DEFAULT_CODE_SCAN_CONCURRENCY = 4;
|
|
20
|
-
const MAX_CODE_EVIDENCE_LIMIT = 100;
|
|
21
|
-
const MAX_CODE_SCAN_FILES = 10_000;
|
|
22
|
-
const MAX_CODE_SCAN_BYTES = 256 * 1024 * 1024;
|
|
23
|
-
const MAX_CODE_SCAN_CONCURRENCY = 16;
|
|
24
|
-
const COMPACT_SNIPPET_LENGTH = 260;
|
|
25
|
-
const CODE_SCAN_MAX_BYTES = 256_000;
|
|
26
|
-
const RESEARCH_RRF_K = 60;
|
|
27
|
-
const PRIMARY_QUERY_WEIGHT_FACTOR = 2;
|
|
28
|
-
const CODE_SCAN_EXTENSIONS = new Set([
|
|
29
|
-
".c",
|
|
30
|
-
".cjs",
|
|
31
|
-
".cpp",
|
|
32
|
-
".cs",
|
|
33
|
-
".go",
|
|
34
|
-
".java",
|
|
35
|
-
".js",
|
|
36
|
-
".json",
|
|
37
|
-
".jsonl",
|
|
38
|
-
".jsx",
|
|
39
|
-
".kt",
|
|
40
|
-
".md",
|
|
41
|
-
".mdx",
|
|
42
|
-
".mjs",
|
|
43
|
-
".mts",
|
|
44
|
-
".php",
|
|
45
|
-
".py",
|
|
46
|
-
".rb",
|
|
47
|
-
".rs",
|
|
48
|
-
".sql",
|
|
49
|
-
".txt",
|
|
50
|
-
".ts",
|
|
51
|
-
".tsx",
|
|
52
|
-
".vue",
|
|
53
|
-
".yaml",
|
|
54
|
-
".yml",
|
|
55
|
-
]);
|
|
56
|
-
const CODE_SCAN_IGNORE = [
|
|
57
|
-
...DEFAULT_FAST_GLOB_IGNORES,
|
|
58
|
-
"**/dist/**",
|
|
59
|
-
"**/build/**",
|
|
60
|
-
"**/.next/**",
|
|
61
|
-
"**/coverage/**",
|
|
62
|
-
"**/release-artifacts/**",
|
|
63
|
-
"**/bun.lock",
|
|
64
|
-
"**/bun.lockb",
|
|
65
|
-
"**/Cargo.lock",
|
|
66
|
-
"**/composer.lock",
|
|
67
|
-
"**/Gemfile.lock",
|
|
68
|
-
"**/go.sum",
|
|
69
|
-
"**/npm-shrinkwrap.json",
|
|
70
|
-
"**/package-lock.json",
|
|
71
|
-
"**/Pipfile.lock",
|
|
72
|
-
"**/pnpm-lock.yaml",
|
|
73
|
-
"**/poetry.lock",
|
|
74
|
-
"**/yarn.lock",
|
|
75
|
-
];
|
|
76
|
-
export async function research(query, options = {}) {
|
|
77
|
-
const config = await loadConfig(String(options.cwd ?? process.cwd()));
|
|
78
|
-
return researchWithConfig(query, options, config);
|
|
79
|
-
}
|
|
80
|
-
export async function researchWithConfig(query, options, config, connection, suppliedSnapshot) {
|
|
81
|
-
const signal = operationSignal(options);
|
|
82
|
-
throwIfAborted(signal);
|
|
83
|
-
const normalizedQuery = query.trim();
|
|
84
|
-
if (!normalizedQuery) {
|
|
85
|
-
throw new Error("Research query must not be empty.");
|
|
86
|
-
}
|
|
87
|
-
const topK = options.topK ?? config.topK;
|
|
88
|
-
const generatedQueries = researchQueries(normalizedQuery);
|
|
89
|
-
const includeCode = config.privacyProfile === "strict" ? false : options.includeCode !== false;
|
|
90
|
-
const budget = researchBudget(options);
|
|
91
|
-
const perQueryTopK = Math.max(2, Math.ceil(topK / 2));
|
|
92
|
-
const ownsSnapshot = suppliedSnapshot === undefined;
|
|
93
|
-
const snapshot = suppliedSnapshot ?? (await loadIndexReadSnapshot(config, connection));
|
|
94
|
-
try {
|
|
95
|
-
throwIfAborted(signal);
|
|
96
|
-
const [health, searchResults, codeScan] = await Promise.all([
|
|
97
|
-
researchHealthSnapshot(config, options.fullAudit === true, snapshot.manifest, signal),
|
|
98
|
-
Promise.all(generatedQueries.map(async (generatedQuery) => ({
|
|
99
|
-
query: generatedQuery,
|
|
100
|
-
results: await searchWithConfig(generatedQuery, {
|
|
101
|
-
cwd: config.projectRoot,
|
|
102
|
-
topK: perQueryTopK,
|
|
103
|
-
contextRadius: 0,
|
|
104
|
-
...(options.includePaths ? { includePaths: options.includePaths } : {}),
|
|
105
|
-
...(options.excludePaths ? { excludePaths: options.excludePaths } : {}),
|
|
106
|
-
...(options.contextPaths ? { contextPaths: options.contextPaths } : {}),
|
|
107
|
-
}, config, connection, signal, snapshot),
|
|
108
|
-
}))),
|
|
109
|
-
includeCode
|
|
110
|
-
? findCodeEvidence(config, normalizedQuery, budget, signal)
|
|
111
|
-
: Promise.resolve(emptyCodeScan()),
|
|
112
|
-
]);
|
|
113
|
-
throwIfAborted(signal);
|
|
114
|
-
const diversity = selectDiverseResearchEvidence(rankResearchEvidence(searchResults, normalizedQuery), topK, config.maxChunksPerDocument);
|
|
115
|
-
const evidence = diversity.evidence;
|
|
116
|
-
const codeEvidence = codeScan.evidence;
|
|
117
|
-
const gaps = researchGaps({
|
|
118
|
-
indexAvailable: health.indexAvailable,
|
|
119
|
-
evidenceCount: evidence.length,
|
|
120
|
-
codeEvidenceCount: codeEvidence.length,
|
|
121
|
-
includeCode,
|
|
122
|
-
missingFromIndex: health.audit.missingFromIndex,
|
|
123
|
-
staleInIndex: health.audit.staleInIndex,
|
|
124
|
-
emptyTextFiles: health.audit.emptyTextFiles,
|
|
125
|
-
securityWarnings: health.securityWarnings.length,
|
|
126
|
-
unsupportedFiles: health.audit.unsupportedFiles,
|
|
127
|
-
oversizedFiles: health.audit.oversizedFiles,
|
|
128
|
-
duplicateCandidates: health.sourceDiagnostics.duplicateCandidates.length,
|
|
129
|
-
archiveCandidates: health.sourceDiagnostics.archiveCandidates.length,
|
|
130
|
-
mirrorCandidates: health.sourceDiagnostics.mirrorCandidates.length,
|
|
131
|
-
});
|
|
132
|
-
await recordAccess(config, {
|
|
133
|
-
action: "research",
|
|
134
|
-
query: normalizedQuery,
|
|
135
|
-
topK,
|
|
136
|
-
resultCount: evidence.length,
|
|
137
|
-
});
|
|
138
|
-
return {
|
|
139
|
-
query: normalizedQuery,
|
|
140
|
-
generatedQueries,
|
|
141
|
-
ready: health.indexAvailable &&
|
|
142
|
-
evidence.length > 0 &&
|
|
143
|
-
health.audit.missingFromIndex === 0 &&
|
|
144
|
-
health.audit.staleInIndex === 0 &&
|
|
145
|
-
health.audit.emptyTextFiles === 0 &&
|
|
146
|
-
health.audit.oversizedFiles === 0 &&
|
|
147
|
-
health.securityWarnings.length === 0,
|
|
148
|
-
audit: health.audit,
|
|
149
|
-
securityWarnings: health.securityWarnings,
|
|
150
|
-
sourceDiagnostics: health.sourceDiagnostics,
|
|
151
|
-
evidence,
|
|
152
|
-
codeEvidence,
|
|
153
|
-
budgets: {
|
|
154
|
-
timeoutMs: options.timeoutMs ?? null,
|
|
155
|
-
evidenceTopK: topK,
|
|
156
|
-
maxChunksPerDocument: config.maxChunksPerDocument,
|
|
157
|
-
diversityBackfillActivated: diversity.backfillActivated,
|
|
158
|
-
codeEvidenceTopK: budget.codeTopK,
|
|
159
|
-
codeScanMaxFiles: budget.codeScanMaxFiles,
|
|
160
|
-
codeScanMaxBytes: budget.codeScanMaxBytes,
|
|
161
|
-
codeScanConcurrency: budget.codeScanConcurrency,
|
|
162
|
-
codeFilesScanned: codeScan.filesScanned,
|
|
163
|
-
codeBytesScanned: codeScan.bytesScanned,
|
|
164
|
-
codeScanTruncated: codeScan.truncated,
|
|
165
|
-
},
|
|
166
|
-
gaps,
|
|
167
|
-
nextSteps: researchNextSteps(gaps),
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
if (ownsSnapshot) {
|
|
172
|
-
closeIndexReadSnapshot(snapshot, config);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
export function compactSearchResults(results, maxLength = COMPACT_SNIPPET_LENGTH) {
|
|
177
|
-
return results.map((result) => ({
|
|
178
|
-
source: result.source,
|
|
179
|
-
relativePath: result.relativePath,
|
|
180
|
-
chunkIndex: result.chunkIndex,
|
|
181
|
-
contextPath: result.contextPath,
|
|
182
|
-
citation: result.citation,
|
|
183
|
-
snippet: compactText(result.text, maxLength),
|
|
184
|
-
distance: result.distance,
|
|
185
|
-
lineStart: result.lineStart,
|
|
186
|
-
lineEnd: result.lineEnd,
|
|
187
|
-
pageStart: result.pageStart,
|
|
188
|
-
pageEnd: result.pageEnd,
|
|
189
|
-
...(result.score === undefined ? {} : { score: result.score }),
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
export function compactResearchReport(report) {
|
|
193
|
-
return {
|
|
194
|
-
...report,
|
|
195
|
-
evidence: report.evidence.map((evidence) => ({
|
|
196
|
-
source: evidence.source,
|
|
197
|
-
relativePath: evidence.relativePath,
|
|
198
|
-
chunkIndex: evidence.chunkIndex,
|
|
199
|
-
contextPath: evidence.contextPath,
|
|
200
|
-
citation: evidence.citation,
|
|
201
|
-
snippet: compactText(evidence.text),
|
|
202
|
-
distance: evidence.distance,
|
|
203
|
-
charStart: evidence.charStart,
|
|
204
|
-
charEnd: evidence.charEnd,
|
|
205
|
-
lineStart: evidence.lineStart,
|
|
206
|
-
lineEnd: evidence.lineEnd,
|
|
207
|
-
pageStart: evidence.pageStart,
|
|
208
|
-
pageEnd: evidence.pageEnd,
|
|
209
|
-
queries: evidence.queries,
|
|
210
|
-
bestRank: evidence.bestRank,
|
|
211
|
-
researchScore: evidence.researchScore,
|
|
212
|
-
})),
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
function researchQueries(query) {
|
|
216
|
-
const trimmed = query.trim();
|
|
217
|
-
const keywordQuery = meaningfulTerms(trimmed).join(" ");
|
|
218
|
-
const queries = [
|
|
219
|
-
trimmed,
|
|
220
|
-
keywordQuery,
|
|
221
|
-
...researchSuffixes(trimmed).map((suffix) => `${trimmed} ${suffix}`),
|
|
222
|
-
].filter(Boolean);
|
|
223
|
-
return [...new Set(queries)].slice(0, DEFAULT_RESEARCH_QUERY_LIMIT);
|
|
224
|
-
}
|
|
225
|
-
export function rankResearchEvidence(searchResults, primaryQuery) {
|
|
226
|
-
const canonicalResults = [...searchResults].sort((left, right) => compareResearchQueries(left.query, right.query, primaryQuery));
|
|
227
|
-
const primaryWeight = Math.max(1, canonicalResults.length * PRIMARY_QUERY_WEIGHT_FACTOR);
|
|
228
|
-
const bySource = new Map();
|
|
229
|
-
for (const searchResult of canonicalResults) {
|
|
230
|
-
const weight = searchResult.query === primaryQuery ? primaryWeight : 1;
|
|
231
|
-
for (const [index, result] of searchResult.results.entries()) {
|
|
232
|
-
const key = `${result.relativePath}\0${result.chunkIndex}`;
|
|
233
|
-
const rank = index + 1;
|
|
234
|
-
const existing = bySource.get(key);
|
|
235
|
-
if (existing) {
|
|
236
|
-
existing.queryRanks.set(searchResult.query, Math.min(existing.queryRanks.get(searchResult.query) ?? rank, rank));
|
|
237
|
-
existing.researchScore += weight / (RESEARCH_RRF_K + rank);
|
|
238
|
-
if (compareResearchResultChoice(searchResult.query, rank, existing.resultQuery, existing.resultRank, primaryQuery) < 0) {
|
|
239
|
-
existing.result = result;
|
|
240
|
-
existing.resultQuery = searchResult.query;
|
|
241
|
-
existing.resultRank = rank;
|
|
242
|
-
}
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
bySource.set(key, {
|
|
246
|
-
result,
|
|
247
|
-
resultQuery: searchResult.query,
|
|
248
|
-
resultRank: rank,
|
|
249
|
-
queryRanks: new Map([[searchResult.query, rank]]),
|
|
250
|
-
researchScore: weight / (RESEARCH_RRF_K + rank),
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return [...bySource.values()]
|
|
255
|
-
.map(({ result, queryRanks, researchScore }) => ({
|
|
256
|
-
source: result.source,
|
|
257
|
-
relativePath: result.relativePath,
|
|
258
|
-
chunkIndex: result.chunkIndex,
|
|
259
|
-
contextPath: result.contextPath,
|
|
260
|
-
citation: result.citation,
|
|
261
|
-
text: result.text,
|
|
262
|
-
distance: result.distance,
|
|
263
|
-
charStart: result.charStart,
|
|
264
|
-
charEnd: result.charEnd,
|
|
265
|
-
lineStart: result.lineStart,
|
|
266
|
-
lineEnd: result.lineEnd,
|
|
267
|
-
pageStart: result.pageStart,
|
|
268
|
-
pageEnd: result.pageEnd,
|
|
269
|
-
queries: [...queryRanks.keys()].sort((left, right) => compareResearchQueries(left, right, primaryQuery)),
|
|
270
|
-
bestRank: Math.min(...queryRanks.values()),
|
|
271
|
-
researchScore: Number(researchScore.toFixed(12)),
|
|
272
|
-
}))
|
|
273
|
-
.sort(compareResearchEvidence);
|
|
274
|
-
}
|
|
275
|
-
export function selectDiverseResearchEvidence(rankedEvidence, topK, maxChunksPerDocument) {
|
|
276
|
-
const uniqueEvidence = deduplicateResearchText(rankedEvidence);
|
|
277
|
-
const evidence = [];
|
|
278
|
-
const selected = new Set();
|
|
279
|
-
const perDocument = new Map();
|
|
280
|
-
const appendEvidence = (candidate, enforceDocumentCap, allowOverlap) => {
|
|
281
|
-
const key = `${candidate.relativePath}\0${candidate.chunkIndex}`;
|
|
282
|
-
if (selected.has(key)) {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
if (!allowOverlap &&
|
|
286
|
-
evidence.some((selectedEvidence) => overlapsResearchSpan(selectedEvidence, candidate))) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
const documentCount = perDocument.get(candidate.relativePath) ?? 0;
|
|
290
|
-
if (enforceDocumentCap && documentCount >= maxChunksPerDocument) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
evidence.push(candidate);
|
|
294
|
-
selected.add(key);
|
|
295
|
-
perDocument.set(candidate.relativePath, documentCount + 1);
|
|
296
|
-
return true;
|
|
297
|
-
};
|
|
298
|
-
for (const candidate of uniqueEvidence) {
|
|
299
|
-
appendEvidence(candidate, true, false);
|
|
300
|
-
if (evidence.length >= topK) {
|
|
301
|
-
return { evidence, backfillActivated: false };
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
let backfillActivated = false;
|
|
305
|
-
for (const candidate of uniqueEvidence) {
|
|
306
|
-
if (appendEvidence(candidate, false, false)) {
|
|
307
|
-
backfillActivated = true;
|
|
308
|
-
}
|
|
309
|
-
if (evidence.length >= topK) {
|
|
310
|
-
return { evidence, backfillActivated };
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
for (const candidate of uniqueEvidence) {
|
|
314
|
-
if (appendEvidence(candidate, false, true)) {
|
|
315
|
-
backfillActivated = true;
|
|
316
|
-
}
|
|
317
|
-
if (evidence.length >= topK) {
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return { evidence, backfillActivated };
|
|
322
|
-
}
|
|
323
|
-
function deduplicateResearchText(rankedEvidence) {
|
|
324
|
-
const textKeys = new Set();
|
|
325
|
-
return rankedEvidence.filter((evidence) => {
|
|
326
|
-
const textKey = evidence.text.replace(/\s+/gu, " ").trim().toLowerCase();
|
|
327
|
-
if (textKeys.has(textKey)) {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
textKeys.add(textKey);
|
|
331
|
-
return true;
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
function overlapsResearchSpan(left, right) {
|
|
335
|
-
return (left.relativePath === right.relativePath &&
|
|
336
|
-
typeof left.charStart === "number" &&
|
|
337
|
-
typeof left.charEnd === "number" &&
|
|
338
|
-
typeof right.charStart === "number" &&
|
|
339
|
-
typeof right.charEnd === "number" &&
|
|
340
|
-
left.charStart < right.charEnd &&
|
|
341
|
-
right.charStart < left.charEnd);
|
|
342
|
-
}
|
|
343
|
-
async function researchHealthSnapshot(config, fullAudit, manifest, signal) {
|
|
344
|
-
const operationOptions = signal ? { signal } : {};
|
|
345
|
-
if (fullAudit) {
|
|
346
|
-
const [auditReport, securityReport] = await Promise.all([
|
|
347
|
-
auditWithConfig(config, operationOptions),
|
|
348
|
-
securityAuditWithConfig(config, operationOptions),
|
|
349
|
-
]);
|
|
350
|
-
throwIfAborted(signal);
|
|
351
|
-
return {
|
|
352
|
-
indexAvailable: manifest !== null,
|
|
353
|
-
audit: {
|
|
354
|
-
mode: "full",
|
|
355
|
-
inventoryVerified: true,
|
|
356
|
-
supportedFiles: auditReport.supportedFiles.length,
|
|
357
|
-
supportedBytes: auditReport.supportedBytes,
|
|
358
|
-
largestFileBytes: auditReport.largestFileBytes,
|
|
359
|
-
skippedFiles: auditReport.skippedFiles.length,
|
|
360
|
-
unsupportedFiles: countSkippedByReason(auditReport.skippedFiles, "unsupported-extension"),
|
|
361
|
-
oversizedFiles: countSkippedByReason(auditReport.skippedFiles, "oversized"),
|
|
362
|
-
indexedFiles: auditReport.indexedFiles.length,
|
|
363
|
-
totalChunks: auditReport.totalChunks,
|
|
364
|
-
missingFromIndex: auditReport.missingFromIndex.length,
|
|
365
|
-
staleInIndex: auditReport.staleInIndex.length,
|
|
366
|
-
emptyTextFiles: auditReport.emptyTextFiles.length,
|
|
367
|
-
},
|
|
368
|
-
securityWarnings: securityReport.warnings,
|
|
369
|
-
sourceDiagnostics: auditReport.sourceDiagnostics,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
const health = manifest?.health;
|
|
373
|
-
return {
|
|
374
|
-
indexAvailable: manifest !== null && health !== undefined,
|
|
375
|
-
audit: {
|
|
376
|
-
mode: "manifest",
|
|
377
|
-
inventoryVerified: false,
|
|
378
|
-
supportedFiles: health?.supportedFiles ?? 0,
|
|
379
|
-
supportedBytes: health?.supportedBytes ?? 0,
|
|
380
|
-
largestFileBytes: health?.largestFileBytes ?? 0,
|
|
381
|
-
skippedFiles: health?.skippedFiles ?? 0,
|
|
382
|
-
unsupportedFiles: health?.unsupportedFiles ?? 0,
|
|
383
|
-
oversizedFiles: health?.oversizedFiles ?? 0,
|
|
384
|
-
indexedFiles: manifest?.fileCount ?? 0,
|
|
385
|
-
totalChunks: manifest?.chunkCount ?? 0,
|
|
386
|
-
missingFromIndex: health?.missingFromIndex ?? 0,
|
|
387
|
-
staleInIndex: health?.staleInIndex ?? manifest?.staleFiles?.length ?? 0,
|
|
388
|
-
emptyTextFiles: health?.emptyTextFiles ?? 0,
|
|
389
|
-
},
|
|
390
|
-
securityWarnings: health?.securityWarnings ?? [],
|
|
391
|
-
sourceDiagnostics: health?.sourceDiagnostics ?? emptySourceDiagnostics(),
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
function researchBudget(options) {
|
|
395
|
-
return {
|
|
396
|
-
codeTopK: boundedResearchOption("codeTopK", options.codeTopK, DEFAULT_CODE_EVIDENCE_LIMIT, MAX_CODE_EVIDENCE_LIMIT),
|
|
397
|
-
codeScanMaxFiles: boundedResearchOption("codeScanMaxFiles", options.codeScanMaxFiles, DEFAULT_CODE_SCAN_MAX_FILES, MAX_CODE_SCAN_FILES),
|
|
398
|
-
codeScanMaxBytes: boundedResearchOption("codeScanMaxBytes", options.codeScanMaxBytes, DEFAULT_CODE_SCAN_MAX_BYTES, MAX_CODE_SCAN_BYTES),
|
|
399
|
-
codeScanConcurrency: boundedResearchOption("codeScanConcurrency", options.codeScanConcurrency, DEFAULT_CODE_SCAN_CONCURRENCY, MAX_CODE_SCAN_CONCURRENCY),
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
function boundedResearchOption(name, value, fallback, maximum) {
|
|
403
|
-
const resolved = value ?? fallback;
|
|
404
|
-
if (!Number.isSafeInteger(resolved) || resolved <= 0 || resolved > maximum) {
|
|
405
|
-
throw new RagmirError("INVALID_ARGUMENT", `${name} must be a positive integer no greater than ${maximum}.`);
|
|
406
|
-
}
|
|
407
|
-
return resolved;
|
|
408
|
-
}
|
|
409
|
-
function compareResearchEvidence(left, right) {
|
|
410
|
-
return (right.researchScore - left.researchScore ||
|
|
411
|
-
left.bestRank - right.bestRank ||
|
|
412
|
-
left.relativePath.localeCompare(right.relativePath) ||
|
|
413
|
-
left.chunkIndex - right.chunkIndex);
|
|
414
|
-
}
|
|
415
|
-
function compareResearchQueries(left, right, primaryQuery) {
|
|
416
|
-
if (left === primaryQuery) {
|
|
417
|
-
return right === primaryQuery ? 0 : -1;
|
|
418
|
-
}
|
|
419
|
-
if (right === primaryQuery) {
|
|
420
|
-
return 1;
|
|
421
|
-
}
|
|
422
|
-
return left.localeCompare(right);
|
|
423
|
-
}
|
|
424
|
-
function compareResearchResultChoice(query, rank, currentQuery, currentRank, primaryQuery) {
|
|
425
|
-
const primaryDifference = Number(query !== primaryQuery) - Number(currentQuery !== primaryQuery);
|
|
426
|
-
return primaryDifference || rank - currentRank || query.localeCompare(currentQuery);
|
|
427
|
-
}
|
|
428
|
-
function researchSuffixes(query) {
|
|
429
|
-
if (/\p{Script=Thai}/u.test(query)) {
|
|
430
|
-
return [
|
|
431
|
-
"ขอบเขต ข้อกำหนด กฎ",
|
|
432
|
-
"ผู้เกี่ยวข้อง สิทธิ์ ขั้นตอน สถานะ การตรวจสอบ",
|
|
433
|
-
"วันที่ กำหนดเวลา ความเสี่ยง อุปสรรค",
|
|
434
|
-
"การเชื่อมต่อ ข้อมูล การพึ่งพา",
|
|
435
|
-
];
|
|
436
|
-
}
|
|
437
|
-
if (/\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}/u.test(query)) {
|
|
438
|
-
return [
|
|
439
|
-
"範囲 要件 規則",
|
|
440
|
-
"関係者 権限 手順 状態 検証",
|
|
441
|
-
"日付 期限 計画 リスク",
|
|
442
|
-
"統合 API データ 依存関係",
|
|
443
|
-
];
|
|
444
|
-
}
|
|
445
|
-
if (/[àâçéèêëîïôùûüÿœ]/iu.test(query) ||
|
|
446
|
-
/\b(avec|dans|des|exigences|les|politique|pour|quelle?|règles?|une)\b/iu.test(query)) {
|
|
447
|
-
return [
|
|
448
|
-
"périmètre exigences règles",
|
|
449
|
-
"acteurs permissions processus statut validation",
|
|
450
|
-
"dates échéances planification risques blocages",
|
|
451
|
-
"intégration API données dépendances",
|
|
452
|
-
];
|
|
453
|
-
}
|
|
454
|
-
return [
|
|
455
|
-
"scope requirements rules",
|
|
456
|
-
"actors permissions workflow status validation",
|
|
457
|
-
"dates deadlines planning risks blockers",
|
|
458
|
-
"integration API data model export dependencies",
|
|
459
|
-
];
|
|
460
|
-
}
|
|
461
|
-
function emptySourceDiagnostics() {
|
|
462
|
-
return { duplicateCandidates: [], archiveCandidates: [], mirrorCandidates: [] };
|
|
463
|
-
}
|
|
464
|
-
async function findCodeEvidence(config, query, budget, signal) {
|
|
465
|
-
throwIfAborted(signal);
|
|
466
|
-
const terms = meaningfulTerms(query);
|
|
467
|
-
if (terms.length === 0) {
|
|
468
|
-
return emptyCodeScan();
|
|
469
|
-
}
|
|
470
|
-
const ignore = [...CODE_SCAN_IGNORE, ...projectRelativeIgnores(config)];
|
|
471
|
-
const minimumMatchedTerms = terms.length === 1 ? 1 : 2;
|
|
472
|
-
const entries = (await fg("**/*", {
|
|
473
|
-
cwd: config.projectRoot,
|
|
474
|
-
absolute: true,
|
|
475
|
-
onlyFiles: true,
|
|
476
|
-
dot: true,
|
|
477
|
-
followSymbolicLinks: false,
|
|
478
|
-
ignore,
|
|
479
|
-
objectMode: true,
|
|
480
|
-
stats: true,
|
|
481
|
-
unique: true,
|
|
482
|
-
}));
|
|
483
|
-
const eligible = entries
|
|
484
|
-
.map((entry) => {
|
|
485
|
-
const absolutePath = path.isAbsolute(entry.path)
|
|
486
|
-
? entry.path
|
|
487
|
-
: path.resolve(config.projectRoot, entry.path);
|
|
488
|
-
return {
|
|
489
|
-
absolutePath,
|
|
490
|
-
relativePath: path.relative(config.projectRoot, absolutePath),
|
|
491
|
-
size: entry.stats?.size ?? 0,
|
|
492
|
-
};
|
|
493
|
-
})
|
|
494
|
-
.filter((entry) => isScannableCodePath(entry.absolutePath) &&
|
|
495
|
-
!isSensitiveFilePath(entry.absolutePath) &&
|
|
496
|
-
entry.size <= CODE_SCAN_MAX_BYTES)
|
|
497
|
-
.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
498
|
-
const selected = [];
|
|
499
|
-
let bytesScanned = 0;
|
|
500
|
-
for (const entry of eligible) {
|
|
501
|
-
if (selected.length >= budget.codeScanMaxFiles) {
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
if (bytesScanned + entry.size > budget.codeScanMaxBytes) {
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
selected.push(entry);
|
|
508
|
-
bytesScanned += entry.size;
|
|
509
|
-
}
|
|
510
|
-
const candidates = [];
|
|
511
|
-
for (let offset = 0; offset < selected.length; offset += budget.codeScanConcurrency) {
|
|
512
|
-
throwIfAborted(signal);
|
|
513
|
-
const batch = selected.slice(offset, offset + budget.codeScanConcurrency);
|
|
514
|
-
const matches = await Promise.all(batch.map((entry) => scanCodeEntry(entry, query, terms, minimumMatchedTerms, config, signal)));
|
|
515
|
-
for (const match of matches) {
|
|
516
|
-
if (match) {
|
|
517
|
-
candidates.push(match);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return {
|
|
522
|
-
evidence: candidates.sort(compareCodeEvidence).slice(0, budget.codeTopK),
|
|
523
|
-
filesScanned: selected.length,
|
|
524
|
-
bytesScanned,
|
|
525
|
-
truncated: selected.length < eligible.length,
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
async function scanCodeEntry(entry, query, terms, minimumMatchedTerms, config, signal) {
|
|
529
|
-
const content = await readFile(entry.absolutePath, { encoding: "utf8", signal }).catch(() => null);
|
|
530
|
-
throwIfAborted(signal);
|
|
531
|
-
if (content === null) {
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
const normalizedQuery = normalizeForMatch(query);
|
|
535
|
-
let best = null;
|
|
536
|
-
for (const [index, line] of content.split(/\r?\n/u).entries()) {
|
|
537
|
-
const normalizedLine = normalizeForMatch(line);
|
|
538
|
-
const matchedTerms = terms.filter((term) => normalizedLine.includes(term));
|
|
539
|
-
if (matchedTerms.length < minimumMatchedTerms) {
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
const redactedSnippet = redactText(line.trim(), config).text;
|
|
543
|
-
const candidate = {
|
|
544
|
-
relativePath: entry.relativePath,
|
|
545
|
-
lineNumber: index + 1,
|
|
546
|
-
snippet: redactedSnippet.slice(0, COMPACT_SNIPPET_LENGTH),
|
|
547
|
-
matchedTerms,
|
|
548
|
-
score: matchedTerms.length * 100 + Number(normalizedLine.includes(normalizedQuery)) * 1_000,
|
|
549
|
-
};
|
|
550
|
-
if (!best || compareCodeEvidence(candidate, best) < 0) {
|
|
551
|
-
best = candidate;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
return best;
|
|
555
|
-
}
|
|
556
|
-
function emptyCodeScan() {
|
|
557
|
-
return { evidence: [], filesScanned: 0, bytesScanned: 0, truncated: false };
|
|
558
|
-
}
|
|
559
|
-
function compareCodeEvidence(a, b) {
|
|
560
|
-
return (b.score - a.score ||
|
|
561
|
-
b.matchedTerms.length - a.matchedTerms.length ||
|
|
562
|
-
a.relativePath.localeCompare(b.relativePath) ||
|
|
563
|
-
a.lineNumber - b.lineNumber);
|
|
564
|
-
}
|
|
565
|
-
function projectRelativeIgnores(config) {
|
|
566
|
-
return [config.rawDir, config.storageDir, config.embeddingModelPath]
|
|
567
|
-
.map((absolutePath) => path.relative(config.projectRoot, absolutePath))
|
|
568
|
-
.filter((relativePath) => relativePath && !relativePath.startsWith(".."))
|
|
569
|
-
.map((relativePath) => `${relativePath}/**`);
|
|
570
|
-
}
|
|
571
|
-
function isScannableCodePath(absolutePath) {
|
|
572
|
-
const extension = path.extname(absolutePath).toLowerCase();
|
|
573
|
-
return CODE_SCAN_EXTENSIONS.has(extension);
|
|
574
|
-
}
|
|
575
|
-
function meaningfulTerms(query) {
|
|
576
|
-
return [
|
|
577
|
-
...new Set(normalizeForMatch(query)
|
|
578
|
-
.match(/[\p{L}\p{N}]{3,}/gu)
|
|
579
|
-
?.filter((term) => !STOP_WORDS.has(term)) ?? []),
|
|
580
|
-
].slice(0, 8);
|
|
581
|
-
}
|
|
582
|
-
function researchGaps(input) {
|
|
583
|
-
const gaps = [];
|
|
584
|
-
if (!input.indexAvailable) {
|
|
585
|
-
gaps.push("No active index manifest is available for research.");
|
|
586
|
-
}
|
|
587
|
-
if (input.evidenceCount === 0) {
|
|
588
|
-
gaps.push("No retrieved evidence matched the research query.");
|
|
589
|
-
}
|
|
590
|
-
if (input.includeCode && input.codeEvidenceCount === 0) {
|
|
591
|
-
gaps.push("No code evidence matched the research query.");
|
|
592
|
-
}
|
|
593
|
-
if (input.missingFromIndex > 0) {
|
|
594
|
-
gaps.push(`${input.missingFromIndex} supported source files are missing from the index.`);
|
|
595
|
-
}
|
|
596
|
-
if (input.staleInIndex > 0) {
|
|
597
|
-
gaps.push(`${input.staleInIndex} indexed source files are stale.`);
|
|
598
|
-
}
|
|
599
|
-
if (input.emptyTextFiles > 0) {
|
|
600
|
-
gaps.push(`${input.emptyTextFiles} supported source files produced no indexable text.`);
|
|
601
|
-
}
|
|
602
|
-
if (input.securityWarnings > 0) {
|
|
603
|
-
gaps.push(`${input.securityWarnings} security warnings require review.`);
|
|
604
|
-
}
|
|
605
|
-
if (input.unsupportedFiles > 0) {
|
|
606
|
-
gaps.push(`${input.unsupportedFiles} source files were skipped because their type is unsupported.`);
|
|
607
|
-
}
|
|
608
|
-
if (input.oversizedFiles > 0) {
|
|
609
|
-
gaps.push(`${input.oversizedFiles} source files exceeded maxFileBytes and were skipped.`);
|
|
610
|
-
}
|
|
611
|
-
if (input.duplicateCandidates > 0) {
|
|
612
|
-
gaps.push(`${input.duplicateCandidates} possible duplicate source groups need source-truth review.`);
|
|
613
|
-
}
|
|
614
|
-
if (input.archiveCandidates > 0 || input.mirrorCandidates > 0) {
|
|
615
|
-
gaps.push("Some source paths look like archives, exports, raw files, or drive mirrors.");
|
|
616
|
-
}
|
|
617
|
-
return gaps;
|
|
618
|
-
}
|
|
619
|
-
function researchNextSteps(gaps) {
|
|
620
|
-
if (gaps.length === 0) {
|
|
621
|
-
return [
|
|
622
|
-
"Use the cited evidence as grounded context for an AI agent or reviewer.",
|
|
623
|
-
"Run targeted searches for names, dates, amounts, and decisions before high-stakes conclusions.",
|
|
624
|
-
];
|
|
625
|
-
}
|
|
626
|
-
return gaps.map((gap) => {
|
|
627
|
-
if (gap.includes("missing") || gap.includes("stale")) {
|
|
628
|
-
return "Run `rgr doctor --fix`, then rerun `rgr research`.";
|
|
629
|
-
}
|
|
630
|
-
if (gap.includes("unsupported") || gap.includes("maxFileBytes")) {
|
|
631
|
-
return "Run `rgr audit --unsupported` and transcribe, OCR, convert, or explicitly configure unsupported formats.";
|
|
632
|
-
}
|
|
633
|
-
if (gap.includes("indexable text")) {
|
|
634
|
-
return "Configure local OCR, convert the affected files, or add extracted text, then re-ingest.";
|
|
635
|
-
}
|
|
636
|
-
if (gap.includes("duplicate") || gap.includes("archive") || gap.includes("mirror")) {
|
|
637
|
-
return "Review source diagnostics and prefer the canonical source before presenting conclusions.";
|
|
638
|
-
}
|
|
639
|
-
if (gap.includes("code evidence")) {
|
|
640
|
-
return "Run repository-aware code search to compare documents with implementation.";
|
|
641
|
-
}
|
|
642
|
-
return "Add or refresh source documents, then rerun the research command.";
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
function compactText(text, maxLength = COMPACT_SNIPPET_LENGTH) {
|
|
646
|
-
const normalized = text.replace(/\s+/gu, " ").trim();
|
|
647
|
-
if (normalized.length <= maxLength) {
|
|
648
|
-
return normalized;
|
|
649
|
-
}
|
|
650
|
-
return `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
651
|
-
}
|
|
652
|
-
const STOP_WORDS = new Set([
|
|
653
|
-
"about",
|
|
654
|
-
"avec",
|
|
655
|
-
"dans",
|
|
656
|
-
"des",
|
|
657
|
-
"for",
|
|
658
|
-
"les",
|
|
659
|
-
"pour",
|
|
660
|
-
"que",
|
|
661
|
-
"qui",
|
|
662
|
-
"sur",
|
|
663
|
-
"the",
|
|
664
|
-
"une",
|
|
665
|
-
"what",
|
|
666
|
-
]);
|
|
667
|
-
//# sourceMappingURL=research.js.map
|