@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.
- package/dist/Graph.d.ts +66 -0
- package/dist/Graph.d.ts.map +1 -0
- package/dist/Graph.js +122 -0
- package/dist/Graph.js.map +1 -0
- package/package.json +4 -3
- package/src/Graph.ts +248 -0
package/dist/Graph.d.ts
ADDED
|
@@ -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.
|
|
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.
|
|
73
|
-
"@llm4ts/flow": "2.
|
|
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
|
+
})
|