ts-knowledge-graph 0.1.1
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/.env-sample +34 -0
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/agent/agent-tools.d.ts +13 -0
- package/dist/agent/agent-tools.d.ts.map +1 -0
- package/dist/agent/agent-tools.js +153 -0
- package/dist/agent/agent-tools.js.map +1 -0
- package/dist/agent/code-editor.d.ts +18 -0
- package/dist/agent/code-editor.d.ts.map +1 -0
- package/dist/agent/code-editor.js +43 -0
- package/dist/agent/code-editor.js.map +1 -0
- package/dist/agent/optimizer-agent.d.ts +30 -0
- package/dist/agent/optimizer-agent.d.ts.map +1 -0
- package/dist/agent/optimizer-agent.js +97 -0
- package/dist/agent/optimizer-agent.js.map +1 -0
- package/dist/agent/verifier.d.ts +9 -0
- package/dist/agent/verifier.d.ts.map +1 -0
- package/dist/agent/verifier.js +19 -0
- package/dist/agent/verifier.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +221 -0
- package/dist/cli.js.map +1 -0
- package/dist/extract/graph-builder.d.ts +16 -0
- package/dist/extract/graph-builder.d.ts.map +1 -0
- package/dist/extract/graph-builder.js +39 -0
- package/dist/extract/graph-builder.js.map +1 -0
- package/dist/extract/node-id.d.ts +8 -0
- package/dist/extract/node-id.d.ts.map +1 -0
- package/dist/extract/node-id.js +22 -0
- package/dist/extract/node-id.js.map +1 -0
- package/dist/extract/project-loader.d.ts +5 -0
- package/dist/extract/project-loader.d.ts.map +1 -0
- package/dist/extract/project-loader.js +19 -0
- package/dist/extract/project-loader.js.map +1 -0
- package/dist/extract/semantic-extractor.d.ts +22 -0
- package/dist/extract/semantic-extractor.d.ts.map +1 -0
- package/dist/extract/semantic-extractor.js +254 -0
- package/dist/extract/semantic-extractor.js.map +1 -0
- package/dist/extract/structural-extractor.d.ts +18 -0
- package/dist/extract/structural-extractor.d.ts.map +1 -0
- package/dist/extract/structural-extractor.js +97 -0
- package/dist/extract/structural-extractor.js.map +1 -0
- package/dist/query/graph-query.d.ts +28 -0
- package/dist/query/graph-query.d.ts.map +1 -0
- package/dist/query/graph-query.js +93 -0
- package/dist/query/graph-query.js.map +1 -0
- package/dist/schema/edge.d.ts +25 -0
- package/dist/schema/edge.d.ts.map +1 -0
- package/dist/schema/edge.js +25 -0
- package/dist/schema/edge.js.map +1 -0
- package/dist/schema/node.d.ts +73 -0
- package/dist/schema/node.d.ts.map +1 -0
- package/dist/schema/node.js +31 -0
- package/dist/schema/node.js.map +1 -0
- package/dist/store/jsonl-reader.d.ts +11 -0
- package/dist/store/jsonl-reader.d.ts.map +1 -0
- package/dist/store/jsonl-reader.js +19 -0
- package/dist/store/jsonl-reader.js.map +1 -0
- package/dist/store/jsonl-store.d.ts +7 -0
- package/dist/store/jsonl-store.d.ts.map +1 -0
- package/dist/store/jsonl-store.js +13 -0
- package/dist/store/jsonl-store.js.map +1 -0
- package/dist/store/kuzu-store.d.ts +14 -0
- package/dist/store/kuzu-store.d.ts.map +1 -0
- package/dist/store/kuzu-store.js +52 -0
- package/dist/store/kuzu-store.js.map +1 -0
- package/package.json +41 -0
package/.env-sample
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copy to .env and pick ONE provider block below.
|
|
2
|
+
# The agent uses the OpenAI-compatible chat-completions API, so any provider
|
|
3
|
+
# exposing that surface works — only the three variables below change.
|
|
4
|
+
#
|
|
5
|
+
# OPENAI_API_KEY auth token for the provider (any non-empty string for local servers)
|
|
6
|
+
# OPENAI_BASE_URL the provider's OpenAI-compatible endpoint (omit for openai.com)
|
|
7
|
+
# OPENAI_MODEL model name, in the provider's naming scheme
|
|
8
|
+
|
|
9
|
+
# --- OpenAI (default) -------------------------------------------------------
|
|
10
|
+
OPENAI_API_KEY=sk-...
|
|
11
|
+
# OPENAI_BASE_URL is not needed — defaults to https://api.openai.com/v1
|
|
12
|
+
OPENAI_MODEL=gpt-5.1
|
|
13
|
+
|
|
14
|
+
# --- OpenRouter (any model behind one key) ----------------------------------
|
|
15
|
+
# OPENAI_API_KEY=sk-or-v1-...
|
|
16
|
+
# OPENAI_BASE_URL=https://openrouter.ai/api/v1
|
|
17
|
+
# OPENAI_MODEL=anthropic/claude-sonnet-4.5
|
|
18
|
+
# OPENAI_MODEL=openai/gpt-5.1
|
|
19
|
+
# OPENAI_MODEL=deepseek/deepseek-chat-v3.1
|
|
20
|
+
|
|
21
|
+
# --- Ollama (local, free) ---------------------------------------------------
|
|
22
|
+
# OPENAI_API_KEY=ollama
|
|
23
|
+
# OPENAI_BASE_URL=http://localhost:11434/v1
|
|
24
|
+
# OPENAI_MODEL=qwen2.5-coder:32b
|
|
25
|
+
|
|
26
|
+
# --- LM Studio (local) ------------------------------------------------------
|
|
27
|
+
# OPENAI_API_KEY=lm-studio
|
|
28
|
+
# OPENAI_BASE_URL=http://localhost:1234/v1
|
|
29
|
+
# OPENAI_MODEL=local-model
|
|
30
|
+
|
|
31
|
+
# --- vLLM (self-hosted) -----------------------------------------------------
|
|
32
|
+
# OPENAI_API_KEY=token-abc123
|
|
33
|
+
# OPENAI_BASE_URL=http://localhost:8000/v1
|
|
34
|
+
# OPENAI_MODEL=Qwen/Qwen2.5-Coder-32B-Instruct
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jerome Etienne
|
|
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,153 @@
|
|
|
1
|
+
# open_ts_optim_ai
|
|
2
|
+
|
|
3
|
+
Parse TypeScript source code into a **knowledge graph**, then use that graph as
|
|
4
|
+
the substrate for an autonomous AI agent that finds and applies code
|
|
5
|
+
optimizations.
|
|
6
|
+
|
|
7
|
+
## Why a graph
|
|
8
|
+
|
|
9
|
+
An optimization agent constantly needs to reason about *blast radius*:
|
|
10
|
+
|
|
11
|
+
- *If I rewrite this function, who calls it and what breaks?* — `CALLS` edges
|
|
12
|
+
- *Is this export dead code I can delete?* — cross-file reference resolution
|
|
13
|
+
- *What is affected if I change this type?* — `USES_TYPE` / type-checker edges
|
|
14
|
+
|
|
15
|
+
These questions require **semantic** parsing (symbol + type resolution), which is
|
|
16
|
+
why the extractor is built on [`ts-morph`](https://ts-morph.com) (the TypeScript
|
|
17
|
+
Compiler API) rather than a syntax-only parser.
|
|
18
|
+
|
|
19
|
+
## Graph model
|
|
20
|
+
|
|
21
|
+
**Nodes** — `Module`, `Class`, `Interface`, `TypeAlias`, `Enum`, `Function`,
|
|
22
|
+
`Method`, `Property`, `Parameter`, `Variable`, `ExternalModule`.
|
|
23
|
+
|
|
24
|
+
**Edges**
|
|
25
|
+
|
|
26
|
+
| Layer | Edges |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| Structural | `CONTAINS`, `IMPORTS`, `EXPORTS` |
|
|
29
|
+
| Type | `EXTENDS`, `IMPLEMENTS`, `USES_TYPE`, `RETURNS`, `PARAM_TYPE` |
|
|
30
|
+
| Behavioral | `CALLS`, `INSTANTIATES`, `OVERRIDES`, `READS`, `WRITES` |
|
|
31
|
+
|
|
32
|
+
The structural layer is cheap and always emitted. The type + behavioral layers
|
|
33
|
+
require symbol resolution and are emitted with `--semantic`.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
|
|
40
|
+
# structural graph only (fast)
|
|
41
|
+
npm run extract -- <path-to-project>
|
|
42
|
+
|
|
43
|
+
# full graph with heritage + CALLS edges
|
|
44
|
+
npm run extract -- <path-to-project> --semantic
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Output is two JSONL files — `outputs/graph/nodes.jsonl` and
|
|
48
|
+
`outputs/graph/edges.jsonl` (override with `--out`) — one record per line, easy
|
|
49
|
+
to inspect, diff, and load into any store.
|
|
50
|
+
|
|
51
|
+
### Querying the graph
|
|
52
|
+
|
|
53
|
+
Load the JSONL into an embedded [Kùzu](https://kuzudb.com) database, then run the
|
|
54
|
+
query tools:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run dev -- load # reads ./outputs/graph, writes ./outputs/graph.kuzu
|
|
58
|
+
|
|
59
|
+
npm run dev -- find <name> # resolve a name to node ids
|
|
60
|
+
npm run dev -- who-calls <id> # direct callers of a symbol
|
|
61
|
+
npm run dev -- calls <id> # what a symbol calls
|
|
62
|
+
npm run dev -- blast-radius <id> --depth 10 # transitive callers (impact set)
|
|
63
|
+
npm run dev -- references <id> # everything that references a symbol/type
|
|
64
|
+
npm run dev -- dead-exports # exported symbols with no inbound refs
|
|
65
|
+
npm run dev -- neighbors <id> # one-hop neighbourhood (in + out)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Every query command accepts `--json` to emit machine-readable output — this is
|
|
69
|
+
the shape the optimization agent consumes. Node ids come from `find` or another
|
|
70
|
+
query's results; do not hand-write them.
|
|
71
|
+
|
|
72
|
+
The query methods on `GraphQuery` (`whoCalls`, `blastRadius`, `deadExports`,
|
|
73
|
+
`neighborhood`, …) are designed to map one-to-one onto agent tools: JSON in,
|
|
74
|
+
JSON out.
|
|
75
|
+
|
|
76
|
+
> **`dead-exports` accuracy:** it is member-aware (a class/interface counts as
|
|
77
|
+
> live when any contained member is referenced) and considers `CALLS`,
|
|
78
|
+
> `EXTENDS`, `IMPLEMENTS`, `USES_TYPE`, `RETURNS`, `PARAM_TYPE`, `INSTANTIATES`,
|
|
79
|
+
> and `READS` (value-identifier) edges. On this repository it reports exactly the
|
|
80
|
+
> two genuinely-unused type aliases — no false positives.
|
|
81
|
+
|
|
82
|
+
### The optimization agent
|
|
83
|
+
|
|
84
|
+
The end goal: an agent that uses the graph to find and apply optimizations,
|
|
85
|
+
verifying each one before keeping it.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cp .env-sample .env # pick a provider block, set key + model
|
|
89
|
+
npm run dev -- optimize --db ./outputs/graph.kuzu
|
|
90
|
+
npm run dev -- optimize "Inline the single-use helper X" --db ./outputs/graph.kuzu --model gpt-5.1
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The LLM layer sits on the **OpenAI-compatible chat-completions API**, so any
|
|
94
|
+
provider exposing that surface works — OpenAI, OpenRouter, Ollama, LM Studio,
|
|
95
|
+
vLLM — configured entirely through `OPENAI_API_KEY`, `OPENAI_BASE_URL`, and
|
|
96
|
+
`OPENAI_MODEL` (see [.env-sample](.env-sample)).
|
|
97
|
+
|
|
98
|
+
The agent runs a tool-calling loop. Its tools are the read-only `GraphQuery`
|
|
99
|
+
methods plus `read_file`; it gathers context, confirms blast radius, then calls
|
|
100
|
+
`propose_optimization`. The harness **applies the edit, runs `tsc --noEmit`,
|
|
101
|
+
and keeps it only if type-checking passes** — otherwise it reverts and hands
|
|
102
|
+
the compiler errors back for another attempt. Edits are unique-match
|
|
103
|
+
find/replace with in-memory backups; run on a clean git tree so you can review
|
|
104
|
+
(and `git checkout`) what it kept.
|
|
105
|
+
|
|
106
|
+
## Architecture
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
src/
|
|
110
|
+
schema/ Zod schemas for nodes and edges (the wire format)
|
|
111
|
+
extract/
|
|
112
|
+
project-loader.ts load a ts-morph Project from tsconfig
|
|
113
|
+
node-id.ts deterministic, position-stable node ids
|
|
114
|
+
structural-extractor.ts modules, declarations, imports, containment
|
|
115
|
+
semantic-extractor.ts heritage, CALLS, INSTANTIATES, type edges
|
|
116
|
+
graph-builder.ts orchestrates extraction, dedupes by id
|
|
117
|
+
store/
|
|
118
|
+
jsonl-store.ts serialize the graph to JSONL
|
|
119
|
+
jsonl-reader.ts read + Zod-validate the JSONL back in
|
|
120
|
+
kuzu-store.ts load the graph into embedded Kùzu, run Cypher
|
|
121
|
+
query/
|
|
122
|
+
graph-query.ts the agent's query tools (who-calls, blast-radius…)
|
|
123
|
+
agent/
|
|
124
|
+
agent-tools.ts graph queries + read_file + propose_optimization, as LLM tools
|
|
125
|
+
code-editor.ts unique-match find/replace with in-memory backup + revert
|
|
126
|
+
verifier.ts runs `tsc --noEmit`, returns pass/fail + output
|
|
127
|
+
optimizer-agent.ts the LLM tool-calling loop (propose → verify → keep/revert)
|
|
128
|
+
cli.ts extract / load / query / optimize commands
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Node ids are derived purely from the declaration (`kind:relPath#name@line`), so
|
|
132
|
+
any extractor computes the same id for the same symbol without a shared
|
|
133
|
+
registry — that is what lets the semantic layer link a call site to the exact
|
|
134
|
+
declaration node the structural layer emitted.
|
|
135
|
+
|
|
136
|
+
## Roadmap
|
|
137
|
+
|
|
138
|
+
- [x] **Embedded query layer** — load into [Kùzu](https://kuzudb.com) (embedded,
|
|
139
|
+
Cypher) with traversal tools: `who-calls`, `calls`, `blast-radius`,
|
|
140
|
+
`dead-exports`, `neighbors`, `find`.
|
|
141
|
+
- [x] **Type edges** — `USES_TYPE`, `RETURNS`, `PARAM_TYPE` (plus `INSTANTIATES`)
|
|
142
|
+
resolved through import aliases.
|
|
143
|
+
- [x] **Member-aware reference counting** — a class/interface is live when any
|
|
144
|
+
contained member is referenced.
|
|
145
|
+
- [x] **Value-reference (`READS`) edges** — value-identifier usage, so exported
|
|
146
|
+
`const`s (e.g. schemas) are no longer false-positive dead exports.
|
|
147
|
+
- [x] **Optimization agent** — an LLM tool-calling loop (OpenAI-compatible API,
|
|
148
|
+
provider-agnostic) that proposes edits and keeps them only if `tsc --noEmit`
|
|
149
|
+
passes (otherwise reverts).
|
|
150
|
+
- [ ] **Test verification** — run the test suite alongside `tsc` in the verify
|
|
151
|
+
step, so behavior-changing edits are caught, not just type errors.
|
|
152
|
+
- [ ] **Vector index** — embed per-node summaries for hybrid graph + semantic
|
|
153
|
+
retrieval, so the agent can find candidates by meaning, not just by name.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { GraphQuery } from '../query/graph-query.js';
|
|
3
|
+
export declare const PROPOSE_TOOL_NAME = "propose_optimization";
|
|
4
|
+
export declare const AGENT_TOOLS: OpenAI.Chat.Completions.ChatCompletionTool[];
|
|
5
|
+
export declare class AgentTools {
|
|
6
|
+
private readonly query;
|
|
7
|
+
private readonly rootPath;
|
|
8
|
+
constructor(query: GraphQuery, rootPath: string);
|
|
9
|
+
dispatch(name: string, input: Record<string, unknown>): Promise<string>;
|
|
10
|
+
private readFile;
|
|
11
|
+
private static stringify;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/agent/agent-tools.ts"],"names":[],"mappings":"AAEA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAExD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,EAyGnE,CAAC;AAEF,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM;IAKzC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;YAqB/D,QAAQ;IAetB,OAAO,CAAC,MAAM,CAAC,SAAS;CAGxB"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export const PROPOSE_TOOL_NAME = 'propose_optimization';
|
|
4
|
+
export const AGENT_TOOLS = [
|
|
5
|
+
{
|
|
6
|
+
type: 'function',
|
|
7
|
+
function: {
|
|
8
|
+
name: 'find_symbol',
|
|
9
|
+
description: 'Resolve a symbol name (substring, case-insensitive) to candidate node ids. Always start here to obtain ids; never invent ids.',
|
|
10
|
+
parameters: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: { name: { type: 'string', description: 'symbol name or substring' } },
|
|
13
|
+
required: ['name'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: 'function',
|
|
19
|
+
function: {
|
|
20
|
+
name: 'who_calls',
|
|
21
|
+
description: 'List the direct callers of a function/method node id.',
|
|
22
|
+
parameters: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: { id: { type: 'string', description: 'node id from find_symbol' } },
|
|
25
|
+
required: ['id'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'function',
|
|
31
|
+
function: {
|
|
32
|
+
name: 'references',
|
|
33
|
+
description: 'List everything that references a symbol or type (calls, type usage, heritage, instantiation, value reads). Use this to judge whether a symbol is safe to remove.',
|
|
34
|
+
parameters: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: { id: { type: 'string', description: 'node id from find_symbol' } },
|
|
37
|
+
required: ['id'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'function',
|
|
43
|
+
function: {
|
|
44
|
+
name: 'blast_radius',
|
|
45
|
+
description: 'List every symbol transitively impacted by changing a node id (transitive callers).',
|
|
46
|
+
parameters: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
id: { type: 'string', description: 'node id from find_symbol' },
|
|
50
|
+
depth: { type: 'integer', description: 'max traversal depth (default 10)' },
|
|
51
|
+
},
|
|
52
|
+
required: ['id'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'function',
|
|
58
|
+
function: {
|
|
59
|
+
name: 'neighbors',
|
|
60
|
+
description: 'Show the one-hop neighbourhood (incoming and outgoing edges) of a node id.',
|
|
61
|
+
parameters: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: { id: { type: 'string', description: 'node id from find_symbol' } },
|
|
64
|
+
required: ['id'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'function',
|
|
70
|
+
function: {
|
|
71
|
+
name: 'dead_exports',
|
|
72
|
+
description: 'List exported symbols that have no inbound references anywhere in the project — prime candidates for safe removal.',
|
|
73
|
+
parameters: { type: 'object', properties: {} },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'function',
|
|
78
|
+
function: {
|
|
79
|
+
name: 'read_file',
|
|
80
|
+
description: 'Read a project source file (optionally a line range) to see exact text before proposing an edit.',
|
|
81
|
+
parameters: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
path: { type: 'string', description: 'project-relative file path, e.g. src/schema/node.ts' },
|
|
85
|
+
startLine: { type: 'integer', description: 'first line (1-based, optional)' },
|
|
86
|
+
endLine: { type: 'integer', description: 'last line (inclusive, optional)' },
|
|
87
|
+
},
|
|
88
|
+
required: ['path'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'function',
|
|
94
|
+
function: {
|
|
95
|
+
name: PROPOSE_TOOL_NAME,
|
|
96
|
+
description: 'Propose ONE safe edit. The harness applies it, runs the TypeScript type-checker, and keeps it only if type-checking passes (otherwise it is reverted and you get the errors). `find` must match the file exactly and uniquely.',
|
|
97
|
+
parameters: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
filePath: { type: 'string', description: 'project-relative file path' },
|
|
101
|
+
find: { type: 'string', description: 'exact text to replace (must be unique in the file)' },
|
|
102
|
+
replace: { type: 'string', description: 'replacement text (empty string to delete)' },
|
|
103
|
+
rationale: { type: 'string', description: 'why this change is safe and beneficial' },
|
|
104
|
+
},
|
|
105
|
+
required: ['filePath', 'find', 'replace', 'rationale'],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
export class AgentTools {
|
|
111
|
+
constructor(query, rootPath) {
|
|
112
|
+
this.query = query;
|
|
113
|
+
this.rootPath = rootPath;
|
|
114
|
+
}
|
|
115
|
+
async dispatch(name, input) {
|
|
116
|
+
switch (name) {
|
|
117
|
+
case 'find_symbol':
|
|
118
|
+
return AgentTools.stringify(await this.query.find(String(input.name)));
|
|
119
|
+
case 'who_calls':
|
|
120
|
+
return AgentTools.stringify(await this.query.whoCalls(String(input.id)));
|
|
121
|
+
case 'references':
|
|
122
|
+
return AgentTools.stringify(await this.query.references(String(input.id)));
|
|
123
|
+
case 'blast_radius':
|
|
124
|
+
return AgentTools.stringify(await this.query.blastRadius(String(input.id), Number(input.depth ?? 10)));
|
|
125
|
+
case 'neighbors':
|
|
126
|
+
return AgentTools.stringify(await this.query.neighborhood(String(input.id)));
|
|
127
|
+
case 'dead_exports':
|
|
128
|
+
return AgentTools.stringify(await this.query.deadExports());
|
|
129
|
+
case 'read_file':
|
|
130
|
+
return this.readFile(input);
|
|
131
|
+
default:
|
|
132
|
+
return `unknown tool: ${name}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async readFile(input) {
|
|
136
|
+
const absolute = resolve(this.rootPath, String(input.path));
|
|
137
|
+
const content = await readFile(absolute, 'utf8').catch(() => undefined);
|
|
138
|
+
if (content === undefined) {
|
|
139
|
+
return `file not found: ${String(input.path)}`;
|
|
140
|
+
}
|
|
141
|
+
const lines = content.split('\n');
|
|
142
|
+
const start = input.startLine === undefined ? 1 : Number(input.startLine);
|
|
143
|
+
const end = input.endLine === undefined ? lines.length : Number(input.endLine);
|
|
144
|
+
return lines
|
|
145
|
+
.slice(start - 1, end)
|
|
146
|
+
.map((line, index) => `${start + index}\t${line}`)
|
|
147
|
+
.join('\n');
|
|
148
|
+
}
|
|
149
|
+
static stringify(value) {
|
|
150
|
+
return JSON.stringify(value, null, 2);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=agent-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.js","sourceRoot":"","sources":["../../src/agent/agent-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type EditRequest = {
|
|
2
|
+
filePath: string;
|
|
3
|
+
find: string;
|
|
4
|
+
replace: string;
|
|
5
|
+
};
|
|
6
|
+
export type EditResult = {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
export declare class CodeEditor {
|
|
11
|
+
private readonly rootPath;
|
|
12
|
+
private readonly backups;
|
|
13
|
+
constructor(rootPath: string);
|
|
14
|
+
apply(request: EditRequest): Promise<EditResult>;
|
|
15
|
+
revert(filePath: string): Promise<void>;
|
|
16
|
+
private static readSafe;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=code-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-editor.d.ts","sourceRoot":"","sources":["../../src/agent/code-editor.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;gBAEzC,QAAQ,EAAE,MAAM;IAItB,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAoBhD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;mBAQxB,QAAQ;CAO7B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export class CodeEditor {
|
|
4
|
+
constructor(rootPath) {
|
|
5
|
+
this.backups = new Map();
|
|
6
|
+
this.rootPath = rootPath;
|
|
7
|
+
}
|
|
8
|
+
async apply(request) {
|
|
9
|
+
const absolute = resolve(this.rootPath, request.filePath);
|
|
10
|
+
const original = await CodeEditor.readSafe(absolute);
|
|
11
|
+
if (original === undefined) {
|
|
12
|
+
return { ok: false, message: `file not found: ${request.filePath}` };
|
|
13
|
+
}
|
|
14
|
+
const occurrences = original.split(request.find).length - 1;
|
|
15
|
+
if (occurrences === 0) {
|
|
16
|
+
return { ok: false, message: 'find text not found in file' };
|
|
17
|
+
}
|
|
18
|
+
if (occurrences > 1) {
|
|
19
|
+
return { ok: false, message: `find text matched ${occurrences} times; include more surrounding context to make it unique` };
|
|
20
|
+
}
|
|
21
|
+
if (this.backups.has(absolute) === false) {
|
|
22
|
+
this.backups.set(absolute, original);
|
|
23
|
+
}
|
|
24
|
+
await writeFile(absolute, original.replace(request.find, request.replace), 'utf8');
|
|
25
|
+
return { ok: true, message: 'applied' };
|
|
26
|
+
}
|
|
27
|
+
async revert(filePath) {
|
|
28
|
+
const absolute = resolve(this.rootPath, filePath);
|
|
29
|
+
const original = this.backups.get(absolute);
|
|
30
|
+
if (original !== undefined) {
|
|
31
|
+
await writeFile(absolute, original, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
static async readSafe(absolute) {
|
|
35
|
+
try {
|
|
36
|
+
return await readFile(absolute, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=code-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-editor.js","sourceRoot":"","sources":["../../src/agent/code-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,MAAM,OAAO,UAAU;IAItB,YAAY,QAAgB;QAFX,YAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAGpD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAAoB;QAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;QACtE,CAAC;QACD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAC5D,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;QAC9D,CAAC;QACD,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,WAAW,4DAA4D,EAAE,CAAC;QAC7H,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;YAC1C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QACnF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAgB;QAC7C,IAAI,CAAC;YACJ,OAAO,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,SAAS,CAAC;QAClB,CAAC;IACF,CAAC;CACD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AgentTools } from './agent-tools.js';
|
|
2
|
+
import { CodeEditor } from './code-editor.js';
|
|
3
|
+
export type AppliedEdit = {
|
|
4
|
+
filePath: string;
|
|
5
|
+
rationale: string;
|
|
6
|
+
};
|
|
7
|
+
export type OptimizeOutcome = {
|
|
8
|
+
applied: AppliedEdit[];
|
|
9
|
+
transcript: string[];
|
|
10
|
+
};
|
|
11
|
+
export type OptimizerParams = {
|
|
12
|
+
tools: AgentTools;
|
|
13
|
+
editor: CodeEditor;
|
|
14
|
+
rootPath: string;
|
|
15
|
+
model: string;
|
|
16
|
+
maxSteps?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare class OptimizerAgent {
|
|
19
|
+
private readonly client;
|
|
20
|
+
private readonly tools;
|
|
21
|
+
private readonly editor;
|
|
22
|
+
private readonly rootPath;
|
|
23
|
+
private readonly model;
|
|
24
|
+
private readonly maxSteps;
|
|
25
|
+
constructor(params: OptimizerParams);
|
|
26
|
+
run(task: string): Promise<OptimizeOutcome>;
|
|
27
|
+
private applyAndVerify;
|
|
28
|
+
private static parseArguments;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=optimizer-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizer-agent.d.ts","sourceRoot":"","sources":["../../src/agent/optimizer-agent.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,UAAU,EAAqB,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAmB9C,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC7B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,MAAM,EAAE,eAAe;IAS7B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YA0CnC,cAAc;IA4B5B,OAAO,CAAC,MAAM,CAAC,cAAc;CAW7B"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { AGENT_TOOLS, PROPOSE_TOOL_NAME } from './agent-tools.js';
|
|
3
|
+
import { Verifier } from './verifier.js';
|
|
4
|
+
const SYSTEM_PROMPT = `You are an autonomous TypeScript optimization agent working on a real codebase.
|
|
5
|
+
|
|
6
|
+
You have a code knowledge graph at your disposal through tools. Use it as your eyes.
|
|
7
|
+
|
|
8
|
+
Method (follow it):
|
|
9
|
+
1. Find a candidate. Dead code is the safest win — call dead_exports first, or find_symbol for a named target.
|
|
10
|
+
2. Understand the blast radius. Before proposing ANY change you MUST confirm safety with references / who_calls / blast_radius. A symbol is safe to remove only when it has zero inbound references.
|
|
11
|
+
3. Read the exact text with read_file so your edit matches the file precisely.
|
|
12
|
+
4. Propose exactly ONE edit via ${PROPOSE_TOOL_NAME}. The harness type-checks it and keeps it only if the check passes; on failure you receive the compiler errors and must fix or abandon.
|
|
13
|
+
|
|
14
|
+
Rules:
|
|
15
|
+
- ids come from tool results; never invent them.
|
|
16
|
+
- Act autonomously — do not ask the user questions. Make the call yourself.
|
|
17
|
+
- Prefer removing genuinely dead exports or behavior-preserving simplifications. Never change observable behavior.
|
|
18
|
+
- When you have applied a verified improvement (or concluded there is no safe one), stop and summarize.`;
|
|
19
|
+
export class OptimizerAgent {
|
|
20
|
+
constructor(params) {
|
|
21
|
+
this.client = new OpenAI();
|
|
22
|
+
this.tools = params.tools;
|
|
23
|
+
this.editor = params.editor;
|
|
24
|
+
this.rootPath = params.rootPath;
|
|
25
|
+
this.model = params.model;
|
|
26
|
+
this.maxSteps = params.maxSteps ?? 12;
|
|
27
|
+
}
|
|
28
|
+
async run(task) {
|
|
29
|
+
const messages = [
|
|
30
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
31
|
+
{ role: 'user', content: task },
|
|
32
|
+
];
|
|
33
|
+
const applied = [];
|
|
34
|
+
const transcript = [];
|
|
35
|
+
for (let step = 0; step < this.maxSteps; step += 1) {
|
|
36
|
+
const completion = await this.client.chat.completions.create({
|
|
37
|
+
model: this.model,
|
|
38
|
+
messages,
|
|
39
|
+
tools: AGENT_TOOLS,
|
|
40
|
+
});
|
|
41
|
+
const message = completion.choices[0].message;
|
|
42
|
+
messages.push(message);
|
|
43
|
+
if (typeof message.content === 'string' && message.content.length > 0) {
|
|
44
|
+
transcript.push(message.content);
|
|
45
|
+
}
|
|
46
|
+
const toolCalls = message.tool_calls ?? [];
|
|
47
|
+
if (toolCalls.length === 0) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
for (const call of toolCalls) {
|
|
51
|
+
if (call.type !== 'function') {
|
|
52
|
+
messages.push({ role: 'tool', tool_call_id: call.id, content: 'unsupported tool call type' });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const input = OptimizerAgent.parseArguments(call.function.arguments);
|
|
56
|
+
const content = call.function.name === PROPOSE_TOOL_NAME
|
|
57
|
+
? await this.applyAndVerify(input, applied, transcript)
|
|
58
|
+
: await this.tools.dispatch(call.function.name, input);
|
|
59
|
+
messages.push({ role: 'tool', tool_call_id: call.id, content });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { applied, transcript };
|
|
63
|
+
}
|
|
64
|
+
async applyAndVerify(input, applied, transcript) {
|
|
65
|
+
const request = {
|
|
66
|
+
filePath: String(input.filePath),
|
|
67
|
+
find: String(input.find),
|
|
68
|
+
replace: String(input.replace),
|
|
69
|
+
};
|
|
70
|
+
const rationale = String(input.rationale ?? '');
|
|
71
|
+
const edit = await this.editor.apply(request);
|
|
72
|
+
if (edit.ok === false) {
|
|
73
|
+
return `EDIT REJECTED: ${edit.message}`;
|
|
74
|
+
}
|
|
75
|
+
const verify = await Verifier.typecheck(this.rootPath);
|
|
76
|
+
if (verify.ok === false) {
|
|
77
|
+
await this.editor.revert(request.filePath);
|
|
78
|
+
return `TYPECHECK FAILED — change reverted. Fix the approach or abandon it. Compiler output:\n${verify.output}`;
|
|
79
|
+
}
|
|
80
|
+
applied.push({ filePath: request.filePath, rationale });
|
|
81
|
+
transcript.push(`APPLIED ${request.filePath} — ${rationale}`);
|
|
82
|
+
return 'VERIFIED: the type-checker passed and the edit was kept.';
|
|
83
|
+
}
|
|
84
|
+
static parseArguments(raw) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=optimizer-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizer-agent.js","sourceRoot":"","sources":["../../src/agent/optimizer-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/agent/verifier.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,YAAY,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,QAAQ;WACP,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;mBAI1C,UAAU;CAS/B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const run = promisify(exec);
|
|
4
|
+
export class Verifier {
|
|
5
|
+
static async typecheck(rootPath) {
|
|
6
|
+
return Verifier.runCommand('npx tsc --noEmit', rootPath);
|
|
7
|
+
}
|
|
8
|
+
static async runCommand(command, cwd) {
|
|
9
|
+
try {
|
|
10
|
+
const { stdout, stderr } = await run(command, { cwd, maxBuffer: 10 * 1024 * 1024 });
|
|
11
|
+
return { ok: true, output: `${stdout}${stderr}`.trim() };
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const shaped = error;
|
|
15
|
+
return { ok: false, output: `${shaped.stdout ?? ''}${shaped.stderr ?? ''}`.trim() };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../src/agent/verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAO5B,MAAM,OAAO,QAAQ;IACpB,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,QAAgB;QACtC,OAAO,QAAQ,CAAC,UAAU,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAC1D,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAe,EAAE,GAAW;QAC3D,IAAI,CAAC;YACJ,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;YACpF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,MAAM,GAAG,KAA6C,CAAC;YAC7D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACrF,CAAC;IACF,CAAC;CACD"}
|