@jcode.labs/ragmir 2.0.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 +102 -0
- package/dist/access-log.d.ts +11 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/access-log.js +145 -0
- package/dist/access-log.js.map +1 -0
- package/dist/chunking.d.ts +3 -0
- package/dist/chunking.d.ts.map +1 -0
- package/dist/chunking.js +58 -0
- package/dist/chunking.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +944 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +224 -0
- package/dist/config.js.map +1 -0
- package/dist/defaults.d.ts +17 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +59 -0
- package/dist/defaults.js.map +1 -0
- package/dist/destroy.d.ts +3 -0
- package/dist/destroy.d.ts.map +1 -0
- package/dist/destroy.js +16 -0
- package/dist/destroy.js.map +1 -0
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +117 -0
- package/dist/doctor.js.map +1 -0
- package/dist/embeddings.d.ts +9 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +110 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/evaluate.d.ts +3 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +97 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +388 -0
- package/dist/files.js.map +1 -0
- package/dist/gitignore.d.ts +3 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +27 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/guards.d.ts +2 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +4 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest.d.ts +4 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.js +268 -0
- package/dist/ingest.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +33 -0
- package/dist/init.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +189 -0
- package/dist/mcp.js.map +1 -0
- package/dist/package-manager.d.ts +11 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +93 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/parsing.d.ts +12 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +307 -0
- package/dist/parsing.js.map +1 -0
- package/dist/query.d.ts +5 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +180 -0
- package/dist/query.js.map +1 -0
- package/dist/redaction.d.ts +7 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +93 -0
- package/dist/redaction.js.map +1 -0
- package/dist/research.d.ts +9 -0
- package/dist/research.d.ts.map +1 -0
- package/dist/research.js +335 -0
- package/dist/research.js.map +1 -0
- package/dist/security.d.ts +3 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +106 -0
- package/dist/security.js.map +1 -0
- package/dist/semantic-config.d.ts +9 -0
- package/dist/semantic-config.d.ts.map +1 -0
- package/dist/semantic-config.js +35 -0
- package/dist/semantic-config.js.map +1 -0
- package/dist/setup.d.ts +32 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +74 -0
- package/dist/setup.js.map +1 -0
- package/dist/skill.d.ts +66 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +652 -0
- package/dist/skill.js.map +1 -0
- package/dist/sources.d.ts +16 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +71 -0
- package/dist/sources.js.map +1 -0
- package/dist/store.d.ts +13 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +94 -0
- package/dist/store.js.map +1 -0
- package/dist/text.d.ts +3 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +10 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +322 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/examples/library-api-demo/README.md +42 -0
- package/examples/library-api-demo/run.mjs +61 -0
- package/examples/sovereign-rag-demo/.ragmir/config.json +22 -0
- package/examples/sovereign-rag-demo/.ragmir/sources.txt +2 -0
- package/examples/sovereign-rag-demo/README.md +95 -0
- package/examples/sovereign-rag-demo/golden-queries.json +29 -0
- package/examples/sovereign-rag-demo/raw/dataset-inventory.csv +5 -0
- package/examples/sovereign-rag-demo/raw/facility-scan.heic +3 -0
- package/examples/sovereign-rag-demo/raw/incident-timeline.jsonl +4 -0
- package/examples/sovereign-rag-demo/raw/operations-brief.md +16 -0
- package/examples/sovereign-rag-demo/raw/review-notes.evidence +11 -0
- package/examples/sovereign-rag-demo/raw/security-policy.yaml +14 -0
- package/package.json +87 -0
- package/skills/ragmir/SKILL.md +366 -0
- package/skills/ragmir-audio-summary/SKILL.md +150 -0
- package/skills/ragmir-audio-summary/forge-voice.sh +150 -0
- package/skills/ragmir-audio-summary/split-lines.py +13 -0
- package/skills/ragmir-audio-summary/xtts-voice.py +46 -0
- package/skills/ragmir-legal-dossier/SKILL.md +143 -0
- package/skills/ragmir-markdown-report/SKILL.md +110 -0
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AskResult, SearchOptions, SearchResult } from "./types.js";
|
|
2
|
+
export declare function search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
3
|
+
export declare function vectorCandidateLimit(topK: number): number;
|
|
4
|
+
export declare function ask(query: string, options?: SearchOptions): Promise<AskResult>;
|
|
5
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AA0BxE,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA8BhG;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAsBxF"}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { recordAccess } from "./access-log.js";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { embedText } from "./embeddings.js";
|
|
4
|
+
import { openRowsTable } from "./store.js";
|
|
5
|
+
import { normalizeForMatch, tokenize } from "./text.js";
|
|
6
|
+
const MIN_VECTOR_CANDIDATES = 80;
|
|
7
|
+
const VECTOR_CANDIDATE_MULTIPLIER = 4;
|
|
8
|
+
const HYBRID_TEXT_SCAN_LIMIT = 5_000;
|
|
9
|
+
const VECTOR_SCORE_WEIGHT = 0.55;
|
|
10
|
+
const LEXICAL_SCORE_WEIGHT = 0.45;
|
|
11
|
+
const EXACT_QUERY_BOOST = 0.15;
|
|
12
|
+
const BM25_K1 = 1.2;
|
|
13
|
+
const BM25_B = 0.75;
|
|
14
|
+
export async function search(query, options = {}) {
|
|
15
|
+
const config = await loadConfig(String(options.cwd ?? process.cwd()));
|
|
16
|
+
const table = await openRowsTable(config);
|
|
17
|
+
if (!table) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const topK = options.topK ?? config.topK;
|
|
21
|
+
const vector = await embedText(query, config);
|
|
22
|
+
const vectorRows = (await table
|
|
23
|
+
.vectorSearch(vector)
|
|
24
|
+
.limit(vectorCandidateLimit(topK))
|
|
25
|
+
.toArray());
|
|
26
|
+
const textRows = (await table.query().limit(HYBRID_TEXT_SCAN_LIMIT).toArray());
|
|
27
|
+
const rows = rankHybridRows(query, vectorRows, textRows).slice(0, topK);
|
|
28
|
+
const results = rows.map((row) => ({
|
|
29
|
+
source: row.row.source,
|
|
30
|
+
relativePath: row.row.relativePath,
|
|
31
|
+
chunkIndex: row.row.chunkIndex,
|
|
32
|
+
text: row.row.text,
|
|
33
|
+
distance: typeof row.row._distance === "number" ? row.row._distance : null,
|
|
34
|
+
}));
|
|
35
|
+
await recordAccess(config, {
|
|
36
|
+
action: "search",
|
|
37
|
+
query,
|
|
38
|
+
topK,
|
|
39
|
+
resultCount: results.length,
|
|
40
|
+
});
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
export function vectorCandidateLimit(topK) {
|
|
44
|
+
return Math.max(MIN_VECTOR_CANDIDATES, topK * VECTOR_CANDIDATE_MULTIPLIER);
|
|
45
|
+
}
|
|
46
|
+
export async function ask(query, options = {}) {
|
|
47
|
+
const config = await loadConfig(String(options.cwd ?? process.cwd()));
|
|
48
|
+
const sources = await search(query, options);
|
|
49
|
+
if (sources.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
answer: "No relevant passages were found. Add documents and run `ragmir doctor --fix` first.",
|
|
52
|
+
sources,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
await recordAccess(config, {
|
|
56
|
+
action: "ask",
|
|
57
|
+
query,
|
|
58
|
+
topK: options.topK ?? config.topK,
|
|
59
|
+
resultCount: sources.length,
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
answer: retrievalOnlyAnswer(sources),
|
|
63
|
+
sources,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function retrievalOnlyAnswer(sources) {
|
|
67
|
+
const snippets = sources
|
|
68
|
+
.map((source, index) => {
|
|
69
|
+
const text = source.text.replace(/\s+/gu, " ").trim();
|
|
70
|
+
return `[${index + 1}] ${source.relativePath}#${source.chunkIndex}: ${text}`;
|
|
71
|
+
})
|
|
72
|
+
.join("\n\n");
|
|
73
|
+
return [
|
|
74
|
+
"Ragmir returns retrieval context only. Use these passages as grounded context for your agent or LLM:",
|
|
75
|
+
"",
|
|
76
|
+
snippets,
|
|
77
|
+
].join("\n");
|
|
78
|
+
}
|
|
79
|
+
function rankHybridRows(query, vectorRows, textRows) {
|
|
80
|
+
const queryTokens = tokenize(query);
|
|
81
|
+
const rows = mergeRows(vectorRows, textRows);
|
|
82
|
+
const vectorScores = new Map();
|
|
83
|
+
for (const row of vectorRows) {
|
|
84
|
+
vectorScores.set(rowKey(row), vectorScore(row));
|
|
85
|
+
}
|
|
86
|
+
const lexicalScores = bm25Scores(queryTokens, rows);
|
|
87
|
+
const maxVectorScore = Math.max(...vectorScores.values(), 0);
|
|
88
|
+
const maxLexicalScore = Math.max(...lexicalScores.values(), 0);
|
|
89
|
+
const normalizedQuery = normalizeForMatch(query);
|
|
90
|
+
return rows
|
|
91
|
+
.map((row) => {
|
|
92
|
+
const key = rowKey(row);
|
|
93
|
+
const vector = normalizeScore(vectorScores.get(key) ?? 0, maxVectorScore);
|
|
94
|
+
const lexical = normalizeScore(lexicalScores.get(key) ?? 0, maxLexicalScore);
|
|
95
|
+
const exactBoost = normalizedQuery.length > 0 && normalizeForMatch(row.text).includes(normalizedQuery)
|
|
96
|
+
? EXACT_QUERY_BOOST
|
|
97
|
+
: 0;
|
|
98
|
+
return {
|
|
99
|
+
row,
|
|
100
|
+
vectorScore: vector,
|
|
101
|
+
lexicalScore: lexical,
|
|
102
|
+
combinedScore: vector * VECTOR_SCORE_WEIGHT + lexical * LEXICAL_SCORE_WEIGHT + exactBoost,
|
|
103
|
+
};
|
|
104
|
+
})
|
|
105
|
+
.filter((ranked) => ranked.combinedScore > 0)
|
|
106
|
+
.sort((a, b) => {
|
|
107
|
+
const scoreDelta = b.combinedScore - a.combinedScore;
|
|
108
|
+
if (scoreDelta !== 0) {
|
|
109
|
+
return scoreDelta;
|
|
110
|
+
}
|
|
111
|
+
const distanceDelta = rowDistance(a.row) - rowDistance(b.row);
|
|
112
|
+
if (Number.isFinite(distanceDelta) && distanceDelta !== 0) {
|
|
113
|
+
return distanceDelta;
|
|
114
|
+
}
|
|
115
|
+
return (a.row.relativePath.localeCompare(b.row.relativePath) || a.row.chunkIndex - b.row.chunkIndex);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function mergeRows(vectorRows, textRows) {
|
|
119
|
+
const rows = new Map();
|
|
120
|
+
for (const row of textRows) {
|
|
121
|
+
rows.set(rowKey(row), row);
|
|
122
|
+
}
|
|
123
|
+
for (const row of vectorRows) {
|
|
124
|
+
rows.set(rowKey(row), row);
|
|
125
|
+
}
|
|
126
|
+
return [...rows.values()];
|
|
127
|
+
}
|
|
128
|
+
function bm25Scores(queryTokens, rows) {
|
|
129
|
+
const scores = new Map();
|
|
130
|
+
if (queryTokens.length === 0 || rows.length === 0) {
|
|
131
|
+
return scores;
|
|
132
|
+
}
|
|
133
|
+
const uniqueQueryTokens = [...new Set(queryTokens)];
|
|
134
|
+
const documents = rows.map((row) => {
|
|
135
|
+
const tokens = tokenize(row.text);
|
|
136
|
+
const frequencies = new Map();
|
|
137
|
+
for (const token of tokens) {
|
|
138
|
+
frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
|
|
139
|
+
}
|
|
140
|
+
return { row, tokens, frequencies };
|
|
141
|
+
});
|
|
142
|
+
const averageLength = documents.reduce((sum, document) => sum + document.tokens.length, 0) / documents.length || 1;
|
|
143
|
+
const documentFrequencies = new Map();
|
|
144
|
+
for (const token of uniqueQueryTokens) {
|
|
145
|
+
documentFrequencies.set(token, documents.filter((document) => document.frequencies.has(token)).length);
|
|
146
|
+
}
|
|
147
|
+
for (const document of documents) {
|
|
148
|
+
let score = 0;
|
|
149
|
+
for (const token of uniqueQueryTokens) {
|
|
150
|
+
const frequency = document.frequencies.get(token) ?? 0;
|
|
151
|
+
if (frequency === 0) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const documentFrequency = documentFrequencies.get(token) ?? 0;
|
|
155
|
+
const inverseDocumentFrequency = Math.log(1 + (documents.length - documentFrequency + 0.5) / (documentFrequency + 0.5));
|
|
156
|
+
const denominator = frequency + BM25_K1 * (1 - BM25_B + BM25_B * (document.tokens.length / averageLength));
|
|
157
|
+
score += inverseDocumentFrequency * ((frequency * (BM25_K1 + 1)) / denominator);
|
|
158
|
+
}
|
|
159
|
+
if (score > 0) {
|
|
160
|
+
scores.set(rowKey(document.row), score);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return scores;
|
|
164
|
+
}
|
|
165
|
+
function vectorScore(row) {
|
|
166
|
+
const distance = rowDistance(row);
|
|
167
|
+
return 1 / (1 + distance);
|
|
168
|
+
}
|
|
169
|
+
function rowDistance(row) {
|
|
170
|
+
return typeof row._distance === "number" && row._distance >= 0
|
|
171
|
+
? row._distance
|
|
172
|
+
: Number.POSITIVE_INFINITY;
|
|
173
|
+
}
|
|
174
|
+
function normalizeScore(score, maxScore) {
|
|
175
|
+
return maxScore > 0 ? score / maxScore : 0;
|
|
176
|
+
}
|
|
177
|
+
function rowKey(row) {
|
|
178
|
+
return `${row.relativePath}\0${row.chunkIndex}`;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Config, RedactionCount } from "./types.js";
|
|
2
|
+
export declare function redactText(input: string, config: Config): {
|
|
3
|
+
text: string;
|
|
4
|
+
counts: RedactionCount[];
|
|
5
|
+
};
|
|
6
|
+
export declare function totalRedactions(counts: RedactionCount[]): number;
|
|
7
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAoB,MAAM,YAAY,CAAA;AAkE1E,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,EAAE,CAAA;CAAE,CAyB5C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,CAEhE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const BUILT_IN_PATTERNS = [
|
|
2
|
+
{
|
|
3
|
+
name: "private_key",
|
|
4
|
+
pattern: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----",
|
|
5
|
+
flags: "g",
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: "jwt",
|
|
9
|
+
pattern: "\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b",
|
|
10
|
+
flags: "g",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "api_token",
|
|
14
|
+
pattern: "\\b(?:sk|pk|ghp|gho|github_pat|npm)_[A-Za-z0-9_=-]{20,}\\b|\\b[A-Za-z0-9_-]{32,}\\.[A-Za-z0-9_-]{16,}\\.[A-Za-z0-9_-]{16,}\\b",
|
|
15
|
+
flags: "g",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "openai_api_key",
|
|
19
|
+
pattern: "\\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\\b",
|
|
20
|
+
flags: "g",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "aws_access_key_id",
|
|
24
|
+
pattern: "\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b",
|
|
25
|
+
flags: "g",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "google_api_key",
|
|
29
|
+
pattern: "\\bAIza[0-9A-Za-z_-]{35}\\b",
|
|
30
|
+
flags: "g",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "slack_token",
|
|
34
|
+
pattern: "\\bxox[baprs]-[0-9A-Za-z-]{10,}\\b",
|
|
35
|
+
flags: "g",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "sendgrid_api_key",
|
|
39
|
+
pattern: "\\bSG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}\\b",
|
|
40
|
+
flags: "g",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "url_credentials",
|
|
44
|
+
pattern: "\\b[a-z][a-z0-9+.-]*://[^\\s:/@]+:[^\\s/@]+@",
|
|
45
|
+
flags: "gi",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "email",
|
|
49
|
+
pattern: "\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b",
|
|
50
|
+
flags: "gi",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "iban",
|
|
54
|
+
pattern: "\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b",
|
|
55
|
+
flags: "gi",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "credit_card",
|
|
59
|
+
pattern: "\\b(?:\\d[ -]*?){13,19}\\b",
|
|
60
|
+
flags: "g",
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
export function redactText(input, config) {
|
|
64
|
+
if (!config.redaction.enabled) {
|
|
65
|
+
return { text: input, counts: [] };
|
|
66
|
+
}
|
|
67
|
+
let text = input;
|
|
68
|
+
const counts = [];
|
|
69
|
+
const patterns = [
|
|
70
|
+
...(config.redaction.builtIn ? BUILT_IN_PATTERNS : []),
|
|
71
|
+
...config.redaction.patterns,
|
|
72
|
+
];
|
|
73
|
+
for (const pattern of patterns) {
|
|
74
|
+
const regexp = compilePattern(pattern);
|
|
75
|
+
let count = 0;
|
|
76
|
+
text = text.replace(regexp, () => {
|
|
77
|
+
count += 1;
|
|
78
|
+
return pattern.replacement ?? `[REDACTED_${pattern.name.toUpperCase()}]`;
|
|
79
|
+
});
|
|
80
|
+
if (count > 0) {
|
|
81
|
+
counts.push({ name: pattern.name, count });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { text, counts };
|
|
85
|
+
}
|
|
86
|
+
export function totalRedactions(counts) {
|
|
87
|
+
return counts.reduce((total, entry) => total + entry.count, 0);
|
|
88
|
+
}
|
|
89
|
+
function compilePattern(pattern) {
|
|
90
|
+
const flags = pattern.flags?.includes("g") ? pattern.flags : `${pattern.flags ?? ""}g`;
|
|
91
|
+
return new RegExp(pattern.pattern, flags);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CompactSearchResult, ResearchEvidence, ResearchOptions, ResearchReport, SearchResult } from "./types.js";
|
|
2
|
+
export declare function research(query: string, options?: ResearchOptions): Promise<ResearchReport>;
|
|
3
|
+
export declare function compactSearchResults(results: SearchResult[], maxLength?: number): CompactSearchResult[];
|
|
4
|
+
export declare function compactResearchReport(report: ResearchReport): Omit<ResearchReport, "evidence"> & {
|
|
5
|
+
evidence: Array<Omit<ResearchEvidence, "text"> & {
|
|
6
|
+
snippet: string;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=research.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../src/research.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAEV,mBAAmB,EAEnB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,YAAY,EACb,MAAM,YAAY,CAAA;AAwDnB,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAsEzB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,YAAY,EAAE,EACvB,SAAS,SAAyB,GACjC,mBAAmB,EAAE,CAQvB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,GAAG;IAChG,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACtE,CAYA"}
|
package/dist/research.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
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 { countSkippedByReason, DEFAULT_FAST_GLOB_IGNORES, isSensitiveFilePath } from "./files.js";
|
|
7
|
+
import { audit } from "./ingest.js";
|
|
8
|
+
import { search } from "./query.js";
|
|
9
|
+
import { redactText } from "./redaction.js";
|
|
10
|
+
import { securityAudit } from "./security.js";
|
|
11
|
+
import { normalizeForMatch } from "./text.js";
|
|
12
|
+
const DEFAULT_RESEARCH_QUERY_LIMIT = 5;
|
|
13
|
+
const DEFAULT_CODE_EVIDENCE_LIMIT = 20;
|
|
14
|
+
const CODE_EVIDENCE_CANDIDATE_MULTIPLIER = 5;
|
|
15
|
+
const COMPACT_SNIPPET_LENGTH = 260;
|
|
16
|
+
const CODE_SCAN_MAX_BYTES = 256_000;
|
|
17
|
+
const CODE_SCAN_EXTENSIONS = new Set([
|
|
18
|
+
".c",
|
|
19
|
+
".cjs",
|
|
20
|
+
".cpp",
|
|
21
|
+
".cs",
|
|
22
|
+
".go",
|
|
23
|
+
".java",
|
|
24
|
+
".js",
|
|
25
|
+
".json",
|
|
26
|
+
".jsonl",
|
|
27
|
+
".jsx",
|
|
28
|
+
".kt",
|
|
29
|
+
".md",
|
|
30
|
+
".mdx",
|
|
31
|
+
".mjs",
|
|
32
|
+
".mts",
|
|
33
|
+
".php",
|
|
34
|
+
".py",
|
|
35
|
+
".rb",
|
|
36
|
+
".rs",
|
|
37
|
+
".sql",
|
|
38
|
+
".txt",
|
|
39
|
+
".ts",
|
|
40
|
+
".tsx",
|
|
41
|
+
".vue",
|
|
42
|
+
".yaml",
|
|
43
|
+
".yml",
|
|
44
|
+
]);
|
|
45
|
+
const CODE_SCAN_IGNORE = [
|
|
46
|
+
...DEFAULT_FAST_GLOB_IGNORES,
|
|
47
|
+
"**/dist/**",
|
|
48
|
+
"**/build/**",
|
|
49
|
+
"**/.next/**",
|
|
50
|
+
"**/coverage/**",
|
|
51
|
+
"**/release-artifacts/**",
|
|
52
|
+
"**/bun.lock",
|
|
53
|
+
"**/bun.lockb",
|
|
54
|
+
"**/Cargo.lock",
|
|
55
|
+
"**/composer.lock",
|
|
56
|
+
"**/Gemfile.lock",
|
|
57
|
+
"**/go.sum",
|
|
58
|
+
"**/npm-shrinkwrap.json",
|
|
59
|
+
"**/package-lock.json",
|
|
60
|
+
"**/Pipfile.lock",
|
|
61
|
+
"**/pnpm-lock.yaml",
|
|
62
|
+
"**/poetry.lock",
|
|
63
|
+
"**/yarn.lock",
|
|
64
|
+
];
|
|
65
|
+
export async function research(query, options = {}) {
|
|
66
|
+
const normalizedQuery = query.trim();
|
|
67
|
+
if (!normalizedQuery) {
|
|
68
|
+
throw new Error("Research query must not be empty.");
|
|
69
|
+
}
|
|
70
|
+
const config = await loadConfig(String(options.cwd ?? process.cwd()));
|
|
71
|
+
const topK = options.topK ?? config.topK;
|
|
72
|
+
const [auditReport, securityReport] = await Promise.all([
|
|
73
|
+
audit(config.projectRoot),
|
|
74
|
+
securityAudit(config.projectRoot),
|
|
75
|
+
]);
|
|
76
|
+
const generatedQueries = researchQueries(normalizedQuery);
|
|
77
|
+
const perQueryTopK = Math.max(2, Math.ceil(topK / 2));
|
|
78
|
+
const searchResults = await Promise.all(generatedQueries.map(async (generatedQuery) => ({
|
|
79
|
+
query: generatedQuery,
|
|
80
|
+
results: await search(generatedQuery, { cwd: config.projectRoot, topK: perQueryTopK }),
|
|
81
|
+
})));
|
|
82
|
+
const evidence = mergeEvidence(searchResults).slice(0, topK);
|
|
83
|
+
const codeEvidence = options.includeCode === false
|
|
84
|
+
? []
|
|
85
|
+
: await findCodeEvidence(config, normalizedQuery, DEFAULT_CODE_EVIDENCE_LIMIT);
|
|
86
|
+
const unsupportedFiles = countSkippedByReason(auditReport.skippedFiles, "unsupported-extension");
|
|
87
|
+
const gaps = researchGaps({
|
|
88
|
+
evidenceCount: evidence.length,
|
|
89
|
+
codeEvidenceCount: codeEvidence.length,
|
|
90
|
+
includeCode: options.includeCode !== false,
|
|
91
|
+
missingFromIndex: auditReport.missingFromIndex.length,
|
|
92
|
+
staleInIndex: auditReport.staleInIndex.length,
|
|
93
|
+
securityWarnings: securityReport.warnings.length,
|
|
94
|
+
unsupportedFiles,
|
|
95
|
+
duplicateCandidates: auditReport.sourceDiagnostics.duplicateCandidates.length,
|
|
96
|
+
archiveCandidates: auditReport.sourceDiagnostics.archiveCandidates.length,
|
|
97
|
+
mirrorCandidates: auditReport.sourceDiagnostics.mirrorCandidates.length,
|
|
98
|
+
});
|
|
99
|
+
await recordAccess(config, {
|
|
100
|
+
action: "research",
|
|
101
|
+
query: normalizedQuery,
|
|
102
|
+
topK,
|
|
103
|
+
resultCount: evidence.length,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
query: normalizedQuery,
|
|
107
|
+
generatedQueries,
|
|
108
|
+
ready: evidence.length > 0 &&
|
|
109
|
+
auditReport.missingFromIndex.length === 0 &&
|
|
110
|
+
auditReport.staleInIndex.length === 0 &&
|
|
111
|
+
securityReport.warnings.length === 0,
|
|
112
|
+
audit: {
|
|
113
|
+
supportedFiles: auditReport.supportedFiles.length,
|
|
114
|
+
skippedFiles: auditReport.skippedFiles.length,
|
|
115
|
+
unsupportedFiles,
|
|
116
|
+
indexedFiles: auditReport.indexedFiles.length,
|
|
117
|
+
totalChunks: auditReport.totalChunks,
|
|
118
|
+
missingFromIndex: auditReport.missingFromIndex.length,
|
|
119
|
+
staleInIndex: auditReport.staleInIndex.length,
|
|
120
|
+
emptyTextFiles: auditReport.emptyTextFiles.length,
|
|
121
|
+
},
|
|
122
|
+
securityWarnings: securityReport.warnings,
|
|
123
|
+
sourceDiagnostics: auditReport.sourceDiagnostics,
|
|
124
|
+
evidence,
|
|
125
|
+
codeEvidence,
|
|
126
|
+
gaps,
|
|
127
|
+
nextSteps: researchNextSteps(gaps),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function compactSearchResults(results, maxLength = COMPACT_SNIPPET_LENGTH) {
|
|
131
|
+
return results.map((result) => ({
|
|
132
|
+
source: result.source,
|
|
133
|
+
relativePath: result.relativePath,
|
|
134
|
+
chunkIndex: result.chunkIndex,
|
|
135
|
+
snippet: compactText(result.text, maxLength),
|
|
136
|
+
distance: result.distance,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
export function compactResearchReport(report) {
|
|
140
|
+
return {
|
|
141
|
+
...report,
|
|
142
|
+
evidence: report.evidence.map((evidence) => ({
|
|
143
|
+
source: evidence.source,
|
|
144
|
+
relativePath: evidence.relativePath,
|
|
145
|
+
chunkIndex: evidence.chunkIndex,
|
|
146
|
+
snippet: compactText(evidence.text),
|
|
147
|
+
distance: evidence.distance,
|
|
148
|
+
queries: evidence.queries,
|
|
149
|
+
})),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function researchQueries(query) {
|
|
153
|
+
const trimmed = query.trim();
|
|
154
|
+
const queries = [
|
|
155
|
+
trimmed,
|
|
156
|
+
`${trimmed} scope requirements rules`,
|
|
157
|
+
`${trimmed} actors permissions workflow status validation`,
|
|
158
|
+
`${trimmed} dates deadlines planning risks blockers`,
|
|
159
|
+
`${trimmed} integration API data model export dependencies`,
|
|
160
|
+
];
|
|
161
|
+
return [...new Set(queries)].slice(0, DEFAULT_RESEARCH_QUERY_LIMIT);
|
|
162
|
+
}
|
|
163
|
+
function mergeEvidence(searchResults) {
|
|
164
|
+
const bySource = new Map();
|
|
165
|
+
for (const searchResult of searchResults) {
|
|
166
|
+
for (const result of searchResult.results) {
|
|
167
|
+
const key = `${result.relativePath}\0${result.chunkIndex}`;
|
|
168
|
+
const existing = bySource.get(key);
|
|
169
|
+
if (existing) {
|
|
170
|
+
existing.queries.push(searchResult.query);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
bySource.set(key, {
|
|
174
|
+
source: result.source,
|
|
175
|
+
relativePath: result.relativePath,
|
|
176
|
+
chunkIndex: result.chunkIndex,
|
|
177
|
+
text: result.text,
|
|
178
|
+
distance: result.distance,
|
|
179
|
+
queries: [searchResult.query],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return [...bySource.values()];
|
|
184
|
+
}
|
|
185
|
+
async function findCodeEvidence(config, query, limit) {
|
|
186
|
+
const terms = meaningfulTerms(query);
|
|
187
|
+
if (terms.length === 0) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
const ignore = [...CODE_SCAN_IGNORE, ...projectRelativeIgnores(config)];
|
|
191
|
+
const minimumMatchedTerms = terms.length === 1 ? 1 : 2;
|
|
192
|
+
const candidateLimit = Math.max(limit, limit * CODE_EVIDENCE_CANDIDATE_MULTIPLIER);
|
|
193
|
+
const entries = (await fg("**/*", {
|
|
194
|
+
cwd: config.projectRoot,
|
|
195
|
+
absolute: true,
|
|
196
|
+
onlyFiles: true,
|
|
197
|
+
dot: true,
|
|
198
|
+
followSymbolicLinks: false,
|
|
199
|
+
ignore,
|
|
200
|
+
objectMode: true,
|
|
201
|
+
stats: true,
|
|
202
|
+
unique: true,
|
|
203
|
+
}));
|
|
204
|
+
const candidates = [];
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (candidates.length >= candidateLimit) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
const absolutePath = path.isAbsolute(entry.path)
|
|
210
|
+
? entry.path
|
|
211
|
+
: path.resolve(config.projectRoot, entry.path);
|
|
212
|
+
if (!isScannableCodePath(absolutePath) ||
|
|
213
|
+
isSensitiveFilePath(absolutePath) ||
|
|
214
|
+
(entry.stats?.size ?? 0) > CODE_SCAN_MAX_BYTES) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const relativePath = path.relative(config.projectRoot, absolutePath);
|
|
218
|
+
const content = await readFile(absolutePath, "utf8").catch(() => null);
|
|
219
|
+
if (content === null) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
for (const [index, line] of content.split(/\r?\n/u).entries()) {
|
|
223
|
+
const normalizedLine = normalizeForMatch(line);
|
|
224
|
+
const matchedTerms = terms.filter((term) => normalizedLine.includes(term));
|
|
225
|
+
if (matchedTerms.length < minimumMatchedTerms) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const redactedSnippet = redactText(line.trim(), config).text;
|
|
229
|
+
candidates.push({
|
|
230
|
+
relativePath,
|
|
231
|
+
lineNumber: index + 1,
|
|
232
|
+
snippet: redactedSnippet.slice(0, COMPACT_SNIPPET_LENGTH),
|
|
233
|
+
matchedTerms,
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return candidates.sort(compareCodeEvidence).slice(0, limit);
|
|
239
|
+
}
|
|
240
|
+
function compareCodeEvidence(a, b) {
|
|
241
|
+
return (b.matchedTerms.length - a.matchedTerms.length ||
|
|
242
|
+
a.relativePath.localeCompare(b.relativePath) ||
|
|
243
|
+
a.lineNumber - b.lineNumber);
|
|
244
|
+
}
|
|
245
|
+
function projectRelativeIgnores(config) {
|
|
246
|
+
return [config.rawDir, config.storageDir, config.embeddingModelPath]
|
|
247
|
+
.map((absolutePath) => path.relative(config.projectRoot, absolutePath))
|
|
248
|
+
.filter((relativePath) => relativePath && !relativePath.startsWith(".."))
|
|
249
|
+
.map((relativePath) => `${relativePath}/**`);
|
|
250
|
+
}
|
|
251
|
+
function isScannableCodePath(absolutePath) {
|
|
252
|
+
const extension = path.extname(absolutePath).toLowerCase();
|
|
253
|
+
return CODE_SCAN_EXTENSIONS.has(extension);
|
|
254
|
+
}
|
|
255
|
+
function meaningfulTerms(query) {
|
|
256
|
+
return [
|
|
257
|
+
...new Set(normalizeForMatch(query)
|
|
258
|
+
.match(/[\p{L}\p{N}]{3,}/gu)
|
|
259
|
+
?.filter((term) => !STOP_WORDS.has(term)) ?? []),
|
|
260
|
+
].slice(0, 8);
|
|
261
|
+
}
|
|
262
|
+
function researchGaps(input) {
|
|
263
|
+
const gaps = [];
|
|
264
|
+
if (input.evidenceCount === 0) {
|
|
265
|
+
gaps.push("No retrieved evidence matched the research query.");
|
|
266
|
+
}
|
|
267
|
+
if (input.includeCode && input.codeEvidenceCount === 0) {
|
|
268
|
+
gaps.push("No code evidence matched the research query.");
|
|
269
|
+
}
|
|
270
|
+
if (input.missingFromIndex > 0) {
|
|
271
|
+
gaps.push(`${input.missingFromIndex} supported source files are missing from the index.`);
|
|
272
|
+
}
|
|
273
|
+
if (input.staleInIndex > 0) {
|
|
274
|
+
gaps.push(`${input.staleInIndex} indexed source files are stale.`);
|
|
275
|
+
}
|
|
276
|
+
if (input.securityWarnings > 0) {
|
|
277
|
+
gaps.push(`${input.securityWarnings} security warnings require review.`);
|
|
278
|
+
}
|
|
279
|
+
if (input.unsupportedFiles > 0) {
|
|
280
|
+
gaps.push(`${input.unsupportedFiles} source files were skipped because their type is unsupported.`);
|
|
281
|
+
}
|
|
282
|
+
if (input.duplicateCandidates > 0) {
|
|
283
|
+
gaps.push(`${input.duplicateCandidates} possible duplicate source groups need source-truth review.`);
|
|
284
|
+
}
|
|
285
|
+
if (input.archiveCandidates > 0 || input.mirrorCandidates > 0) {
|
|
286
|
+
gaps.push("Some source paths look like archives, exports, raw files, or drive mirrors.");
|
|
287
|
+
}
|
|
288
|
+
return gaps;
|
|
289
|
+
}
|
|
290
|
+
function researchNextSteps(gaps) {
|
|
291
|
+
if (gaps.length === 0) {
|
|
292
|
+
return [
|
|
293
|
+
"Use the cited evidence as grounded context for an AI agent or reviewer.",
|
|
294
|
+
"Run targeted searches for names, dates, amounts, and decisions before high-stakes conclusions.",
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
return gaps.map((gap) => {
|
|
298
|
+
if (gap.includes("missing") || gap.includes("stale")) {
|
|
299
|
+
return "Run `ragmir doctor --fix`, then rerun `ragmir research`.";
|
|
300
|
+
}
|
|
301
|
+
if (gap.includes("unsupported")) {
|
|
302
|
+
return "Run `ragmir audit --unsupported` and transcribe, OCR, convert, or explicitly configure unsupported formats.";
|
|
303
|
+
}
|
|
304
|
+
if (gap.includes("duplicate") || gap.includes("archive") || gap.includes("mirror")) {
|
|
305
|
+
return "Review source diagnostics and prefer the canonical source before presenting conclusions.";
|
|
306
|
+
}
|
|
307
|
+
if (gap.includes("code evidence")) {
|
|
308
|
+
return "Run repository-aware code search to compare documents with implementation.";
|
|
309
|
+
}
|
|
310
|
+
return "Add or refresh source documents, then rerun the research command.";
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function compactText(text, maxLength = COMPACT_SNIPPET_LENGTH) {
|
|
314
|
+
const normalized = text.replace(/\s+/gu, " ").trim();
|
|
315
|
+
if (normalized.length <= maxLength) {
|
|
316
|
+
return normalized;
|
|
317
|
+
}
|
|
318
|
+
return `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
319
|
+
}
|
|
320
|
+
const STOP_WORDS = new Set([
|
|
321
|
+
"about",
|
|
322
|
+
"avec",
|
|
323
|
+
"dans",
|
|
324
|
+
"des",
|
|
325
|
+
"for",
|
|
326
|
+
"les",
|
|
327
|
+
"pour",
|
|
328
|
+
"que",
|
|
329
|
+
"qui",
|
|
330
|
+
"sur",
|
|
331
|
+
"the",
|
|
332
|
+
"une",
|
|
333
|
+
"what",
|
|
334
|
+
]);
|
|
335
|
+
//# sourceMappingURL=research.js.map
|