@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
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface KeelConfig {
|
|
2
|
+
scan: {
|
|
3
|
+
respectGitignore: boolean;
|
|
4
|
+
};
|
|
5
|
+
python: {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
docs: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
};
|
|
11
|
+
duplication: {
|
|
12
|
+
minTokens: number;
|
|
13
|
+
near: {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
threshold: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
libConflicts: {
|
|
19
|
+
ignoreBuckets: string[];
|
|
20
|
+
};
|
|
21
|
+
claims: {
|
|
22
|
+
sources: string[];
|
|
23
|
+
};
|
|
24
|
+
llm: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_CONFIG: KeelConfig;
|
|
31
|
+
/** Load keel.config.json from `root` and shallow-merge over defaults. */
|
|
32
|
+
export declare function loadConfig(root: string): KeelConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const DEFAULT_CONFIG = {
|
|
4
|
+
scan: { respectGitignore: true },
|
|
5
|
+
python: { enabled: true },
|
|
6
|
+
docs: { enabled: true },
|
|
7
|
+
duplication: { minTokens: 20, near: { enabled: true, threshold: 0.85 } },
|
|
8
|
+
libConflicts: { ignoreBuckets: [] },
|
|
9
|
+
claims: { sources: ["CLAUDE.md", "AGENTS.md"] },
|
|
10
|
+
llm: { enabled: false, provider: "auto", model: "" },
|
|
11
|
+
};
|
|
12
|
+
/** Load keel.config.json from `root` and shallow-merge over defaults. */
|
|
13
|
+
export function loadConfig(root) {
|
|
14
|
+
const path = join(root, "keel.config.json");
|
|
15
|
+
if (!existsSync(path))
|
|
16
|
+
return DEFAULT_CONFIG;
|
|
17
|
+
try {
|
|
18
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
19
|
+
return {
|
|
20
|
+
scan: { ...DEFAULT_CONFIG.scan, ...raw.scan },
|
|
21
|
+
python: { ...DEFAULT_CONFIG.python, ...raw.python },
|
|
22
|
+
docs: { ...DEFAULT_CONFIG.docs, ...raw.docs },
|
|
23
|
+
duplication: {
|
|
24
|
+
...DEFAULT_CONFIG.duplication,
|
|
25
|
+
...raw.duplication,
|
|
26
|
+
near: { ...DEFAULT_CONFIG.duplication.near, ...raw.duplication?.near },
|
|
27
|
+
},
|
|
28
|
+
libConflicts: { ...DEFAULT_CONFIG.libConflicts, ...raw.libConflicts },
|
|
29
|
+
claims: { ...DEFAULT_CONFIG.claims, ...raw.claims },
|
|
30
|
+
llm: { ...DEFAULT_CONFIG.llm, ...raw.llm },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return DEFAULT_CONFIG;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAejC,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,IAAI,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE;IAChC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACzB,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACvB,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE;IACxE,YAAY,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;IACnC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE;IAC/C,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;CACrD,CAAC;AAEF,yEAAyE;AACzE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,cAAc,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAwB,CAAC;QAC1E,OAAO;YACL,IAAI,EAAE,EAAE,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE;YAC7C,MAAM,EAAE,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE;YACnD,IAAI,EAAE,EAAE,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE;YAC7C,WAAW,EAAE;gBACX,GAAG,cAAc,CAAC,WAAW;gBAC7B,GAAG,GAAG,CAAC,WAAW;gBAClB,IAAI,EAAE,EAAE,GAAG,cAAc,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE;aACvE;YACD,YAAY,EAAE,EAAE,GAAG,cAAc,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE;YACrE,MAAM,EAAE,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE;YACnD,GAAG,EAAE,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,GAAG,EAAE;SAC3C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type SourceFile } from "ts-morph";
|
|
2
|
+
import type { ImportFact } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reduce a module specifier to its npm package name, stripping subpaths.
|
|
5
|
+
* "axios" -> "axios"
|
|
6
|
+
* "axios/lib/foo" -> "axios"
|
|
7
|
+
* "@scope/pkg/sub" -> "@scope/pkg"
|
|
8
|
+
* "./local" -> null
|
|
9
|
+
*/
|
|
10
|
+
export declare function toPackageName(specifier: string): string | null;
|
|
11
|
+
/** Extract every static/dynamic import and `require(...)` call from a source file. */
|
|
12
|
+
export declare function extractImports(sourceFile: SourceFile): ImportFact[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Node, SyntaxKind } from "ts-morph";
|
|
2
|
+
import { isBuiltin } from "node:module";
|
|
3
|
+
/**
|
|
4
|
+
* Reduce a module specifier to its npm package name, stripping subpaths.
|
|
5
|
+
* "axios" -> "axios"
|
|
6
|
+
* "axios/lib/foo" -> "axios"
|
|
7
|
+
* "@scope/pkg/sub" -> "@scope/pkg"
|
|
8
|
+
* "./local" -> null
|
|
9
|
+
*/
|
|
10
|
+
export function toPackageName(specifier) {
|
|
11
|
+
if (specifier.startsWith(".") || specifier.startsWith("/"))
|
|
12
|
+
return null;
|
|
13
|
+
const bare = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
|
14
|
+
const parts = bare.split("/");
|
|
15
|
+
if (bare.startsWith("@")) {
|
|
16
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : bare;
|
|
17
|
+
}
|
|
18
|
+
return parts[0] ?? bare;
|
|
19
|
+
}
|
|
20
|
+
function isExternal(specifier) {
|
|
21
|
+
if (specifier.startsWith(".") || specifier.startsWith("/"))
|
|
22
|
+
return false;
|
|
23
|
+
if (specifier.startsWith("node:"))
|
|
24
|
+
return false;
|
|
25
|
+
if (isBuiltin(specifier))
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
/** Extract every static/dynamic import and `require(...)` call from a source file. */
|
|
30
|
+
export function extractImports(sourceFile) {
|
|
31
|
+
const facts = [];
|
|
32
|
+
const push = (specifier, named, line) => {
|
|
33
|
+
facts.push({
|
|
34
|
+
specifier,
|
|
35
|
+
external: isExternal(specifier),
|
|
36
|
+
packageName: toPackageName(specifier),
|
|
37
|
+
named,
|
|
38
|
+
line,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
// ES `import ... from "..."`
|
|
42
|
+
for (const decl of sourceFile.getImportDeclarations()) {
|
|
43
|
+
const named = [];
|
|
44
|
+
const def = decl.getDefaultImport();
|
|
45
|
+
if (def)
|
|
46
|
+
named.push(def.getText());
|
|
47
|
+
const ns = decl.getNamespaceImport();
|
|
48
|
+
if (ns)
|
|
49
|
+
named.push(`* as ${ns.getText()}`);
|
|
50
|
+
for (const spec of decl.getNamedImports())
|
|
51
|
+
named.push(spec.getName());
|
|
52
|
+
push(decl.getModuleSpecifierValue(), named, decl.getStartLineNumber());
|
|
53
|
+
}
|
|
54
|
+
// `export ... from "..."`
|
|
55
|
+
for (const decl of sourceFile.getExportDeclarations()) {
|
|
56
|
+
const spec = decl.getModuleSpecifierValue();
|
|
57
|
+
if (spec)
|
|
58
|
+
push(spec, [], decl.getStartLineNumber());
|
|
59
|
+
}
|
|
60
|
+
// `require("...")` and dynamic `import("...")`
|
|
61
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
62
|
+
const expr = call.getExpression();
|
|
63
|
+
const isRequire = Node.isIdentifier(expr) && expr.getText() === "require";
|
|
64
|
+
const isDynamicImport = expr.getKind() === SyntaxKind.ImportKeyword;
|
|
65
|
+
if (!isRequire && !isDynamicImport)
|
|
66
|
+
continue;
|
|
67
|
+
const arg = call.getArguments()[0];
|
|
68
|
+
if (arg && Node.isStringLiteral(arg)) {
|
|
69
|
+
push(arg.getLiteralValue(), [], call.getStartLineNumber());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return facts;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=imports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.js","sourceRoot":"","sources":["../../src/extract/imports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { KeelConfig } from "../config.js";
|
|
2
|
+
import type { Facts } from "../types.js";
|
|
3
|
+
import type { PythonExtractor } from "./python.js";
|
|
4
|
+
export interface ExtractResult {
|
|
5
|
+
facts: Facts;
|
|
6
|
+
/** Files reused from cache (unchanged since last run). */
|
|
7
|
+
reused: number;
|
|
8
|
+
/** Files freshly parsed this run. */
|
|
9
|
+
parsed: number;
|
|
10
|
+
/** Files skipped as minified/generated bundles. */
|
|
11
|
+
skipped: number;
|
|
12
|
+
/** Source files in a language keel couldn't parse this run (e.g. Python disabled). */
|
|
13
|
+
unsupported: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ExtractOptions {
|
|
16
|
+
/** When false, the .keel cache is neither read nor written (read-only run). */
|
|
17
|
+
persistCache?: boolean;
|
|
18
|
+
/** Override config's scan.respectGitignore (CLI takes precedence). */
|
|
19
|
+
respectGitignore?: boolean;
|
|
20
|
+
/** Pre-loaded Python extractor; when absent, .py files are left unparsed. */
|
|
21
|
+
pythonExtractor?: PythonExtractor;
|
|
22
|
+
}
|
|
23
|
+
/** Walk the repo, parse changed files, and assemble the deterministic Facts. */
|
|
24
|
+
export declare function extract(root: string, config: KeelConfig, opts?: ExtractOptions): ExtractResult;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Project } from "ts-morph";
|
|
4
|
+
import { HashCache } from "../cache/hashCache.js";
|
|
5
|
+
import { sha1 } from "../util/hash.js";
|
|
6
|
+
import { walkSourceFiles } from "./walk.js";
|
|
7
|
+
import { extractImports } from "./imports.js";
|
|
8
|
+
import { extractFunctions } from "./symbols.js";
|
|
9
|
+
import { extractNaming, emptyNamingStats } from "./naming.js";
|
|
10
|
+
import { extractPackageFacts } from "./packageJson.js";
|
|
11
|
+
import { languageOf } from "./language.js";
|
|
12
|
+
import { ignoreLinesOf } from "../suppress.js";
|
|
13
|
+
/** Largest source file we treat as hand-written; bigger is assumed generated. */
|
|
14
|
+
const MAX_SOURCE_BYTES = 512 * 1024;
|
|
15
|
+
/** Longest line in hand-written source; longer implies minified/bundled output. */
|
|
16
|
+
const MAX_LINE_LENGTH = 1000;
|
|
17
|
+
/**
|
|
18
|
+
* Heuristic: does this file look like minified/generated output rather than
|
|
19
|
+
* hand-written source? Such files (Prisma runtimes, webpack bundles) otherwise
|
|
20
|
+
* flood duplication results with library internals.
|
|
21
|
+
*/
|
|
22
|
+
function looksGenerated(content) {
|
|
23
|
+
if (content.length > MAX_SOURCE_BYTES)
|
|
24
|
+
return true;
|
|
25
|
+
let lineLen = 0;
|
|
26
|
+
for (let i = 0; i < content.length; i++) {
|
|
27
|
+
if (content.charCodeAt(i) === 10 /* \n */) {
|
|
28
|
+
lineLen = 0;
|
|
29
|
+
}
|
|
30
|
+
else if (++lineLen > MAX_LINE_LENGTH) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/** Walk the repo, parse changed files, and assemble the deterministic Facts. */
|
|
37
|
+
export function extract(root, config, opts = {}) {
|
|
38
|
+
const persistCache = opts.persistCache ?? true;
|
|
39
|
+
const respectGitignore = opts.respectGitignore ?? config.scan.respectGitignore;
|
|
40
|
+
const cache = new HashCache(root);
|
|
41
|
+
const relPaths = walkSourceFiles(root, { respectGitignore });
|
|
42
|
+
const project = new Project({
|
|
43
|
+
skipAddingFilesFromTsConfig: true,
|
|
44
|
+
skipFileDependencyResolution: true,
|
|
45
|
+
compilerOptions: { allowJs: true },
|
|
46
|
+
});
|
|
47
|
+
const files = [];
|
|
48
|
+
let reused = 0;
|
|
49
|
+
let parsed = 0;
|
|
50
|
+
let skipped = 0;
|
|
51
|
+
let unsupported = 0;
|
|
52
|
+
for (const relPath of relPaths) {
|
|
53
|
+
const language = languageOf(relPath);
|
|
54
|
+
if (!language)
|
|
55
|
+
continue;
|
|
56
|
+
if (language === "python" && !opts.pythonExtractor) {
|
|
57
|
+
unsupported += 1; // .py present but Python parsing not loaded/enabled
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const abs = join(root, relPath);
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = readFileSync(abs, "utf8");
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (looksGenerated(content)) {
|
|
69
|
+
skipped += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const hash = sha1(content);
|
|
73
|
+
const cached = persistCache ? cache.get(relPath, hash) : undefined;
|
|
74
|
+
if (cached) {
|
|
75
|
+
files.push(cached);
|
|
76
|
+
cache.set(relPath, hash, cached);
|
|
77
|
+
reused += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const base = { path: relPath, hash, language, ignoreLines: ignoreLinesOf(content) };
|
|
81
|
+
let facts;
|
|
82
|
+
try {
|
|
83
|
+
if (language === "python") {
|
|
84
|
+
const e = opts.pythonExtractor.extractFile(content, relPath, config.duplication.minTokens);
|
|
85
|
+
facts = { ...base, imports: e.imports, functions: e.functions, naming: e.naming };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const sf = project.createSourceFile(abs, content, { overwrite: true });
|
|
89
|
+
facts = {
|
|
90
|
+
...base,
|
|
91
|
+
imports: extractImports(sf),
|
|
92
|
+
functions: extractFunctions(sf, config.duplication.minTokens, relPath),
|
|
93
|
+
naming: extractNaming(sf),
|
|
94
|
+
};
|
|
95
|
+
project.removeSourceFile(sf);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Unparseable file — record an empty entry so it's cached and skipped.
|
|
100
|
+
facts = { ...base, imports: [], functions: [], naming: emptyNamingStats() };
|
|
101
|
+
}
|
|
102
|
+
files.push(facts);
|
|
103
|
+
if (persistCache)
|
|
104
|
+
cache.set(relPath, hash, facts);
|
|
105
|
+
parsed += 1;
|
|
106
|
+
}
|
|
107
|
+
if (persistCache)
|
|
108
|
+
cache.save();
|
|
109
|
+
return {
|
|
110
|
+
facts: { root, files, pkg: extractPackageFacts(root) },
|
|
111
|
+
reused,
|
|
112
|
+
parsed,
|
|
113
|
+
skipped,
|
|
114
|
+
unsupported,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extract/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2
|
+
const PY_EXTENSIONS = [".py"];
|
|
3
|
+
/** Determine a file's source language from its extension (null if unsupported). */
|
|
4
|
+
export function languageOf(path) {
|
|
5
|
+
if (path.endsWith(".d.ts"))
|
|
6
|
+
return null;
|
|
7
|
+
if (JS_EXTENSIONS.some((ext) => path.endsWith(ext)))
|
|
8
|
+
return "javascript";
|
|
9
|
+
if (PY_EXTENSIONS.some((ext) => path.endsWith(ext)))
|
|
10
|
+
return "python";
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=language.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language.js","sourceRoot":"","sources":["../../src/extract/language.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACrE,MAAM,aAAa,GAAG,CAAC,KAAK,CAAC,CAAC;AAE9B,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,YAAY,CAAC;IACzE,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrE,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type SourceFile } from "ts-morph";
|
|
2
|
+
import type { NamingStats } from "../types.js";
|
|
3
|
+
/** Classify a single identifier into a casing bucket. */
|
|
4
|
+
export declare function classifyCasing(name: string): keyof NamingStats;
|
|
5
|
+
export declare const emptyNamingStats: () => NamingStats;
|
|
6
|
+
/**
|
|
7
|
+
* Tally identifier casing for value-level declarations (variables, functions,
|
|
8
|
+
* parameters, methods). Types/classes are excluded — they're PascalCase by
|
|
9
|
+
* convention and would skew a "camelCase" signal.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractNaming(sourceFile: SourceFile): NamingStats;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Node, SyntaxKind } from "ts-morph";
|
|
2
|
+
/** Classify a single identifier into a casing bucket. */
|
|
3
|
+
export function classifyCasing(name) {
|
|
4
|
+
if (/^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(name) && /_|^[A-Z0-9]+$/.test(name)) {
|
|
5
|
+
// ALL_CAPS or CONSTANT_CASE (with underscores)
|
|
6
|
+
if (/_/.test(name) || name.length > 1)
|
|
7
|
+
return "CONSTANT_CASE";
|
|
8
|
+
}
|
|
9
|
+
if (/^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(name))
|
|
10
|
+
return "snake_case";
|
|
11
|
+
if (/^[a-z][a-z0-9]*$/.test(name))
|
|
12
|
+
return "lower"; // single lowercase word — ambiguous
|
|
13
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(name))
|
|
14
|
+
return "camelCase"; // has an internal capital
|
|
15
|
+
if (/^[A-Z][a-zA-Z0-9]*$/.test(name))
|
|
16
|
+
return "PascalCase";
|
|
17
|
+
return "other";
|
|
18
|
+
}
|
|
19
|
+
export const emptyNamingStats = () => ({
|
|
20
|
+
lower: 0,
|
|
21
|
+
camelCase: 0,
|
|
22
|
+
PascalCase: 0,
|
|
23
|
+
snake_case: 0,
|
|
24
|
+
CONSTANT_CASE: 0,
|
|
25
|
+
other: 0,
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Tally identifier casing for value-level declarations (variables, functions,
|
|
29
|
+
* parameters, methods). Types/classes are excluded — they're PascalCase by
|
|
30
|
+
* convention and would skew a "camelCase" signal.
|
|
31
|
+
*/
|
|
32
|
+
export function extractNaming(sourceFile) {
|
|
33
|
+
const stats = emptyNamingStats();
|
|
34
|
+
const tally = (name) => {
|
|
35
|
+
if (!name || name.startsWith("_") || name.startsWith("$"))
|
|
36
|
+
return;
|
|
37
|
+
stats[classifyCasing(name)] += 1;
|
|
38
|
+
};
|
|
39
|
+
for (const decl of sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
40
|
+
const nameNode = decl.getNameNode();
|
|
41
|
+
if (Node.isIdentifier(nameNode))
|
|
42
|
+
tally(nameNode.getText());
|
|
43
|
+
}
|
|
44
|
+
for (const fn of sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration)) {
|
|
45
|
+
tally(fn.getName());
|
|
46
|
+
}
|
|
47
|
+
for (const method of sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration)) {
|
|
48
|
+
tally(method.getName());
|
|
49
|
+
}
|
|
50
|
+
for (const param of sourceFile.getDescendantsOfKind(SyntaxKind.Parameter)) {
|
|
51
|
+
const nameNode = param.getNameNode();
|
|
52
|
+
if (Node.isIdentifier(nameNode))
|
|
53
|
+
tally(nameNode.getText());
|
|
54
|
+
}
|
|
55
|
+
return stats;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../src/extract/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAmB,MAAM,UAAU,CAAC;AAG7D,yDAAyD;AACzD,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7E,+CAA+C;QAC/C,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,eAAe,CAAC;IAChE,CAAC;IACD,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IACpE,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,oCAAoC;IACvF,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,WAAW,CAAC,CAAC,0BAA0B;IACpF,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IAC1D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAgB,EAAE,CAAC,CAAC;IAClD,KAAK,EAAE,CAAC;IACR,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,CAAC;IACb,aAAa,EAAE,CAAC;IAChB,KAAK,EAAE,CAAC;CACT,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAsB;IAClD,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IAEjC,MAAM,KAAK,GAAG,CAAC,IAAwB,EAAQ,EAAE;QAC/C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QAClE,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YAAE,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACjF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACtB,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACnF,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1B,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1E,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YAAE,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Infer the package manager from the lockfile present in the repo root. */
|
|
4
|
+
function detectPackageManager(root, pkgManagerField) {
|
|
5
|
+
if (pkgManagerField) {
|
|
6
|
+
const name = pkgManagerField.split("@")[0];
|
|
7
|
+
if (name === "pnpm" || name === "yarn" || name === "npm" || name === "bun")
|
|
8
|
+
return name;
|
|
9
|
+
}
|
|
10
|
+
if (existsSync(join(root, "pnpm-lock.yaml")))
|
|
11
|
+
return "pnpm";
|
|
12
|
+
if (existsSync(join(root, "yarn.lock")))
|
|
13
|
+
return "yarn";
|
|
14
|
+
if (existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock")))
|
|
15
|
+
return "bun";
|
|
16
|
+
if (existsSync(join(root, "package-lock.json")))
|
|
17
|
+
return "npm";
|
|
18
|
+
return "unknown";
|
|
19
|
+
}
|
|
20
|
+
/** Read and normalize package.json facts. Returns null when no package.json exists. */
|
|
21
|
+
export function extractPackageFacts(root) {
|
|
22
|
+
const pkgPath = join(root, "package.json");
|
|
23
|
+
if (!existsSync(pkgPath))
|
|
24
|
+
return null;
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null; // malformed package.json — treat as absent
|
|
31
|
+
}
|
|
32
|
+
const dependencies = raw.dependencies ?? {};
|
|
33
|
+
const devDependencies = raw.devDependencies ?? {};
|
|
34
|
+
const allDeps = [...new Set([...Object.keys(dependencies), ...Object.keys(devDependencies)])];
|
|
35
|
+
return {
|
|
36
|
+
name: typeof raw.name === "string" ? raw.name : null,
|
|
37
|
+
dependencies,
|
|
38
|
+
devDependencies,
|
|
39
|
+
allDeps,
|
|
40
|
+
packageManager: detectPackageManager(root, raw.packageManager),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=packageJson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packageJson.js","sourceRoot":"","sources":["../../src/extract/packageJson.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,4EAA4E;AAC5E,SAAS,oBAAoB,CAAC,IAAY,EAAE,eAAwB;IAClE,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;IAC1F,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5D,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IACvD,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5F,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,IAAI,GAA4B,CAAC;IACjC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAA4B,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,2CAA2C;IAC1D,CAAC;IAED,MAAM,YAAY,GAAI,GAAG,CAAC,YAAuC,IAAI,EAAE,CAAC;IACxE,MAAM,eAAe,GAAI,GAAG,CAAC,eAA0C,IAAI,EAAE,CAAC;IAC9E,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9F,OAAO;QACL,IAAI,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;QACpD,YAAY;QACZ,eAAe;QACf,OAAO;QACP,cAAc,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,cAAoC,CAAC;KACrF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FunctionFact, ImportFact, NamingStats } from "../types.js";
|
|
2
|
+
export interface PythonExtraction {
|
|
3
|
+
imports: ImportFact[];
|
|
4
|
+
functions: FunctionFact[];
|
|
5
|
+
naming: NamingStats;
|
|
6
|
+
}
|
|
7
|
+
export interface PythonExtractor {
|
|
8
|
+
extractFile(content: string, filePath: string, minTokens: number): PythonExtraction;
|
|
9
|
+
}
|
|
10
|
+
/** Create a Python extractor (loads the grammar on first call, then reuses it). */
|
|
11
|
+
export declare function createPythonExtractor(): Promise<PythonExtractor>;
|