@llm4ts/runner 2.54.1 → 3.2.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/Kits.d.ts +2 -0
- package/dist/Kits.d.ts.map +1 -1
- package/dist/Kits.js +10 -2
- package/dist/Kits.js.map +1 -1
- package/dist/Profile.d.ts +9 -2
- package/dist/Profile.d.ts.map +1 -1
- package/dist/Profile.js +24 -5
- package/dist/Profile.js.map +1 -1
- package/dist/ToolMain.d.ts +18 -0
- package/dist/ToolMain.d.ts.map +1 -0
- package/dist/ToolMain.js +60 -0
- package/dist/ToolMain.js.map +1 -0
- package/dist/Tools.d.ts +70 -0
- package/dist/Tools.d.ts.map +1 -0
- package/dist/Tools.js +142 -0
- package/dist/Tools.js.map +1 -0
- package/package.json +5 -5
- package/src/Kits.ts +12 -2
- package/src/Profile.ts +27 -6
- package/src/ToolMain.ts +76 -0
- package/src/Tools.ts +204 -0
- package/dist/CoderPolicy.d.ts +0 -12
- package/dist/CoderPolicy.d.ts.map +0 -1
- package/dist/CoderPolicy.js +0 -59
- package/dist/CoderPolicy.js.map +0 -1
- package/dist/NodeRuntime.d.ts +0 -4
- package/dist/NodeRuntime.d.ts.map +0 -1
- package/dist/NodeRuntime.js +0 -11
- package/dist/NodeRuntime.js.map +0 -1
- package/src/CoderPolicy.ts +0 -82
- package/src/NodeRuntime.ts +0 -19
package/dist/Tools.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import { builtinTools } from "@llm4ts/flow/tools/Builtin";
|
|
7
|
+
import { isToolName, renderUsage, specOf } from "@llm4ts/flow/tools/Tool";
|
|
8
|
+
const tierRank = { project: 0, global: 1, builtin: 2 };
|
|
9
|
+
export const catalogEntries = (kits) => {
|
|
10
|
+
const byName = new Map();
|
|
11
|
+
for (const tool of builtinTools) {
|
|
12
|
+
byName.set(tool.name, { _tag: "builtin", name: tool.name, tool });
|
|
13
|
+
}
|
|
14
|
+
const ordered = [...kits].sort((left, right) => tierRank[right.tier] - tierRank[left.tier]);
|
|
15
|
+
for (const kit of ordered) {
|
|
16
|
+
for (const name of kit.tools) {
|
|
17
|
+
if (!isToolName(name)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const file = ["ts", "js"]
|
|
21
|
+
.map((extension) => join(kit.root, "tools", `${name}.${extension}`))
|
|
22
|
+
.find((candidate) => existsSync(candidate));
|
|
23
|
+
if (file !== undefined) {
|
|
24
|
+
byName.set(name, { _tag: "kit", name, kit, file });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
29
|
+
};
|
|
30
|
+
export const findEntry = (catalog, name) => catalog.find((entry) => entry.name === name);
|
|
31
|
+
export class PresetError extends Schema.TaggedError()("PresetError", {
|
|
32
|
+
preset: Schema.String,
|
|
33
|
+
message: Schema.String
|
|
34
|
+
}) {
|
|
35
|
+
}
|
|
36
|
+
export const resolvePreset = (catalog, name, extra) => {
|
|
37
|
+
const preset = catalog.presets.find((candidate) => candidate.name === name);
|
|
38
|
+
if (preset === undefined) {
|
|
39
|
+
return Effect.succeed(undefined);
|
|
40
|
+
}
|
|
41
|
+
const [toolName, ...rest] = preset.command;
|
|
42
|
+
if (toolName === undefined) {
|
|
43
|
+
return Effect.fail(new PresetError({ preset: name, message: "preset names no tool" }));
|
|
44
|
+
}
|
|
45
|
+
const entry = findEntry(catalog.entries, toolName);
|
|
46
|
+
if (entry === undefined) {
|
|
47
|
+
return Effect.fail(new PresetError({
|
|
48
|
+
preset: name,
|
|
49
|
+
message: `preset runs '${toolName}', which is not a catalog tool (run \`llm4ts tool list\`)`
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return Effect.succeed({ preset, entry, argv: [...rest, ...extra] });
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The repository a worktree belongs to: `git rev-parse --git-common-dir`
|
|
56
|
+
* points back at the main checkout from any worktree, so the project tier
|
|
57
|
+
* (`<repo>/.llm4ts/kits`) is found without a file in the worktree or an
|
|
58
|
+
* environment variable the executor would strip (ADR 0048). Falls back to
|
|
59
|
+
* `cwd` outside a repository.
|
|
60
|
+
*/
|
|
61
|
+
export const projectRootOf = (cwd) => {
|
|
62
|
+
try {
|
|
63
|
+
const common = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
64
|
+
cwd,
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
67
|
+
}).trim();
|
|
68
|
+
if (common.length === 0) {
|
|
69
|
+
return cwd;
|
|
70
|
+
}
|
|
71
|
+
const absolute = isAbsolute(common) ? common : resolve(cwd, common);
|
|
72
|
+
return dirname(absolute);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return cwd;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/** One line per catalog entry and preset, for `llm4ts tool list`. */
|
|
79
|
+
export const renderCatalog = (catalog, specs) => {
|
|
80
|
+
const lines = [];
|
|
81
|
+
for (const entry of catalog.entries) {
|
|
82
|
+
const spec = specs.get(entry.name);
|
|
83
|
+
const origin = entry._tag === "builtin" ? "" : ` [${entry.kit.name} kit]`;
|
|
84
|
+
lines.push(spec === undefined
|
|
85
|
+
? `${entry.name}${origin}`
|
|
86
|
+
: `${renderUsage(spec)}${origin}\n ${spec.purpose}`);
|
|
87
|
+
}
|
|
88
|
+
if (catalog.presets.length > 0) {
|
|
89
|
+
lines.push("", "Presets (pack: command):");
|
|
90
|
+
for (const preset of catalog.presets) {
|
|
91
|
+
lines.push(` ${preset.name} (${preset.pack}): ${preset.command.join(" ")}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
};
|
|
96
|
+
export const catalogJson = (catalog, specs) => ({
|
|
97
|
+
tools: catalog.entries.map((entry) => {
|
|
98
|
+
const spec = specs.get(entry.name);
|
|
99
|
+
return {
|
|
100
|
+
name: entry.name,
|
|
101
|
+
origin: entry._tag === "builtin" ? "builtin" : `kit:${entry.kit.name}`,
|
|
102
|
+
...(spec === undefined ? {} : { purpose: spec.purpose, usage: renderUsage(spec) })
|
|
103
|
+
};
|
|
104
|
+
}),
|
|
105
|
+
presets: catalog.presets.map((preset) => ({
|
|
106
|
+
name: preset.name,
|
|
107
|
+
pack: preset.pack,
|
|
108
|
+
command: preset.command.join(" ")
|
|
109
|
+
}))
|
|
110
|
+
});
|
|
111
|
+
/** Exit status for a report: 0 ok, 1 issues. Errors exit 2 (the CLI's own). */
|
|
112
|
+
export const reportExitCode = (report) => (report.ok ? 0 : 1);
|
|
113
|
+
/**
|
|
114
|
+
* The paragraph a coder's brief carries (ADR 0048): one rule, then each
|
|
115
|
+
* preset of the story's pack with the command to run. `LLM4TS_TOOL_BRIEF=off`
|
|
116
|
+
* drops it, for a run without the catalog to compare against.
|
|
117
|
+
*/
|
|
118
|
+
export const toolBrief = (presets, specs, environment = process.env) => {
|
|
119
|
+
if (environment.LLM4TS_TOOL_BRIEF?.trim().toLowerCase() === "off") {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const lines = [
|
|
123
|
+
"Tools: for validating a document, comparing two outputs, or checking a contract, run the",
|
|
124
|
+
"project's tool instead of writing a script — `llm4ts tool <name> [--flag value]` prints JSON",
|
|
125
|
+
"(ok, summary, issues). `llm4ts tool list` shows every tool; `llm4ts tool describe <name>`",
|
|
126
|
+
"its arguments. Never write a Python or Node one-liner for a job a tool covers."
|
|
127
|
+
];
|
|
128
|
+
if (presets.length > 0) {
|
|
129
|
+
lines.push("", "This pack's presets (run them by name from the worktree root):");
|
|
130
|
+
for (const preset of presets) {
|
|
131
|
+
const [toolName, ...rest] = preset.command;
|
|
132
|
+
const spec = toolName === undefined ? undefined : specs.get(toolName);
|
|
133
|
+
const fixed = rest.length === 0 ? "" : ` (fixed: ${rest.join(" ")})`;
|
|
134
|
+
const purpose = spec === undefined ? "" : ` — ${spec.purpose}`;
|
|
135
|
+
lines.push(`- \`llm4ts tool ${preset.name}\`${fixed}${purpose}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
};
|
|
140
|
+
/** Specs of the built-in tools, known without running anything. */
|
|
141
|
+
export const builtinSpecs = () => new Map(builtinTools.map((tool) => [tool.name, specOf(tool)]));
|
|
142
|
+
//# sourceMappingURL=Tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tools.js","sourceRoot":"","sources":["../src/Tools.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": "3.2.0",
|
|
4
4
|
"description": "Node runner, CLI, terminal, and MCP composition for llm4ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
"src"
|
|
14
14
|
],
|
|
15
15
|
"exports": {
|
|
16
|
+
"./ToolMain": "./dist/ToolMain.js",
|
|
17
|
+
"./Tools": "./dist/Tools.js",
|
|
16
18
|
".": "./dist/index.js",
|
|
17
19
|
"./Cli": "./dist/Cli.js",
|
|
18
20
|
"./AgentTree": "./dist/AgentTree.js",
|
|
19
21
|
"./AgentTreeSurface": "./dist/AgentTreeSurface.js",
|
|
20
|
-
"./CoderPolicy": "./dist/CoderPolicy.js",
|
|
21
22
|
"./Connectors": "./dist/Connectors.js",
|
|
22
23
|
"./Costs": "./dist/Costs.js",
|
|
23
24
|
"./Doctor": "./dist/Doctor.js",
|
|
@@ -37,7 +38,6 @@
|
|
|
37
38
|
"./NodeTemporaryFiles": "./dist/NodeTemporaryFiles.js",
|
|
38
39
|
"./NodeWorkspace": "./dist/NodeWorkspace.js",
|
|
39
40
|
"./Otel": "./dist/Otel.js",
|
|
40
|
-
"./NodeRuntime": "./dist/NodeRuntime.js",
|
|
41
41
|
"./Packs": "./dist/Packs.js",
|
|
42
42
|
"./Profile": "./dist/Profile.js",
|
|
43
43
|
"./PiModels": "./dist/PiModels.js",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"typescript"
|
|
76
76
|
],
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@llm4ts/core": "2.
|
|
79
|
-
"@llm4ts/flow": "2.
|
|
78
|
+
"@llm4ts/core": "3.2.0",
|
|
79
|
+
"@llm4ts/flow": "3.2.0"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"effect": "4.0.0"
|
package/src/Kits.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface DiscoveredKit {
|
|
|
29
29
|
readonly packs: ReadonlyArray<string>
|
|
30
30
|
/** Flow names: every `flows/<name>.ts|js` (not `lib/` or `test/`). */
|
|
31
31
|
readonly flows: ReadonlyArray<string>
|
|
32
|
+
/** Tool names: every `tools/<name>.ts|js` (ADR 0048), run by `llm4ts tool`. */
|
|
33
|
+
readonly tools: ReadonlyArray<string>
|
|
32
34
|
readonly shadows: ReadonlyArray<KitTier>
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -118,7 +120,14 @@ const readKit = (
|
|
|
118
120
|
.map((entry) => entry.replace(/\.(ts|js)$/, ""))
|
|
119
121
|
.sort()
|
|
120
122
|
: []
|
|
121
|
-
|
|
123
|
+
const toolsDir = join(root, "tools")
|
|
124
|
+
const tools = isDirectory(toolsDir)
|
|
125
|
+
? readdirSync(toolsDir)
|
|
126
|
+
.filter((entry) => entry.endsWith(".ts") || entry.endsWith(".js"))
|
|
127
|
+
.map((entry) => entry.replace(/\.(ts|js)$/, ""))
|
|
128
|
+
.sort()
|
|
129
|
+
: []
|
|
130
|
+
if (packs.length === 0 && flows.length === 0 && tools.length === 0) {
|
|
122
131
|
return undefined
|
|
123
132
|
}
|
|
124
133
|
const readme = join(root, "README.md")
|
|
@@ -131,7 +140,8 @@ const readKit = (
|
|
|
131
140
|
tier,
|
|
132
141
|
...(description === undefined ? {} : { description }),
|
|
133
142
|
packs,
|
|
134
|
-
flows
|
|
143
|
+
flows,
|
|
144
|
+
tools
|
|
135
145
|
}
|
|
136
146
|
}
|
|
137
147
|
|
package/src/Profile.ts
CHANGED
|
@@ -65,11 +65,16 @@ export const Gap = Schema.Struct({
|
|
|
65
65
|
})
|
|
66
66
|
export type Gap = typeof Gap.Type
|
|
67
67
|
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* The coder's tool calls by kind of work (build, install and git count as
|
|
70
|
+
* other). `script` is the ad-hoc Python/Node one-liners, heredocs and scratch
|
|
71
|
+
* files it improvised; profiles saved before it existed decode it as 0.
|
|
72
|
+
*/
|
|
69
73
|
export const ToolCalls = Schema.Struct({
|
|
70
74
|
explore: Schema.Int,
|
|
71
75
|
edit: Schema.Int,
|
|
72
76
|
test: Schema.Int,
|
|
77
|
+
script: Schema.Int.pipe(Schema.withDecodingDefaultKey(Effect.succeed(0))),
|
|
73
78
|
other: Schema.Int
|
|
74
79
|
})
|
|
75
80
|
export type ToolCalls = typeof ToolCalls.Type
|
|
@@ -146,7 +151,7 @@ export const ProfileReport = Schema.Struct({
|
|
|
146
151
|
models: Schema.Array(ModelRow),
|
|
147
152
|
gates: Schema.Array(GateRow),
|
|
148
153
|
tools: Schema.Array(CountRow),
|
|
149
|
-
/** Coder tool time by kind of work: explore, edit, test, build, install, git, other. */
|
|
154
|
+
/** Coder tool time by kind of work: explore, edit, test, script, build, install, git, other. */
|
|
150
155
|
toolCategories: Schema.Array(CountRow),
|
|
151
156
|
waits: Schema.Array(CountRow),
|
|
152
157
|
/** Timed on the run's events, in no story: the run's own planning, or a seat no story owns. */
|
|
@@ -184,7 +189,7 @@ interface Lane {
|
|
|
184
189
|
tasks: number
|
|
185
190
|
/** When the coder's first task began: setup and task planning come before it. */
|
|
186
191
|
firstTask: number | undefined
|
|
187
|
-
toolCalls: { explore: number; edit: number; test: number; other: number }
|
|
192
|
+
toolCalls: { explore: number; edit: number; test: number; script: number; other: number }
|
|
188
193
|
firstEdit: number | undefined
|
|
189
194
|
exploreBeforeEdit: number
|
|
190
195
|
unverifiedClaims: number
|
|
@@ -363,7 +368,7 @@ export const profileOf = (inputs: ReadonlyArray<TreeInput>): ProfileReport => {
|
|
|
363
368
|
openTools: 0,
|
|
364
369
|
tasks: 0,
|
|
365
370
|
firstTask: undefined,
|
|
366
|
-
toolCalls: { explore: 0, edit: 0, test: 0, other: 0 },
|
|
371
|
+
toolCalls: { explore: 0, edit: 0, test: 0, script: 0, other: 0 },
|
|
367
372
|
firstEdit: undefined,
|
|
368
373
|
exploreBeforeEdit: 0,
|
|
369
374
|
unverifiedClaims: 0,
|
|
@@ -397,7 +402,10 @@ export const profileOf = (inputs: ReadonlyArray<TreeInput>): ProfileReport => {
|
|
|
397
402
|
lane?.timed.push({ at, event })
|
|
398
403
|
if (lane !== undefined && event.kind === "tool" && event.category !== undefined) {
|
|
399
404
|
const kind =
|
|
400
|
-
event.category === "explore" ||
|
|
405
|
+
event.category === "explore" ||
|
|
406
|
+
event.category === "edit" ||
|
|
407
|
+
event.category === "test" ||
|
|
408
|
+
event.category === "script"
|
|
401
409
|
? event.category
|
|
402
410
|
: "other"
|
|
403
411
|
lane.toolCalls[kind] += 1
|
|
@@ -808,6 +816,15 @@ const findingsOf = (facts: {
|
|
|
808
816
|
]
|
|
809
817
|
: []
|
|
810
818
|
)
|
|
819
|
+
const improvised = facts.stories.flatMap((story) =>
|
|
820
|
+
story.toolCalls.script >= 3
|
|
821
|
+
? [
|
|
822
|
+
{
|
|
823
|
+
text: `${story.id}: ${story.toolCalls.script} ad-hoc scripts — a pack tool preset (llm4ts tool) would make this repeatable`
|
|
824
|
+
}
|
|
825
|
+
]
|
|
826
|
+
: []
|
|
827
|
+
)
|
|
811
828
|
const growth = facts.stories.flatMap((story) =>
|
|
812
829
|
story.turns.promptGrowth &&
|
|
813
830
|
story.turns.firstPrompt !== undefined &&
|
|
@@ -819,7 +836,7 @@ const findingsOf = (facts: {
|
|
|
819
836
|
]
|
|
820
837
|
: []
|
|
821
838
|
)
|
|
822
|
-
return [...ranked, ...fabricated, ...growth]
|
|
839
|
+
return [...ranked, ...fabricated, ...improvised, ...growth]
|
|
823
840
|
}
|
|
824
841
|
|
|
825
842
|
const table = (
|
|
@@ -908,6 +925,7 @@ export const renderProfile = (report: ProfileReport): string => {
|
|
|
908
925
|
story.toolCalls.explore +
|
|
909
926
|
story.toolCalls.edit +
|
|
910
927
|
story.toolCalls.test +
|
|
928
|
+
story.toolCalls.script +
|
|
911
929
|
story.toolCalls.other ===
|
|
912
930
|
0
|
|
913
931
|
)
|
|
@@ -922,6 +940,7 @@ export const renderProfile = (report: ProfileReport): string => {
|
|
|
922
940
|
"explore",
|
|
923
941
|
"edit",
|
|
924
942
|
"test",
|
|
943
|
+
"script",
|
|
925
944
|
"other",
|
|
926
945
|
"before 1st edit",
|
|
927
946
|
"1st edit at"
|
|
@@ -932,6 +951,7 @@ export const renderProfile = (report: ProfileReport): string => {
|
|
|
932
951
|
String(story.toolCalls.explore),
|
|
933
952
|
String(story.toolCalls.edit),
|
|
934
953
|
String(story.toolCalls.test),
|
|
954
|
+
String(story.toolCalls.script),
|
|
935
955
|
String(story.toolCalls.other),
|
|
936
956
|
story.firstEditMs === undefined ? "" : `${story.exploreBeforeEdit} explore`,
|
|
937
957
|
story.firstEditMs === undefined ? "never" : duration(story.firstEditMs)
|
|
@@ -1091,6 +1111,7 @@ export const profileDelta = (before: ProfileReport, after: ProfileReport): Profi
|
|
|
1091
1111
|
count("coder turns", (r) => sum(r.stories.map((s) => s.turns.count))),
|
|
1092
1112
|
count("coder edits", (r) => sum(r.stories.map((s) => s.toolCalls.edit)), "none"),
|
|
1093
1113
|
count("coder test runs", (r) => sum(r.stories.map((s) => s.toolCalls.test)), "none"),
|
|
1114
|
+
count("coder scripts", (r) => sum(r.stories.map((s) => s.toolCalls.script))),
|
|
1094
1115
|
count("reviewer calls", (r) => roleCalls(r, "reviewer")),
|
|
1095
1116
|
count("judge calls", (r) => roleCalls(r, "judge")),
|
|
1096
1117
|
count("gate runs", (r) => sum(r.gates.map((g) => g.runs))),
|
package/src/ToolMain.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect"
|
|
2
|
+
import * as Schema from "effect/Schema"
|
|
3
|
+
import * as Layer from "effect/Layer"
|
|
4
|
+
import { SoapTransport, makeNodeSoapTransport } from "@llm4ts/flow/endpoint/Transport"
|
|
5
|
+
import { ToolContext, ToolReport, ToolSpec, specOf, type ToolShape } from "@llm4ts/flow/tools/Tool"
|
|
6
|
+
import { NodePlainFileStoreLive } from "./NodePlainFileStore.ts"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The main of a kit tool file (`kits/<kit>/tools/<name>.ts`, ADR 0048):
|
|
10
|
+
* `llm4ts tool` runs the file as a Node child and speaks this protocol —
|
|
11
|
+
* `--describe` prints the `ToolSpec`; otherwise the parsed arguments arrive
|
|
12
|
+
* as one JSON object on stdin and the `ToolReport` goes out on stdout. Exit
|
|
13
|
+
* 0 when the report is ok, 1 when it carries issues, 2 when the tool could
|
|
14
|
+
* not run (the error is JSON on stdout too, `{"error": …}`). `--project
|
|
15
|
+
* <root>` names the project the tool runs for (default: the current
|
|
16
|
+
* directory); secrets resolve from this process's environment.
|
|
17
|
+
*/
|
|
18
|
+
export const runToolMain = (tool: ToolShape): void => {
|
|
19
|
+
if (process.argv.includes("--describe")) {
|
|
20
|
+
process.stdout.write(`${JSON.stringify(Schema.encodeSync(ToolSpec)(specOf(tool)))}\n`)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
void readStdin().then((text) => {
|
|
24
|
+
let values: Record<string, unknown>
|
|
25
|
+
try {
|
|
26
|
+
const parsed: unknown = text.trim().length === 0 ? {} : JSON.parse(text)
|
|
27
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
28
|
+
throw new Error("stdin must hold one JSON object of arguments")
|
|
29
|
+
}
|
|
30
|
+
values = Object.fromEntries(Object.entries(parsed))
|
|
31
|
+
} catch (error) {
|
|
32
|
+
process.stdout.write(
|
|
33
|
+
`${JSON.stringify({ error: error instanceof Error ? error.message : String(error) })}\n`
|
|
34
|
+
)
|
|
35
|
+
process.exitCode = 2
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
const projectFlag = process.argv.indexOf("--project")
|
|
39
|
+
const projectRoot =
|
|
40
|
+
projectFlag >= 0 && process.argv[projectFlag + 1] !== undefined
|
|
41
|
+
? process.argv[projectFlag + 1]
|
|
42
|
+
: process.cwd()
|
|
43
|
+
return Effect.runPromise(
|
|
44
|
+
tool.execute(values).pipe(
|
|
45
|
+
Effect.provide(
|
|
46
|
+
Layer.mergeAll(
|
|
47
|
+
NodePlainFileStoreLive,
|
|
48
|
+
Layer.succeed(ToolContext)({ projectRoot, environment: process.env }),
|
|
49
|
+
Layer.succeed(SoapTransport)(makeNodeSoapTransport())
|
|
50
|
+
)
|
|
51
|
+
),
|
|
52
|
+
Effect.match({
|
|
53
|
+
onFailure: (error) => {
|
|
54
|
+
process.stdout.write(`${JSON.stringify({ error: error.message })}\n`)
|
|
55
|
+
process.exitCode = 2
|
|
56
|
+
},
|
|
57
|
+
onSuccess: (report) => {
|
|
58
|
+
process.stdout.write(`${JSON.stringify(Schema.encodeSync(ToolReport)(report))}\n`)
|
|
59
|
+
process.exitCode = report.ok ? 0 : 1
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const readStdin = (): Promise<string> =>
|
|
68
|
+
new Promise((resolvePromise, rejectPromise) => {
|
|
69
|
+
const chunks: Array<Buffer> = []
|
|
70
|
+
process.stdin.on("data", (chunk: Buffer) => chunks.push(chunk))
|
|
71
|
+
process.stdin.on("end", () => resolvePromise(Buffer.concat(chunks).toString("utf8")))
|
|
72
|
+
process.stdin.on("error", rejectPromise)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
/** The child's stdout as the parent reads it: a report, or the error it printed. */
|
|
76
|
+
export const ChildToolOutput = Schema.Union([Schema.Struct({ error: Schema.String }), ToolReport])
|
package/src/Tools.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path"
|
|
4
|
+
import * as Effect from "effect/Effect"
|
|
5
|
+
import * as Schema from "effect/Schema"
|
|
6
|
+
import { builtinTools } from "@llm4ts/flow/tools/Builtin"
|
|
7
|
+
import type { ToolReport, ToolSpec } from "@llm4ts/flow/tools/Tool"
|
|
8
|
+
import { isToolName, renderUsage, specOf, type ToolShape } from "@llm4ts/flow/tools/Tool"
|
|
9
|
+
import type { ToolPreset } from "@llm4ts/flow/Pack"
|
|
10
|
+
import type { DiscoveredKit } from "./Kits.ts"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The tool catalog (ADR 0048): the built-in tools, every discovered kit's
|
|
14
|
+
* `tools/<name>.ts`, and the presets of the packs in reach. A kit tool with
|
|
15
|
+
* a built-in's name shadows it, project kits first, as flows do.
|
|
16
|
+
*/
|
|
17
|
+
export type CatalogEntry =
|
|
18
|
+
| { readonly _tag: "builtin"; readonly name: string; readonly tool: ToolShape }
|
|
19
|
+
| {
|
|
20
|
+
readonly _tag: "kit"
|
|
21
|
+
readonly name: string
|
|
22
|
+
readonly kit: DiscoveredKit
|
|
23
|
+
readonly file: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ToolCatalog {
|
|
27
|
+
readonly entries: ReadonlyArray<CatalogEntry>
|
|
28
|
+
readonly presets: ReadonlyArray<ToolPreset & { readonly pack: string }>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tierRank = { project: 0, global: 1, builtin: 2 } as const
|
|
32
|
+
|
|
33
|
+
export const catalogEntries = (kits: ReadonlyArray<DiscoveredKit>): ReadonlyArray<CatalogEntry> => {
|
|
34
|
+
const byName = new Map<string, CatalogEntry>()
|
|
35
|
+
for (const tool of builtinTools) {
|
|
36
|
+
byName.set(tool.name, { _tag: "builtin", name: tool.name, tool })
|
|
37
|
+
}
|
|
38
|
+
const ordered = [...kits].sort((left, right) => tierRank[right.tier] - tierRank[left.tier])
|
|
39
|
+
for (const kit of ordered) {
|
|
40
|
+
for (const name of kit.tools) {
|
|
41
|
+
if (!isToolName(name)) {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
const file = ["ts", "js"]
|
|
45
|
+
.map((extension) => join(kit.root, "tools", `${name}.${extension}`))
|
|
46
|
+
.find((candidate) => existsSync(candidate))
|
|
47
|
+
if (file !== undefined) {
|
|
48
|
+
byName.set(name, { _tag: "kit", name, kit, file })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const findEntry = (
|
|
56
|
+
catalog: ReadonlyArray<CatalogEntry>,
|
|
57
|
+
name: string
|
|
58
|
+
): CatalogEntry | undefined => catalog.find((entry) => entry.name === name)
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A preset is `tool --flag value …`: the catalog tool, then its arguments,
|
|
62
|
+
* with whatever the caller appends. A preset never names another preset.
|
|
63
|
+
*/
|
|
64
|
+
export interface ResolvedPreset {
|
|
65
|
+
readonly preset: ToolPreset & { readonly pack: string }
|
|
66
|
+
readonly entry: CatalogEntry
|
|
67
|
+
readonly argv: ReadonlyArray<string>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class PresetError extends Schema.TaggedError<PresetError>()("PresetError", {
|
|
71
|
+
preset: Schema.String,
|
|
72
|
+
message: Schema.String
|
|
73
|
+
}) {}
|
|
74
|
+
|
|
75
|
+
export const resolvePreset = (
|
|
76
|
+
catalog: ToolCatalog,
|
|
77
|
+
name: string,
|
|
78
|
+
extra: ReadonlyArray<string>
|
|
79
|
+
): Effect.Effect<ResolvedPreset | undefined, PresetError> => {
|
|
80
|
+
const preset = catalog.presets.find((candidate) => candidate.name === name)
|
|
81
|
+
if (preset === undefined) {
|
|
82
|
+
return Effect.succeed(undefined)
|
|
83
|
+
}
|
|
84
|
+
const [toolName, ...rest] = preset.command
|
|
85
|
+
if (toolName === undefined) {
|
|
86
|
+
return Effect.fail(new PresetError({ preset: name, message: "preset names no tool" }))
|
|
87
|
+
}
|
|
88
|
+
const entry = findEntry(catalog.entries, toolName)
|
|
89
|
+
if (entry === undefined) {
|
|
90
|
+
return Effect.fail(
|
|
91
|
+
new PresetError({
|
|
92
|
+
preset: name,
|
|
93
|
+
message: `preset runs '${toolName}', which is not a catalog tool (run \`llm4ts tool list\`)`
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
return Effect.succeed({ preset, entry, argv: [...rest, ...extra] })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The repository a worktree belongs to: `git rev-parse --git-common-dir`
|
|
102
|
+
* points back at the main checkout from any worktree, so the project tier
|
|
103
|
+
* (`<repo>/.llm4ts/kits`) is found without a file in the worktree or an
|
|
104
|
+
* environment variable the executor would strip (ADR 0048). Falls back to
|
|
105
|
+
* `cwd` outside a repository.
|
|
106
|
+
*/
|
|
107
|
+
export const projectRootOf = (cwd: string): string => {
|
|
108
|
+
try {
|
|
109
|
+
const common = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
110
|
+
cwd,
|
|
111
|
+
encoding: "utf8",
|
|
112
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
113
|
+
}).trim()
|
|
114
|
+
if (common.length === 0) {
|
|
115
|
+
return cwd
|
|
116
|
+
}
|
|
117
|
+
const absolute = isAbsolute(common) ? common : resolve(cwd, common)
|
|
118
|
+
return dirname(absolute)
|
|
119
|
+
} catch {
|
|
120
|
+
return cwd
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** One line per catalog entry and preset, for `llm4ts tool list`. */
|
|
125
|
+
export const renderCatalog = (
|
|
126
|
+
catalog: ToolCatalog,
|
|
127
|
+
specs: ReadonlyMap<string, ToolSpec>
|
|
128
|
+
): string => {
|
|
129
|
+
const lines: Array<string> = []
|
|
130
|
+
for (const entry of catalog.entries) {
|
|
131
|
+
const spec = specs.get(entry.name)
|
|
132
|
+
const origin = entry._tag === "builtin" ? "" : ` [${entry.kit.name} kit]`
|
|
133
|
+
lines.push(
|
|
134
|
+
spec === undefined
|
|
135
|
+
? `${entry.name}${origin}`
|
|
136
|
+
: `${renderUsage(spec)}${origin}\n ${spec.purpose}`
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
if (catalog.presets.length > 0) {
|
|
140
|
+
lines.push("", "Presets (pack: command):")
|
|
141
|
+
for (const preset of catalog.presets) {
|
|
142
|
+
lines.push(` ${preset.name} (${preset.pack}): ${preset.command.join(" ")}`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return lines.join("\n")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const catalogJson = (
|
|
149
|
+
catalog: ToolCatalog,
|
|
150
|
+
specs: ReadonlyMap<string, ToolSpec>
|
|
151
|
+
): typeof Schema.Json.Type => ({
|
|
152
|
+
tools: catalog.entries.map((entry) => {
|
|
153
|
+
const spec = specs.get(entry.name)
|
|
154
|
+
return {
|
|
155
|
+
name: entry.name,
|
|
156
|
+
origin: entry._tag === "builtin" ? "builtin" : `kit:${entry.kit.name}`,
|
|
157
|
+
...(spec === undefined ? {} : { purpose: spec.purpose, usage: renderUsage(spec) })
|
|
158
|
+
}
|
|
159
|
+
}),
|
|
160
|
+
presets: catalog.presets.map((preset) => ({
|
|
161
|
+
name: preset.name,
|
|
162
|
+
pack: preset.pack,
|
|
163
|
+
command: preset.command.join(" ")
|
|
164
|
+
}))
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
/** Exit status for a report: 0 ok, 1 issues. Errors exit 2 (the CLI's own). */
|
|
168
|
+
export const reportExitCode = (report: ToolReport): number => (report.ok ? 0 : 1)
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The paragraph a coder's brief carries (ADR 0048): one rule, then each
|
|
172
|
+
* preset of the story's pack with the command to run. `LLM4TS_TOOL_BRIEF=off`
|
|
173
|
+
* drops it, for a run without the catalog to compare against.
|
|
174
|
+
*/
|
|
175
|
+
export const toolBrief = (
|
|
176
|
+
presets: ReadonlyArray<ToolPreset>,
|
|
177
|
+
specs: ReadonlyMap<string, ToolSpec>,
|
|
178
|
+
environment: Readonly<Record<string, string | undefined>> = process.env
|
|
179
|
+
): string | undefined => {
|
|
180
|
+
if (environment.LLM4TS_TOOL_BRIEF?.trim().toLowerCase() === "off") {
|
|
181
|
+
return undefined
|
|
182
|
+
}
|
|
183
|
+
const lines = [
|
|
184
|
+
"Tools: for validating a document, comparing two outputs, or checking a contract, run the",
|
|
185
|
+
"project's tool instead of writing a script — `llm4ts tool <name> [--flag value]` prints JSON",
|
|
186
|
+
"(ok, summary, issues). `llm4ts tool list` shows every tool; `llm4ts tool describe <name>`",
|
|
187
|
+
"its arguments. Never write a Python or Node one-liner for a job a tool covers."
|
|
188
|
+
]
|
|
189
|
+
if (presets.length > 0) {
|
|
190
|
+
lines.push("", "This pack's presets (run them by name from the worktree root):")
|
|
191
|
+
for (const preset of presets) {
|
|
192
|
+
const [toolName, ...rest] = preset.command
|
|
193
|
+
const spec = toolName === undefined ? undefined : specs.get(toolName)
|
|
194
|
+
const fixed = rest.length === 0 ? "" : ` (fixed: ${rest.join(" ")})`
|
|
195
|
+
const purpose = spec === undefined ? "" : ` — ${spec.purpose}`
|
|
196
|
+
lines.push(`- \`llm4ts tool ${preset.name}\`${fixed}${purpose}`)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return lines.join("\n")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Specs of the built-in tools, known without running anything. */
|
|
203
|
+
export const builtinSpecs = (): ReadonlyMap<string, ToolSpec> =>
|
|
204
|
+
new Map(builtinTools.map((tool) => [tool.name, specOf(tool)]))
|
package/dist/CoderPolicy.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import * as Schema from "effect/Schema";
|
|
2
|
-
import type { Grants } from "@llm4ts/core/Capability";
|
|
3
|
-
import { CliConnectorConfig } from "@llm4ts/core/ConnectorConfig";
|
|
4
|
-
declare const AppliedCoderPolicy_base: Schema.Class<AppliedCoderPolicy, Schema.Struct<{
|
|
5
|
-
readonly config: typeof CliConnectorConfig;
|
|
6
|
-
readonly unenforceable: Schema.$Array<Schema.String>;
|
|
7
|
-
}>, {}>;
|
|
8
|
-
export declare class AppliedCoderPolicy extends AppliedCoderPolicy_base {
|
|
9
|
-
}
|
|
10
|
-
export declare const applyCoderPolicy: (config: CliConnectorConfig, grants: Grants) => AppliedCoderPolicy;
|
|
11
|
-
export {};
|
|
12
|
-
//# sourceMappingURL=CoderPolicy.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CoderPolicy.d.ts","sourceRoot":"","sources":["../src/CoderPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;;;;;AAGjE,qBAAa,kBAAmB,SAAQ,uBAGtC;CAAG;AAgDL,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,kBAAkB,EAC1B,QAAQ,MAAM,KACb,kBAqBF,CAAA"}
|