contextdiet-cli 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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/bin/contextdiet.js +4 -0
  4. package/dist/cli/index.d.ts +11 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +104 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/core/bundler/index.d.ts +49 -0
  9. package/dist/core/bundler/index.d.ts.map +1 -0
  10. package/dist/core/bundler/index.js +57 -0
  11. package/dist/core/bundler/index.js.map +1 -0
  12. package/dist/core/graph/index.d.ts +29 -0
  13. package/dist/core/graph/index.d.ts.map +1 -0
  14. package/dist/core/graph/index.js +195 -0
  15. package/dist/core/graph/index.js.map +1 -0
  16. package/dist/core/graph/selector.d.ts +29 -0
  17. package/dist/core/graph/selector.d.ts.map +1 -0
  18. package/dist/core/graph/selector.js +49 -0
  19. package/dist/core/graph/selector.js.map +1 -0
  20. package/dist/core/graph/symbol-selector.d.ts +31 -0
  21. package/dist/core/graph/symbol-selector.d.ts.map +1 -0
  22. package/dist/core/graph/symbol-selector.js +0 -0
  23. package/dist/core/graph/symbol-selector.js.map +1 -0
  24. package/dist/core/graph/types.d.ts +58 -0
  25. package/dist/core/graph/types.d.ts.map +1 -0
  26. package/dist/core/graph/types.js +11 -0
  27. package/dist/core/graph/types.js.map +1 -0
  28. package/dist/core/metrics/index.d.ts +51 -0
  29. package/dist/core/metrics/index.d.ts.map +1 -0
  30. package/dist/core/metrics/index.js +91 -0
  31. package/dist/core/metrics/index.js.map +1 -0
  32. package/dist/core/parser/index.d.ts +32 -0
  33. package/dist/core/parser/index.d.ts.map +1 -0
  34. package/dist/core/parser/index.js +274 -0
  35. package/dist/core/parser/index.js.map +1 -0
  36. package/dist/core/parser/types.d.ts +127 -0
  37. package/dist/core/parser/types.d.ts.map +1 -0
  38. package/dist/core/parser/types.js +24 -0
  39. package/dist/core/parser/types.js.map +1 -0
  40. package/dist/core/pipeline.d.ts +38 -0
  41. package/dist/core/pipeline.d.ts.map +1 -0
  42. package/dist/core/pipeline.js +69 -0
  43. package/dist/core/pipeline.js.map +1 -0
  44. package/dist/core/pruner/index.d.ts +25 -0
  45. package/dist/core/pruner/index.d.ts.map +1 -0
  46. package/dist/core/pruner/index.js +56 -0
  47. package/dist/core/pruner/index.js.map +1 -0
  48. package/dist/core/pruner/types.d.ts +18 -0
  49. package/dist/core/pruner/types.d.ts.map +1 -0
  50. package/dist/core/pruner/types.js +2 -0
  51. package/dist/core/pruner/types.js.map +1 -0
  52. package/dist/core/ranker/index.d.ts +26 -0
  53. package/dist/core/ranker/index.d.ts.map +1 -0
  54. package/dist/core/ranker/index.js +149 -0
  55. package/dist/core/ranker/index.js.map +1 -0
  56. package/dist/core/ranker/types.d.ts +61 -0
  57. package/dist/core/ranker/types.d.ts.map +1 -0
  58. package/dist/core/ranker/types.js +12 -0
  59. package/dist/core/ranker/types.js.map +1 -0
  60. package/package.json +59 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * DependencyGraphResolver (Task 2.0).
3
+ *
4
+ * Builds a {@link DependencyGraph} for a codebase by:
5
+ * 1. enumerating source files under the root (skipping `node_modules`/dotdirs),
6
+ * 2. parsing each with the {@link Parser} to get symbols + dependency specifiers,
7
+ * 3. resolving each specifier to an absolute file path (or marking it external).
8
+ *
9
+ * Cycle safety: the graph is built by filesystem enumeration + per-file edge
10
+ * resolution — NOT by recursively following imports. Import cycles therefore
11
+ * cannot cause infinite loops or stack overflow at build time; they simply appear
12
+ * as cycles in the adjacency data. Consumers that walk the graph must use a
13
+ * visited set — see {@link collectReachable}.
14
+ */
15
+ import { promises as fs } from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import { AstGrepperParser } from '../parser/index.js';
18
+ import { ParseError } from '../parser/types.js';
19
+ /** Extensions we treat as parseable source. */
20
+ const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
21
+ /** JS-family extensions an ESM specifier may use to point at a TS-family file. */
22
+ const JS_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs']);
23
+ const TS_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts'];
24
+ /** Directory names never descended into. */
25
+ const IGNORED_DIRS = new Set(['node_modules', 'dist', 'build', 'out', 'coverage']);
26
+ export class DependencyGraphResolver {
27
+ parser;
28
+ constructor(parser = new AstGrepperParser()) {
29
+ this.parser = parser;
30
+ }
31
+ async buildGraph(rootDir) {
32
+ const root = path.resolve(rootDir);
33
+ const files = await collectSourceFiles(root);
34
+ files.sort();
35
+ const nodes = new Map();
36
+ for (const filePath of files) {
37
+ nodes.set(filePath, await this.buildNode(root, filePath));
38
+ }
39
+ // Derive the traversable adjacency list: first-party edges only, deduped,
40
+ // and only pointing at files that are actually nodes in this graph.
41
+ const adjacency = new Map();
42
+ for (const [filePath, node] of nodes) {
43
+ const seen = new Set();
44
+ const neighbours = [];
45
+ for (const edge of node.dependencies) {
46
+ const target = edge.resolvedPath;
47
+ if (target !== null && nodes.has(target) && !seen.has(target)) {
48
+ seen.add(target);
49
+ neighbours.push(target);
50
+ }
51
+ }
52
+ adjacency.set(filePath, neighbours);
53
+ }
54
+ return { rootDir: root, nodes, adjacency };
55
+ }
56
+ async buildNode(root, filePath) {
57
+ const source = await fs.readFile(filePath, 'utf8');
58
+ let symbols = [];
59
+ let dependencies = [];
60
+ try {
61
+ symbols = await this.parser.extractSymbols(source, filePath);
62
+ const refs = await this.parser.extractDependencies(source, filePath);
63
+ dependencies = await Promise.all(refs.map(async (ref) => {
64
+ const resolvedPath = await resolveSpecifier(ref.source, path.dirname(filePath));
65
+ return {
66
+ kind: ref.kind,
67
+ specifier: ref.source,
68
+ resolvedPath,
69
+ external: resolvedPath === null,
70
+ };
71
+ }));
72
+ }
73
+ catch (error) {
74
+ // A single unparseable file must not sink the whole graph: record it as a
75
+ // node with no symbols/edges so the rest of the codebase still maps.
76
+ if (!(error instanceof ParseError))
77
+ throw error;
78
+ }
79
+ return {
80
+ filePath,
81
+ relativePath: path.relative(root, filePath),
82
+ symbols,
83
+ dependencies,
84
+ };
85
+ }
86
+ }
87
+ /**
88
+ * Iteratively collect every node reachable from `startPath` (inclusive), up to
89
+ * `maxHops` edges away. Uses a visited set + explicit queue, so cycles terminate
90
+ * and deep graphs never overflow the call stack.
91
+ */
92
+ export function collectReachable(graph, startPath, maxHops = Number.POSITIVE_INFINITY) {
93
+ const start = path.resolve(startPath);
94
+ const visited = new Set();
95
+ if (!graph.nodes.has(start))
96
+ return visited;
97
+ visited.add(start);
98
+ let frontier = [start];
99
+ let depth = 0;
100
+ while (frontier.length > 0 && depth < maxHops) {
101
+ const next = [];
102
+ for (const current of frontier) {
103
+ for (const neighbour of graph.adjacency.get(current) ?? []) {
104
+ if (!visited.has(neighbour)) {
105
+ visited.add(neighbour);
106
+ next.push(neighbour);
107
+ }
108
+ }
109
+ }
110
+ frontier = next;
111
+ depth += 1;
112
+ }
113
+ return visited;
114
+ }
115
+ // --- module-private helpers -----------------------------------------------
116
+ /** Depth-limited-free directory walk (over the acyclic dir tree) for source files. */
117
+ async function collectSourceFiles(root) {
118
+ const out = [];
119
+ const stack = [root];
120
+ while (stack.length > 0) {
121
+ const dir = stack.pop();
122
+ if (dir === undefined)
123
+ break;
124
+ let entries;
125
+ try {
126
+ entries = await fs.readdir(dir, { withFileTypes: true });
127
+ }
128
+ catch {
129
+ continue; // unreadable dir (e.g. permissions) — skip, don't crash the build
130
+ }
131
+ for (const entry of entries) {
132
+ const full = path.join(dir, entry.name);
133
+ // `isDirectory()` is false for symlinks, so symlinked dirs are skipped —
134
+ // another reason the walk can't cycle.
135
+ if (entry.isDirectory()) {
136
+ if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith('.'))
137
+ continue;
138
+ stack.push(full);
139
+ }
140
+ else if (entry.isFile() && SOURCE_EXTENSIONS.includes(path.extname(entry.name))) {
141
+ out.push(full);
142
+ }
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+ /**
148
+ * Resolve a module specifier to an absolute file path, or `null` if it is
149
+ * external (bare package, `node:` builtin) or cannot be found on disk.
150
+ *
151
+ * Only relative specifiers (`.`/`..`) are ever resolved — everything else is,
152
+ * by definition, beyond the `node_modules` boundary.
153
+ */
154
+ async function resolveSpecifier(specifier, importerDir) {
155
+ if (!specifier.startsWith('.'))
156
+ return null; // bare / node: / absolute-package → external
157
+ const base = path.resolve(importerDir, specifier);
158
+ for (const candidate of candidatePaths(base)) {
159
+ if (await isFile(candidate))
160
+ return candidate;
161
+ }
162
+ return null;
163
+ }
164
+ /** Ordered candidate paths for a resolved base (extension + index resolution). */
165
+ function candidatePaths(base) {
166
+ const candidates = [];
167
+ const ext = path.extname(base);
168
+ if (ext !== '') {
169
+ candidates.push(base); // e.g. './x.ts' written explicitly
170
+ if (JS_EXTENSIONS.has(ext)) {
171
+ // ESM-style './x.js' specifier that actually targets a TS source file.
172
+ const stem = base.slice(0, -ext.length);
173
+ for (const tsExt of TS_EXTENSIONS)
174
+ candidates.push(stem + tsExt);
175
+ }
176
+ }
177
+ else {
178
+ for (const sourceExt of SOURCE_EXTENSIONS)
179
+ candidates.push(base + sourceExt);
180
+ }
181
+ // Directory import: `./dir` → `./dir/index.*`
182
+ for (const sourceExt of SOURCE_EXTENSIONS) {
183
+ candidates.push(path.join(base, `index${sourceExt}`));
184
+ }
185
+ return candidates;
186
+ }
187
+ async function isFile(candidate) {
188
+ try {
189
+ return (await fs.stat(candidate)).isFile();
190
+ }
191
+ catch {
192
+ return false;
193
+ }
194
+ }
195
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/graph/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * GraphSelector (Task 3.0).
3
+ *
4
+ * Bridges the ranker's seeds to a concrete "keep set". Given `SeedNode[]` (the
5
+ * symbols most relevant to a focus query) and the {@link DependencyGraph}, it
6
+ * walks dependency edges N hops out from each seed's file and returns exactly
7
+ * which symbols in which files are allowed to survive.
8
+ *
9
+ * Granularity note (see ADR-011): the dependency graph is file-level, so
10
+ * selection keeps *whole reachable files* — every top-level symbol of a file the
11
+ * seeds transitively depend on. The result is nonetheless expressed at symbol
12
+ * granularity (`Map<filePath, Set<symbolName>>`) so the pruner and a future
13
+ * symbol-level call graph need no interface change.
14
+ */
15
+ import type { DependencyGraph } from './types.js';
16
+ import type { SeedNode } from '../ranker/types.js';
17
+ /** file abs path → the set of top-level symbol names permitted to survive. */
18
+ export type Selection = ReadonlyMap<string, ReadonlySet<string>>;
19
+ export interface SelectionOptions {
20
+ /** Dependency-graph traversal depth from each seed file. Default `2`. */
21
+ readonly hops?: number;
22
+ }
23
+ export interface Selector {
24
+ select(seeds: readonly SeedNode[], graph: DependencyGraph, options?: SelectionOptions): Selection;
25
+ }
26
+ export declare class GraphSelector implements Selector {
27
+ select(seeds: readonly SeedNode[], graph: DependencyGraph, options?: SelectionOptions): Selection;
28
+ }
29
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../../src/core/graph/selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACnG;AAID,qBAAa,aAAc,YAAW,QAAQ;IAC5C,MAAM,CACJ,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CA6BX;CACF"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * GraphSelector (Task 3.0).
3
+ *
4
+ * Bridges the ranker's seeds to a concrete "keep set". Given `SeedNode[]` (the
5
+ * symbols most relevant to a focus query) and the {@link DependencyGraph}, it
6
+ * walks dependency edges N hops out from each seed's file and returns exactly
7
+ * which symbols in which files are allowed to survive.
8
+ *
9
+ * Granularity note (see ADR-011): the dependency graph is file-level, so
10
+ * selection keeps *whole reachable files* — every top-level symbol of a file the
11
+ * seeds transitively depend on. The result is nonetheless expressed at symbol
12
+ * granularity (`Map<filePath, Set<symbolName>>`) so the pruner and a future
13
+ * symbol-level call graph need no interface change.
14
+ */
15
+ import * as path from 'node:path';
16
+ import { collectReachable } from './index.js';
17
+ const DEFAULT_HOPS = 2;
18
+ export class GraphSelector {
19
+ select(seeds, graph, options = {}) {
20
+ const hops = options.hops ?? DEFAULT_HOPS;
21
+ const keep = new Map();
22
+ if (seeds.length === 0)
23
+ return keep;
24
+ // 1. Files that directly contain a seed symbol (that are actually in the graph).
25
+ const seedFiles = new Set();
26
+ for (const seed of seeds) {
27
+ const file = path.resolve(seed.symbol.filePath);
28
+ if (graph.nodes.has(file))
29
+ seedFiles.add(file);
30
+ }
31
+ // 2. Expand each seed file N hops along dependency edges. `collectReachable`
32
+ // is cycle-safe, so import cycles among reachable files terminate cleanly.
33
+ const keepFiles = new Set();
34
+ for (const file of seedFiles) {
35
+ for (const reached of collectReachable(graph, file, hops)) {
36
+ keepFiles.add(reached);
37
+ }
38
+ }
39
+ // 3. Whole-file granularity: every top-level symbol of each kept file survives.
40
+ for (const file of keepFiles) {
41
+ const node = graph.nodes.get(file);
42
+ if (node === undefined)
43
+ continue;
44
+ keep.set(file, new Set(node.symbols.map((s) => s.name)));
45
+ }
46
+ return keep;
47
+ }
48
+ }
49
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../../src/core/graph/selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAgB9C,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,MAAM,OAAO,aAAa;IACxB,MAAM,CACJ,KAA0B,EAC1B,KAAsB,EACtB,OAAO,GAAqB,EAAE;QAE9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,YAAY,CAAC;QAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;QAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpC,iFAAiF;QACjF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC;QAED,6EAA6E;QAC7E,8EAA8E;QAC9E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,KAAK,MAAM,OAAO,IAAI,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;gBAC1D,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,gFAAgF;QAChF,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ReferenceClosureSelector — symbol-level selection (Task 7 / ADR-016).
3
+ *
4
+ * Upgrades whole-file selection (ADR-011) to precise, per-symbol selection. It
5
+ * walks the *reference closure* of the seed symbols and keeps only what is
6
+ * actually reachable, so the pruner (already symbol-level) can drop internal
7
+ * helpers no kept symbol touches.
8
+ *
9
+ * Correctness rule — kept code must never dangle on a same-file symbol:
10
+ * - intra-file references are followed UNBOUNDED (a kept symbol's same-file
11
+ * helpers are always kept), guaranteeing each emitted file is self-consistent;
12
+ * - cross-file references are followed only within the `--hops` file horizon
13
+ * (identical boundary to the file-level selector), so context stays bounded;
14
+ * - namespace/default imports keep the whole target module (we cannot tell
15
+ * which members are used through a `*`/default binding).
16
+ *
17
+ * Composes {@link GraphSelector} to compute the horizon, then refines within it.
18
+ */
19
+ import type { DependencyGraph } from './types.js';
20
+ import type { Selection, SelectionOptions } from './selector.js';
21
+ import type { SeedNode } from '../ranker/types.js';
22
+ import type { Parser } from '../parser/types.js';
23
+ export interface SymbolSelector {
24
+ select(seeds: readonly SeedNode[], graph: DependencyGraph, sources: ReadonlyMap<string, string>, options?: SelectionOptions): Promise<Selection>;
25
+ }
26
+ export declare class ReferenceClosureSelector implements SymbolSelector {
27
+ private readonly parser;
28
+ constructor(parser?: Parser);
29
+ select(seeds: readonly SeedNode[], graph: DependencyGraph, sources: ReadonlyMap<string, string>, options?: SelectionOptions): Promise<Selection>;
30
+ }
31
+ //# sourceMappingURL=symbol-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-selector.d.ts","sourceRoot":"","sources":["../../../src/core/graph/symbol-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAc,MAAM,EAAc,MAAM,oBAAoB,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,MAAM,CACJ,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,SAAS,CAAC,CAAC;CACvB;AAED,qBAAa,wBAAyB,YAAW,cAAc;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,MAAM,GAAE,MAA+B,EAElD;IAEK,MAAM,CACV,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,SAAS,CAAC,CAsGpB;CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-selector.js","sourceRoot":"","sources":["../../../src/core/graph/symbol-selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Dependency-graph contracts for ContextDiet (Task 2.0).
3
+ *
4
+ * The graph is the map the Selector (Task 3.0) walks to decide which code to keep
5
+ * for a given `--focus`. It is a pure data structure: nodes (files) plus an
6
+ * adjacency list of first-party edges. Cycles are represented directly — the graph
7
+ * never resolves them away, so consumers must traverse with a visited set
8
+ * (see `collectReachable`).
9
+ */
10
+ import type { DependencyRefKind, SymbolNode } from '../parser/types.js';
11
+ /**
12
+ * A single resolved outward edge from a file to a module it depends on.
13
+ *
14
+ * `resolvedPath` is the absolute path of the target file when the specifier
15
+ * points at first-party source; it is `null` for anything beyond the project
16
+ * boundary (bare package specifiers, `node:` builtins, unresolvable paths), in
17
+ * which case `external` is `true`. External edges are recorded but never
18
+ * traversed — the `node_modules` boundary is strict.
19
+ */
20
+ export interface DependencyEdge {
21
+ readonly kind: DependencyRefKind;
22
+ /** The specifier exactly as written in source (e.g. `./b`, `node:fs`, `express`). */
23
+ readonly specifier: string;
24
+ /** Absolute path of the target file, or `null` if external/unresolvable. */
25
+ readonly resolvedPath: string | null;
26
+ /** True when the edge crosses the project boundary (not traversed). */
27
+ readonly external: boolean;
28
+ }
29
+ /** One file in the graph: its identity, its symbols, and its outward edges. */
30
+ export interface GraphNode {
31
+ /** Absolute path of this file (also its key in {@link DependencyGraph.nodes}). */
32
+ readonly filePath: string;
33
+ /** Path relative to the graph root — stable and display-friendly. */
34
+ readonly relativePath: string;
35
+ /** Top-level declarations in this file (from the parser). */
36
+ readonly symbols: readonly SymbolNode[];
37
+ /** Every outward dependency (imports + re-exports), resolved to disk. */
38
+ readonly dependencies: readonly DependencyEdge[];
39
+ }
40
+ /**
41
+ * The whole codebase as a directed graph.
42
+ *
43
+ * `adjacency` is the derived, deduped view used for traversal: it contains only
44
+ * first-party edges (external dependencies excluded). It may contain cycles.
45
+ */
46
+ export interface DependencyGraph {
47
+ /** Absolute root the graph was built from. */
48
+ readonly rootDir: string;
49
+ /** All file nodes, keyed by absolute path. */
50
+ readonly nodes: ReadonlyMap<string, GraphNode>;
51
+ /** file abs path → abs paths of the first-party files it depends on (deduped). */
52
+ readonly adjacency: ReadonlyMap<string, readonly string[]>;
53
+ }
54
+ /** Builds a {@link DependencyGraph} for a codebase rooted at `rootDir`. */
55
+ export interface GraphResolver {
56
+ buildGraph(rootDir: string): Promise<DependencyGraph>;
57
+ }
58
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/graph/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,SAAS,cAAc,EAAE,CAAC;CAClD;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC/C,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;CAC5D;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACvD"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Dependency-graph contracts for ContextDiet (Task 2.0).
3
+ *
4
+ * The graph is the map the Selector (Task 3.0) walks to decide which code to keep
5
+ * for a given `--focus`. It is a pure data structure: nodes (files) plus an
6
+ * adjacency list of first-party edges. Cycles are represented directly — the graph
7
+ * never resolves them away, so consumers must traverse with a visited set
8
+ * (see `collectReachable`).
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/graph/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Metrics — Task 5.0 (efficiency tracking).
3
+ *
4
+ * Proves the headline claim: >80% token reduction. We compare the baseline
5
+ * codebase against the pruned bundle on two axes:
6
+ * - raw bytes (UTF-8), an exact, dependency-free ground truth, and
7
+ * - estimated tokens, since LLM cost is billed per token, not per byte.
8
+ *
9
+ * Token estimation is a local heuristic — zero network, zero heavy deps. We do
10
+ * NOT bundle a full BPE vocabulary; instead we approximate how tokenizers split
11
+ * text (words, sub-words, punctuation, whitespace runs) which lands within a few
12
+ * percent of real tiktoken counts on source code, and is fully deterministic.
13
+ */
14
+ /** Result of comparing a baseline against a pruned/compressed variant. */
15
+ export interface ReductionMetrics {
16
+ /** UTF-8 byte length of the original, unpruned input. */
17
+ readonly rawBytes: number;
18
+ /** UTF-8 byte length of the pruned bundle. */
19
+ readonly compressedBytes: number;
20
+ /** Estimated token count of the original input. */
21
+ readonly rawTokens: number;
22
+ /** Estimated token count of the pruned bundle. */
23
+ readonly compressedTokens: number;
24
+ /**
25
+ * Percentage reduction in estimated tokens, `[0, 100]`, rounded to two
26
+ * decimals. `(rawTokens - compressedTokens) / rawTokens * 100`.
27
+ */
28
+ readonly tokenReductionPercentage: number;
29
+ /** Percentage reduction in raw bytes, `[0, 100]`, rounded to two decimals. */
30
+ readonly byteReductionPercentage: number;
31
+ }
32
+ /** UTF-8 byte length of a string, without allocating a Buffer per call path. */
33
+ export declare function byteLength(text: string): number;
34
+ /**
35
+ * Estimate the number of tokens a BPE tokenizer would produce for `text`.
36
+ *
37
+ * Strategy: split into atomic pieces the way real tokenizers tend to — runs of
38
+ * word characters, individual punctuation/symbols, and whitespace runs — then
39
+ * further split long word-runs into ~4-char sub-word chunks (BPE rarely emits a
40
+ * token longer than that for code identifiers like `authMiddleware`). We blend
41
+ * this with the classic chars/4 heuristic and take the max, so we never wildly
42
+ * under-count dense text.
43
+ */
44
+ export declare function estimateTokens(text: string): number;
45
+ /**
46
+ * Compute the byte/token reduction between an original codebase string and its
47
+ * pruned bundle. `original` is the concatenation of the baseline files;
48
+ * `pruned` is the bundler output (or pruned concatenation).
49
+ */
50
+ export declare function computeReduction(original: string, pruned: string): ReductionMetrics;
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,8EAA8E;IAC9E,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;CAC1C;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAWD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsBnD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAcnF"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Metrics — Task 5.0 (efficiency tracking).
3
+ *
4
+ * Proves the headline claim: >80% token reduction. We compare the baseline
5
+ * codebase against the pruned bundle on two axes:
6
+ * - raw bytes (UTF-8), an exact, dependency-free ground truth, and
7
+ * - estimated tokens, since LLM cost is billed per token, not per byte.
8
+ *
9
+ * Token estimation is a local heuristic — zero network, zero heavy deps. We do
10
+ * NOT bundle a full BPE vocabulary; instead we approximate how tokenizers split
11
+ * text (words, sub-words, punctuation, whitespace runs) which lands within a few
12
+ * percent of real tiktoken counts on source code, and is fully deterministic.
13
+ */
14
+ /** UTF-8 byte length of a string, without allocating a Buffer per call path. */
15
+ export function byteLength(text) {
16
+ // TextEncoder is a Web/Node global and counts real UTF-8 bytes (multi-byte
17
+ // aware), unlike `string.length` which counts UTF-16 code units.
18
+ return TEXT_ENCODER.encode(text).length;
19
+ }
20
+ const TEXT_ENCODER = new TextEncoder();
21
+ /**
22
+ * Average characters-per-token observed for source code with common BPE
23
+ * tokenizers (cl100k/o200k). Used only as a sanity fallback; the primary
24
+ * estimate is structural (see {@link estimateTokens}).
25
+ */
26
+ const CHARS_PER_TOKEN_FALLBACK = 4;
27
+ /**
28
+ * Estimate the number of tokens a BPE tokenizer would produce for `text`.
29
+ *
30
+ * Strategy: split into atomic pieces the way real tokenizers tend to — runs of
31
+ * word characters, individual punctuation/symbols, and whitespace runs — then
32
+ * further split long word-runs into ~4-char sub-word chunks (BPE rarely emits a
33
+ * token longer than that for code identifiers like `authMiddleware`). We blend
34
+ * this with the classic chars/4 heuristic and take the max, so we never wildly
35
+ * under-count dense text.
36
+ */
37
+ export function estimateTokens(text) {
38
+ if (text.length === 0)
39
+ return 0;
40
+ let structural = 0;
41
+ const pieces = text.match(/[A-Za-z0-9]+|\s+|[^A-Za-z0-9\s]/g) ?? [];
42
+ for (const piece of pieces) {
43
+ if (/^\s+$/.test(piece)) {
44
+ // Whitespace: newlines and indentation usually cost ~1 token per run,
45
+ // but very long runs get chunked.
46
+ structural += Math.max(1, Math.ceil(piece.length / 8));
47
+ }
48
+ else if (/^[A-Za-z0-9]+$/.test(piece)) {
49
+ // Word/identifier run: ~4 chars per sub-word token, min 1.
50
+ structural += Math.max(1, Math.ceil(piece.length / 4));
51
+ }
52
+ else {
53
+ // A single punctuation/symbol char is almost always its own token.
54
+ structural += 1;
55
+ }
56
+ }
57
+ const fallback = Math.ceil(text.length / CHARS_PER_TOKEN_FALLBACK);
58
+ return Math.max(structural, fallback);
59
+ }
60
+ /**
61
+ * Compute the byte/token reduction between an original codebase string and its
62
+ * pruned bundle. `original` is the concatenation of the baseline files;
63
+ * `pruned` is the bundler output (or pruned concatenation).
64
+ */
65
+ export function computeReduction(original, pruned) {
66
+ const rawBytes = byteLength(original);
67
+ const compressedBytes = byteLength(pruned);
68
+ const rawTokens = estimateTokens(original);
69
+ const compressedTokens = estimateTokens(pruned);
70
+ return {
71
+ rawBytes,
72
+ compressedBytes,
73
+ rawTokens,
74
+ compressedTokens,
75
+ tokenReductionPercentage: percentDrop(rawTokens, compressedTokens),
76
+ byteReductionPercentage: percentDrop(rawBytes, compressedBytes),
77
+ };
78
+ }
79
+ /**
80
+ * Percentage drop from `before` to `after`, clamped to `[0, 100]` and rounded
81
+ * to two decimals. Returns 0 when `before` is 0 (nothing to reduce) and never
82
+ * reports a negative reduction if the "pruned" text somehow grew.
83
+ */
84
+ function percentDrop(before, after) {
85
+ if (before <= 0)
86
+ return 0;
87
+ const drop = ((before - after) / before) * 100;
88
+ const clamped = Math.min(100, Math.max(0, drop));
89
+ return Math.round(clamped * 100) / 100;
90
+ }
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAqBH,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,2EAA2E;IAC3E,iEAAiE;IACjE,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;AAEvC;;;;GAIG;AACH,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAEnC;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEhC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,kCAAkC,CAAC,IAAI,EAAE,CAAC;IAEpE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,sEAAsE;YACtE,kCAAkC;YAClC,UAAU,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;aAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,2DAA2D;YAC3D,UAAU,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;aAAM,CAAC;YACN,mEAAmE;YACnE,UAAU,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,wBAAwB,CAAC,CAAC;IACnE,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,MAAc;IAC/D,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,gBAAgB,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAEhD,OAAO;QACL,QAAQ;QACR,eAAe;QACf,SAAS;QACT,gBAAgB;QAChB,wBAAwB,EAAE,WAAW,CAAC,SAAS,EAAE,gBAAgB,CAAC;QAClE,uBAAuB,EAAE,WAAW,CAAC,QAAQ,EAAE,eAAe,CAAC;KAChE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,MAAc,EAAE,KAAa;IAChD,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACzC,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ast-grep–backed implementation of the {@link Parser} contract (Task 1.0).
3
+ *
4
+ * Uses `@ast-grep/napi` to parse TypeScript into a concrete syntax tree, then
5
+ * walks it to extract import bindings and top-level symbols. All node access is
6
+ * defensive: tree-sitter is error-tolerant, so we validate the tree for ERROR
7
+ * nodes up front and translate failures into {@link ParseError}.
8
+ */
9
+ import type { DependencyRef, ImportNode, ImportStatement, Parser, Range, SymbolNode } from './types.js';
10
+ export declare class AstGrepperParser implements Parser {
11
+ extractImports(source: string, filePath: string): Promise<ImportNode[]>;
12
+ extractSymbols(source: string, filePath: string): Promise<SymbolNode[]>;
13
+ extractDependencies(source: string, filePath: string): Promise<DependencyRef[]>;
14
+ extractImportStatements(source: string, filePath: string): Promise<ImportStatement[]>;
15
+ collectReferences(source: string, filePath: string, ranges: readonly Range[]): Promise<Set<string>>;
16
+ sliceNode(source: string, range: Range): string;
17
+ /** Parse to a root node, translating any syntax error into a {@link ParseError}. */
18
+ private parseOrThrow;
19
+ /** Translate one child of an `import_clause` into zero or more bindings. */
20
+ private collectClauseBindings;
21
+ private makeImport;
22
+ /**
23
+ * Turn one top-level declaration into zero or more symbols.
24
+ *
25
+ * `exported` and `exportStatement` come from the caller's position in the tree
26
+ * (whether this declaration sat inside an `export_statement`). For exported
27
+ * declarations the range widens to the whole `export …` statement so a slice
28
+ * reproduces a self-contained, re-emittable chunk (with the `export` keyword).
29
+ */
30
+ private collectDeclaration;
31
+ }
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/parser/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EAEV,eAAe,EACf,MAAM,EACN,KAAK,EAEL,UAAU,EACX,MAAM,YAAY,CAAC;AAmBpB,qBAAa,gBAAiB,YAAW,MAAM;IACvC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAkB5E;IAEK,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAkB5E;IAEK,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA+BpF;IAEK,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB1F;IAEK,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,KAAK,EAAE,GACvB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAwBtB;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAE9C;IAID,oFAAoF;IACpF,OAAO,CAAC,YAAY;IAapB,4EAA4E;IAC5E,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,UAAU;IAWlB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;CA2B3B"}