@smeltjs/core 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 +202 -0
- package/README.md +55 -0
- package/THIRD-PARTY.md +140 -0
- package/dist/apply.d.ts +145 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/apply.js +244 -0
- package/dist/apply.js.map +1 -0
- package/dist/cache/prefix.d.ts +164 -0
- package/dist/cache/prefix.d.ts.map +1 -0
- package/dist/cache/prefix.js +361 -0
- package/dist/cache/prefix.js.map +1 -0
- package/dist/cli/args.d.ts +71 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +308 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +161 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/config.d.ts +73 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +144 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/init.d.ts +56 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +449 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/report.d.ts +44 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +113 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/resolve.d.ts +103 -0
- package/dist/cli/resolve.d.ts.map +1 -0
- package/dist/cli/resolve.js +106 -0
- package/dist/cli/resolve.js.map +1 -0
- package/dist/cli/run.d.ts +99 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +351 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/detect.d.ts +16 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +18 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +88 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +107 -0
- package/dist/errors.js.map +1 -0
- package/dist/hash.d.ts +13 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +16 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/lang/bash.d.ts +3 -0
- package/dist/lang/bash.d.ts.map +1 -0
- package/dist/lang/bash.js +28 -0
- package/dist/lang/bash.js.map +1 -0
- package/dist/lang/c.d.ts +3 -0
- package/dist/lang/c.d.ts.map +1 -0
- package/dist/lang/c.js +39 -0
- package/dist/lang/c.js.map +1 -0
- package/dist/lang/c_sharp.d.ts +3 -0
- package/dist/lang/c_sharp.d.ts.map +1 -0
- package/dist/lang/c_sharp.js +37 -0
- package/dist/lang/c_sharp.js.map +1 -0
- package/dist/lang/cpp.d.ts +3 -0
- package/dist/lang/cpp.d.ts.map +1 -0
- package/dist/lang/cpp.js +46 -0
- package/dist/lang/cpp.js.map +1 -0
- package/dist/lang/go.d.ts +3 -0
- package/dist/lang/go.d.ts.map +1 -0
- package/dist/lang/go.js +31 -0
- package/dist/lang/go.js.map +1 -0
- package/dist/lang/java.d.ts +3 -0
- package/dist/lang/java.d.ts.map +1 -0
- package/dist/lang/java.js +34 -0
- package/dist/lang/java.js.map +1 -0
- package/dist/lang/javascript.d.ts +3 -0
- package/dist/lang/javascript.d.ts.map +1 -0
- package/dist/lang/javascript.js +28 -0
- package/dist/lang/javascript.js.map +1 -0
- package/dist/lang/kotlin.d.ts +3 -0
- package/dist/lang/kotlin.d.ts.map +1 -0
- package/dist/lang/kotlin.js +37 -0
- package/dist/lang/kotlin.js.map +1 -0
- package/dist/lang/php.d.ts +3 -0
- package/dist/lang/php.d.ts.map +1 -0
- package/dist/lang/php.js +37 -0
- package/dist/lang/php.js.map +1 -0
- package/dist/lang/profile.d.ts +130 -0
- package/dist/lang/profile.d.ts.map +1 -0
- package/dist/lang/profile.js +2 -0
- package/dist/lang/profile.js.map +1 -0
- package/dist/lang/python.d.ts +3 -0
- package/dist/lang/python.d.ts.map +1 -0
- package/dist/lang/python.js +33 -0
- package/dist/lang/python.js.map +1 -0
- package/dist/lang/registry.d.ts +31 -0
- package/dist/lang/registry.d.ts.map +1 -0
- package/dist/lang/registry.js +89 -0
- package/dist/lang/registry.js.map +1 -0
- package/dist/lang/ruby.d.ts +3 -0
- package/dist/lang/ruby.d.ts.map +1 -0
- package/dist/lang/ruby.js +47 -0
- package/dist/lang/ruby.js.map +1 -0
- package/dist/lang/rust.d.ts +3 -0
- package/dist/lang/rust.d.ts.map +1 -0
- package/dist/lang/rust.js +45 -0
- package/dist/lang/rust.js.map +1 -0
- package/dist/lang/swift.d.ts +3 -0
- package/dist/lang/swift.d.ts.map +1 -0
- package/dist/lang/swift.js +34 -0
- package/dist/lang/swift.js.map +1 -0
- package/dist/lang/tsx.d.ts +4 -0
- package/dist/lang/tsx.d.ts.map +1 -0
- package/dist/lang/tsx.js +11 -0
- package/dist/lang/tsx.js.map +1 -0
- package/dist/lang/typescript.d.ts +18 -0
- package/dist/lang/typescript.d.ts.map +1 -0
- package/dist/lang/typescript.js +61 -0
- package/dist/lang/typescript.js.map +1 -0
- package/dist/net/policy.d.ts +49 -0
- package/dist/net/policy.d.ts.map +1 -0
- package/dist/net/policy.js +120 -0
- package/dist/net/policy.js.map +1 -0
- package/dist/plan/grammar.d.ts +38 -0
- package/dist/plan/grammar.d.ts.map +1 -0
- package/dist/plan/grammar.js +85 -0
- package/dist/plan/grammar.js.map +1 -0
- package/dist/plan/lexical.d.ts +51 -0
- package/dist/plan/lexical.d.ts.map +1 -0
- package/dist/plan/lexical.js +167 -0
- package/dist/plan/lexical.js.map +1 -0
- package/dist/plan/planners.d.ts +35 -0
- package/dist/plan/planners.d.ts.map +1 -0
- package/dist/plan/planners.js +26 -0
- package/dist/plan/planners.js.map +1 -0
- package/dist/plan/structural.d.ts +68 -0
- package/dist/plan/structural.d.ts.map +1 -0
- package/dist/plan/structural.js +458 -0
- package/dist/plan/structural.js.map +1 -0
- package/dist/repomap/cache.d.ts +53 -0
- package/dist/repomap/cache.d.ts.map +1 -0
- package/dist/repomap/cache.js +139 -0
- package/dist/repomap/cache.js.map +1 -0
- package/dist/repomap/map.d.ts +161 -0
- package/dist/repomap/map.d.ts.map +1 -0
- package/dist/repomap/map.js +302 -0
- package/dist/repomap/map.js.map +1 -0
- package/dist/repomap/rank.d.ts +52 -0
- package/dist/repomap/rank.d.ts.map +1 -0
- package/dist/repomap/rank.js +155 -0
- package/dist/repomap/rank.js.map +1 -0
- package/dist/repomap/tags.d.ts +47 -0
- package/dist/repomap/tags.d.ts.map +1 -0
- package/dist/repomap/tags.js +101 -0
- package/dist/repomap/tags.js.map +1 -0
- package/dist/retrieve.d.ts +14 -0
- package/dist/retrieve.d.ts.map +1 -0
- package/dist/retrieve.js +44 -0
- package/dist/retrieve.js.map +1 -0
- package/dist/stages.d.ts +22 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +33 -0
- package/dist/stages.js.map +1 -0
- package/dist/stats.d.ts +40 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +37 -0
- package/dist/stats.js.map +1 -0
- package/dist/store-dir.d.ts +98 -0
- package/dist/store-dir.d.ts.map +1 -0
- package/dist/store-dir.js +379 -0
- package/dist/store-dir.js.map +1 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +56 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +298 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +68 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { contentHash } from '../hash.js';
|
|
5
|
+
/**
|
|
6
|
+
* The repo map's disk cache — per-file tags, keyed by content hash, JSON on disk.
|
|
7
|
+
*
|
|
8
|
+
* Modelled on Aider's repo-map tags cache (https://aider.chat/docs/repomap.html),
|
|
9
|
+
* with one deliberate substitution: Aider persists through SQLite; this repo ships
|
|
10
|
+
* zero new runtime dependencies, so entries are plain JSON files under a directory
|
|
11
|
+
* **the caller explicitly hands in**. smelt never writes outside a store or cache it
|
|
12
|
+
* was handed — no default location, no home-directory guessing.
|
|
13
|
+
*
|
|
14
|
+
* The key is a content hash over the tag-format version, the language, and the file's
|
|
15
|
+
* exact text. That is the whole invalidation story: edit a file and its key changes,
|
|
16
|
+
* so the stale entry is simply never looked up again. Nothing needs a timestamp.
|
|
17
|
+
*
|
|
18
|
+
* A corrupt entry — unparseable JSON, or JSON of the wrong shape — is **discarded
|
|
19
|
+
* loudly, never trusted**: the entry file is deleted, the tags are re-extracted from
|
|
20
|
+
* source, and the caller's result carries a warning naming the file. Trusting a
|
|
21
|
+
* damaged cache would silently drop symbols from the map, which is this project's
|
|
22
|
+
* signature failure mode.
|
|
23
|
+
*/
|
|
24
|
+
/** The format name every cache entry carries. */
|
|
25
|
+
export const TAGS_CACHE_FORMAT = 'smelt-repomap-tags';
|
|
26
|
+
/** Bump this when the tag shape changes: old entries then miss instead of misleading. */
|
|
27
|
+
export const TAGS_CACHE_VERSION = 1;
|
|
28
|
+
/**
|
|
29
|
+
* The cache key for one file's tags. The version and language are part of the hashed
|
|
30
|
+
* material, so a format bump or a re-detected language can never resurrect an entry
|
|
31
|
+
* extracted under different rules.
|
|
32
|
+
*/
|
|
33
|
+
export function tagsCacheKey(language, content) {
|
|
34
|
+
return contentHash(`${TAGS_CACHE_FORMAT}/${String(TAGS_CACHE_VERSION)}\0${language}\0${content}`);
|
|
35
|
+
}
|
|
36
|
+
export class TagsCache {
|
|
37
|
+
#entriesDir;
|
|
38
|
+
/** `dir` is the directory the caller handed in; entries live under `<dir>/tags/`. */
|
|
39
|
+
constructor(dir) {
|
|
40
|
+
this.#entriesDir = join(dir, 'tags');
|
|
41
|
+
mkdirSync(this.#entriesDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The cached tags under `key`, `undefined` on a miss, `'corrupt'` when an entry
|
|
45
|
+
* existed but could not be trusted — in which case it has already been deleted, so
|
|
46
|
+
* the corruption is reported exactly once and never re-read.
|
|
47
|
+
*/
|
|
48
|
+
read(key) {
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = readFileSync(this.#entryPath(key), 'utf8');
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === 'ENOENT')
|
|
55
|
+
return undefined;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
this.#discard(key);
|
|
64
|
+
return 'corrupt';
|
|
65
|
+
}
|
|
66
|
+
const tags = validateEntry(parsed);
|
|
67
|
+
if (tags === undefined) {
|
|
68
|
+
this.#discard(key);
|
|
69
|
+
return 'corrupt';
|
|
70
|
+
}
|
|
71
|
+
return tags;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Persist one file's tags. Written to a temp name and renamed into place, so a
|
|
75
|
+
* concurrent reader never sees a half-written entry; both writers of the same key
|
|
76
|
+
* are writing identical bytes (the key covers the content), so last-rename-wins is
|
|
77
|
+
* harmless. No fsync: unlike the elision store, every entry here is derivable from
|
|
78
|
+
* source, so losing one to a crash costs a re-parse, not a broken promise.
|
|
79
|
+
*/
|
|
80
|
+
write(key, tags) {
|
|
81
|
+
const body = `${JSON.stringify({
|
|
82
|
+
format: TAGS_CACHE_FORMAT,
|
|
83
|
+
version: TAGS_CACHE_VERSION,
|
|
84
|
+
defs: tags.defs,
|
|
85
|
+
refs: tags.refs,
|
|
86
|
+
})}\n`;
|
|
87
|
+
const target = this.#entryPath(key);
|
|
88
|
+
const temp = `${target}.tmp-${String(process.pid)}`;
|
|
89
|
+
writeFileSync(temp, body, 'utf8');
|
|
90
|
+
renameSync(temp, target);
|
|
91
|
+
}
|
|
92
|
+
#entryPath(key) {
|
|
93
|
+
return join(this.#entriesDir, `${key}.json`);
|
|
94
|
+
}
|
|
95
|
+
/** Delete a corrupt entry so it can never be re-read as truth. Best effort. */
|
|
96
|
+
#discard(key) {
|
|
97
|
+
try {
|
|
98
|
+
unlinkSync(this.#entryPath(key));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Already gone, or undeletable — either way it will be overwritten by the
|
|
102
|
+
// rewrite that follows every discard.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** The parsed entry as `FileTags`, or `undefined` when its shape cannot be trusted. */
|
|
107
|
+
function validateEntry(parsed) {
|
|
108
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
109
|
+
return undefined;
|
|
110
|
+
const entry = parsed;
|
|
111
|
+
if (entry.format !== TAGS_CACHE_FORMAT || entry.version !== TAGS_CACHE_VERSION)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (!Array.isArray(entry.defs) || !Array.isArray(entry.refs))
|
|
114
|
+
return undefined;
|
|
115
|
+
const defs = [];
|
|
116
|
+
for (const item of entry.defs) {
|
|
117
|
+
const def = item;
|
|
118
|
+
if (typeof def.name !== 'string' || def.name === '')
|
|
119
|
+
return undefined;
|
|
120
|
+
if (typeof def.kind !== 'string' || def.kind === '')
|
|
121
|
+
return undefined;
|
|
122
|
+
if (typeof def.line !== 'number' || !Number.isInteger(def.line) || def.line < 1) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
defs.push({ name: def.name, kind: def.kind, line: def.line });
|
|
126
|
+
}
|
|
127
|
+
const refs = [];
|
|
128
|
+
for (const item of entry.refs) {
|
|
129
|
+
const ref = item;
|
|
130
|
+
if (typeof ref.name !== 'string' || ref.name === '')
|
|
131
|
+
return undefined;
|
|
132
|
+
if (typeof ref.count !== 'number' || !Number.isInteger(ref.count) || ref.count < 1) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
refs.push({ name: ref.name, count: ref.count });
|
|
136
|
+
}
|
|
137
|
+
return { defs, refs };
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/repomap/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The repo-map builder — Slice 7, the cross-file shape.
|
|
3
|
+
*
|
|
4
|
+
* **Modelled on Aider's repo-map, and credited as such.** The whole design is prior
|
|
5
|
+
* art: Paul Gauthier's Aider extracts tree-sitter definition/reference tags per file,
|
|
6
|
+
* runs PageRank over the cross-file reference graph, fits the ranked result to a
|
|
7
|
+
* budget, and caches tags on disk keyed by content — see
|
|
8
|
+
* https://aider.chat/docs/repomap.html and `aider/repomap.py` in
|
|
9
|
+
* https://github.com/Aider-AI/aider. Nothing about the approach is this project's
|
|
10
|
+
* invention; what this module adds is only smelt's own house rules:
|
|
11
|
+
*
|
|
12
|
+
* - **Local files only** (Law 1). The caller names the root; symlinks are never
|
|
13
|
+
* followed, so the walk cannot leave it; binary files are skipped; no network.
|
|
14
|
+
* - **Every inclusion is explainable** (Law 2, applied to inclusion rather than
|
|
15
|
+
* elision): each symbol in the map carries a rule id and a sentence stating its
|
|
16
|
+
* definition site and the measured reference counts that ranked it.
|
|
17
|
+
* - **Deterministic**: fixed PageRank constants, sorted walks, a total tie-break by
|
|
18
|
+
* path, name, and line. Two runs over the same tree are byte-identical.
|
|
19
|
+
* - **The budget is bytes**, same contract as the planners, and it is respected by
|
|
20
|
+
* construction: symbols are added in rank order until the next line would not fit.
|
|
21
|
+
* - **The cache lives only in a directory the caller explicitly hands in**, and a
|
|
22
|
+
* corrupt entry is discarded loudly — a warning in the result — never trusted.
|
|
23
|
+
*
|
|
24
|
+
* **Deliberately NOT a `Planner`.** `buildRepoMap` returns a {@link RepoMap},
|
|
25
|
+
* not an `ElisionPlan`: nothing here is elided, nothing is stored under a hash, and
|
|
26
|
+
* there is no original to reconstruct — the map is a *summary built up under a
|
|
27
|
+
* budget*, not a *removal to be reversed*. Forcing the Planner interface onto it
|
|
28
|
+
* would claim Law 3 (reversibility) about output that has no bytes to give back,
|
|
29
|
+
* which is exactly the kind of lie the interface exists to prevent. The CLI serves
|
|
30
|
+
* it as its own subcommand (`smelt map`), never as a `--strategy`.
|
|
31
|
+
*/
|
|
32
|
+
/** The id stamped on every map this module emits. */
|
|
33
|
+
export declare const REPO_MAP_ID = "repomap/v1";
|
|
34
|
+
/**
|
|
35
|
+
* Rule id for a symbol something in the scanned tree references — its own file
|
|
36
|
+
* included, so its rank may still be zero when every reference is same-file (only
|
|
37
|
+
* cross-file references move rank).
|
|
38
|
+
*/
|
|
39
|
+
export declare const REPO_MAP_RANKED_RULE = "ranked-definition";
|
|
40
|
+
/** Rule id for a definition nothing in the scanned tree references. */
|
|
41
|
+
export declare const REPO_MAP_UNREFERENCED_RULE = "unreferenced-definition";
|
|
42
|
+
/**
|
|
43
|
+
* Rule id for a definition promoted because it matches a caller-supplied focus term.
|
|
44
|
+
* The rank stays the measured PageRank share — focus moves a symbol's *place in the
|
|
45
|
+
* fill order*, never its numbers.
|
|
46
|
+
*/
|
|
47
|
+
export declare const REPO_MAP_FOCUS_RULE = "focus-match";
|
|
48
|
+
/** Rule id for a file listed by path because no definitions were extracted from it. */
|
|
49
|
+
export declare const REPO_MAP_PATH_ONLY_RULE = "path-only";
|
|
50
|
+
/** Rule id for the warning left behind when a corrupt cache entry is discarded. */
|
|
51
|
+
export declare const REPO_MAP_CACHE_CORRUPT_RULE = "cache-entry-corrupt";
|
|
52
|
+
/**
|
|
53
|
+
* The ignore list used when the caller supplies none. Deliberately tiny: `.git` is
|
|
54
|
+
* object storage, `node_modules` is other people's code. A caller with opinions passes
|
|
55
|
+
* its own list, which *replaces* this one.
|
|
56
|
+
*/
|
|
57
|
+
export declare const DEFAULT_REPO_IGNORE: readonly string[];
|
|
58
|
+
/** Why an entry is in the map — same two-register shape as {@link ElisionReason}. */
|
|
59
|
+
export interface RepoMapReason {
|
|
60
|
+
/** Stable machine id, e.g. `'ranked-definition'`. */
|
|
61
|
+
readonly rule: string;
|
|
62
|
+
/** A sentence a human can read: definition site and the counts that ranked it. */
|
|
63
|
+
readonly explanation: string;
|
|
64
|
+
}
|
|
65
|
+
export interface RepoMapOptions {
|
|
66
|
+
/** The repository root to read. Local files only; symlinks are never followed. */
|
|
67
|
+
readonly root: string;
|
|
68
|
+
/** Ceiling for the rendered map, in UTF-8 bytes. Respected, not aimed at. */
|
|
69
|
+
readonly budgetBytes: number;
|
|
70
|
+
/**
|
|
71
|
+
* What the task is actually about — same meaning as a planner's focus. A
|
|
72
|
+
* definition whose name or path contains a term (case-insensitive, like the
|
|
73
|
+
* lexical planner's default) is promoted to the front of the fill order, so it
|
|
74
|
+
* survives a tight budget. Promotion only: the measured rank and reference
|
|
75
|
+
* counts are never altered, and each promoted entry says which term it matched.
|
|
76
|
+
*/
|
|
77
|
+
readonly focus?: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* Paths to skip, replacing {@link DEFAULT_REPO_IGNORE}. An entry containing `/` is
|
|
80
|
+
* matched as a root-relative path prefix — a trailing slash counts, so `build/`
|
|
81
|
+
* means the root-level `build` tree, never every `build` segment anywhere; a bare
|
|
82
|
+
* name matches any path segment.
|
|
83
|
+
*/
|
|
84
|
+
readonly ignore?: readonly string[];
|
|
85
|
+
/**
|
|
86
|
+
* Directory for the tags cache. **Only** when this is passed is anything written
|
|
87
|
+
* to disk — smelt never writes outside a store or cache it was explicitly handed.
|
|
88
|
+
*/
|
|
89
|
+
readonly cacheDir?: string;
|
|
90
|
+
}
|
|
91
|
+
/** One symbol included in the rendered map, with the receipt for its inclusion. */
|
|
92
|
+
export interface RepoMapEntry {
|
|
93
|
+
readonly path: string;
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly kind: string;
|
|
96
|
+
/** 1-based line of the definition. */
|
|
97
|
+
readonly line: number;
|
|
98
|
+
/** PageRank share this definition attracted. `0` for unreferenced definitions. */
|
|
99
|
+
readonly rank: number;
|
|
100
|
+
/** Measured: total references to this name across the scanned tree. */
|
|
101
|
+
readonly refsIn: number;
|
|
102
|
+
/** Measured: distinct files holding those references. */
|
|
103
|
+
readonly refsInFiles: number;
|
|
104
|
+
/** Measured: references the defining file makes to names defined elsewhere. */
|
|
105
|
+
readonly refsOut: number;
|
|
106
|
+
readonly reason: RepoMapReason;
|
|
107
|
+
}
|
|
108
|
+
/** A file listed by path because no definitions were extracted from it. */
|
|
109
|
+
export interface RepoMapPathEntry {
|
|
110
|
+
readonly path: string;
|
|
111
|
+
readonly reason: RepoMapReason;
|
|
112
|
+
}
|
|
113
|
+
/** Something worth telling the caller that is not worth failing the build over. */
|
|
114
|
+
export interface RepoMapWarning {
|
|
115
|
+
readonly rule: string;
|
|
116
|
+
readonly explanation: string;
|
|
117
|
+
}
|
|
118
|
+
/** Measured cache activity for one build. Counts only — smelt claims no rates. */
|
|
119
|
+
export interface RepoMapCacheCounts {
|
|
120
|
+
/** Lookups answered from disk. */
|
|
121
|
+
readonly hits: number;
|
|
122
|
+
/** Lookups that found nothing and re-extracted from source. */
|
|
123
|
+
readonly misses: number;
|
|
124
|
+
/** Corrupt entries deleted and re-extracted; each one also left a warning. */
|
|
125
|
+
readonly discarded: number;
|
|
126
|
+
}
|
|
127
|
+
export interface RepoMap {
|
|
128
|
+
readonly id: typeof REPO_MAP_ID;
|
|
129
|
+
/** The rendered map. Always at most `budgetBytes` UTF-8 bytes. */
|
|
130
|
+
readonly text: string;
|
|
131
|
+
readonly outputBytes: number;
|
|
132
|
+
readonly budgetBytes: number;
|
|
133
|
+
/** The symbols that fit, in fill order (focus matches first, then rank). */
|
|
134
|
+
readonly entries: readonly RepoMapEntry[];
|
|
135
|
+
/** Path-only files that fit, after the symbols, in path order. */
|
|
136
|
+
readonly pathOnly: readonly RepoMapPathEntry[];
|
|
137
|
+
/** All ranked definitions found, before the budget cut anything. */
|
|
138
|
+
readonly definitionsTotal: number;
|
|
139
|
+
/** All path-only candidates found, before the budget cut anything. */
|
|
140
|
+
readonly pathOnlyTotal: number;
|
|
141
|
+
/** Regular files examined under the root, after the ignore list. */
|
|
142
|
+
readonly filesScanned: number;
|
|
143
|
+
/** Files skipped because their bytes contain a NUL — binary, not mappable. */
|
|
144
|
+
readonly binarySkipped: number;
|
|
145
|
+
readonly warnings: readonly RepoMapWarning[];
|
|
146
|
+
/** Present only when the caller handed in a `cacheDir`. Never invented. */
|
|
147
|
+
readonly cache?: RepoMapCacheCounts;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build a ranked symbol map of the repository under `options.root`.
|
|
151
|
+
*
|
|
152
|
+
* Reads local files only. Deterministic: two runs over the same tree, with or without
|
|
153
|
+
* a warm cache, produce byte-identical maps — asserted by the guard in
|
|
154
|
+
* `test/guards/repo-map.test.ts`, not assumed.
|
|
155
|
+
*
|
|
156
|
+
* @throws {SmeltError} when `budgetBytes` is not a positive integer.
|
|
157
|
+
* @throws {GrammarUnavailableError} when a supported language's grammar cannot load —
|
|
158
|
+
* never a silent skip that would make the map quietly incomplete.
|
|
159
|
+
*/
|
|
160
|
+
export declare function buildRepoMap(options: RepoMapOptions): Promise<RepoMap>;
|
|
161
|
+
//# sourceMappingURL=map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../src/repomap/map.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,qDAAqD;AACrD,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD,uFAAuF;AACvF,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAEnD,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAA6B,CAAC;AAE/E,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,OAAO,WAAW,CAAC;IAChC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,oEAAoE;IACpE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAyF5E"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { detectLanguage } from '../detect.js';
|
|
4
|
+
import { SmeltError } from '../errors.js';
|
|
5
|
+
import { TagsCache, tagsCacheKey } from './cache.js';
|
|
6
|
+
import { rankDefinitions } from './rank.js';
|
|
7
|
+
import { extractTags } from './tags.js';
|
|
8
|
+
/**
|
|
9
|
+
* The repo-map builder — Slice 7, the cross-file shape.
|
|
10
|
+
*
|
|
11
|
+
* **Modelled on Aider's repo-map, and credited as such.** The whole design is prior
|
|
12
|
+
* art: Paul Gauthier's Aider extracts tree-sitter definition/reference tags per file,
|
|
13
|
+
* runs PageRank over the cross-file reference graph, fits the ranked result to a
|
|
14
|
+
* budget, and caches tags on disk keyed by content — see
|
|
15
|
+
* https://aider.chat/docs/repomap.html and `aider/repomap.py` in
|
|
16
|
+
* https://github.com/Aider-AI/aider. Nothing about the approach is this project's
|
|
17
|
+
* invention; what this module adds is only smelt's own house rules:
|
|
18
|
+
*
|
|
19
|
+
* - **Local files only** (Law 1). The caller names the root; symlinks are never
|
|
20
|
+
* followed, so the walk cannot leave it; binary files are skipped; no network.
|
|
21
|
+
* - **Every inclusion is explainable** (Law 2, applied to inclusion rather than
|
|
22
|
+
* elision): each symbol in the map carries a rule id and a sentence stating its
|
|
23
|
+
* definition site and the measured reference counts that ranked it.
|
|
24
|
+
* - **Deterministic**: fixed PageRank constants, sorted walks, a total tie-break by
|
|
25
|
+
* path, name, and line. Two runs over the same tree are byte-identical.
|
|
26
|
+
* - **The budget is bytes**, same contract as the planners, and it is respected by
|
|
27
|
+
* construction: symbols are added in rank order until the next line would not fit.
|
|
28
|
+
* - **The cache lives only in a directory the caller explicitly hands in**, and a
|
|
29
|
+
* corrupt entry is discarded loudly — a warning in the result — never trusted.
|
|
30
|
+
*
|
|
31
|
+
* **Deliberately NOT a `Planner`.** `buildRepoMap` returns a {@link RepoMap},
|
|
32
|
+
* not an `ElisionPlan`: nothing here is elided, nothing is stored under a hash, and
|
|
33
|
+
* there is no original to reconstruct — the map is a *summary built up under a
|
|
34
|
+
* budget*, not a *removal to be reversed*. Forcing the Planner interface onto it
|
|
35
|
+
* would claim Law 3 (reversibility) about output that has no bytes to give back,
|
|
36
|
+
* which is exactly the kind of lie the interface exists to prevent. The CLI serves
|
|
37
|
+
* it as its own subcommand (`smelt map`), never as a `--strategy`.
|
|
38
|
+
*/
|
|
39
|
+
/** The id stamped on every map this module emits. */
|
|
40
|
+
export const REPO_MAP_ID = 'repomap/v1';
|
|
41
|
+
/**
|
|
42
|
+
* Rule id for a symbol something in the scanned tree references — its own file
|
|
43
|
+
* included, so its rank may still be zero when every reference is same-file (only
|
|
44
|
+
* cross-file references move rank).
|
|
45
|
+
*/
|
|
46
|
+
export const REPO_MAP_RANKED_RULE = 'ranked-definition';
|
|
47
|
+
/** Rule id for a definition nothing in the scanned tree references. */
|
|
48
|
+
export const REPO_MAP_UNREFERENCED_RULE = 'unreferenced-definition';
|
|
49
|
+
/**
|
|
50
|
+
* Rule id for a definition promoted because it matches a caller-supplied focus term.
|
|
51
|
+
* The rank stays the measured PageRank share — focus moves a symbol's *place in the
|
|
52
|
+
* fill order*, never its numbers.
|
|
53
|
+
*/
|
|
54
|
+
export const REPO_MAP_FOCUS_RULE = 'focus-match';
|
|
55
|
+
/** Rule id for a file listed by path because no definitions were extracted from it. */
|
|
56
|
+
export const REPO_MAP_PATH_ONLY_RULE = 'path-only';
|
|
57
|
+
/** Rule id for the warning left behind when a corrupt cache entry is discarded. */
|
|
58
|
+
export const REPO_MAP_CACHE_CORRUPT_RULE = 'cache-entry-corrupt';
|
|
59
|
+
/**
|
|
60
|
+
* The ignore list used when the caller supplies none. Deliberately tiny: `.git` is
|
|
61
|
+
* object storage, `node_modules` is other people's code. A caller with opinions passes
|
|
62
|
+
* its own list, which *replaces* this one.
|
|
63
|
+
*/
|
|
64
|
+
export const DEFAULT_REPO_IGNORE = ['.git', 'node_modules'];
|
|
65
|
+
/**
|
|
66
|
+
* Build a ranked symbol map of the repository under `options.root`.
|
|
67
|
+
*
|
|
68
|
+
* Reads local files only. Deterministic: two runs over the same tree, with or without
|
|
69
|
+
* a warm cache, produce byte-identical maps — asserted by the guard in
|
|
70
|
+
* `test/guards/repo-map.test.ts`, not assumed.
|
|
71
|
+
*
|
|
72
|
+
* @throws {SmeltError} when `budgetBytes` is not a positive integer.
|
|
73
|
+
* @throws {GrammarUnavailableError} when a supported language's grammar cannot load —
|
|
74
|
+
* never a silent skip that would make the map quietly incomplete.
|
|
75
|
+
*/
|
|
76
|
+
export async function buildRepoMap(options) {
|
|
77
|
+
const { root, budgetBytes } = options;
|
|
78
|
+
if (!Number.isInteger(budgetBytes) || budgetBytes < 1) {
|
|
79
|
+
throw new SmeltError(`smelt: budgetBytes must be a positive integer, got ${String(budgetBytes)}. ` +
|
|
80
|
+
`There is no default budget — a budget smelt invented would silently decide ` +
|
|
81
|
+
`how much of the map to throw away.`);
|
|
82
|
+
}
|
|
83
|
+
const ignore = options.ignore ?? DEFAULT_REPO_IGNORE;
|
|
84
|
+
const cache = options.cacheDir === undefined ? undefined : new TagsCache(options.cacheDir);
|
|
85
|
+
const cacheCounts = { hits: 0, misses: 0, discarded: 0 };
|
|
86
|
+
const warnings = [];
|
|
87
|
+
const files = scanFiles(root, ignore);
|
|
88
|
+
let binarySkipped = 0;
|
|
89
|
+
const parsed = [];
|
|
90
|
+
const pathOnlyPaths = [];
|
|
91
|
+
for (const rel of files) {
|
|
92
|
+
const bytes = readFileSync(join(root, ...rel.split('/')));
|
|
93
|
+
if (bytes.includes(0)) {
|
|
94
|
+
binarySkipped += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const language = detectLanguage(rel);
|
|
98
|
+
if (language === 'unknown') {
|
|
99
|
+
pathOnlyPaths.push(rel);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const text = bytes.toString('utf8');
|
|
103
|
+
const tags = await tagsFor(text, language, cache, cacheCounts, warnings, rel);
|
|
104
|
+
if (tags.defs.length === 0)
|
|
105
|
+
pathOnlyPaths.push(rel);
|
|
106
|
+
if (tags.defs.length > 0 || tags.refs.length > 0)
|
|
107
|
+
parsed.push({ path: rel, tags });
|
|
108
|
+
}
|
|
109
|
+
const ranked = rankDefinitions(parsed);
|
|
110
|
+
const focus = (options.focus ?? []).filter((term) => term.length > 0);
|
|
111
|
+
const ordered = orderWithFocus(ranked, focus);
|
|
112
|
+
// Fit to the budget: ranked symbols first — focus matches promoted to the front,
|
|
113
|
+
// each partition in rank order — then path-only files, in path order. Filling
|
|
114
|
+
// stops at the first line that does not fit, so the included set is always a
|
|
115
|
+
// prefix of that order — a map that skipped its #2 symbol to squeeze in its #9
|
|
116
|
+
// would be lying about what mattered.
|
|
117
|
+
const lines = [];
|
|
118
|
+
let bytes = 0;
|
|
119
|
+
const tryAppend = (line) => {
|
|
120
|
+
const lineBytes = Buffer.byteLength(line, 'utf8') + 1; // its trailing newline
|
|
121
|
+
if (bytes + lineBytes > budgetBytes)
|
|
122
|
+
return false;
|
|
123
|
+
lines.push(line);
|
|
124
|
+
bytes += lineBytes;
|
|
125
|
+
return true;
|
|
126
|
+
};
|
|
127
|
+
const entries = [];
|
|
128
|
+
for (const { definition, focusTerm } of ordered) {
|
|
129
|
+
if (!tryAppend(renderDefinition(definition)))
|
|
130
|
+
break;
|
|
131
|
+
entries.push(toEntry(definition, focusTerm));
|
|
132
|
+
}
|
|
133
|
+
const pathOnly = [];
|
|
134
|
+
if (entries.length === ordered.length) {
|
|
135
|
+
for (const rel of pathOnlyPaths) {
|
|
136
|
+
if (!tryAppend(`${rel} [path only]`))
|
|
137
|
+
break;
|
|
138
|
+
pathOnly.push({
|
|
139
|
+
path: rel,
|
|
140
|
+
reason: {
|
|
141
|
+
rule: REPO_MAP_PATH_ONLY_RULE,
|
|
142
|
+
explanation: `no definitions extracted from ${rel}; listed by path so the file stays visible`,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const text = lines.length === 0 ? '' : `${lines.join('\n')}\n`;
|
|
148
|
+
return {
|
|
149
|
+
id: REPO_MAP_ID,
|
|
150
|
+
text,
|
|
151
|
+
outputBytes: Buffer.byteLength(text, 'utf8'),
|
|
152
|
+
budgetBytes,
|
|
153
|
+
entries,
|
|
154
|
+
pathOnly,
|
|
155
|
+
definitionsTotal: ranked.length,
|
|
156
|
+
pathOnlyTotal: pathOnlyPaths.length,
|
|
157
|
+
filesScanned: files.length,
|
|
158
|
+
binarySkipped,
|
|
159
|
+
warnings,
|
|
160
|
+
...(cache === undefined ? {} : { cache: { ...cacheCounts } }),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** One file's tags — from the cache when possible, from the grammar when not. */
|
|
164
|
+
async function tagsFor(text, language, cache, counts, warnings, rel) {
|
|
165
|
+
if (cache === undefined)
|
|
166
|
+
return extractTags(text, language);
|
|
167
|
+
const key = tagsCacheKey(language, text);
|
|
168
|
+
const found = cache.read(key);
|
|
169
|
+
if (found === 'corrupt') {
|
|
170
|
+
counts.discarded += 1;
|
|
171
|
+
warnings.push({
|
|
172
|
+
rule: REPO_MAP_CACHE_CORRUPT_RULE,
|
|
173
|
+
explanation: `discarded a corrupt cache entry for ${rel} and re-extracted its tags from ` +
|
|
174
|
+
`source; the entry file was deleted so it cannot be read again`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (found !== undefined) {
|
|
178
|
+
counts.hits += 1;
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
counts.misses += 1;
|
|
183
|
+
}
|
|
184
|
+
const tags = await extractTags(text, language);
|
|
185
|
+
cache.write(key, tags);
|
|
186
|
+
return tags;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Walk the tree under `root`, depth-first in sorted order, returning `/`-separated
|
|
190
|
+
* relative paths of every regular file that survives the ignore list.
|
|
191
|
+
*
|
|
192
|
+
* Symlinks are skipped outright — file or directory, in-root or out. Never following
|
|
193
|
+
* one is the simplest true implementation of "never follow a symlink out of the
|
|
194
|
+
* root": there is no resolution step to get wrong.
|
|
195
|
+
*/
|
|
196
|
+
function scanFiles(root, ignore) {
|
|
197
|
+
const found = [];
|
|
198
|
+
const walk = (dir, relDir) => {
|
|
199
|
+
for (const entry of readdirSync(dir).toSorted()) {
|
|
200
|
+
const rel = relDir === '' ? entry : `${relDir}/${entry}`;
|
|
201
|
+
if (isIgnored(rel, ignore))
|
|
202
|
+
continue;
|
|
203
|
+
const full = join(dir, entry);
|
|
204
|
+
const stat = lstatSync(full);
|
|
205
|
+
if (stat.isSymbolicLink())
|
|
206
|
+
continue;
|
|
207
|
+
if (stat.isDirectory())
|
|
208
|
+
walk(full, rel);
|
|
209
|
+
else if (stat.isFile())
|
|
210
|
+
found.push(rel);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
walk(root, '');
|
|
214
|
+
return found;
|
|
215
|
+
}
|
|
216
|
+
/** See {@link RepoMapOptions.ignore} for the two match modes. */
|
|
217
|
+
function isIgnored(rel, ignore) {
|
|
218
|
+
for (const entry of ignore) {
|
|
219
|
+
const cleaned = entry.replace(/\/+$/, '');
|
|
220
|
+
if (cleaned === '')
|
|
221
|
+
continue;
|
|
222
|
+
// Prefix mode is decided on the entry AS WRITTEN, before the trailing slash is
|
|
223
|
+
// trimmed: `build/` contains a `/` and so is the documented root-relative prefix
|
|
224
|
+
// (the root-level `build` tree), not a bare name that would match a `build`
|
|
225
|
+
// segment at any depth.
|
|
226
|
+
if (entry.includes('/')) {
|
|
227
|
+
if (rel === cleaned || rel.startsWith(`${cleaned}/`))
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
else if (rel.split('/').includes(cleaned)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The fill order: focus-matched definitions first, then the rest, each partition
|
|
238
|
+
* keeping the ranker's total order. A stable partition of a deterministic order is
|
|
239
|
+
* itself deterministic, so the map's byte-for-byte claim survives focus untouched.
|
|
240
|
+
* The match is a case-insensitive substring over name and path — the lexical
|
|
241
|
+
* planner's default, so "focus" means the same thing in both places — and the
|
|
242
|
+
* *first* matching term in caller order is the one the receipt names.
|
|
243
|
+
*/
|
|
244
|
+
function orderWithFocus(ranked, focus) {
|
|
245
|
+
if (focus.length === 0)
|
|
246
|
+
return ranked.map((definition) => ({ definition }));
|
|
247
|
+
const needles = focus.map((term) => term.toLowerCase());
|
|
248
|
+
const matched = [];
|
|
249
|
+
const rest = [];
|
|
250
|
+
for (const definition of ranked) {
|
|
251
|
+
const haystack = `${definition.path}\0${definition.name}`.toLowerCase();
|
|
252
|
+
const index = needles.findIndex((needle) => haystack.includes(needle));
|
|
253
|
+
if (index === -1)
|
|
254
|
+
rest.push({ definition });
|
|
255
|
+
else
|
|
256
|
+
matched.push({ definition, focusTerm: focus[index] });
|
|
257
|
+
}
|
|
258
|
+
return [...matched, ...rest];
|
|
259
|
+
}
|
|
260
|
+
/** One rendered map line. Everything in it is measured, nothing estimated. */
|
|
261
|
+
function renderDefinition(definition) {
|
|
262
|
+
return (`${definition.path}:${String(definition.line)} ${definition.kind} ${definition.name} ` +
|
|
263
|
+
`[${String(definition.refsIn)} in from ${String(definition.refsInFiles)} ` +
|
|
264
|
+
`${plural('file', definition.refsInFiles)}, ${String(definition.refsOut)} out]`);
|
|
265
|
+
}
|
|
266
|
+
/** Law 2, applied to inclusion: the receipt every included symbol carries. */
|
|
267
|
+
function toEntry(definition, focusTerm) {
|
|
268
|
+
const site = `defined at ${definition.path}:${String(definition.line)}`;
|
|
269
|
+
const counts = `${String(definition.refsIn)} ${plural('reference', definition.refsIn)} in ` +
|
|
270
|
+
`from ${String(definition.refsInFiles)} ${plural('file', definition.refsInFiles)}; ` +
|
|
271
|
+
`its file makes ${String(definition.refsOut)} ` +
|
|
272
|
+
`${plural('reference', definition.refsOut)} out`;
|
|
273
|
+
const reason = focusTerm !== undefined
|
|
274
|
+
? {
|
|
275
|
+
rule: REPO_MAP_FOCUS_RULE,
|
|
276
|
+
explanation: `${site}; matches focus "${focusTerm}"; ${counts}`,
|
|
277
|
+
}
|
|
278
|
+
: definition.refsIn === 0
|
|
279
|
+
? {
|
|
280
|
+
rule: REPO_MAP_UNREFERENCED_RULE,
|
|
281
|
+
explanation: `${site}; no references to it anywhere in the scanned tree`,
|
|
282
|
+
}
|
|
283
|
+
: {
|
|
284
|
+
rule: REPO_MAP_RANKED_RULE,
|
|
285
|
+
explanation: `${site}; ${counts}`,
|
|
286
|
+
};
|
|
287
|
+
return {
|
|
288
|
+
path: definition.path,
|
|
289
|
+
name: definition.name,
|
|
290
|
+
kind: definition.kind,
|
|
291
|
+
line: definition.line,
|
|
292
|
+
rank: definition.rank,
|
|
293
|
+
refsIn: definition.refsIn,
|
|
294
|
+
refsInFiles: definition.refsInFiles,
|
|
295
|
+
refsOut: definition.refsOut,
|
|
296
|
+
reason,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function plural(word, count) {
|
|
300
|
+
return count === 1 ? word : `${word}s`;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../../src/repomap/map.ts"],"names":[],"mappings":"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"}
|