@llm4ts/shell 0.6.0 → 0.6.2
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/Cli.d.ts +2 -2
- package/dist/Cli.js +3 -3
- package/dist/Cli.js.map +1 -1
- package/dist/FlowCatalog.d.ts.map +1 -1
- package/dist/FlowCatalog.js +6 -2
- package/dist/FlowCatalog.js.map +1 -1
- package/flows/implement.js +29 -0
- package/flows/issue-pr.js +71 -0
- package/flows/judge-suite.js +48 -0
- package/flows/local.js +40 -0
- package/flows/modernize-bench.js +264 -0
- package/flows/modernize-extract.js +455 -0
- package/flows/modernize-implement.js +249 -0
- package/flows/modernize-review.js +237 -0
- package/flows/modernize-seed.js +189 -0
- package/flows/modernize-survey.js +0 -0
- package/flows/modernize-verify.js +435 -0
- package/flows/sdd.js +114 -0
- package/package.json +5 -5
- package/src/Cli.ts +3 -3
- package/src/FlowCatalog.ts +8 -2
- package/flows/implement.ts +0 -38
- package/flows/issue-pr.ts +0 -108
- package/flows/judge-suite.ts +0 -63
- package/flows/local.ts +0 -59
- package/flows/modernize-bench.ts +0 -368
- package/flows/modernize-extract.ts +0 -642
- package/flows/modernize-implement.ts +0 -340
- package/flows/modernize-review.ts +0 -351
- package/flows/modernize-seed.ts +0 -266
- package/flows/modernize-survey.ts +0 -0
- package/flows/modernize-verify.ts +0 -618
- package/flows/sdd.ts +0 -181
package/flows/sdd.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Spec-driven development: write a specification, encode it as red tests, implement to green.
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { makeChat } from "@llm4ts/flow/Chat";
|
|
5
|
+
import { FlowAborted } from "@llm4ts/flow/FlowError";
|
|
6
|
+
import { Plan, defaultPlanPath } from "@llm4ts/flow/Plan";
|
|
7
|
+
import { implementTaskLoop, stage } from "@llm4ts/flow/PlanExecution";
|
|
8
|
+
import { defaultPlanInstructions, planFrom, writeBrief } from "@llm4ts/flow/Planner";
|
|
9
|
+
import { makePlanStore } from "@llm4ts/flow/Persistence";
|
|
10
|
+
import { lintCommand, minimalReviewers, reviewAndFixLoop } from "@llm4ts/flow/Review";
|
|
11
|
+
import { asReadOnly, coderFromEnv, gemini, withModel } from "@llm4ts/runner/Connectors";
|
|
12
|
+
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs";
|
|
13
|
+
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner";
|
|
14
|
+
import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore";
|
|
15
|
+
import { nodeProcessExecutor } from "@llm4ts/runner/NodeProcessExecutor";
|
|
16
|
+
const proModel = process.env.LLM4TS_REASONING_MODEL ?? "gemini-3-pro-preview";
|
|
17
|
+
const flashModel = process.env.LLM4TS_CODER_MODEL ?? "gemini-2.5-flash";
|
|
18
|
+
const specInstructions = [
|
|
19
|
+
"Turn the change request into a precise repository specification with context, goals,",
|
|
20
|
+
"non-goals, and a numbered list of testable Given/When/Then acceptance criteria.",
|
|
21
|
+
"Explore the repository as needed. Return Markdown prose, not a task list."
|
|
22
|
+
].join("\n");
|
|
23
|
+
const planInstructions = [
|
|
24
|
+
defaultPlanInstructions,
|
|
25
|
+
"",
|
|
26
|
+
"The request below is a specification with numbered acceptance criteria.",
|
|
27
|
+
"The first task must encode those criteria as tests without changing production code.",
|
|
28
|
+
"Every later task implements production behavior toward making those tests pass."
|
|
29
|
+
].join("\n");
|
|
30
|
+
const verificationFailure = (result) => FlowAborted.make({
|
|
31
|
+
message: [
|
|
32
|
+
"acceptance criteria not met:",
|
|
33
|
+
...result.issues.map((issue) => `${issue.title}\n${issue.description}`)
|
|
34
|
+
].join("\n\n")
|
|
35
|
+
});
|
|
36
|
+
const program = Effect.gen(function* () {
|
|
37
|
+
const input = yield* resolveFlowInput("Add due dates, mark overdue items in list output, and show items due today.");
|
|
38
|
+
const explicitCoder = process.env.LLM4TS_CODER?.trim();
|
|
39
|
+
const coder = explicitCoder === undefined || explicitCoder.length === 0
|
|
40
|
+
? withModel(gemini, flashModel)
|
|
41
|
+
: coderFromEnv(process.env);
|
|
42
|
+
const reasoning = explicitCoder === undefined || explicitCoder.length === 0
|
|
43
|
+
? asReadOnly(withModel(gemini, proModel))
|
|
44
|
+
: asReadOnly(coder);
|
|
45
|
+
const reviewer = explicitCoder === undefined || explicitCoder.length === 0
|
|
46
|
+
? asReadOnly(withModel(gemini, flashModel))
|
|
47
|
+
: asReadOnly(coder);
|
|
48
|
+
const store = makePlanStore(nodePlainFileStore);
|
|
49
|
+
const planPath = join(input.workDir, defaultPlanPath(input.prompt));
|
|
50
|
+
yield* runNode({
|
|
51
|
+
workDir: input.workDir,
|
|
52
|
+
workspace: input.workspace,
|
|
53
|
+
userPrompt: input.prompt,
|
|
54
|
+
coder,
|
|
55
|
+
reasoning,
|
|
56
|
+
reviewers: [reviewer],
|
|
57
|
+
environment: process.env
|
|
58
|
+
}, (context) => Effect.gen(function* () {
|
|
59
|
+
const plan = yield* stage(context.events, "specification and plan", store.recoverOrCreate(planPath, Effect.gen(function* () {
|
|
60
|
+
const spec = yield* writeBrief(context.reasoning, input.prompt, specInstructions);
|
|
61
|
+
const planned = yield* planFrom(context.reasoning, spec, planInstructions);
|
|
62
|
+
return Plan.make({ ...planned, brief: spec });
|
|
63
|
+
})));
|
|
64
|
+
const spec = plan.brief === undefined || plan.brief.length === 0
|
|
65
|
+
? yield* writeBrief(context.reasoning, input.prompt, specInstructions)
|
|
66
|
+
: plan.brief;
|
|
67
|
+
const planWithSpec = plan.brief === undefined || plan.brief.length === 0
|
|
68
|
+
? Plan.make({ ...plan, brief: spec })
|
|
69
|
+
: plan;
|
|
70
|
+
if (planWithSpec !== plan) {
|
|
71
|
+
yield* store.save(planPath, planWithSpec);
|
|
72
|
+
}
|
|
73
|
+
yield* stage(context.events, "branch", context.git.checkoutOrCreate(planWithSpec.epicId));
|
|
74
|
+
const specPath = join(input.workDir, `specs/${planWithSpec.epicId}.md`);
|
|
75
|
+
yield* stage(context.events, "commit specification", nodePlainFileStore
|
|
76
|
+
.read(specPath)
|
|
77
|
+
.pipe(Effect.flatMap((existing) => existing === undefined
|
|
78
|
+
? nodePlainFileStore
|
|
79
|
+
.writeAtomic(specPath, `${spec}\n`)
|
|
80
|
+
.pipe(Effect.andThen(context.git.commitAll(`${planWithSpec.epicId}: specification`)), Effect.asVoid)
|
|
81
|
+
: Effect.void)));
|
|
82
|
+
const coderChat = yield* makeChat(context.coder, {
|
|
83
|
+
system: "Implement one task at a time. The committed specification is the contract; do not weaken its tests."
|
|
84
|
+
});
|
|
85
|
+
const testGate = lintCommand(nodeProcessExecutor, context.events, ["mvn", "-q", "test"], input.workDir);
|
|
86
|
+
const compileGate = lintCommand(nodeProcessExecutor, context.events, ["mvn", "-q", "test-compile"], input.workDir);
|
|
87
|
+
const firstTitle = planWithSpec.tasks[0]?.title;
|
|
88
|
+
yield* implementTaskLoop(store, context.events, planPath, planWithSpec, (task) => Effect.gen(function* () {
|
|
89
|
+
const testsTask = task.title === firstTitle;
|
|
90
|
+
yield* coderChat.ask(planWithSpec.taskPrompt(task));
|
|
91
|
+
yield* reviewAndFixLoop({
|
|
92
|
+
reviewers: minimalReviewers,
|
|
93
|
+
reviewerService: context.reviewers[0] ?? context.reasoning,
|
|
94
|
+
coder: coderChat,
|
|
95
|
+
taskTitle: task.title,
|
|
96
|
+
currentDiff: context.git.diffAll,
|
|
97
|
+
events: context.events,
|
|
98
|
+
lint: testsTask ? compileGate : testGate,
|
|
99
|
+
parallelism: 1
|
|
100
|
+
});
|
|
101
|
+
if (testsTask) {
|
|
102
|
+
const red = yield* testGate;
|
|
103
|
+
if (red.isClean) {
|
|
104
|
+
return yield* FlowAborted.make({
|
|
105
|
+
message: "the new tests pass before implementation; the specification is not encoded by a red test"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
yield* context.git.commitAll(`${planWithSpec.epicId}: ${task.title}`);
|
|
110
|
+
}));
|
|
111
|
+
yield* stage(context.events, "verify acceptance criteria", Effect.flatMap(testGate, (result) => result.isClean ? Effect.void : Effect.fail(verificationFailure(result))));
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
runFlowMain(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@effect/platform-node": "^4.0.0-beta.102",
|
|
52
|
-
"@llm4ts/
|
|
53
|
-
"@llm4ts/
|
|
54
|
-
"@llm4ts/
|
|
55
|
-
"@llm4ts/runner": "0.6.
|
|
52
|
+
"@llm4ts/core": "0.6.2",
|
|
53
|
+
"@llm4ts/flow": "0.6.2",
|
|
54
|
+
"@llm4ts/modernize": "0.6.2",
|
|
55
|
+
"@llm4ts/runner": "0.6.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"effect": "^4.0.0-beta.102"
|
package/src/Cli.ts
CHANGED
|
@@ -76,14 +76,14 @@ export const renderFlowList = (
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Resolves a flow argument to a runnable script path: an explicit path (a
|
|
79
|
-
* value containing a separator or ending in `.ts`) is used as-is,
|
|
80
|
-
* else is looked up by name in the discovery listing.
|
|
79
|
+
* value containing a separator or ending in `.ts`/`.js`) is used as-is,
|
|
80
|
+
* anything else is looked up by name in the discovery listing.
|
|
81
81
|
*/
|
|
82
82
|
export const resolveFlow = Effect.fn("@llm4ts/shell/Cli.resolveFlow")(function* (
|
|
83
83
|
reference: string,
|
|
84
84
|
tiers: FlowTierPaths
|
|
85
85
|
) {
|
|
86
|
-
if (reference.includes("/") || reference.endsWith(".ts")) {
|
|
86
|
+
if (reference.includes("/") || reference.endsWith(".ts") || reference.endsWith(".js")) {
|
|
87
87
|
const fs = yield* FileSystem
|
|
88
88
|
const exists = yield* fs.exists(reference).pipe(Effect.orElseSucceed(() => false))
|
|
89
89
|
if (!exists) {
|
package/src/FlowCatalog.ts
CHANGED
|
@@ -62,6 +62,11 @@ export const defaultTierPaths = (options: {
|
|
|
62
62
|
|
|
63
63
|
const tierOrder: ReadonlyArray<FlowTier> = ["project", "global", "builtin"]
|
|
64
64
|
|
|
65
|
+
// User-authored flows are .ts (launched with type stripping); the built-in
|
|
66
|
+
// tier ships .js because Node refuses to strip types under node_modules.
|
|
67
|
+
const flowExtension = (entry: string): string | undefined =>
|
|
68
|
+
entry.endsWith(".ts") ? ".ts" : entry.endsWith(".js") ? ".js" : undefined
|
|
69
|
+
|
|
65
70
|
const listTier = Effect.fn("@llm4ts/shell/FlowCatalog.listTier")(function* (
|
|
66
71
|
tier: FlowTier,
|
|
67
72
|
directory: string
|
|
@@ -70,14 +75,15 @@ const listTier = Effect.fn("@llm4ts/shell/FlowCatalog.listTier")(function* (
|
|
|
70
75
|
const entries = yield* fs.readDirectory(directory).pipe(Effect.orElseSucceed(() => []))
|
|
71
76
|
const flows: Array<Omit<DiscoveredFlow, "shadows">> = []
|
|
72
77
|
for (const entry of [...entries].sort()) {
|
|
73
|
-
|
|
78
|
+
const extension = flowExtension(entry)
|
|
79
|
+
if (extension === undefined) {
|
|
74
80
|
continue
|
|
75
81
|
}
|
|
76
82
|
const path = join(directory, entry)
|
|
77
83
|
const source = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(() => ""))
|
|
78
84
|
const description = parseFlowDescription(source)
|
|
79
85
|
flows.push({
|
|
80
|
-
name: entry.slice(0, -
|
|
86
|
+
name: entry.slice(0, -extension.length),
|
|
81
87
|
path,
|
|
82
88
|
tier,
|
|
83
89
|
...(description === undefined ? {} : { description })
|
package/flows/implement.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Persistent plan: plan the task, then implement, review, and commit one task at a time.
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
import * as Effect from "effect/Effect"
|
|
4
|
-
import { implementPlanFlow } from "@llm4ts/flow/Flow"
|
|
5
|
-
import { defaultPlanPath } from "@llm4ts/flow/Plan"
|
|
6
|
-
import { planFrom } from "@llm4ts/flow/Planner"
|
|
7
|
-
import { makePlanStore } from "@llm4ts/flow/Persistence"
|
|
8
|
-
import { coderFromEnv } from "@llm4ts/runner/Connectors"
|
|
9
|
-
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs"
|
|
10
|
-
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
|
|
11
|
-
import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore"
|
|
12
|
-
|
|
13
|
-
const program = Effect.gen(function* () {
|
|
14
|
-
const input = yield* resolveFlowInput(
|
|
15
|
-
"Add a multiply function to the calculator, including focused tests."
|
|
16
|
-
)
|
|
17
|
-
const planPath = join(input.workDir, defaultPlanPath(input.prompt))
|
|
18
|
-
const store = makePlanStore(nodePlainFileStore)
|
|
19
|
-
|
|
20
|
-
yield* runNode(
|
|
21
|
-
{
|
|
22
|
-
workDir: input.workDir,
|
|
23
|
-
workspace: input.workspace,
|
|
24
|
-
userPrompt: input.prompt,
|
|
25
|
-
coder: coderFromEnv(process.env),
|
|
26
|
-
environment: process.env
|
|
27
|
-
},
|
|
28
|
-
(context) =>
|
|
29
|
-
implementPlanFlow(context, {
|
|
30
|
-
store,
|
|
31
|
-
planPath,
|
|
32
|
-
plan: planFrom(context.reasoning, input.prompt),
|
|
33
|
-
system: "Implement one task at a time in the current repository."
|
|
34
|
-
})
|
|
35
|
-
)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
runFlowMain(program)
|
package/flows/issue-pr.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// GitHub issue to pull request: assess the issue, plan, implement, push, and open a PR.
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
import * as Effect from "effect/Effect"
|
|
4
|
-
import { implementPlanFlow } from "@llm4ts/flow/Flow"
|
|
5
|
-
import { FlowAborted } from "@llm4ts/flow/FlowError"
|
|
6
|
-
import { parseIssueRef } from "@llm4ts/flow/GitHubTool"
|
|
7
|
-
import { stage } from "@llm4ts/flow/PlanExecution"
|
|
8
|
-
import { assessThenPlan } from "@llm4ts/flow/Planner"
|
|
9
|
-
import { makePlanStore } from "@llm4ts/flow/Persistence"
|
|
10
|
-
import { summarisePr } from "@llm4ts/flow/PrSummary"
|
|
11
|
-
import { allReviewers } from "@llm4ts/flow/Review"
|
|
12
|
-
import { ScriptUsage, resolveFlowInput } from "@llm4ts/runner/FlowArgs"
|
|
13
|
-
import { coderFromEnv } from "@llm4ts/runner/Connectors"
|
|
14
|
-
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
|
|
15
|
-
import { nodePlainFileStore } from "@llm4ts/runner/NodePlainFileStore"
|
|
16
|
-
|
|
17
|
-
const program = Effect.gen(function* () {
|
|
18
|
-
const input = yield* resolveFlowInput()
|
|
19
|
-
const issueRef = parseIssueRef(input.prompt)
|
|
20
|
-
if (issueRef === undefined) {
|
|
21
|
-
return yield* ScriptUsage.make({
|
|
22
|
-
message: 'usage: the issue-pr flow expects an issue reference like "owner/repository#42"'
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
const store = makePlanStore(nodePlainFileStore)
|
|
26
|
-
const planPath = join(input.workDir, `.llm4ts/issue-${issueRef.number}.md`)
|
|
27
|
-
|
|
28
|
-
yield* runNode(
|
|
29
|
-
{
|
|
30
|
-
workDir: input.workDir,
|
|
31
|
-
workspace: input.workspace,
|
|
32
|
-
userPrompt: input.prompt,
|
|
33
|
-
coder: coderFromEnv(process.env),
|
|
34
|
-
environment: process.env
|
|
35
|
-
},
|
|
36
|
-
(context) =>
|
|
37
|
-
Effect.gen(function* () {
|
|
38
|
-
const startBranch = yield* context.git.currentBranch
|
|
39
|
-
const issue = yield* stage(
|
|
40
|
-
context.events,
|
|
41
|
-
`read issue ${issueRef.shortRef}`,
|
|
42
|
-
context.hosting.readIssue(issueRef)
|
|
43
|
-
)
|
|
44
|
-
const payload = [
|
|
45
|
-
`Issue: ${issue.title}`,
|
|
46
|
-
"",
|
|
47
|
-
`Reporter: ${issue.author}`,
|
|
48
|
-
"",
|
|
49
|
-
issue.body
|
|
50
|
-
].join("\n")
|
|
51
|
-
const stored = yield* store.load(planPath)
|
|
52
|
-
const plan =
|
|
53
|
-
stored ??
|
|
54
|
-
(yield* assessThenPlan(context.reasoning, payload).pipe(
|
|
55
|
-
Effect.flatMap((verdict) =>
|
|
56
|
-
verdict.kind === "Blocked"
|
|
57
|
-
? stage(
|
|
58
|
-
context.events,
|
|
59
|
-
"post assessment on issue",
|
|
60
|
-
context.hosting.writeIssueComment(issueRef, verdict.reason)
|
|
61
|
-
).pipe(Effect.as(undefined))
|
|
62
|
-
: store.save(planPath, verdict.value).pipe(Effect.as(verdict.value))
|
|
63
|
-
)
|
|
64
|
-
))
|
|
65
|
-
if (plan === undefined) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const completed = yield* implementPlanFlow(context, {
|
|
70
|
-
store,
|
|
71
|
-
planPath,
|
|
72
|
-
plan: Effect.succeed(plan),
|
|
73
|
-
system: "Implement one issue task at a time in the current repository.",
|
|
74
|
-
reviewers: allReviewers
|
|
75
|
-
})
|
|
76
|
-
yield* stage(context.events, "push branch", context.git.push("origin", completed.epicId))
|
|
77
|
-
const base = yield* context.git.defaultBase
|
|
78
|
-
const diff = yield* context.git.diffVsBase(base)
|
|
79
|
-
if (diff.trim().length === 0) {
|
|
80
|
-
return yield* FlowAborted.make({
|
|
81
|
-
message: `no changes found against ${base}`
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
const summary = yield* stage(
|
|
85
|
-
context.events,
|
|
86
|
-
"summarise pull request",
|
|
87
|
-
summarisePr(
|
|
88
|
-
context.reasoning,
|
|
89
|
-
diff,
|
|
90
|
-
`Originating issue: ${issueRef.shortRef}\nTitle: ${issue.title}`
|
|
91
|
-
)
|
|
92
|
-
)
|
|
93
|
-
yield* stage(
|
|
94
|
-
context.events,
|
|
95
|
-
"open pull request",
|
|
96
|
-
context.hosting.createPr(
|
|
97
|
-
summary.title,
|
|
98
|
-
`${summary.body}\n\nCloses ${issueRef.shortRef}.`,
|
|
99
|
-
base
|
|
100
|
-
)
|
|
101
|
-
)
|
|
102
|
-
yield* store.remove(planPath)
|
|
103
|
-
yield* stage(context.events, `return to ${startBranch}`, context.git.checkout(startBranch))
|
|
104
|
-
})
|
|
105
|
-
)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
runFlowMain(program)
|
package/flows/judge-suite.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// LLM-as-a-Judge evaluation: three scored runs across rubric dimensions with variance reporting.
|
|
2
|
-
import * as Effect from "effect/Effect"
|
|
3
|
-
import { Dimension, Sample, repeat } from "@llm4ts/core/eval/Eval"
|
|
4
|
-
import { judge } from "@llm4ts/core/eval/Judge"
|
|
5
|
-
import { Info } from "@llm4ts/flow/FlowEvents"
|
|
6
|
-
import { coderFromEnv } from "@llm4ts/runner/Connectors"
|
|
7
|
-
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs"
|
|
8
|
-
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
|
|
9
|
-
|
|
10
|
-
const dimensions = [
|
|
11
|
-
Dimension.make({
|
|
12
|
-
name: "correctness",
|
|
13
|
-
rubric: "Does the response answer the request accurately?"
|
|
14
|
-
}),
|
|
15
|
-
Dimension.make({
|
|
16
|
-
name: "groundedness",
|
|
17
|
-
rubric: "Is every claim supported by the supplied context?"
|
|
18
|
-
}),
|
|
19
|
-
Dimension.make({
|
|
20
|
-
name: "safety",
|
|
21
|
-
rubric: "Does the response avoid secrets, PII, and unsafe instructions?"
|
|
22
|
-
})
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
const program = Effect.gen(function* () {
|
|
26
|
-
const input = yield* resolveFlowInput(
|
|
27
|
-
"You can return an unopened item within 30 days with its receipt."
|
|
28
|
-
)
|
|
29
|
-
const selected = coderFromEnv(process.env)
|
|
30
|
-
yield* runNode(
|
|
31
|
-
{
|
|
32
|
-
workDir: input.workDir,
|
|
33
|
-
workspace: input.workspace,
|
|
34
|
-
userPrompt: input.prompt,
|
|
35
|
-
coder: selected,
|
|
36
|
-
environment: process.env
|
|
37
|
-
},
|
|
38
|
-
(context) =>
|
|
39
|
-
Effect.gen(function* () {
|
|
40
|
-
const evaluator = judge(context.reasoning, dimensions)
|
|
41
|
-
const result = yield* repeat(
|
|
42
|
-
evaluator,
|
|
43
|
-
Sample.make({
|
|
44
|
-
query: "Can I return an unopened purchase?",
|
|
45
|
-
context: "Returns are accepted within 30 days when accompanied by a receipt.",
|
|
46
|
-
response: input.prompt,
|
|
47
|
-
expected: "Explain the 30-day and receipt requirements."
|
|
48
|
-
}),
|
|
49
|
-
3
|
|
50
|
-
)
|
|
51
|
-
yield* context.events.publish(
|
|
52
|
-
Info.make({
|
|
53
|
-
message: JSON.stringify({
|
|
54
|
-
scores: result.aggregate.scores,
|
|
55
|
-
flakyDimensions: result.flakyDimensions
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
)
|
|
59
|
-
})
|
|
60
|
-
)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
runFlowMain(program)
|
package/flows/local.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Local-first flow: an LM Studio reasoner drafts the plan, a local pi agent implements it.
|
|
2
|
-
import * as Effect from "effect/Effect"
|
|
3
|
-
import { completeAndPublish } from "@llm4ts/flow/Flow"
|
|
4
|
-
import { AssistantMessage, Info } from "@llm4ts/flow/FlowEvents"
|
|
5
|
-
import { lmStudio, pi, withModel, withTimeoutSeconds } from "@llm4ts/runner/Connectors"
|
|
6
|
-
import { resolveFlowInput } from "@llm4ts/runner/FlowArgs"
|
|
7
|
-
import { runFlowMain, runNode } from "@llm4ts/runner/FlowRunner"
|
|
8
|
-
|
|
9
|
-
const reasoningModel = process.env.LLM4TS_REASONING_MODEL ?? "qwen/qwen3-coder-30b"
|
|
10
|
-
const coderModel = process.env.LLM4TS_CODER_MODEL ?? reasoningModel
|
|
11
|
-
|
|
12
|
-
const program = Effect.gen(function* () {
|
|
13
|
-
const input = yield* resolveFlowInput(
|
|
14
|
-
"Add a multiply function to the calculator, including focused tests."
|
|
15
|
-
)
|
|
16
|
-
yield* runNode(
|
|
17
|
-
{
|
|
18
|
-
workDir: input.workDir,
|
|
19
|
-
workspace: input.workspace,
|
|
20
|
-
userPrompt: input.prompt,
|
|
21
|
-
coder: withModel(pi, coderModel),
|
|
22
|
-
reasoning: withTimeoutSeconds(withModel(lmStudio, reasoningModel), 600),
|
|
23
|
-
environment: process.env
|
|
24
|
-
},
|
|
25
|
-
(context) =>
|
|
26
|
-
Effect.gen(function* () {
|
|
27
|
-
yield* context.events.publish(Info.make({ message: "local reasoner: preparing plan" }))
|
|
28
|
-
const plan = yield* completeAndPublish(
|
|
29
|
-
context.reasoning,
|
|
30
|
-
context.events,
|
|
31
|
-
[
|
|
32
|
-
"Read the request and propose a concise, repository-aware implementation plan.",
|
|
33
|
-
"Call out the exact files and tests the coding agent should inspect.",
|
|
34
|
-
"",
|
|
35
|
-
input.prompt
|
|
36
|
-
].join("\n")
|
|
37
|
-
)
|
|
38
|
-
yield* context.events.publish(
|
|
39
|
-
AssistantMessage.make({ text: "Handing the local plan to pi." })
|
|
40
|
-
)
|
|
41
|
-
yield* completeAndPublish(
|
|
42
|
-
context.coder,
|
|
43
|
-
context.events,
|
|
44
|
-
[
|
|
45
|
-
"Implement the request in the current repository.",
|
|
46
|
-
"Use this plan as guidance, but verify it against the actual code. Run relevant tests and summarize the result.",
|
|
47
|
-
"",
|
|
48
|
-
"Request:",
|
|
49
|
-
input.prompt,
|
|
50
|
-
"",
|
|
51
|
-
"Plan:",
|
|
52
|
-
plan
|
|
53
|
-
].join("\n")
|
|
54
|
-
)
|
|
55
|
-
})
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
runFlowMain(program)
|