@curiousnerd/keel 0.1.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 +250 -0
- package/data/capability-buckets.json +15 -0
- package/dist/analyze/docDrift.d.ts +9 -0
- package/dist/analyze/docDrift.js +116 -0
- package/dist/analyze/docDrift.js.map +1 -0
- package/dist/analyze/drift.d.ts +4 -0
- package/dist/analyze/drift.js +134 -0
- package/dist/analyze/drift.js.map +1 -0
- package/dist/analyze/duplication.d.ts +7 -0
- package/dist/analyze/duplication.js +46 -0
- package/dist/analyze/duplication.js.map +1 -0
- package/dist/analyze/index.d.ts +10 -0
- package/dist/analyze/index.js +28 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/analyze/libConflicts.d.ts +9 -0
- package/dist/analyze/libConflicts.js +36 -0
- package/dist/analyze/libConflicts.js.map +1 -0
- package/dist/analyze/nearDup.d.ts +11 -0
- package/dist/analyze/nearDup.js +67 -0
- package/dist/analyze/nearDup.js.map +1 -0
- package/dist/analyze/score.d.ts +6 -0
- package/dist/analyze/score.js +39 -0
- package/dist/analyze/score.js.map +1 -0
- package/dist/analyze/shared.d.ts +19 -0
- package/dist/analyze/shared.js +53 -0
- package/dist/analyze/shared.js.map +1 -0
- package/dist/cache/hashCache.d.ts +19 -0
- package/dist/cache/hashCache.js +49 -0
- package/dist/cache/hashCache.js.map +1 -0
- package/dist/claims/parseBlock.d.ts +4 -0
- package/dist/claims/parseBlock.js +66 -0
- package/dist/claims/parseBlock.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +136 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.js +37 -0
- package/dist/config.js.map +1 -0
- package/dist/extract/imports.d.ts +12 -0
- package/dist/extract/imports.js +74 -0
- package/dist/extract/imports.js.map +1 -0
- package/dist/extract/index.d.ts +24 -0
- package/dist/extract/index.js +117 -0
- package/dist/extract/index.js.map +1 -0
- package/dist/extract/language.d.ts +3 -0
- package/dist/extract/language.js +13 -0
- package/dist/extract/language.js.map +1 -0
- package/dist/extract/naming.d.ts +11 -0
- package/dist/extract/naming.js +57 -0
- package/dist/extract/naming.js.map +1 -0
- package/dist/extract/packageJson.d.ts +3 -0
- package/dist/extract/packageJson.js +43 -0
- package/dist/extract/packageJson.js.map +1 -0
- package/dist/extract/python.d.ts +11 -0
- package/dist/extract/python.js +244 -0
- package/dist/extract/python.js.map +1 -0
- package/dist/extract/scan.d.ts +12 -0
- package/dist/extract/scan.js +16 -0
- package/dist/extract/scan.js.map +1 -0
- package/dist/extract/symbols.d.ts +9 -0
- package/dist/extract/symbols.js +120 -0
- package/dist/extract/symbols.js.map +1 -0
- package/dist/extract/walk.d.ts +10 -0
- package/dist/extract/walk.js +115 -0
- package/dist/extract/walk.js.map +1 -0
- package/dist/llm/cache.d.ts +17 -0
- package/dist/llm/cache.js +50 -0
- package/dist/llm/cache.js.map +1 -0
- package/dist/llm/claimsFromDocs.d.ts +16 -0
- package/dist/llm/claimsFromDocs.js +95 -0
- package/dist/llm/claimsFromDocs.js.map +1 -0
- package/dist/llm/explain.d.ts +10 -0
- package/dist/llm/explain.js +63 -0
- package/dist/llm/explain.js.map +1 -0
- package/dist/llm/improve.d.ts +9 -0
- package/dist/llm/improve.js +37 -0
- package/dist/llm/improve.js.map +1 -0
- package/dist/llm/provider.d.ts +24 -0
- package/dist/llm/provider.js +210 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +43 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +173 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/report/json.d.ts +3 -0
- package/dist/report/json.js +5 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/markdown.d.ts +9 -0
- package/dist/report/markdown.js +97 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/text.d.ts +11 -0
- package/dist/report/text.js +76 -0
- package/dist/report/text.js.map +1 -0
- package/dist/suppress.d.ts +22 -0
- package/dist/suppress.js +80 -0
- package/dist/suppress.js.map +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/util/fingerprint.d.ts +12 -0
- package/dist/util/fingerprint.js +60 -0
- package/dist/util/fingerprint.js.map +1 -0
- package/dist/util/hash.d.ts +4 -0
- package/dist/util/hash.js +15 -0
- package/dist/util/hash.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import Parser from "web-tree-sitter";
|
|
3
|
+
import { sha1 } from "../util/hash.js";
|
|
4
|
+
import { computeFingerprints } from "../util/fingerprint.js";
|
|
5
|
+
import { classifyCasing, emptyNamingStats } from "./naming.js";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
let parserPromise = null;
|
|
8
|
+
/** Initialize web-tree-sitter once and load the Python grammar (cached). */
|
|
9
|
+
async function getParser() {
|
|
10
|
+
if (!parserPromise) {
|
|
11
|
+
parserPromise = (async () => {
|
|
12
|
+
await Parser.init();
|
|
13
|
+
const parser = new Parser();
|
|
14
|
+
const wasmPath = require.resolve("tree-sitter-wasms/out/tree-sitter-python.wasm");
|
|
15
|
+
const Python = await Parser.Language.load(wasmPath);
|
|
16
|
+
parser.setLanguage(Python);
|
|
17
|
+
return parser;
|
|
18
|
+
})();
|
|
19
|
+
}
|
|
20
|
+
return parserPromise;
|
|
21
|
+
}
|
|
22
|
+
/** Create a Python extractor (loads the grammar on first call, then reuses it). */
|
|
23
|
+
export async function createPythonExtractor() {
|
|
24
|
+
const parser = await getParser();
|
|
25
|
+
return {
|
|
26
|
+
extractFile(content, filePath, minTokens) {
|
|
27
|
+
const tree = parser.parse(content);
|
|
28
|
+
try {
|
|
29
|
+
return {
|
|
30
|
+
imports: extractImports(tree.rootNode),
|
|
31
|
+
functions: extractFunctions(tree.rootNode, minTokens, filePath),
|
|
32
|
+
naming: extractNaming(tree.rootNode),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
tree.delete();
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Traversal helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
function* walk(node) {
|
|
45
|
+
yield node;
|
|
46
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
47
|
+
const child = node.child(i);
|
|
48
|
+
if (child)
|
|
49
|
+
yield* walk(child);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Text of the first `identifier` descendant (the top-level name). */
|
|
53
|
+
function firstIdentifier(node) {
|
|
54
|
+
if (node.type === "identifier")
|
|
55
|
+
return node.text;
|
|
56
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
57
|
+
const child = node.child(i);
|
|
58
|
+
if (!child)
|
|
59
|
+
continue;
|
|
60
|
+
const found = firstIdentifier(child);
|
|
61
|
+
if (found)
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** Top-level module of a dotted name: "os.path" -> "os". */
|
|
67
|
+
const topModule = (dotted) => dotted.split(".")[0] ?? dotted;
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Imports
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
function extractImports(root) {
|
|
72
|
+
const facts = [];
|
|
73
|
+
for (const node of walk(root)) {
|
|
74
|
+
if (node.type === "import_statement") {
|
|
75
|
+
// import os, sys | import numpy as np
|
|
76
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
77
|
+
const child = node.namedChild(i);
|
|
78
|
+
if (!child)
|
|
79
|
+
continue;
|
|
80
|
+
const nameNode = child.type === "aliased_import" ? child.childForFieldName("name") : child;
|
|
81
|
+
const id = nameNode ? firstIdentifier(nameNode) : null;
|
|
82
|
+
if (id) {
|
|
83
|
+
facts.push({ specifier: nameNode.text, external: true, packageName: topModule(id), named: [], line: node.startPosition.row + 1 });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (node.type === "import_from_statement") {
|
|
88
|
+
// from x.y import a, b | from . import c | from .mod import d
|
|
89
|
+
const moduleName = node.childForFieldName("module_name");
|
|
90
|
+
const relative = !moduleName || moduleName.type === "relative_import" || moduleName.text.startsWith(".");
|
|
91
|
+
const id = moduleName ? firstIdentifier(moduleName) : null;
|
|
92
|
+
facts.push({
|
|
93
|
+
specifier: moduleName?.text ?? ".",
|
|
94
|
+
external: !relative,
|
|
95
|
+
packageName: relative || !id ? null : topModule(id),
|
|
96
|
+
named: [],
|
|
97
|
+
line: node.startPosition.row + 1,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return facts;
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Functions (with normalized body hashing + fingerprints)
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
/** Collect identifier leaves of a node, skipping comments. */
|
|
107
|
+
function collectLeaves(node, out) {
|
|
108
|
+
if (node.type === "comment")
|
|
109
|
+
return;
|
|
110
|
+
if (node.childCount === 0) {
|
|
111
|
+
out.push(node);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
115
|
+
const child = node.child(i);
|
|
116
|
+
if (child)
|
|
117
|
+
collectLeaves(child, out);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Names bound locally inside a function: parameters + assignment/for targets. */
|
|
121
|
+
function collectLocals(fn) {
|
|
122
|
+
const locals = new Set();
|
|
123
|
+
const params = fn.childForFieldName("parameters");
|
|
124
|
+
if (params) {
|
|
125
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
126
|
+
const id = firstIdentifier(params.namedChild(i));
|
|
127
|
+
if (id)
|
|
128
|
+
locals.add(id);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const body = fn.childForFieldName("body");
|
|
132
|
+
if (body) {
|
|
133
|
+
for (const node of walk(body)) {
|
|
134
|
+
if (node.type === "assignment" || node.type === "augmented_assignment") {
|
|
135
|
+
const left = node.childForFieldName("left");
|
|
136
|
+
if (left)
|
|
137
|
+
for (const id of identifiersInTarget(left))
|
|
138
|
+
locals.add(id);
|
|
139
|
+
}
|
|
140
|
+
else if (node.type === "for_statement") {
|
|
141
|
+
const left = node.childForFieldName("left");
|
|
142
|
+
if (left)
|
|
143
|
+
for (const id of identifiersInTarget(left))
|
|
144
|
+
locals.add(id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return locals;
|
|
149
|
+
}
|
|
150
|
+
/** Identifiers bound by an assignment/for target (skips attribute/subscript writes). */
|
|
151
|
+
function identifiersInTarget(target) {
|
|
152
|
+
if (target.type === "identifier")
|
|
153
|
+
return [target.text];
|
|
154
|
+
// tuple/list/pattern_list of identifiers
|
|
155
|
+
const ids = [];
|
|
156
|
+
for (let i = 0; i < target.namedChildCount; i++) {
|
|
157
|
+
const child = target.namedChild(i);
|
|
158
|
+
if (child.type === "identifier")
|
|
159
|
+
ids.push(child.text);
|
|
160
|
+
}
|
|
161
|
+
return ids;
|
|
162
|
+
}
|
|
163
|
+
/** Normalize a function body into a token stream (locals renamed, members kept). */
|
|
164
|
+
function normalizeBody(body, locals) {
|
|
165
|
+
const leaves = [];
|
|
166
|
+
collectLeaves(body, leaves);
|
|
167
|
+
const tokens = [];
|
|
168
|
+
const placeholders = new Map();
|
|
169
|
+
let prevType;
|
|
170
|
+
for (const leaf of leaves) {
|
|
171
|
+
const afterDot = prevType === ".";
|
|
172
|
+
if (leaf.type === "identifier" && !afterDot && locals.has(leaf.text)) {
|
|
173
|
+
let ph = placeholders.get(leaf.text);
|
|
174
|
+
if (!ph) {
|
|
175
|
+
ph = `_v${placeholders.size}`;
|
|
176
|
+
placeholders.set(leaf.text, ph);
|
|
177
|
+
}
|
|
178
|
+
tokens.push(ph);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
tokens.push(leaf.text);
|
|
182
|
+
}
|
|
183
|
+
prevType = leaf.type;
|
|
184
|
+
}
|
|
185
|
+
return tokens;
|
|
186
|
+
}
|
|
187
|
+
function extractFunctions(root, minTokens, filePath) {
|
|
188
|
+
const facts = [];
|
|
189
|
+
for (const node of walk(root)) {
|
|
190
|
+
if (node.type !== "function_definition")
|
|
191
|
+
continue;
|
|
192
|
+
const nameNode = node.childForFieldName("name");
|
|
193
|
+
const body = node.childForFieldName("body");
|
|
194
|
+
if (!body)
|
|
195
|
+
continue;
|
|
196
|
+
const tokens = normalizeBody(body, collectLocals(node));
|
|
197
|
+
const tokenCount = tokens.length;
|
|
198
|
+
const passesGate = tokenCount >= minTokens;
|
|
199
|
+
const params = node.childForFieldName("parameters");
|
|
200
|
+
facts.push({
|
|
201
|
+
name: nameNode?.text ?? null,
|
|
202
|
+
filePath,
|
|
203
|
+
startLine: node.startPosition.row + 1,
|
|
204
|
+
endLine: node.endPosition.row + 1,
|
|
205
|
+
bodyHash: passesGate ? sha1(tokens.join(" ")) : null,
|
|
206
|
+
tokenCount,
|
|
207
|
+
fingerprints: passesGate ? computeFingerprints(tokens) : [],
|
|
208
|
+
signature: `(${params ? params.namedChildCount : 0})`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return facts;
|
|
212
|
+
}
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Naming
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
function extractNaming(root) {
|
|
217
|
+
const stats = emptyNamingStats();
|
|
218
|
+
const tally = (name) => {
|
|
219
|
+
if (!name || name.startsWith("_"))
|
|
220
|
+
return;
|
|
221
|
+
stats[classifyCasing(name)] += 1;
|
|
222
|
+
};
|
|
223
|
+
for (const node of walk(root)) {
|
|
224
|
+
if (node.type === "function_definition") {
|
|
225
|
+
tally(node.childForFieldName("name")?.text);
|
|
226
|
+
const params = node.childForFieldName("parameters");
|
|
227
|
+
if (params) {
|
|
228
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
229
|
+
const id = firstIdentifier(params.namedChild(i));
|
|
230
|
+
if (id && id !== "self" && id !== "cls")
|
|
231
|
+
tally(id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (node.type === "assignment") {
|
|
236
|
+
const left = node.childForFieldName("left");
|
|
237
|
+
if (left)
|
|
238
|
+
for (const id of identifiersInTarget(left))
|
|
239
|
+
tally(id);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return stats;
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=python.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.js","sourceRoot":"","sources":["../../src/extract/python.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KeelConfig } from "../config.js";
|
|
2
|
+
import { type ExtractOptions, type ExtractResult } from "./index.js";
|
|
3
|
+
export interface ScanOptions extends ExtractOptions {
|
|
4
|
+
/** Whether Python parsing is allowed (the CLI's --no-python maps to false). */
|
|
5
|
+
pythonEnabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Extract Facts for a repo, loading the Python parser only when the repo
|
|
9
|
+
* actually contains `.py` files (so JS-only scans pay nothing). Shared by the
|
|
10
|
+
* CLI and the MCP server.
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadFacts(root: string, config: KeelConfig, opts?: ScanOptions): Promise<ExtractResult>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { extract } from "./index.js";
|
|
2
|
+
import { walkSourceFiles } from "./walk.js";
|
|
3
|
+
import { createPythonExtractor } from "./python.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extract Facts for a repo, loading the Python parser only when the repo
|
|
6
|
+
* actually contains `.py` files (so JS-only scans pay nothing). Shared by the
|
|
7
|
+
* CLI and the MCP server.
|
|
8
|
+
*/
|
|
9
|
+
export async function loadFacts(root, config, opts = {}) {
|
|
10
|
+
const respectGitignore = opts.respectGitignore ?? config.scan.respectGitignore;
|
|
11
|
+
const pythonEnabled = (opts.pythonEnabled ?? true) && config.python.enabled;
|
|
12
|
+
const hasPython = pythonEnabled && walkSourceFiles(root, { respectGitignore }).some((p) => p.endsWith(".py"));
|
|
13
|
+
const pythonExtractor = hasPython ? await createPythonExtractor() : undefined;
|
|
14
|
+
return extract(root, config, { ...opts, respectGitignore, pythonExtractor });
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/extract/scan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAA2C,MAAM,YAAY,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAOpD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,MAAkB,EAAE,OAAoB,EAAE;IACtF,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;IAC/E,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IAC5E,MAAM,SAAS,GAAG,aAAa,IAAI,eAAe,CAAC,IAAI,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9G,MAAM,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,qBAAqB,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9E,OAAO,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC,CAAC;AAC/E,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SourceFile } from "ts-morph";
|
|
2
|
+
import type { FunctionFact } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extract function/method facts from a source file.
|
|
5
|
+
* Bodies below `minTokens` significant tokens get a null hash and no
|
|
6
|
+
* fingerprints (excluded from duplication detection) to avoid flagging
|
|
7
|
+
* trivial one-liners.
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractFunctions(sourceFile: SourceFile, minTokens: number, filePath: string): FunctionFact[];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Node, SyntaxKind, ts } from "ts-morph";
|
|
2
|
+
import { sha1 } from "../util/hash.js";
|
|
3
|
+
import { computeFingerprints } from "../util/fingerprint.js";
|
|
4
|
+
/** Best-effort human name for a function-like node (null for true anonymous). */
|
|
5
|
+
function functionName(node) {
|
|
6
|
+
if (Node.isFunctionDeclaration(node) || Node.isMethodDeclaration(node)) {
|
|
7
|
+
return node.getName() ?? null;
|
|
8
|
+
}
|
|
9
|
+
// Function expression / arrow: name comes from how it's bound.
|
|
10
|
+
const parent = node.getParent();
|
|
11
|
+
if (Node.isVariableDeclaration(parent)) {
|
|
12
|
+
const nameNode = parent.getNameNode();
|
|
13
|
+
return Node.isIdentifier(nameNode) ? nameNode.getText() : null;
|
|
14
|
+
}
|
|
15
|
+
if (Node.isPropertyAssignment(parent) || Node.isPropertyDeclaration(parent)) {
|
|
16
|
+
return parent.getName();
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/** All identifier texts declared locally (params + locally-declared variables). */
|
|
21
|
+
function collectLocals(node) {
|
|
22
|
+
const locals = new Set();
|
|
23
|
+
for (const param of node.getParameters()) {
|
|
24
|
+
for (const id of param.getNameNode().getDescendantsOfKind(SyntaxKind.Identifier)) {
|
|
25
|
+
locals.add(id.getText());
|
|
26
|
+
}
|
|
27
|
+
if (Node.isIdentifier(param.getNameNode()))
|
|
28
|
+
locals.add(param.getNameNode().getText());
|
|
29
|
+
}
|
|
30
|
+
const body = node.getBody();
|
|
31
|
+
if (body) {
|
|
32
|
+
for (const decl of body.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
33
|
+
const nameNode = decl.getNameNode();
|
|
34
|
+
if (Node.isIdentifier(nameNode)) {
|
|
35
|
+
locals.add(nameNode.getText());
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
for (const id of nameNode.getDescendantsOfKind(SyntaxKind.Identifier)) {
|
|
39
|
+
locals.add(id.getText());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return locals;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Normalize a function body into a token stream:
|
|
48
|
+
* - comments and whitespace are dropped (scanner skips trivia)
|
|
49
|
+
* - local identifiers (params + locals) are renamed to positional placeholders
|
|
50
|
+
* so two bodies identical up to local naming hash the same
|
|
51
|
+
* - member names after `.`/`?.` are left intact (not treated as locals)
|
|
52
|
+
* - literals are kept verbatim, keeping "exact" conservative
|
|
53
|
+
*/
|
|
54
|
+
function normalizeBody(bodyText, locals) {
|
|
55
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest,
|
|
56
|
+
/* skipTrivia */ true, ts.LanguageVariant.Standard, bodyText);
|
|
57
|
+
const tokens = [];
|
|
58
|
+
const placeholders = new Map();
|
|
59
|
+
let prevKind;
|
|
60
|
+
let kind = scanner.scan();
|
|
61
|
+
while (kind !== ts.SyntaxKind.EndOfFileToken) {
|
|
62
|
+
const afterDot = prevKind === ts.SyntaxKind.DotToken || prevKind === ts.SyntaxKind.QuestionDotToken;
|
|
63
|
+
if (kind === ts.SyntaxKind.Identifier) {
|
|
64
|
+
const text = scanner.getTokenText();
|
|
65
|
+
if (!afterDot && locals.has(text)) {
|
|
66
|
+
let ph = placeholders.get(text);
|
|
67
|
+
if (!ph) {
|
|
68
|
+
ph = `_v${placeholders.size}`;
|
|
69
|
+
placeholders.set(text, ph);
|
|
70
|
+
}
|
|
71
|
+
tokens.push(ph);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
tokens.push(text);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
tokens.push(scanner.getTokenText());
|
|
79
|
+
}
|
|
80
|
+
prevKind = kind;
|
|
81
|
+
kind = scanner.scan();
|
|
82
|
+
}
|
|
83
|
+
return tokens;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extract function/method facts from a source file.
|
|
87
|
+
* Bodies below `minTokens` significant tokens get a null hash and no
|
|
88
|
+
* fingerprints (excluded from duplication detection) to avoid flagging
|
|
89
|
+
* trivial one-liners.
|
|
90
|
+
*/
|
|
91
|
+
export function extractFunctions(sourceFile, minTokens, filePath) {
|
|
92
|
+
const nodes = [
|
|
93
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
94
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
|
|
95
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
|
|
96
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
|
|
97
|
+
];
|
|
98
|
+
const facts = [];
|
|
99
|
+
for (const node of nodes) {
|
|
100
|
+
const body = node.getBody();
|
|
101
|
+
if (!body)
|
|
102
|
+
continue;
|
|
103
|
+
const locals = collectLocals(node);
|
|
104
|
+
const tokens = normalizeBody(body.getText(), locals);
|
|
105
|
+
const tokenCount = tokens.length;
|
|
106
|
+
const passesGate = tokenCount >= minTokens;
|
|
107
|
+
facts.push({
|
|
108
|
+
name: functionName(node),
|
|
109
|
+
filePath,
|
|
110
|
+
startLine: node.getStartLineNumber(),
|
|
111
|
+
endLine: node.getEndLineNumber(),
|
|
112
|
+
bodyHash: passesGate ? sha1(tokens.join(" ")) : null,
|
|
113
|
+
tokenCount,
|
|
114
|
+
fingerprints: passesGate ? computeFingerprints(tokens) : [],
|
|
115
|
+
signature: `(${node.getParameters().length})`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return facts;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=symbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/extract/symbols.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAmB,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAS7D,iFAAiF;AACjF,SAAS,YAAY,CAAC,IAAkB;IACtC,IAAI,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC;IAChC,CAAC;IACD,+DAA+D;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,IAAI,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,CAAC;IACD,IAAI,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mFAAmF;AACnF,SAAS,aAAa,CAAC,IAAkB;IACvC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;QACzC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACjF,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC5B,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAChC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,oBAAoB,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;oBACtE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,QAAgB,EAAE,MAAmB;IAC1D,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAC9B,EAAE,CAAC,YAAY,CAAC,MAAM;IACtB,gBAAgB,CAAC,IAAI,EACrB,EAAE,CAAC,eAAe,CAAC,QAAQ,EAC3B,QAAQ,CACT,CAAC;IACF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,IAAI,QAAmC,CAAC;IAExC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1B,OAAO,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QAC7C,MAAM,QAAQ,GAAG,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,QAAQ,IAAI,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC;QACpG,IAAI,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,IAAI,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,CAAC,EAAE,EAAE,CAAC;oBACR,EAAE,GAAG,KAAK,YAAY,CAAC,IAAI,EAAE,CAAC;oBAC9B,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC7B,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAsB,EAAE,SAAiB,EAAE,QAAgB;IAC1F,MAAM,KAAK,GAAmB;QAC5B,GAAG,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,mBAAmB,CAAC;QAClE,GAAG,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAChE,GAAG,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,kBAAkB,CAAC;QACjE,GAAG,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,aAAa,CAAC;KAC7D,CAAC;IAEF,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;QACjC,MAAM,UAAU,GAAG,UAAU,IAAI,SAAS,CAAC;QAE3C,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC;YACxB,QAAQ;YACR,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE;YACpC,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE;YAChC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;YACpD,UAAU;YACV,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;YAC3D,SAAS,EAAE,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,GAAG;SAC9C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Directories we never descend into, regardless of .gitignore. */
|
|
2
|
+
export declare const DEFAULT_IGNORE_DIRS: Set<string>;
|
|
3
|
+
export interface WalkOptions {
|
|
4
|
+
/** Skip files/dirs matched by .gitignore files (root + nested). Default true. */
|
|
5
|
+
respectGitignore?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Enumerate analyzable source files (JS/TS/Python) under `root`. */
|
|
8
|
+
export declare function walkSourceFiles(root: string, opts?: WalkOptions): string[];
|
|
9
|
+
/** Enumerate Markdown documentation files under `root`. */
|
|
10
|
+
export declare function walkMarkdownFiles(root: string, opts?: WalkOptions): string[];
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import ignore from "ignore";
|
|
4
|
+
// `ignore` is a CommonJS callable, but NodeNext's types surface it as a
|
|
5
|
+
// namespace, so cast to its real call signature. Runtime is unaffected.
|
|
6
|
+
const createIgnore = ignore;
|
|
7
|
+
/** Directories we never descend into, regardless of .gitignore. */
|
|
8
|
+
export const DEFAULT_IGNORE_DIRS = new Set([
|
|
9
|
+
"node_modules",
|
|
10
|
+
".git",
|
|
11
|
+
".keel",
|
|
12
|
+
"dist",
|
|
13
|
+
"build",
|
|
14
|
+
"out",
|
|
15
|
+
"coverage",
|
|
16
|
+
".next",
|
|
17
|
+
".turbo",
|
|
18
|
+
".cache",
|
|
19
|
+
// Generated / vendored code — never hand-written, so never our concern.
|
|
20
|
+
"generated",
|
|
21
|
+
"__generated__",
|
|
22
|
+
".prisma",
|
|
23
|
+
"vendor",
|
|
24
|
+
]);
|
|
25
|
+
const SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"];
|
|
26
|
+
function isSourceFile(name) {
|
|
27
|
+
if (name.endsWith(".d.ts"))
|
|
28
|
+
return false;
|
|
29
|
+
return SOURCE_EXTENSIONS.some((ext) => name.endsWith(ext));
|
|
30
|
+
}
|
|
31
|
+
function loadLayer(absDir, relDir) {
|
|
32
|
+
const path = join(absDir, ".gitignore");
|
|
33
|
+
if (!existsSync(path))
|
|
34
|
+
return null;
|
|
35
|
+
try {
|
|
36
|
+
return { base: relDir, ig: createIgnore().add(readFileSync(path, "utf8")) };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Test a path against every active .gitignore layer. Each layer matches the
|
|
44
|
+
* path relative to its own directory (git's semantics). Directories are tested
|
|
45
|
+
* with a trailing slash so directory-only patterns (`dist/`) match.
|
|
46
|
+
*/
|
|
47
|
+
function isIgnored(layers, relPath, isDir) {
|
|
48
|
+
for (const { base, ig } of layers) {
|
|
49
|
+
const sub = base ? relPath.slice(base.length + 1) : relPath;
|
|
50
|
+
if (!sub)
|
|
51
|
+
continue;
|
|
52
|
+
if (ig.ignores(isDir ? `${sub}/` : sub))
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Recursively enumerate files under `root` for which `accept(filename)` is
|
|
59
|
+
* true, honoring .gitignore. Returns paths relative to `root` with POSIX
|
|
60
|
+
* separators, sorted.
|
|
61
|
+
*/
|
|
62
|
+
function walk(root, accept, opts) {
|
|
63
|
+
const respectGitignore = opts.respectGitignore ?? true;
|
|
64
|
+
const found = [];
|
|
65
|
+
const recurse = (absDir, relDir, layers) => {
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = readdirSync(absDir);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return; // unreadable directory — skip
|
|
72
|
+
}
|
|
73
|
+
// A .gitignore in this directory applies to everything beneath it.
|
|
74
|
+
let activeLayers = layers;
|
|
75
|
+
if (respectGitignore) {
|
|
76
|
+
const layer = loadLayer(absDir, relDir);
|
|
77
|
+
if (layer)
|
|
78
|
+
activeLayers = [...layers, layer];
|
|
79
|
+
}
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const abs = join(absDir, entry);
|
|
82
|
+
const relPath = relDir ? `${relDir}/${entry}` : entry;
|
|
83
|
+
let stat;
|
|
84
|
+
try {
|
|
85
|
+
stat = statSync(abs);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue; // broken symlink etc.
|
|
89
|
+
}
|
|
90
|
+
if (stat.isDirectory()) {
|
|
91
|
+
if (DEFAULT_IGNORE_DIRS.has(entry) || entry.startsWith("."))
|
|
92
|
+
continue;
|
|
93
|
+
if (respectGitignore && isIgnored(activeLayers, relPath, true))
|
|
94
|
+
continue;
|
|
95
|
+
recurse(abs, relPath, activeLayers);
|
|
96
|
+
}
|
|
97
|
+
else if (stat.isFile() && accept(entry)) {
|
|
98
|
+
if (respectGitignore && isIgnored(activeLayers, relPath, false))
|
|
99
|
+
continue;
|
|
100
|
+
found.push(relPath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
recurse(root, "", []);
|
|
105
|
+
return found.sort();
|
|
106
|
+
}
|
|
107
|
+
/** Enumerate analyzable source files (JS/TS/Python) under `root`. */
|
|
108
|
+
export function walkSourceFiles(root, opts = {}) {
|
|
109
|
+
return walk(root, isSourceFile, opts);
|
|
110
|
+
}
|
|
111
|
+
/** Enumerate Markdown documentation files under `root`. */
|
|
112
|
+
export function walkMarkdownFiles(root, opts = {}) {
|
|
113
|
+
return walk(root, (name) => name.endsWith(".md") || name.endsWith(".mdx"), opts);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/extract/walk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny content-addressed cache for LLM responses, persisted at
|
|
3
|
+
* `.keel/llm-cache.json`. Keyed by provider + prompt, so identical requests on
|
|
4
|
+
* a re-run cost nothing. Disabled (in-memory, never written) under --no-cache.
|
|
5
|
+
*/
|
|
6
|
+
export declare class LLMCache {
|
|
7
|
+
private readonly enabled;
|
|
8
|
+
private readonly path;
|
|
9
|
+
private data;
|
|
10
|
+
private dirty;
|
|
11
|
+
constructor(root: string, enabled: boolean);
|
|
12
|
+
private load;
|
|
13
|
+
private key;
|
|
14
|
+
get(providerName: string, prompt: string): string | undefined;
|
|
15
|
+
set(providerName: string, prompt: string, value: string): void;
|
|
16
|
+
save(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { sha1 } from "../util/hash.js";
|
|
4
|
+
/**
|
|
5
|
+
* A tiny content-addressed cache for LLM responses, persisted at
|
|
6
|
+
* `.keel/llm-cache.json`. Keyed by provider + prompt, so identical requests on
|
|
7
|
+
* a re-run cost nothing. Disabled (in-memory, never written) under --no-cache.
|
|
8
|
+
*/
|
|
9
|
+
export class LLMCache {
|
|
10
|
+
enabled;
|
|
11
|
+
path;
|
|
12
|
+
data;
|
|
13
|
+
dirty = false;
|
|
14
|
+
constructor(root, enabled) {
|
|
15
|
+
this.enabled = enabled;
|
|
16
|
+
this.path = join(root, ".keel", "llm-cache.json");
|
|
17
|
+
this.data = this.load();
|
|
18
|
+
}
|
|
19
|
+
load() {
|
|
20
|
+
if (!this.enabled || !existsSync(this.path))
|
|
21
|
+
return {};
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(this.path, "utf8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
key(providerName, prompt) {
|
|
30
|
+
return sha1(`${providerName}::${prompt}`);
|
|
31
|
+
}
|
|
32
|
+
get(providerName, prompt) {
|
|
33
|
+
if (!this.enabled)
|
|
34
|
+
return undefined;
|
|
35
|
+
return this.data[this.key(providerName, prompt)];
|
|
36
|
+
}
|
|
37
|
+
set(providerName, prompt, value) {
|
|
38
|
+
if (!this.enabled)
|
|
39
|
+
return;
|
|
40
|
+
this.data[this.key(providerName, prompt)] = value;
|
|
41
|
+
this.dirty = true;
|
|
42
|
+
}
|
|
43
|
+
save() {
|
|
44
|
+
if (!this.enabled || !this.dirty)
|
|
45
|
+
return;
|
|
46
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
47
|
+
writeFileSync(this.path, JSON.stringify(this.data));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/llm/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC;;;;GAIG;AACH,MAAM,OAAO,QAAQ;IAOA;IANF,IAAI,CAAS;IACtB,IAAI,CAAyB;IAC7B,KAAK,GAAG,KAAK,CAAC;IAEtB,YACE,IAAY,EACK,OAAgB;QAAhB,YAAO,GAAP,OAAO,CAAS;QAEjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAA2B,CAAC;QAC/E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,GAAG,CAAC,YAAoB,EAAE,MAAc;QAC9C,OAAO,IAAI,CAAC,GAAG,YAAY,KAAK,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,GAAG,CAAC,YAAoB,EAAE,MAAc;QACtC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QACpC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,GAAG,CAAC,YAAoB,EAAE,MAAc,EAAE,KAAa;QACrD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC;QAClD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO;QACzC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { KeelConfig } from "../config.js";
|
|
2
|
+
import type { Claim } from "../types.js";
|
|
3
|
+
import type { LLMProvider } from "./provider.js";
|
|
4
|
+
import type { LLMCache } from "./cache.js";
|
|
5
|
+
/**
|
|
6
|
+
* Tier 2 of doc/knowledge drift: use the LLM to extract checkable stack claims
|
|
7
|
+
* from freeform documentation prose. The LLM only turns English into claims —
|
|
8
|
+
* the deterministic drift engine still does the verifying. Returns [] on any
|
|
9
|
+
* failure or when no provider is available. Cached by prompt.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractClaimsFromDocs(root: string, config: KeelConfig, provider: LLMProvider, cache: LLMCache): Promise<Claim[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Add inferred stack claims that the structured block didn't already cover.
|
|
14
|
+
* Structured claims always win (a hand-written `## Stack` beats prose inference).
|
|
15
|
+
*/
|
|
16
|
+
export declare function mergeInferredClaims(existing: Claim[], inferred: Claim[]): Claim[];
|