@nanonets/graft 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/ai/crux.d.ts +31 -0
- package/dist/ai/crux.d.ts.map +1 -0
- package/dist/ai/crux.js +91 -0
- package/dist/ai/crux.js.map +1 -0
- package/dist/ai/providers.d.ts +37 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +17 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/ai/summarize.d.ts +25 -0
- package/dist/ai/summarize.d.ts.map +1 -0
- package/dist/ai/summarize.js +41 -0
- package/dist/ai/summarize.js.map +1 -0
- package/dist/ai/synthesize.d.ts +32 -0
- package/dist/ai/synthesize.d.ts.map +1 -0
- package/dist/ai/synthesize.js +132 -0
- package/dist/ai/synthesize.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +131 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/build.d.ts +33 -0
- package/dist/context/build.d.ts.map +1 -0
- package/dist/context/build.js +265 -0
- package/dist/context/build.js.map +1 -0
- package/dist/context/check.d.ts +21 -0
- package/dist/context/check.d.ts.map +1 -0
- package/dist/context/check.js +127 -0
- package/dist/context/check.js.map +1 -0
- package/dist/context/node-file.d.ts +80 -0
- package/dist/context/node-file.d.ts.map +1 -0
- package/dist/context/node-file.js +165 -0
- package/dist/context/node-file.js.map +1 -0
- package/dist/engine.d.ts +58 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +87 -0
- package/dist/engine.js.map +1 -0
- package/dist/graph/build.d.ts +33 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +95 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/check.d.ts +18 -0
- package/dist/graph/check.d.ts.map +1 -0
- package/dist/graph/check.js +126 -0
- package/dist/graph/check.js.map +1 -0
- package/dist/graph/enrich.d.ts +42 -0
- package/dist/graph/enrich.d.ts.map +1 -0
- package/dist/graph/enrich.js +141 -0
- package/dist/graph/enrich.js.map +1 -0
- package/dist/graph/extract.d.ts +23 -0
- package/dist/graph/extract.d.ts.map +1 -0
- package/dist/graph/extract.js +249 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/resolve.d.ts +4 -0
- package/dist/graph/resolve.d.ts.map +1 -0
- package/dist/graph/resolve.js +0 -0
- package/dist/graph/resolve.js.map +1 -0
- package/dist/graph/types.d.ts +56 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +14 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/write.d.ts +9 -0
- package/dist/graph/write.d.ts.map +1 -0
- package/dist/graph/write.js +39 -0
- package/dist/graph/write.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/fs.d.ts +10 -0
- package/dist/ingest/fs.d.ts.map +1 -0
- package/dist/ingest/fs.js +48 -0
- package/dist/ingest/fs.js.map +1 -0
- package/dist/util/id.d.ts +7 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +14 -0
- package/dist/util/id.js.map +1 -0
- package/dist/viewer/app.js +1 -0
- package/dist/viewer/index.html +48 -0
- package/dist/viewer/style.css +119 -0
- package/dist/viz/assemble.d.ts +33 -0
- package/dist/viz/assemble.d.ts.map +1 -0
- package/dist/viz/assemble.js +101 -0
- package/dist/viz/assemble.js.map +1 -0
- package/dist/viz/serve.d.ts +12 -0
- package/dist/viz/serve.d.ts.map +1 -0
- package/dist/viz/serve.js +128 -0
- package/dist/viz/serve.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Context Graph Engine contributors
|
|
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,228 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<!-- placeholder: swap for the Graft logo asset when ready -->
|
|
4
|
+
<!-- <img src="assets/graft.png" alt="Graft" width="220"/> -->
|
|
5
|
+
|
|
6
|
+
# Graft
|
|
7
|
+
|
|
8
|
+
**Your coding agent reads 30 files to change 3.**
|
|
9
|
+
**Graft gives it the map it should have read first.**
|
|
10
|
+
|
|
11
|
+
<p>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@nanonets/graft"><img src="https://img.shields.io/npm/v/%40nanonets%2Fgraft?style=for-the-badge&logo=npm&logoColor=white&label=npm" /></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/@nanonets/graft"><img src="https://img.shields.io/npm/dm/%40nanonets%2Fgraft?style=for-the-badge&logo=npm&logoColor=white&label=downloads" /></a>
|
|
14
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/%40nanonets%2Fgraft?style=for-the-badge&logo=nodedotjs&logoColor=white" /></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=for-the-badge&logo=typescript&logoColor=white" />
|
|
16
|
+
<img src="https://img.shields.io/badge/License-MIT-20C997?style=for-the-badge" />
|
|
17
|
+
<img src="https://img.shields.io/badge/runs-100%25%20local-546FFF?style=for-the-badge&logo=ollama&logoColor=white" />
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<!-- placeholder: numbers pending a committed benchmark run, see Benchmark below -->
|
|
21
|
+
**31% fewer tool calls · 23% less cost · 17% lower latency.** That is what an agent gets from reading the graph first instead of going in cold.
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- placeholder: swap this diagram for a produced hero gif/screenshot (assets/hero.gif) when ready -->
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart LR
|
|
29
|
+
A["Your code"] -->|"graft init"| B[".context/<br/>a graph of linked<br/>markdown nodes"]
|
|
30
|
+
B -->|"git commit"| C[("your repo")]
|
|
31
|
+
C -->|"git clone"| D["teammate + their agent"]
|
|
32
|
+
D -->|"reads the graph first"| E["starts already knowing<br/>how the repo fits together"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx @nanonets/graft init
|
|
41
|
+
# builds .context/ from your code, one node per system, API, or concept
|
|
42
|
+
|
|
43
|
+
git add .context && git commit -m "add context graph"
|
|
44
|
+
# commit it so everyone who clones the repo (and their agents) gets the graph
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or install it once and get the `graft` command everywhere:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install -g @nanonets/graft
|
|
51
|
+
graft init
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That is it. Point your agent at `.context/` and it reads the graph before it starts working.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## The problem
|
|
59
|
+
|
|
60
|
+
Every task, your coding agent starts blind. Before it changes anything, it re-explores the repo: grep a term, open a file, follow an import, back out, try again. It is rebuilding a picture of a codebase it mapped an hour ago and threw away. That rediscovery burns most of a run's tool calls, tokens, and latency, and it is pure overhead:
|
|
61
|
+
|
|
62
|
+
- **Repeated.** Every task pays the exploration cost again, from zero.
|
|
63
|
+
- **Discarded.** Whatever the agent figured out dies with the session.
|
|
64
|
+
- **Unshared.** The next teammate, and their agent, start from scratch too.
|
|
65
|
+
|
|
66
|
+
Humans onboard to a codebase once. Agents onboard every single time.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## What Graft does
|
|
71
|
+
|
|
72
|
+
Graft builds that understanding **once** and writes it into your repo as a folder of linked markdown files, one node per system, API, or concept.
|
|
73
|
+
|
|
74
|
+
- **Real explanations, not a list of symbols.** Each node says, in plain English, what a part of the system does and how it connects to the rest, the way a senior engineer would explain it. That is the part an agent actually needs so it can skip the exploration. It is not a dump of function names.
|
|
75
|
+
- **A real graph you can read.** No embeddings, no similarity search, no index to keep warm. The graph is a set of linked files your agent opens, greps, and follows, exactly the way it reads any other file in the repo.
|
|
76
|
+
- **Grafted into git.** The graph is just files in `.context/`. Commit it, and anyone who clones the repo has it. No database, no server, no setup. Git does the syncing, and a stale graph shows up as a diff in review instead of rotting in some external store.
|
|
77
|
+
- **The diff lives with the code.** When a change moves things around, you see it in the graph diff in the same pull request, right next to the code that caused it.
|
|
78
|
+
- **Local by default.** The graph builds with a local model, so your code stays on your machine. Cloud models are opt-in for richer summaries.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## How the graph gets built
|
|
83
|
+
|
|
84
|
+
Graft builds the graph in two passes, both powered by a language model:
|
|
85
|
+
|
|
86
|
+
1. **Read each file.** Every source file is summarized once into a short description of what it does.
|
|
87
|
+
2. **Group into nodes.** Those summaries are grouped into a curated set of nodes (subsystems, key files, and concepts) with typed links between them. Graft chooses the right level of detail for you instead of making one node per file, so a big repo becomes a few dozen readable nodes.
|
|
88
|
+
|
|
89
|
+
Both passes are cached by content hash. Re-running only touches the files that changed, so the second build is fast and cheap.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## What's in a node
|
|
94
|
+
|
|
95
|
+
A node is a single markdown file. Most code maps stop at an address: this thing lives in that file, on that line. That tells an agent where to look, not what it will find, so it still has to open the source and read. A Graft node holds the meaning inline, so the agent learns what it needs up front and opens the file only when it wants more.
|
|
96
|
+
|
|
97
|
+
Each node holds:
|
|
98
|
+
|
|
99
|
+
| Part | What it holds |
|
|
100
|
+
|---|---|
|
|
101
|
+
| **Summary** | A plain-English explanation of what the code does, written by the model and cached. It is there whether or not the code was ever documented, and it is regenerated when the source changes. |
|
|
102
|
+
| **Crux** | The handful of lines that actually carry the logic: the guard, the skip condition, the state change. Lifted straight from the source and stored inline, so the agent sees *how* it works, not just what. |
|
|
103
|
+
| **Sources** | The exact files the node is built from, each tracked by a content hash, so Graft can tell precisely when a node has gone stale. |
|
|
104
|
+
| **Links** | Typed connections to other nodes (`depends_on`, `part_of`, `uses`, `implements`, `produces`), written as `[[wikilinks]]` your agent can follow. |
|
|
105
|
+
| **Notes** | Anything you write below the generated block. It is preserved across regenerations, so your own context is never overwritten. |
|
|
106
|
+
|
|
107
|
+
That is three depths in one file: the summary says *what* the code does, the crux shows *how*, and the sources point to the rest if the agent needs it. A plain index makes it read a whole file to learn one thing. A Graft node hands it the answer inline, and the follow-up read often never happens.
|
|
108
|
+
|
|
109
|
+
The crux is stored as the code itself, not as a line range, on purpose. Line numbers drift whenever unrelated code above them shifts, but the lines that matter do not. Keeping the text, not the numbers, means the crux stays correct even as the file around it moves.
|
|
110
|
+
|
|
111
|
+
_Summary, sources, links, and notes ship today. The inline crux excerpt is rolling out as the node schema lands._
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Keeping it honest (CI)
|
|
116
|
+
|
|
117
|
+
A graph that has drifted from the code is worse than no graph. `graft check` compares each node's tracked source hashes against the current files and fails if the graph is stale. Drop it in CI so a pull request cannot merge with an out-of-date map.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
graft check # exits non-zero if .context/ has drifted
|
|
121
|
+
graft check --json # machine-readable drift report
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
<!-- placeholder: planned, not yet in the CLI -->
|
|
125
|
+
> **Planned: auto-sync on commit.** `graft hook install` will add a post-commit hook that regenerates changed nodes automatically, so the graph stays current without anyone remembering to run `init`. Until then, regenerate with `graft init` and commit it alongside your code.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Runs locally
|
|
130
|
+
|
|
131
|
+
Everything runs on your machine. No accounts, no cloud calls, no keys required.
|
|
132
|
+
|
|
133
|
+
- **Default.** A local model via [Ollama](https://ollama.com) builds the graph. Nothing leaves your machine.
|
|
134
|
+
- **Optional.** Set `OPENROUTER_API_KEY` to use a cloud model for richer summaries. Pass `--local` to force local even when a key is set.
|
|
135
|
+
- **No telemetry** and no analytics.
|
|
136
|
+
|
|
137
|
+
See [`.env.example`](.env.example) for the full list of settings (model names, base URLs).
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## CLI
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
graft init [dir] # build .context/ from the code at [dir] (default: .)
|
|
145
|
+
graft init --extensions .ts .py # only include these code extensions
|
|
146
|
+
graft init --local # force local Ollama even if OPENROUTER_API_KEY is set
|
|
147
|
+
|
|
148
|
+
graft check [dir] # fail (exit 1) if .context/ has drifted from the code
|
|
149
|
+
graft check --json # print the drift report as JSON
|
|
150
|
+
|
|
151
|
+
graft viz [dir] # see the graph: serves an interactive viewer on localhost
|
|
152
|
+
graft viz --port 5000 --no-open # pick a port; don't auto-open the browser
|
|
153
|
+
|
|
154
|
+
# global
|
|
155
|
+
graft --dir <path> # use a context dir other than <repo>/.context
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Visualize it (`graft viz`)
|
|
159
|
+
|
|
160
|
+
`graft viz` opens a local, interactive view of both graphs — no install, no dev
|
|
161
|
+
server; the viewer ships prebuilt inside the package.
|
|
162
|
+
|
|
163
|
+
- **Context** tab — the architecture graph from `.context/*.md`. Nodes colored by
|
|
164
|
+
type, sized by connectedness.
|
|
165
|
+
- **Code** tab — the per-symbol graph from `graph.json` (run `graft graph` first).
|
|
166
|
+
- **Outline** tab — the file → class → method hierarchy as a collapsible tree.
|
|
167
|
+
|
|
168
|
+
Edges speak the code's language. Every link is one of a closed set of verbs, each
|
|
169
|
+
answering a question someone building or reviewing code actually asks:
|
|
170
|
+
|
|
171
|
+
| Verb | The question it answers |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `part_of` / `contains` | where does this live? |
|
|
174
|
+
| `uses` / `calls` / `imports` / `depends_on` | what breaks if I change this? |
|
|
175
|
+
| `produces` | where does this output come from? |
|
|
176
|
+
| `configures` | what changes its behavior without a code change? |
|
|
177
|
+
| `validates` | what checks or judges this? (tests, drift checks, scoring) |
|
|
178
|
+
| `extends` / `implements` | what contract must this honor? |
|
|
179
|
+
|
|
180
|
+
Select a node and its edges take on direction: **amber = what it depends on,
|
|
181
|
+
teal = what depends on it**, with the verb written on each highlighted edge.
|
|
182
|
+
Chips above the canvas filter by verb; tree-sitter-extracted edges draw solid
|
|
183
|
+
while LLM-inferred ones draw dashed. The viewer live-reloads when `.context/`
|
|
184
|
+
changes on disk. Older graphs with vague verbs (`influences`, `supports`) are
|
|
185
|
+
normalized on load — no regeneration needed.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Benchmark
|
|
190
|
+
|
|
191
|
+
<!-- placeholder: fill from a committed bench/results/ run before launch -->
|
|
192
|
+
|
|
193
|
+
The claim Graft has to earn is simple: an agent that reads the graph first is cheaper and faster without getting more answers wrong. The harness runs every task twice through the same agent with the same file tools. One run is **cold** (it explores from zero) and one is **graph** (it gets the `.context/` bundle up front). A separate model judges correctness, with a required-keyword floor so a fast-but-wrong answer cannot win.
|
|
194
|
+
|
|
195
|
+
| Metric | Cold | Graph | Change |
|
|
196
|
+
|---|---|---|---|
|
|
197
|
+
| Tool calls | _TBD_ | _TBD_ | **−31%** |
|
|
198
|
+
| Cost | _TBD_ | _TBD_ | **−23%** |
|
|
199
|
+
| Latency | _TBD_ | _TBD_ | **−17%** |
|
|
200
|
+
| Correctness | _TBD_ | _TBD_ | _at least as good as cold_ |
|
|
201
|
+
|
|
202
|
+
_The numbers above are placeholders pending a committed run._ Reproduce it yourself:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
npm run bench -- --smoke # 1 corpus, 1 task, a quick plumbing check
|
|
206
|
+
npm run bench # full: all corpora, all tasks
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
See [`bench/README.md`](bench/README.md) for the method and how to add your own repo.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
git clone https://github.com/NanoNets/context-graph-engine.git && cd context-graph-engine
|
|
217
|
+
npm install
|
|
218
|
+
npm run build
|
|
219
|
+
npm test
|
|
220
|
+
|
|
221
|
+
npm run cli -- init . # run the CLI from source
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Kind } from "../graph/types.js";
|
|
2
|
+
/** One definition we want described, located by its line span within the file. */
|
|
3
|
+
export interface NodeRef {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: Kind;
|
|
6
|
+
signature: string | null;
|
|
7
|
+
startLine: number;
|
|
8
|
+
endLine: number;
|
|
9
|
+
}
|
|
10
|
+
export interface FileCruxInput {
|
|
11
|
+
path: string;
|
|
12
|
+
source: string;
|
|
13
|
+
nodes: NodeRef[];
|
|
14
|
+
}
|
|
15
|
+
export interface NodeCrux {
|
|
16
|
+
id: string;
|
|
17
|
+
summary: string;
|
|
18
|
+
crux_start: number;
|
|
19
|
+
crux_end: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CruxSummarizer {
|
|
22
|
+
describeFile(input: FileCruxInput): Promise<NodeCrux[]>;
|
|
23
|
+
}
|
|
24
|
+
/** Crux summarizer backed by OpenRouter's OpenAI-compatible chat API. */
|
|
25
|
+
export declare class OpenRouterCruxSummarizer implements CruxSummarizer {
|
|
26
|
+
private client;
|
|
27
|
+
private model;
|
|
28
|
+
constructor(apiKey: string, model: string, baseUrl?: string);
|
|
29
|
+
describeFile(input: FileCruxInput): Promise<NodeCrux[]>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=crux.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crux.d.ts","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kFAAkF;AAClF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACzD;AAwDD,yEAAyE;AACzE,qBAAa,wBAAyB,YAAW,cAAc;IAC7D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAiC;IAS7E,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAa9D"}
|
package/dist/ai/crux.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier-2 "meaning" call for the code graph — batched one request per file.
|
|
3
|
+
*
|
|
4
|
+
* Given a source file (with 1-based line numbers) and the list of definitions in
|
|
5
|
+
* it, one call returns, for each definition:
|
|
6
|
+
* 1. `summary` — one plain-English sentence: what the symbol is *for*, at the
|
|
7
|
+
* business-logic level, not a restatement of its signature.
|
|
8
|
+
* 2. `crux_start`/`crux_end` — the smallest contiguous range of FILE line
|
|
9
|
+
* numbers (inside that symbol's own span) that a reviewer must read to see
|
|
10
|
+
* the decision or rule the code encodes. `0/0` means there is no single
|
|
11
|
+
* crux (a trivial getter, a plain data holder).
|
|
12
|
+
*
|
|
13
|
+
* Batching per file means N definitions cost one request, not N — and the model
|
|
14
|
+
* sees each symbol's neighbours, which sharpens the summaries. Line numbers are
|
|
15
|
+
* consumed once, at write time, to slice the crux text verbatim from source.
|
|
16
|
+
*/
|
|
17
|
+
import OpenAI from "openai";
|
|
18
|
+
const SYSTEM_PROMPT = `You explain code definitions for a code graph that helps engineers navigate a codebase.
|
|
19
|
+
|
|
20
|
+
You are given ONE source file with 1-based line numbers, and a list of TARGET definitions in it. For every target, return its purpose and the line range of its core logic.
|
|
21
|
+
|
|
22
|
+
Return STRICT JSON, exactly:
|
|
23
|
+
{ "symbols": [ { "id": string, "summary": string, "crux_start": number, "crux_end": number }, ... ] }
|
|
24
|
+
|
|
25
|
+
Rules:
|
|
26
|
+
- Emit exactly one entry per target id given, using that id verbatim.
|
|
27
|
+
- summary: ONE sentence — what the symbol is FOR at the business-logic level. Say what problem it solves or rule it enforces, not what its signature already says.
|
|
28
|
+
- crux_start / crux_end: FILE line numbers (as shown), inside that symbol's own line range. Pick the SINGLE most important contiguous span — the core branch, formula, guard, or state change. Keep it TIGHT: at most ~8 lines, and NEVER the whole function. If you can't narrow it below that, the symbol has no distinct crux — use 0/0.
|
|
29
|
+
- Skip boilerplate, logging, and plumbing. If a symbol has no meaningful crux (trivial getter, data holder, one-line delegation, or logic spread evenly with no focal point), use "crux_start": 0 and "crux_end": 0.
|
|
30
|
+
- Output ONLY the JSON object. No prose, no code fences.`;
|
|
31
|
+
/** Cap the file text sent per request so one huge file can't blow the context. */
|
|
32
|
+
const MAX_CODE_CHARS = 18_000;
|
|
33
|
+
function numberLines(source) {
|
|
34
|
+
const clipped = source.length > MAX_CODE_CHARS ? `${source.slice(0, MAX_CODE_CHARS)}\n… (truncated)` : source;
|
|
35
|
+
return clipped
|
|
36
|
+
.split("\n")
|
|
37
|
+
.map((line, i) => `${i + 1}\t${line}`)
|
|
38
|
+
.join("\n");
|
|
39
|
+
}
|
|
40
|
+
function userContent(input) {
|
|
41
|
+
const targets = input.nodes
|
|
42
|
+
.map((n) => `- id=${n.id} | ${n.kind} | lines L${n.startLine}-L${n.endLine}` +
|
|
43
|
+
(n.signature ? ` | ${n.signature}` : ""))
|
|
44
|
+
.join("\n");
|
|
45
|
+
return `FILE: ${input.path}\n\n${numberLines(input.source)}\n\nTARGETS:\n${targets}`;
|
|
46
|
+
}
|
|
47
|
+
/** Best-effort parse of a model's JSON reply into a {@link NodeCrux} list. */
|
|
48
|
+
function parseResults(raw) {
|
|
49
|
+
const text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|
|
50
|
+
const obj = JSON.parse(text);
|
|
51
|
+
if (!Array.isArray(obj.symbols))
|
|
52
|
+
return [];
|
|
53
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : 0);
|
|
54
|
+
return obj.symbols
|
|
55
|
+
.map((s) => s)
|
|
56
|
+
.filter((s) => typeof s.id === "string")
|
|
57
|
+
.map((s) => ({
|
|
58
|
+
id: s.id,
|
|
59
|
+
summary: typeof s.summary === "string" ? s.summary.trim() : "",
|
|
60
|
+
crux_start: num(s.crux_start),
|
|
61
|
+
crux_end: num(s.crux_end),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
/** Crux summarizer backed by OpenRouter's OpenAI-compatible chat API. */
|
|
65
|
+
export class OpenRouterCruxSummarizer {
|
|
66
|
+
client;
|
|
67
|
+
model;
|
|
68
|
+
constructor(apiKey, model, baseUrl = "https://openrouter.ai/api/v1") {
|
|
69
|
+
this.client = new OpenAI({
|
|
70
|
+
apiKey,
|
|
71
|
+
baseURL: baseUrl,
|
|
72
|
+
defaultHeaders: { "X-Title": "Context Graph Engine" },
|
|
73
|
+
});
|
|
74
|
+
this.model = model;
|
|
75
|
+
}
|
|
76
|
+
async describeFile(input) {
|
|
77
|
+
if (input.nodes.length === 0)
|
|
78
|
+
return [];
|
|
79
|
+
const response = await this.client.chat.completions.create({
|
|
80
|
+
model: this.model,
|
|
81
|
+
temperature: 0,
|
|
82
|
+
response_format: { type: "json_object" },
|
|
83
|
+
messages: [
|
|
84
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
85
|
+
{ role: "user", content: userContent(input) },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
return parseResults(response.choices[0]?.message?.content ?? "");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=crux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crux.js","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Summarizer } from "./summarize.js";
|
|
2
|
+
import type { Synthesizer } from "./synthesize.js";
|
|
3
|
+
/**
|
|
4
|
+
* User-facing configuration. Anything omitted falls back to environment
|
|
5
|
+
* variables and then to sensible defaults. The engine calls OpenRouter, so an
|
|
6
|
+
* API key is required for any LLM-backed operation.
|
|
7
|
+
*/
|
|
8
|
+
export interface EngineConfig {
|
|
9
|
+
/** Where the graph lives. Env: GRAFT_DIR. Default: `<repo>/.context`. */
|
|
10
|
+
contextDir?: string;
|
|
11
|
+
/** OpenRouter key for extraction + summarization. Env: OPENROUTER_API_KEY. */
|
|
12
|
+
openrouterApiKey?: string;
|
|
13
|
+
/** OpenRouter model id. Env: GRAFT_OPENROUTER_MODEL. Default: openai/gpt-4o-mini */
|
|
14
|
+
openrouterModel?: string;
|
|
15
|
+
/** OpenRouter API base URL. Env: OPENROUTER_BASE_URL. Default: https://openrouter.ai/api/v1 */
|
|
16
|
+
openrouterBaseUrl?: string;
|
|
17
|
+
/** Override the synthesizer (defaults to OpenRouter). */
|
|
18
|
+
synthesizer?: Synthesizer;
|
|
19
|
+
/** Override the code summarizer (defaults to OpenRouter). */
|
|
20
|
+
summarizer?: Summarizer;
|
|
21
|
+
}
|
|
22
|
+
/** Fully-resolved configuration with all defaults applied. */
|
|
23
|
+
export interface ResolvedConfig {
|
|
24
|
+
contextDir?: string;
|
|
25
|
+
openrouterApiKey?: string;
|
|
26
|
+
openrouterModel: string;
|
|
27
|
+
openrouterBaseUrl: string;
|
|
28
|
+
synthesizer?: Synthesizer;
|
|
29
|
+
summarizer?: Summarizer;
|
|
30
|
+
}
|
|
31
|
+
export declare const DEFAULTS: {
|
|
32
|
+
readonly openrouterModel: "openai/gpt-4o-mini";
|
|
33
|
+
readonly openrouterBaseUrl: "https://openrouter.ai/api/v1";
|
|
34
|
+
};
|
|
35
|
+
/** Merge user config with environment variables and defaults. */
|
|
36
|
+
export declare function resolveConfig(config?: EngineConfig): ResolvedConfig;
|
|
37
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,yDAAyD;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,MAAM,GAAE,YAAiB,GAAG,cAAc,CAYvE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
openrouterModel: "openai/gpt-4o-mini",
|
|
3
|
+
openrouterBaseUrl: "https://openrouter.ai/api/v1",
|
|
4
|
+
};
|
|
5
|
+
/** Merge user config with environment variables and defaults. */
|
|
6
|
+
export function resolveConfig(config = {}) {
|
|
7
|
+
const env = process.env;
|
|
8
|
+
return {
|
|
9
|
+
contextDir: config.contextDir ?? env.GRAFT_DIR,
|
|
10
|
+
openrouterApiKey: config.openrouterApiKey ?? env.OPENROUTER_API_KEY,
|
|
11
|
+
openrouterModel: config.openrouterModel ?? env.GRAFT_OPENROUTER_MODEL ?? DEFAULTS.openrouterModel,
|
|
12
|
+
openrouterBaseUrl: config.openrouterBaseUrl ?? env.OPENROUTER_BASE_URL ?? DEFAULTS.openrouterBaseUrl,
|
|
13
|
+
synthesizer: config.synthesizer,
|
|
14
|
+
summarizer: config.summarizer,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,eAAe,EAAE,oBAAoB;IACrC,iBAAiB,EAAE,8BAA8B;CACzC,CAAC;AAEX,iEAAiE;AACjE,MAAM,UAAU,aAAa,CAAC,SAAuB,EAAE;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC,SAAS;QAC9C,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,GAAG,CAAC,kBAAkB;QACnE,eAAe,EACb,MAAM,CAAC,eAAe,IAAI,GAAG,CAAC,sBAAsB,IAAI,QAAQ,CAAC,eAAe;QAClF,iBAAiB,EACf,MAAM,CAAC,iBAAiB,IAAI,GAAG,CAAC,mBAAmB,IAAI,QAAQ,CAAC,iBAAiB;QACnF,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns one source-code file into a short prose summary for the knowledge graph.
|
|
3
|
+
*
|
|
4
|
+
* Code repos are deliberately NOT fed through the per-chunk entity extractor:
|
|
5
|
+
* static structure is better served by grep/tree-sitter in the consuming agent,
|
|
6
|
+
* and LLM extraction over raw code produces noisy, duplicated entities. Instead
|
|
7
|
+
* the repo ingest path summarizes each file into plain English (purpose, key
|
|
8
|
+
* exports, dependencies, design decisions) and ingests those summaries — prose
|
|
9
|
+
* the existing extraction pipeline is actually good at.
|
|
10
|
+
*/
|
|
11
|
+
export interface Summarizer {
|
|
12
|
+
summarize(code: string, opts: {
|
|
13
|
+
path: string;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
/** Summarizer backed by OpenRouter's OpenAI-compatible chat API. */
|
|
17
|
+
export declare class OpenRouterSummarizer implements Summarizer {
|
|
18
|
+
private client;
|
|
19
|
+
private model;
|
|
20
|
+
constructor(apiKey: string, model: string, baseUrl?: string);
|
|
21
|
+
summarize(code: string, opts: {
|
|
22
|
+
path: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=summarize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClE;AAqBD,oEAAoE;AACpE,qBAAa,oBAAqB,YAAW,UAAU;IACrD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAiC;IAS7E,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAWvE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
const SYSTEM_PROMPT = `You document source code for a team knowledge base. Given one source file, write a compact plain-English summary covering:
|
|
3
|
+
1. The purpose of the file — what it exists to do.
|
|
4
|
+
2. The key exported functions/classes/types and what each is for.
|
|
5
|
+
3. Important dependencies: internal modules it builds on, external libraries or services it talks to.
|
|
6
|
+
4. Notable design decisions, constraints, or gotchas evident in the code.
|
|
7
|
+
|
|
8
|
+
Write 3-8 sentences of flowing prose. Name concrete identifiers (modules, classes, services) so they can become graph entities. No code blocks, no line-by-line narration, no filler.`;
|
|
9
|
+
/** Cap the code sent per file so a single giant file can't blow the context. */
|
|
10
|
+
const MAX_CODE_CHARS = 24_000;
|
|
11
|
+
function userContent(code, path) {
|
|
12
|
+
const clipped = code.length > MAX_CODE_CHARS
|
|
13
|
+
? `${code.slice(0, MAX_CODE_CHARS)}\n… (truncated at ${MAX_CODE_CHARS} characters)`
|
|
14
|
+
: code;
|
|
15
|
+
return `File: ${path}\n\n${clipped}`;
|
|
16
|
+
}
|
|
17
|
+
/** Summarizer backed by OpenRouter's OpenAI-compatible chat API. */
|
|
18
|
+
export class OpenRouterSummarizer {
|
|
19
|
+
client;
|
|
20
|
+
model;
|
|
21
|
+
constructor(apiKey, model, baseUrl = "https://openrouter.ai/api/v1") {
|
|
22
|
+
this.client = new OpenAI({
|
|
23
|
+
apiKey,
|
|
24
|
+
baseURL: baseUrl,
|
|
25
|
+
defaultHeaders: { "X-Title": "Context Graph Engine" },
|
|
26
|
+
});
|
|
27
|
+
this.model = model;
|
|
28
|
+
}
|
|
29
|
+
async summarize(code, opts) {
|
|
30
|
+
const response = await this.client.chat.completions.create({
|
|
31
|
+
model: this.model,
|
|
32
|
+
temperature: 0,
|
|
33
|
+
messages: [
|
|
34
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
35
|
+
{ role: "user", content: userContent(code, opts.path) },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
return response.choices[0]?.message?.content?.trim() ?? "";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=summarize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAgB5B,MAAM,aAAa,GAAG;;;;;;sLAMgK,CAAC;AAEvL,gFAAgF;AAChF,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC7C,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,GAAG,cAAc;QAC1B,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,qBAAqB,cAAc,cAAc;QACnF,CAAC,CAAC,IAAI,CAAC;IACX,OAAO,SAAS,IAAI,OAAO,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,oEAAoE;AACpE,MAAM,OAAO,oBAAoB;IACvB,MAAM,CAAS;IACf,KAAK,CAAS;IAEtB,YAAY,MAAc,EAAE,KAAa,EAAE,OAAO,GAAG,8BAA8B;QACjF,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACvB,MAAM;YACN,OAAO,EAAE,OAAO;YAChB,cAAc,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE;SACtD,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,IAAsB;QAClD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YACzD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,CAAC;YACd,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE;gBAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;aACxD;SACF,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7D,CAAC;CACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A directed edge to another node, by node name (resolved to a slug later). */
|
|
2
|
+
export interface SynthLink {
|
|
3
|
+
to: string;
|
|
4
|
+
relation: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
/** A node as proposed by the synthesizer, before it is written to disk. */
|
|
8
|
+
export interface SynthNode {
|
|
9
|
+
name: string;
|
|
10
|
+
/** system | api | file | concept */
|
|
11
|
+
type: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
/** Source file paths (from the provided set) this node is grounded in. */
|
|
14
|
+
sources: string[];
|
|
15
|
+
links: SynthLink[];
|
|
16
|
+
}
|
|
17
|
+
/** One labeled file summary fed into synthesis. */
|
|
18
|
+
export interface FileSummary {
|
|
19
|
+
path: string;
|
|
20
|
+
summary: string;
|
|
21
|
+
}
|
|
22
|
+
export interface Synthesizer {
|
|
23
|
+
synthesize(files: FileSummary[]): Promise<SynthNode[]>;
|
|
24
|
+
}
|
|
25
|
+
/** Synthesizer backed by OpenRouter's OpenAI-compatible chat API (tool calling). */
|
|
26
|
+
export declare class OpenRouterSynthesizer implements Synthesizer {
|
|
27
|
+
private client;
|
|
28
|
+
private model;
|
|
29
|
+
constructor(apiKey: string, model: string, baseUrl?: string);
|
|
30
|
+
synthesize(files: FileSummary[]): Promise<SynthNode[]>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=synthesize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../src/ai/synthesize.ts"],"names":[],"mappings":"AAUA,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACxD;AAgGD,oFAAoF;AACpF,qBAAa,qBAAsB,YAAW,WAAW;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAiC;IAK7E,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAoB7D"}
|