@llm4ts/runner 2.36.0 → 2.38.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.
@@ -0,0 +1,66 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { type FlowError } from "@llm4ts/flow/FlowError";
3
+ import type { Pack } from "@llm4ts/flow/Pack";
4
+ import type { PlainFileStoreShape } from "@llm4ts/flow/Persistence";
5
+ import { type WorkspaceError, type WorkspaceShape } from "@llm4ts/flow/Workspace";
6
+ import { type KitTierPaths } from "./Kits.ts";
7
+ import { type PackNotFound } from "./Packs.ts";
8
+ /**
9
+ * `llm4ts graph` (ADR 0030 decision 7): the code graph of a legacy estate as a
10
+ * CLI. `build` is the scanner pass (the LLM worklist runs in modernize-survey);
11
+ * every other command reads the cache through `freshGraph`, so a stale cache
12
+ * is rebuilt rather than refused.
13
+ */
14
+ export type GraphFormat = "text" | "json" | "mermaid" | "dot";
15
+ export type GraphCommand = {
16
+ readonly _tag: "build";
17
+ readonly repo: string;
18
+ readonly pack?: string;
19
+ } | {
20
+ readonly _tag: "query";
21
+ readonly repo: string;
22
+ readonly pack?: string;
23
+ readonly text: string;
24
+ readonly kind?: string;
25
+ readonly hops: number;
26
+ readonly format: GraphFormat;
27
+ readonly all: boolean;
28
+ readonly force: boolean;
29
+ } | {
30
+ readonly _tag: "path";
31
+ readonly repo: string;
32
+ readonly pack?: string;
33
+ readonly from: string;
34
+ readonly to: string;
35
+ readonly max: number;
36
+ readonly format: GraphFormat;
37
+ readonly force: boolean;
38
+ } | {
39
+ readonly _tag: "closure";
40
+ readonly repo: string;
41
+ readonly pack?: string;
42
+ readonly program: string;
43
+ readonly max: number;
44
+ readonly format: GraphFormat;
45
+ readonly force: boolean;
46
+ } | {
47
+ readonly _tag: "stats";
48
+ readonly repo: string;
49
+ readonly pack?: string;
50
+ readonly format: GraphFormat;
51
+ } | {
52
+ readonly _tag: "probe";
53
+ readonly repo: string;
54
+ readonly pack?: string;
55
+ readonly format: GraphFormat;
56
+ };
57
+ export type GraphProgramError = FlowError | PackNotFound | WorkspaceError;
58
+ export interface GraphDependencies {
59
+ readonly files: PlainFileStoreShape;
60
+ readonly workspace: (root: string) => Effect.Effect<WorkspaceShape, WorkspaceError>;
61
+ readonly openPack: (repo: string, pack: string | undefined) => Effect.Effect<Pack, GraphProgramError>;
62
+ }
63
+ export declare const makeGraphProgram: (command: GraphCommand, deps: GraphDependencies) => Effect.Effect<string, GraphProgramError, never>;
64
+ /** Node-backed dependencies: the disk, a legacy-sized workspace, packs from the kit tiers. */
65
+ export declare const nodeGraphDependencies: (environment: Readonly<Record<string, string | undefined>>, kits: KitTierPaths) => GraphDependencies;
66
+ //# sourceMappingURL=Graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Graph.d.ts","sourceRoot":"","sources":["../src/Graph.ts"],"names":[],"mappings":"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"}
package/dist/Graph.js ADDED
@@ -0,0 +1,122 @@
1
+ import { homedir } from "node:os";
2
+ import * as Effect from "effect/Effect";
3
+ import { FlowAborted } from "@llm4ts/flow/FlowError";
4
+ import { freshGraph, graphCachePath } from "@llm4ts/flow/GraphCache";
5
+ import { closureView, graphStats, neighborhood, pathBetween, probeResults, resolveNodeRef, searchNodes, viewOfPath, wholeView } from "@llm4ts/flow/GraphQuery";
6
+ import { renderGraphDot, renderGraphMermaid, renderGraphText } from "@llm4ts/flow/GraphRender";
7
+ import { nodeId } from "@llm4ts/flow/Survey";
8
+ import { legacySourceWorkspaceLimits, workspaceLimitsFromEnv } from "@llm4ts/flow/Workspace";
9
+ import { kitTierPaths } from "./Kits.js";
10
+ import { nodePlainFileStore } from "./NodePlainFileStore.js";
11
+ import { makeNodeWorkspace } from "./NodeWorkspace.js";
12
+ import { openPack } from "./Packs.js";
13
+ const render = (view, graph, format, force) => {
14
+ switch (format) {
15
+ case "json":
16
+ return JSON.stringify(view, undefined, 2);
17
+ case "mermaid":
18
+ return renderGraphMermaid(view, graph, { force });
19
+ case "dot":
20
+ return renderGraphDot(view, graph);
21
+ case "text":
22
+ return renderGraphText(view);
23
+ }
24
+ };
25
+ const counts = (record) => Object.entries(record)
26
+ .map(([key, count]) => `${key} ${count}`)
27
+ .join(", ");
28
+ const requireNode = (graph, ref) => {
29
+ const node = resolveNodeRef(graph, ref);
30
+ return node === undefined
31
+ ? Effect.fail(FlowAborted.make({
32
+ message: `no node matches '${ref}' (try: llm4ts graph query "${ref}")`
33
+ }))
34
+ : Effect.succeed(nodeId(node));
35
+ };
36
+ export const makeGraphProgram = Effect.fn("@llm4ts/runner/Graph.make")(function* (command, deps) {
37
+ const pack = yield* deps.openPack(command.repo, command.pack);
38
+ const workspace = yield* deps.workspace(command.repo);
39
+ const cachePath = graphCachePath(command.repo, pack.name);
40
+ const { graph, reused } = yield* freshGraph(deps.files, cachePath, pack, workspace);
41
+ switch (command._tag) {
42
+ case "build": {
43
+ const stats = graphStats(graph);
44
+ return [
45
+ `pack ${pack.name} — graph ${reused ? "reused from" : "written to"} ${cachePath}`,
46
+ `nodes: ${stats.total.nodes} (${counts(stats.nodes)})`,
47
+ `edges: ${stats.total.edges} (${counts(stats.edges)})`,
48
+ `unresolved: ${stats.total.unresolved} (${counts(stats.unresolved) || "none"})`
49
+ ].join("\n");
50
+ }
51
+ case "stats": {
52
+ const stats = graphStats(graph);
53
+ if (command.format === "json") {
54
+ return JSON.stringify(stats, undefined, 2);
55
+ }
56
+ const rows = (label, record) => Object.entries(record).map(([key, count]) => `${label} ${key}\t${count}`);
57
+ return [
58
+ "kind\tcount",
59
+ ...rows("node", stats.nodes),
60
+ ...rows("edge", stats.edges),
61
+ ...rows("mechanism", stats.mechanism),
62
+ ...rows("origin", stats.origin),
63
+ ...rows("confidence", stats.confidence),
64
+ ...rows("unresolved", stats.unresolved)
65
+ ].join("\n");
66
+ }
67
+ case "query": {
68
+ const hits = searchNodes(graph, command.text, command.kind === undefined ? {} : { kind: command.kind });
69
+ if (hits.length === 0) {
70
+ return `no node matches '${command.text}'`;
71
+ }
72
+ const view = command.all
73
+ ? wholeView(graph)
74
+ : neighborhood(graph, hits.map((hit) => nodeId(hit.node)), command.hops);
75
+ return render(view, graph, command.format, command.force);
76
+ }
77
+ case "path": {
78
+ const from = yield* requireNode(graph, command.from);
79
+ const to = yield* requireNode(graph, command.to);
80
+ const found = pathBetween(graph, from, to, command.max, { projected: true });
81
+ const path = found.forward ?? found.backward;
82
+ if (path === undefined) {
83
+ return `no path within ${command.max} hops between ${from} and ${to} in either direction`;
84
+ }
85
+ const direction = found.forward === undefined ? `(reverse: ${to} → ${from})\n` : "";
86
+ return `${direction}${render(viewOfPath(graph, path), graph, command.format, command.force)}`;
87
+ }
88
+ case "closure":
89
+ return render(closureView(graph, command.program, command.max), graph, command.format, command.force);
90
+ case "probe": {
91
+ const results = probeResults(graph, pack.graph.probes);
92
+ if (command.format === "json") {
93
+ return JSON.stringify(results.map((result) => ({
94
+ name: result.probe.name,
95
+ status: result.status,
96
+ ...(result.path === undefined ? {} : { hops: result.path.length })
97
+ })), undefined, 2);
98
+ }
99
+ return results.length === 0
100
+ ? `pack ${pack.name} declares no '## Probe:' section`
101
+ : results
102
+ .map((result) => `${result.probe.name}: ${result.status}${result.path === undefined
103
+ ? ""
104
+ : ` via ${result.path.map((edge) => edge.kind).join(" → ")}`}`)
105
+ .join("\n");
106
+ }
107
+ }
108
+ });
109
+ /** Node-backed dependencies: the disk, a legacy-sized workspace, packs from the kit tiers. */
110
+ export const nodeGraphDependencies = (environment, kits) => ({
111
+ files: nodePlainFileStore,
112
+ workspace: (root) => makeNodeWorkspace(root, workspaceLimitsFromEnv(environment, legacySourceWorkspaceLimits)),
113
+ openPack: (repo, pack) => openPack({
114
+ environment: { ...environment, ...(pack === undefined ? {} : { LLM4TS_PACK: pack }) },
115
+ launchDir: repo,
116
+ flowDir: repo,
117
+ kits: kits.builtin === undefined
118
+ ? kitTierPaths({ cwd: repo, homeDir: homedir(), environment })
119
+ : kits
120
+ }).pipe(Effect.map((opened) => opened.pack))
121
+ });
122
+ //# sourceMappingURL=Graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Graph.js","sourceRoot":"","sources":["../src/Graph.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/runner",
3
- "version": "2.36.0",
3
+ "version": "2.38.0",
4
4
  "description": "Node runner, CLI, terminal, and MCP composition for llm4ts",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,6 +24,7 @@
24
24
  "./ExecutorRoster": "./dist/ExecutorRoster.js",
25
25
  "./FlowArgs": "./dist/FlowArgs.js",
26
26
  "./FlowRunner": "./dist/FlowRunner.js",
27
+ "./Graph": "./dist/Graph.js",
27
28
  "./Kits": "./dist/Kits.js",
28
29
  "./McpStdio": "./dist/McpStdio.js",
29
30
  "./NodeGeminiAcpBridge": "./dist/NodeGeminiAcpBridge.js",
@@ -69,8 +70,8 @@
69
70
  "typescript"
70
71
  ],
71
72
  "dependencies": {
72
- "@llm4ts/core": "2.36.0",
73
- "@llm4ts/flow": "2.36.0"
73
+ "@llm4ts/core": "2.38.0",
74
+ "@llm4ts/flow": "2.38.0"
74
75
  },
75
76
  "peerDependencies": {
76
77
  "effect": "4.0.0"
package/src/Graph.ts ADDED
@@ -0,0 +1,248 @@
1
+ import { homedir } from "node:os"
2
+ import * as Effect from "effect/Effect"
3
+ import { FlowAborted, type FlowError } from "@llm4ts/flow/FlowError"
4
+ import { freshGraph, graphCachePath } from "@llm4ts/flow/GraphCache"
5
+ import {
6
+ closureView,
7
+ graphStats,
8
+ neighborhood,
9
+ pathBetween,
10
+ probeResults,
11
+ resolveNodeRef,
12
+ searchNodes,
13
+ viewOfPath,
14
+ wholeView,
15
+ type GraphView
16
+ } from "@llm4ts/flow/GraphQuery"
17
+ import { renderGraphDot, renderGraphMermaid, renderGraphText } from "@llm4ts/flow/GraphRender"
18
+ import type { Pack } from "@llm4ts/flow/Pack"
19
+ import type { PlainFileStoreShape } from "@llm4ts/flow/Persistence"
20
+ import { nodeId, type SurveyGraph } from "@llm4ts/flow/Survey"
21
+ import {
22
+ legacySourceWorkspaceLimits,
23
+ workspaceLimitsFromEnv,
24
+ type WorkspaceError,
25
+ type WorkspaceShape
26
+ } from "@llm4ts/flow/Workspace"
27
+ import { kitTierPaths, type KitTierPaths } from "./Kits.ts"
28
+ import { nodePlainFileStore } from "./NodePlainFileStore.ts"
29
+ import { makeNodeWorkspace } from "./NodeWorkspace.ts"
30
+ import { openPack, type PackNotFound } from "./Packs.ts"
31
+
32
+ /**
33
+ * `llm4ts graph` (ADR 0030 decision 7): the code graph of a legacy estate as a
34
+ * CLI. `build` is the scanner pass (the LLM worklist runs in modernize-survey);
35
+ * every other command reads the cache through `freshGraph`, so a stale cache
36
+ * is rebuilt rather than refused.
37
+ */
38
+ export type GraphFormat = "text" | "json" | "mermaid" | "dot"
39
+
40
+ export type GraphCommand =
41
+ | { readonly _tag: "build"; readonly repo: string; readonly pack?: string }
42
+ | {
43
+ readonly _tag: "query"
44
+ readonly repo: string
45
+ readonly pack?: string
46
+ readonly text: string
47
+ readonly kind?: string
48
+ readonly hops: number
49
+ readonly format: GraphFormat
50
+ readonly all: boolean
51
+ readonly force: boolean
52
+ }
53
+ | {
54
+ readonly _tag: "path"
55
+ readonly repo: string
56
+ readonly pack?: string
57
+ readonly from: string
58
+ readonly to: string
59
+ readonly max: number
60
+ readonly format: GraphFormat
61
+ readonly force: boolean
62
+ }
63
+ | {
64
+ readonly _tag: "closure"
65
+ readonly repo: string
66
+ readonly pack?: string
67
+ readonly program: string
68
+ readonly max: number
69
+ readonly format: GraphFormat
70
+ readonly force: boolean
71
+ }
72
+ | {
73
+ readonly _tag: "stats"
74
+ readonly repo: string
75
+ readonly pack?: string
76
+ readonly format: GraphFormat
77
+ }
78
+ | {
79
+ readonly _tag: "probe"
80
+ readonly repo: string
81
+ readonly pack?: string
82
+ readonly format: GraphFormat
83
+ }
84
+
85
+ export type GraphProgramError = FlowError | PackNotFound | WorkspaceError
86
+
87
+ export interface GraphDependencies {
88
+ readonly files: PlainFileStoreShape
89
+ readonly workspace: (root: string) => Effect.Effect<WorkspaceShape, WorkspaceError>
90
+ readonly openPack: (
91
+ repo: string,
92
+ pack: string | undefined
93
+ ) => Effect.Effect<Pack, GraphProgramError>
94
+ }
95
+
96
+ const render = (
97
+ view: GraphView,
98
+ graph: SurveyGraph,
99
+ format: GraphFormat,
100
+ force: boolean
101
+ ): string => {
102
+ switch (format) {
103
+ case "json":
104
+ return JSON.stringify(view, undefined, 2)
105
+ case "mermaid":
106
+ return renderGraphMermaid(view, graph, { force })
107
+ case "dot":
108
+ return renderGraphDot(view, graph)
109
+ case "text":
110
+ return renderGraphText(view)
111
+ }
112
+ }
113
+
114
+ const counts = (record: Readonly<Record<string, number>>): string =>
115
+ Object.entries(record)
116
+ .map(([key, count]) => `${key} ${count}`)
117
+ .join(", ")
118
+
119
+ const requireNode = (graph: SurveyGraph, ref: string): Effect.Effect<string, FlowAborted> => {
120
+ const node = resolveNodeRef(graph, ref)
121
+ return node === undefined
122
+ ? Effect.fail(
123
+ FlowAborted.make({
124
+ message: `no node matches '${ref}' (try: llm4ts graph query "${ref}")`
125
+ })
126
+ )
127
+ : Effect.succeed(nodeId(node))
128
+ }
129
+
130
+ export const makeGraphProgram = Effect.fn("@llm4ts/runner/Graph.make")(function* (
131
+ command: GraphCommand,
132
+ deps: GraphDependencies
133
+ ): Effect.fn.Return<string, GraphProgramError> {
134
+ const pack = yield* deps.openPack(command.repo, command.pack)
135
+ const workspace = yield* deps.workspace(command.repo)
136
+ const cachePath = graphCachePath(command.repo, pack.name)
137
+ const { graph, reused } = yield* freshGraph(deps.files, cachePath, pack, workspace)
138
+ switch (command._tag) {
139
+ case "build": {
140
+ const stats = graphStats(graph)
141
+ return [
142
+ `pack ${pack.name} — graph ${reused ? "reused from" : "written to"} ${cachePath}`,
143
+ `nodes: ${stats.total.nodes} (${counts(stats.nodes)})`,
144
+ `edges: ${stats.total.edges} (${counts(stats.edges)})`,
145
+ `unresolved: ${stats.total.unresolved} (${counts(stats.unresolved) || "none"})`
146
+ ].join("\n")
147
+ }
148
+ case "stats": {
149
+ const stats = graphStats(graph)
150
+ if (command.format === "json") {
151
+ return JSON.stringify(stats, undefined, 2)
152
+ }
153
+ const rows = (label: string, record: Readonly<Record<string, number>>) =>
154
+ Object.entries(record).map(([key, count]) => `${label} ${key}\t${count}`)
155
+ return [
156
+ "kind\tcount",
157
+ ...rows("node", stats.nodes),
158
+ ...rows("edge", stats.edges),
159
+ ...rows("mechanism", stats.mechanism),
160
+ ...rows("origin", stats.origin),
161
+ ...rows("confidence", stats.confidence),
162
+ ...rows("unresolved", stats.unresolved)
163
+ ].join("\n")
164
+ }
165
+ case "query": {
166
+ const hits = searchNodes(
167
+ graph,
168
+ command.text,
169
+ command.kind === undefined ? {} : { kind: command.kind }
170
+ )
171
+ if (hits.length === 0) {
172
+ return `no node matches '${command.text}'`
173
+ }
174
+ const view = command.all
175
+ ? wholeView(graph)
176
+ : neighborhood(
177
+ graph,
178
+ hits.map((hit) => nodeId(hit.node)),
179
+ command.hops
180
+ )
181
+ return render(view, graph, command.format, command.force)
182
+ }
183
+ case "path": {
184
+ const from = yield* requireNode(graph, command.from)
185
+ const to = yield* requireNode(graph, command.to)
186
+ const found = pathBetween(graph, from, to, command.max, { projected: true })
187
+ const path = found.forward ?? found.backward
188
+ if (path === undefined) {
189
+ return `no path within ${command.max} hops between ${from} and ${to} in either direction`
190
+ }
191
+ const direction = found.forward === undefined ? `(reverse: ${to} → ${from})\n` : ""
192
+ return `${direction}${render(viewOfPath(graph, path), graph, command.format, command.force)}`
193
+ }
194
+ case "closure":
195
+ return render(
196
+ closureView(graph, command.program, command.max),
197
+ graph,
198
+ command.format,
199
+ command.force
200
+ )
201
+ case "probe": {
202
+ const results = probeResults(graph, pack.graph.probes)
203
+ if (command.format === "json") {
204
+ return JSON.stringify(
205
+ results.map((result) => ({
206
+ name: result.probe.name,
207
+ status: result.status,
208
+ ...(result.path === undefined ? {} : { hops: result.path.length })
209
+ })),
210
+ undefined,
211
+ 2
212
+ )
213
+ }
214
+ return results.length === 0
215
+ ? `pack ${pack.name} declares no '## Probe:' section`
216
+ : results
217
+ .map(
218
+ (result) =>
219
+ `${result.probe.name}: ${result.status}${
220
+ result.path === undefined
221
+ ? ""
222
+ : ` via ${result.path.map((edge) => edge.kind).join(" → ")}`
223
+ }`
224
+ )
225
+ .join("\n")
226
+ }
227
+ }
228
+ })
229
+
230
+ /** Node-backed dependencies: the disk, a legacy-sized workspace, packs from the kit tiers. */
231
+ export const nodeGraphDependencies = (
232
+ environment: Readonly<Record<string, string | undefined>>,
233
+ kits: KitTierPaths
234
+ ): GraphDependencies => ({
235
+ files: nodePlainFileStore,
236
+ workspace: (root) =>
237
+ makeNodeWorkspace(root, workspaceLimitsFromEnv(environment, legacySourceWorkspaceLimits)),
238
+ openPack: (repo, pack) =>
239
+ openPack({
240
+ environment: { ...environment, ...(pack === undefined ? {} : { LLM4TS_PACK: pack }) },
241
+ launchDir: repo,
242
+ flowDir: repo,
243
+ kits:
244
+ kits.builtin === undefined
245
+ ? kitTierPaths({ cwd: repo, homeDir: homedir(), environment })
246
+ : kits
247
+ }).pipe(Effect.map((opened) => opened.pack))
248
+ })