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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rizzhab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ <div align="center">
2
+
3
+ # ContextDiet
4
+
5
+ ### An AST-based token optimizer that slashes AI agent context bloat — up to **99% measured token reduction**, with **$0 network overhead**.
6
+
7
+ [![CI](https://github.com/risshabs22-quantified/contextDiet/actions/workflows/ci.yml/badge.svg)](https://github.com/risshabs22-quantified/contextDiet/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
9
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)](https://www.typescriptlang.org/)
11
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg)](#-contributing)
12
+
13
+ **Stop paying to send dead code to your LLM.**
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## The 30-Second Pitch
20
+
21
+ Your AI agent doesn't need your entire repo to fix a JWT bug. It needs the token-handling middleware, `verifyToken` underneath it, and the crypto util they both depend on — and **nothing else**.
22
+
23
+ Naive context packers dump every byte they can find. ContextDiet parses your code into an **Abstract Syntax Tree**, builds a real **dependency graph**, ranks the symbols that match your task, and keeps *only the reachable code paths*. The result is a dense, surgical Markdown bundle that fits your intent — not your file count.
24
+
25
+ ```bash
26
+ npx contextdiet-cli trim ./src --focus "verify the token signature"
27
+ ```
28
+
29
+ The **Markdown bundle streams to stdout** (pipe it straight into a file, your clipboard, or an LLM), while a human-readable summary dashboard is printed to **stderr** — so redirecting stdout gives you a pristine bundle:
30
+
31
+ ```bash
32
+ npx contextdiet-cli trim ./src --focus "verify the token signature" > context.md
33
+ ```
34
+
35
+ Here's a **real, reproducible run** — ContextDiet trimming its own source (`npm run build && node bin/contextdiet.js trim ./src --focus "malformed syntax ParseError"`):
36
+
37
+ ```
38
+ ContextDiet · trim
39
+ ────────────────────────────────────────────────────
40
+ focus malformed syntax ParseError
41
+ root ./src (ContextDiet's own engine)
42
+ seeds 2 matched · kept 2/14 files
43
+ ────────────────────────────────────────────────────
44
+ original 62.6 KB ~30,472 tok
45
+ pruned 616 B ~302 tok
46
+ ────────────────────────────────────────────────────
47
+ reduction 99.0% tokens ✓ >80% target met
48
+ 99.0% bytes
49
+ output (stdout)
50
+ ```
51
+
52
+ > Compression scales with how precisely your task maps to code: narrow, single-symbol tasks measure up to **99.0%**; broader tasks that legitimately touch more code measure less (e.g. **68.2%** on our auth-monolith fixture — see the example below). All numbers are measured runs, logged in `PROJECT_STATUS.md`.
53
+
54
+ ---
55
+
56
+ ## Why ContextDiet?
57
+
58
+ Most "repo-to-prompt" tools (think `repomix`, `gitingest`, and friends) are **text dumpers**. They walk your filesystem, concatenate files, and maybe strip comments. That's it. They have no idea what your code *means*, so they send everything and let you (and your wallet) sort it out.
59
+
60
+ That's expensive in three ways:
61
+
62
+ | | Naive text packers | **ContextDiet** |
63
+ |---|---|---|
64
+ | **Unit of understanding** | Files & bytes | AST symbols & call graph |
65
+ | **What gets included** | Everything it can glob | Only code reachable from your task |
66
+ | **Dead code** | Shipped to the model | Sliced away |
67
+ | **Cost model** | You pay for the whole repo | You pay for the relevant slice |
68
+ | **Circular imports** | Duplicated / mangled | Cycle-safe graph traversal |
69
+ | **Re-exports & aliases** | Often broken by regex | Resolved via real AST |
70
+ | **Network / API calls** | Sometimes (embeddings) | **Zero. Fully local.** |
71
+ | **Determinism** | Order-dependent | Byte-identical every run |
72
+
73
+ > **The core insight:** LLM cost is billed per token, and most tokens in a naive dump are *irrelevant*. A billing module, a PDF generator, and an analytics service have **zero structural connection** to your auth bug — so why pay to send them? ContextDiet answers "what does the model actually need to see?" with graph theory, not guesswork.
74
+
75
+ ---
76
+
77
+ ## Quick Start
78
+
79
+ ```bash
80
+ # Zero install — run it directly
81
+ npx contextdiet-cli trim ./src --focus "verify the token signature"
82
+
83
+ # Or install globally (the installed command keeps the short name)
84
+ npm install -g contextdiet-cli
85
+ contextdiet trim ./src --focus "add rate limiting to the login route"
86
+ ```
87
+
88
+ **`contextdiet trim <path> [options]`**
89
+
90
+ | Flag | Description | Default |
91
+ |---|---|---|
92
+ | `<path>` | Root directory to analyze. | *(required)* |
93
+ | `-f, --focus <query>` | Natural-language description of your task. Drives seed selection. | *(required)* |
94
+ | `--hops <n>` | Dependency-graph traversal depth from the seed nodes. | `2` |
95
+ | `-o, --output <file>` | Write the bundle to a file instead of stdout. | *(stdout)* |
96
+
97
+ > The bundle is written to **stdout** and the summary dashboard to **stderr**, so `contextdiet trim … > bundle.md` yields a clean file with no dashboard noise mixed in.
98
+
99
+ ---
100
+
101
+ ## How It Works
102
+
103
+ ContextDiet is a five-stage pipeline. Each stage is a pure, independently testable module — source in, data out, no hidden state.
104
+
105
+ ```
106
+ ┌───────────────────────────────────────────────┐
107
+ ./src ──────────────▶│ ContextDiet │
108
+ --focus "verify…" └───────────────────────────────────────────────┘
109
+
110
+ ┌─────────────────────────────────────┼─────────────────────────────────────┐
111
+ ▼ ▼ ▼
112
+ ┌───────────────┐ symbols + ┌───────────────────┐ graph ┌──────────────────────┐
113
+ │ 1. PARSER │ imports │ 2. DEPENDENCY │ nodes │ 3. LEXICAL SEED │
114
+ │ │──────────────▶│ GRAPH │───────────▶│ RANKER │
115
+ │ AST via │ │ │ │ │
116
+ │ ast-grep │ │ resolves imports, │ │ tokenizes the focus │
117
+ │ → functions, │ │ re-exports, │ │ string, scores it │
118
+ │ classes, │ │ aliases; handles │ │ against symbol names │
119
+ │ vars, types │ │ circular deps │ │ → seed nodes │
120
+ └───────────────┘ └───────────────────┘ └──────────────────────┘
121
+
122
+ ┌────────────────────────────────────────────────────────────────────┘
123
+ ▼ ▼
124
+ ┌───────────────────────┐ kept code ┌──────────────────────────┐
125
+ │ 4. SELECT + PRUNE │───────────────▶│ 5. MARKDOWN BUNDLER │─────▶ stdout (→ pipe to file)
126
+ │ │ │ │ + metrics → stderr
127
+ │ walks the graph from │ │ fences each kept file │
128
+ │ the seeds (reference │ │ with START/END delimiters│ Raw: 30,472 tok
129
+ │ closure), keeps only │ │ optimized for LLM │ Compressed: 302 tok
130
+ │ reachable declarations│ │ context parsing │ Reduction: 99.0%
131
+ └───────────────────────┘ └──────────────────────────┘
132
+ ```
133
+
134
+ | Stage | Module | Responsibility |
135
+ |---|---|---|
136
+ | **1. Parser** | `src/core/parser` | Extracts imports & top-level symbols from each file via a real AST (ast-grep). Never returns a silently-wrong partial tree — surfaces a catchable `ParseError`. |
137
+ | **2. Dependency Graph** | `src/core/graph` | Resolves the import/export edges between symbols. Cycle-safe (`a → b → a` won't loop). |
138
+ | **3. Lexical Seed Ranker** | `src/core/ranker` | Turns your `--focus` string into weighted seed nodes. Strips stop words, splits camelCase, scores overlap. **$0, deterministic, local.** |
139
+ | **4. Selector + Pruner** | `src/core/graph` · `src/core/pruner` | Walks the dependency graph from the seed symbols (symbol-level reference closure — same-file helpers always follow, so nothing dangles), then re-emits only the surviving declarations, verbatim. |
140
+ | **5. Markdown Bundler** | `src/core/bundler` | Serializes the survivors into a dense, delimiter-fenced stream — plus a `metrics` report proving the reduction. |
141
+
142
+ ---
143
+
144
+ ## Example: The Monolith Auth App
145
+
146
+ Given a backend where `index.ts` boots Express and mounts **auth**, **billing**, and **analytics** routes:
147
+
148
+ ```
149
+ src/
150
+ ├── index.ts ← 🔴 sliced (entry point — a dependent, not a dependency)
151
+ ├── routes/auth.ts ← 🔴 sliced (calls the token code; not needed to understand it)
152
+ ├── middleware/authMiddleware ← 🟢 kept (its token handlers match the focus)
153
+ ├── utils/jwtUtils.ts ← 🟢 kept (verifyToken and friends)
154
+ ├── utils/crypto.ts ← 🟢 kept (jwtUtils depends on it)
155
+ ├── routes/billing.ts ← 🔴 sliced
156
+ ├── utils/pdfGenerator.ts ← 🔴 sliced
157
+ └── services/analytics.ts ← 🔴 sliced
158
+ ```
159
+
160
+ Running `--focus "verify the token signature"` seeds the symbols that match the task (the middleware's token handlers, `verifyToken`) and keeps their **dependency closure**: `authMiddleware → jwtUtils → crypto` — 3 of 8 files, measured at **9,386 → 2,985 estimated tokens (68.2%)**.
161
+
162
+ Note the direction: ContextDiet keeps what your focused code **depends on**, never what merely *calls into it*. The entry point and the auth route import the JWT code, but the model doesn't need them to reason about token verification — and billing, the PDF generator, and analytics have zero structural connection to the token path, so they never stand a chance.
163
+
164
+ Narrower focus, deeper cuts: on ContextDiet's own repo, `--focus "malformed syntax ParseError"` maps to two leaf symbols and measures **99.0%** (30,472 → 302 tokens).
165
+
166
+ ---
167
+
168
+ ## Local Development
169
+
170
+ ```bash
171
+ git clone https://github.com/risshabs22-quantified/contextDiet.git
172
+ cd contextDiet
173
+ npm install
174
+
175
+ npm run typecheck # tsc --noEmit, strict mode
176
+ npm test # full Vitest suite
177
+ npm run build # compile to dist/
178
+ ```
179
+
180
+ **Requirements:** Node.js `>= 20`.
181
+
182
+ ---
183
+
184
+ ## Contributing
185
+
186
+ Contributions are welcome and appreciated — ContextDiet is built to be extended.
187
+
188
+ 1. **Fork** the repo and create a feature branch (`git checkout -b feat/my-improvement`).
189
+ 2. **Keep it strict.** All code must pass `npm run typecheck` (strict TypeScript, no implicit `any`) and `npm test`.
190
+ 3. **Test first.** Every new capability ships with Vitest coverage — see `tests/unit` for the style.
191
+ 4. **Stay modular.** Each pipeline stage is a pure module; new backends (tree-sitter, SWC) should slot in behind the existing interfaces without touching callers.
192
+ 5. **Open a PR.** CI runs typecheck + the full test suite on every push and pull request. Green check = ready for review.
193
+
194
+ Good first issues: additional language parsers, a smarter ranker (embeddings-optional), and richer bundle formats (JSON, XML).
195
+
196
+ ---
197
+
198
+ ## License
199
+
200
+ Released under the **[MIT License](./LICENSE)**. Use it, fork it, ship it, sell it — no strings attached. Copyright © 2026 the ContextDiet contributors.
201
+
202
+ <div align="center">
203
+
204
+ **If ContextDiet saves you tokens, [give it a ⭐](https://github.com/risshabs22-quantified/contextDiet) — it genuinely helps.**
205
+
206
+ </div>
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Executable shim for the published package. Delegates to the compiled CLI.
3
+ // For development: `npm run build && node bin/contextdiet.js …` (emits dist/).
4
+ import './../dist/cli/index.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ContextDiet CLI (Task 5.0).
3
+ *
4
+ * contextdiet trim <path> --focus "<query>" [--hops <n>] [-o <file>]
5
+ *
6
+ * Contract: the Markdown bundle is the ONLY thing written to stdout (so it pipes
7
+ * cleanly into an LLM / clipboard / file). The human-facing summary dashboard is
8
+ * written to stderr, so `contextdiet trim … > bundle.md` yields a pristine file.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * ContextDiet CLI (Task 5.0).
3
+ *
4
+ * contextdiet trim <path> --focus "<query>" [--hops <n>] [-o <file>]
5
+ *
6
+ * Contract: the Markdown bundle is the ONLY thing written to stdout (so it pipes
7
+ * cleanly into an LLM / clipboard / file). The human-facing summary dashboard is
8
+ * written to stderr, so `contextdiet trim … > bundle.md` yields a pristine file.
9
+ */
10
+ import { Command } from 'commander';
11
+ import { promises as fs } from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import { trim } from '../core/pipeline.js';
14
+ const program = new Command();
15
+ program
16
+ .name('contextdiet')
17
+ .description('AST-based token optimizer — trim a codebase to just what an AI agent needs.')
18
+ .version('0.1.0');
19
+ program
20
+ .command('trim')
21
+ .description('Trim a codebase to the code relevant to a focus task, as one dense Markdown stream.')
22
+ .argument('<path>', 'root directory to analyze')
23
+ .requiredOption('-f, --focus <query>', 'natural-language task to focus on')
24
+ .option('--hops <n>', 'dependency-graph traversal depth from seeds', '2')
25
+ .option('-o, --output <file>', 'write the bundle to a file instead of stdout')
26
+ .action(async (targetPath, options) => {
27
+ try {
28
+ const hops = Number.parseInt(options.hops, 10);
29
+ if (!Number.isFinite(hops) || hops < 0) {
30
+ throw new Error(`--hops must be a non-negative integer (got "${options.hops}")`);
31
+ }
32
+ const root = path.resolve(targetPath);
33
+ const result = await trim(root, options.focus, { hops });
34
+ if (result.totalFiles === 0) {
35
+ process.stderr.write(`\n contextdiet: no source files found under ${root}\n\n`);
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ const outputPath = options.output === undefined ? null : path.resolve(options.output);
40
+ if (outputPath !== null) {
41
+ await fs.writeFile(outputPath, ensureTrailingNewline(result.bundle), 'utf8');
42
+ }
43
+ else {
44
+ process.stdout.write(ensureTrailingNewline(result.bundle));
45
+ }
46
+ printDashboard(result, outputPath);
47
+ }
48
+ catch (error) {
49
+ process.stderr.write(`\n contextdiet: ${error.message}\n\n`);
50
+ process.exitCode = 1;
51
+ }
52
+ });
53
+ program.parseAsync(process.argv).catch((error) => {
54
+ process.stderr.write(`\n contextdiet: ${error.message}\n\n`);
55
+ process.exitCode = 1;
56
+ });
57
+ // --- presentation ---------------------------------------------------------
58
+ const useColor = Boolean(process.stderr.isTTY) && process.env.NO_COLOR === undefined;
59
+ const paint = (code, text) => useColor ? `\x1b[${code}m${text}\x1b[0m` : text;
60
+ const bold = (t) => paint('1', t);
61
+ const dim = (t) => paint('2', t);
62
+ const cyan = (t) => paint('36', t);
63
+ const green = (t) => paint('32', t);
64
+ const yellow = (t) => paint('33', t);
65
+ function printDashboard(result, outputPath) {
66
+ const m = result.metrics;
67
+ const hitTarget = m.tokenReductionPercentage >= 80;
68
+ const pct = `${m.tokenReductionPercentage.toFixed(1)}%`;
69
+ const pctText = hitTarget ? green(bold(pct)) : yellow(bold(pct));
70
+ const targetText = hitTarget ? green('✓ >80% target met') : yellow('⚠ below >80% target');
71
+ const rule = dim('─'.repeat(52));
72
+ const label = (t) => dim(t.padEnd(11));
73
+ const lines = [
74
+ '',
75
+ ` ${bold(cyan('ContextDiet'))} ${dim('· trim')}`,
76
+ ` ${rule}`,
77
+ ` ${label('focus')}${result.focus}`,
78
+ ` ${label('root')}${result.rootDir}`,
79
+ ` ${label('seeds')}${bold(String(result.seedCount))} matched ${dim('·')} kept ${bold(`${result.keptFiles.length}/${result.totalFiles}`)} files`,
80
+ ` ${rule}`,
81
+ ` ${label('original')}${humanBytes(m.rawBytes).padStart(9)} ${dim(`~${grouped(m.rawTokens)} tok`)}`,
82
+ ` ${label('pruned')}${humanBytes(m.compressedBytes).padStart(9)} ${dim(`~${grouped(m.compressedTokens)} tok`)}`,
83
+ ` ${rule}`,
84
+ ` ${label('reduction')}${pctText} ${dim('tokens')} ${targetText}`,
85
+ ` ${dim(''.padEnd(11))}${dim(`${m.byteReductionPercentage.toFixed(1)}% bytes`)}`,
86
+ ` ${label('output')}${outputPath === null ? dim('(stdout)') : outputPath}`,
87
+ '',
88
+ ];
89
+ process.stderr.write(lines.join('\n') + '\n');
90
+ }
91
+ function ensureTrailingNewline(text) {
92
+ return text.endsWith('\n') ? text : `${text}\n`;
93
+ }
94
+ function humanBytes(bytes) {
95
+ if (bytes < 1024)
96
+ return `${bytes} B`;
97
+ if (bytes < 1024 * 1024)
98
+ return `${(bytes / 1024).toFixed(1)} KB`;
99
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
100
+ }
101
+ function grouped(n) {
102
+ return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
103
+ }
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAS3C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,6EAA6E,CAAC;KAC1F,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,qFAAqF,CAAC;KAClG,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,CAAC;KAC/C,cAAc,CAAC,qBAAqB,EAAE,mCAAmC,CAAC;KAC1E,MAAM,CAAC,YAAY,EAAE,6CAA6C,EAAE,GAAG,CAAC;KACxE,MAAM,CAAC,qBAAqB,EAAE,8CAA8C,CAAC;KAC7E,MAAM,CAAC,KAAK,EAAE,UAAkB,EAAE,OAAuB,EAAE,EAAE;IAC5D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,+CAA+C,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAEzD,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,IAAI,MAAM,CAAC,CAAC;YACjF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtF,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,CAAC;QAED,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAqB,KAAe,CAAC,OAAO,MAAM,CAAC,CAAC;QACzE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAqB,KAAe,CAAC,OAAO,MAAM,CAAC,CAAC;IACzE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,6EAA6E;AAE7E,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC;AACrF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,IAAY,EAAU,EAAE,CACnD,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAClD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACjD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACnD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpD,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAErD,SAAS,cAAc,CAAC,MAAkB,EAAE,UAAyB;IACnE,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;IACzB,MAAM,SAAS,GAAG,CAAC,CAAC,wBAAwB,IAAI,EAAE,CAAC;IACnD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAE1F,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvD,MAAM,KAAK,GAAG;QACZ,EAAE;QACF,KAAK,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,EAAE;QACjD,KAAK,IAAI,EAAE;QACX,KAAK,KAAK,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE;QACpC,KAAK,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE;QACrC,KAAK,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,QAAQ;QACpJ,KAAK,IAAI,EAAE;QACX,KAAK,KAAK,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;QACtG,KAAK,KAAK,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE;QAClH,KAAK,IAAI,EAAE;QACX,KAAK,KAAK,CAAC,WAAW,CAAC,GAAG,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,UAAU,EAAE;QACpE,KAAK,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE;QACjF,KAAK,KAAK,CAAC,QAAQ,CAAC,GAAG,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE;QAC3E,EAAE;KACH,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;AAClD,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * MarkdownBundler — Task 5.0 (output delivery).
3
+ *
4
+ * The final stage of the pipeline: take the pruned files and serialize them
5
+ * into one hyper-dense, LLM-friendly stream. The format is deliberately plain
6
+ * (explicit START/END delimiters rather than triple-backtick fences) because:
7
+ * - it survives nested code that itself contains ``` fences without escaping,
8
+ * - the path appears on both boundaries so a model can attribute every line,
9
+ * - it costs almost no extra tokens per file.
10
+ *
11
+ * Pure and deterministic: same input files always produce byte-identical output.
12
+ * No I/O, no network.
13
+ */
14
+ /** A single file to include in the bundle. */
15
+ export interface BundleFile {
16
+ /** Path shown in the delimiters — should be repo-relative for readability. */
17
+ readonly path: string;
18
+ /** The (already pruned) file contents. */
19
+ readonly content: string;
20
+ }
21
+ /** Tunables for how the stream is assembled. */
22
+ export interface BundlerOptions {
23
+ /**
24
+ * Blank line(s) inserted between consecutive file blocks. Defaults to `1`.
25
+ * Purely cosmetic; does not affect a block's own delimiters.
26
+ */
27
+ readonly blockSeparation?: number;
28
+ /**
29
+ * Sort files by path before emitting, for stable output regardless of input
30
+ * order. Defaults to `false` (preserve caller order, typically graph order).
31
+ */
32
+ readonly sortByPath?: boolean;
33
+ }
34
+ /**
35
+ * Wrap a single file's content in the START/END delimiters. Exported so callers
36
+ * (and tests) can format one block in isolation.
37
+ */
38
+ export declare function formatFileBlock(file: BundleFile): string;
39
+ export declare class MarkdownBundler {
40
+ private readonly blockSeparation;
41
+ private readonly sortByPath;
42
+ constructor(options?: BundlerOptions);
43
+ /**
44
+ * Combine all files into a single Markdown stream. Returns an empty string for
45
+ * an empty file list.
46
+ */
47
+ bundle(files: readonly BundleFile[]): string;
48
+ }
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/bundler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,8CAA8C;AAC9C,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAOxD;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IAErC,YAAY,OAAO,GAAE,cAAmB,EAGvC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAS3C;CACF"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * MarkdownBundler — Task 5.0 (output delivery).
3
+ *
4
+ * The final stage of the pipeline: take the pruned files and serialize them
5
+ * into one hyper-dense, LLM-friendly stream. The format is deliberately plain
6
+ * (explicit START/END delimiters rather than triple-backtick fences) because:
7
+ * - it survives nested code that itself contains ``` fences without escaping,
8
+ * - the path appears on both boundaries so a model can attribute every line,
9
+ * - it costs almost no extra tokens per file.
10
+ *
11
+ * Pure and deterministic: same input files always produce byte-identical output.
12
+ * No I/O, no network.
13
+ */
14
+ const START_PREFIX = '--- START FILE: ';
15
+ const END_PREFIX = '--- END FILE: ';
16
+ const DELIM_SUFFIX = ' ---';
17
+ /**
18
+ * Wrap a single file's content in the START/END delimiters. Exported so callers
19
+ * (and tests) can format one block in isolation.
20
+ */
21
+ export function formatFileBlock(file) {
22
+ const header = `${START_PREFIX}${file.path}${DELIM_SUFFIX}`;
23
+ const footer = `${END_PREFIX}${file.path}${DELIM_SUFFIX}`;
24
+ // Preserve content verbatim; only guarantee the delimiters sit on their own
25
+ // lines by trimming a single trailing newline so the footer never doubles up.
26
+ const body = stripSingleTrailingNewline(file.content);
27
+ return `${header}\n${body}\n${footer}`;
28
+ }
29
+ export class MarkdownBundler {
30
+ blockSeparation;
31
+ sortByPath;
32
+ constructor(options = {}) {
33
+ this.blockSeparation = Math.max(0, options.blockSeparation ?? 1);
34
+ this.sortByPath = options.sortByPath ?? false;
35
+ }
36
+ /**
37
+ * Combine all files into a single Markdown stream. Returns an empty string for
38
+ * an empty file list.
39
+ */
40
+ bundle(files) {
41
+ if (files.length === 0)
42
+ return '';
43
+ const ordered = this.sortByPath
44
+ ? [...files].sort((a, b) => a.path.localeCompare(b.path))
45
+ : files;
46
+ const separator = '\n'.repeat(this.blockSeparation + 1);
47
+ return ordered.map(formatFileBlock).join(separator);
48
+ }
49
+ }
50
+ function stripSingleTrailingNewline(content) {
51
+ if (content.endsWith('\r\n'))
52
+ return content.slice(0, -2);
53
+ if (content.endsWith('\n'))
54
+ return content.slice(0, -1);
55
+ return content;
56
+ }
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/bundler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwBH,MAAM,YAAY,GAAG,kBAAkB,CAAC;AACxC,MAAM,UAAU,GAAG,gBAAgB,CAAC;AACpC,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,MAAM,MAAM,GAAG,GAAG,YAAY,GAAG,IAAI,CAAC,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5D,MAAM,MAAM,GAAG,GAAG,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,YAAY,EAAE,CAAC;IAC1D,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAM,IAAI,GAAG,0BAA0B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,OAAO,GAAG,MAAM,KAAK,IAAI,KAAK,MAAM,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,OAAO,eAAe;IACT,eAAe,CAAS;IACxB,UAAU,CAAU;IAErC,YAAY,OAAO,GAAmB,EAAE;QACtC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC;IAChD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAA4B;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAElC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU;YAC7B,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACzD,CAAC,CAAC,KAAK,CAAC;QAEV,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;QACxD,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;CACF;AAED,SAAS,0BAA0B,CAAC,OAAe;IACjD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,29 @@
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 type { Parser } from '../parser/types.js';
16
+ import type { DependencyGraph, GraphResolver } from './types.js';
17
+ export declare class DependencyGraphResolver implements GraphResolver {
18
+ private readonly parser;
19
+ constructor(parser?: Parser);
20
+ buildGraph(rootDir: string): Promise<DependencyGraph>;
21
+ private buildNode;
22
+ }
23
+ /**
24
+ * Iteratively collect every node reachable from `startPath` (inclusive), up to
25
+ * `maxHops` edges away. Uses a visited set + explicit queue, so cycles terminate
26
+ * and deep graphs never overflow the call stack.
27
+ */
28
+ export declare function collectReachable(graph: DependencyGraph, startPath: string, maxHops?: number): Set<string>;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/graph/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAEV,eAAe,EAEf,aAAa,EACd,MAAM,YAAY,CAAC;AAUpB,qBAAa,uBAAwB,YAAW,aAAa;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,MAAM,GAAE,MAA+B,EAElD;IAEK,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA2B1D;YAEa,SAAS;CAgCxB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAiC,GACzC,GAAG,CAAC,MAAM,CAAC,CAwBb"}