@llm4ts/flow 0.1.3 → 0.1.4
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/Flow.d.ts +7 -0
- package/dist/Flow.d.ts.map +1 -1
- package/dist/Flow.js +50 -9
- package/dist/Flow.js.map +1 -1
- package/dist/PlanExecution.d.ts +1 -1
- package/dist/PlanExecution.d.ts.map +1 -1
- package/dist/PlanExecution.js +1 -1
- package/dist/PlanExecution.js.map +1 -1
- package/package.json +2 -2
- package/src/Flow.ts +95 -37
- package/src/PlanExecution.ts +2 -2
package/dist/Flow.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ export interface ImplementPlanOptions {
|
|
|
14
14
|
readonly planPath: string;
|
|
15
15
|
readonly plan: Effect.Effect<Plan, FlowError>;
|
|
16
16
|
readonly system?: string;
|
|
17
|
+
/**
|
|
18
|
+
* When true, each task gets a fresh Chat (system prompt plus the plan's
|
|
19
|
+
* current render, showing prior tasks' completion status); that task's
|
|
20
|
+
* review-fix rounds share the same Chat. When false or omitted (default),
|
|
21
|
+
* one Chat is shared across every task in the plan.
|
|
22
|
+
*/
|
|
23
|
+
readonly chatPerTask?: boolean;
|
|
17
24
|
readonly reviewers?: ReadonlyArray<Reviewer>;
|
|
18
25
|
readonly commitMessage?: (plan: Plan, task: Task) => string;
|
|
19
26
|
readonly checkoutBranch?: boolean;
|
package/dist/Flow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Flow.d.ts","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC9E,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,KAAG,eACd,CAAA;AAE3C,eAAO,MAAM,kBAAkB,mHAU7B,CAAA;AAEF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IAC3D,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;CACjD;
|
|
1
|
+
{"version":3,"file":"Flow.d.ts","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC9E,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,KAAG,eACd,CAAA;AAE3C,eAAO,MAAM,kBAAkB,mHAU7B,CAAA;AAEF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IAC3D,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;CACjD;AAOD,eAAO,MAAM,iBAAiB,2gBAgG5B,CAAA"}
|
package/dist/Flow.js
CHANGED
|
@@ -12,22 +12,60 @@ export const completeAndPublish = Effect.fn("@llm4ts/flow/Flow.completeAndPublis
|
|
|
12
12
|
return response.content;
|
|
13
13
|
});
|
|
14
14
|
const defaultCommitMessage = (plan, task) => `${plan.epicId}: ${task.title}`;
|
|
15
|
+
const composeSystem = (base, note) => [base, note].filter((part) => part !== undefined && part.length > 0).join("\n\n");
|
|
15
16
|
export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(function* (context, options) {
|
|
16
17
|
const plan = yield* options.store.recoverOrCreate(options.planPath, options.plan);
|
|
17
18
|
if (options.checkoutBranch !== false) {
|
|
18
19
|
yield* stage(context.events, "branch", context.git.checkoutOrCreate(plan.epicId));
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
let sharedCoder;
|
|
22
|
+
if (options.chatPerTask !== true) {
|
|
23
|
+
sharedCoder = yield* makeChat(context.coder, {
|
|
24
|
+
...(options.system === undefined ? {} : { system: options.system })
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return yield* implementTaskLoop(options.store, context.events, options.planPath, plan, (task, planSoFar) => Effect.gen(function* () {
|
|
28
|
+
// `sharedCoder`'s definedness mirrors `options.chatPerTask !== true`
|
|
29
|
+
// above: when it's set, every task reuses it; when it's undefined,
|
|
30
|
+
// chatPerTask is active and each task builds its own fresh Chat.
|
|
31
|
+
let coder;
|
|
32
|
+
if (sharedCoder !== undefined) {
|
|
33
|
+
coder = sharedCoder;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
coder = yield* makeChat(context.coder, {
|
|
37
|
+
system: composeSystem(options.system, planSoFar.render)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// `plan.taskPrompt` deliberately reads the frozen `plan` captured at
|
|
41
|
+
// the top of this function: a task prompt only needs that task's own
|
|
42
|
+
// details. `planSoFar`, threaded through by implementTaskLoop, is the
|
|
43
|
+
// single source of truth for completion progress instead.
|
|
24
44
|
yield* coder.ask(plan.taskPrompt(task));
|
|
25
45
|
const produced = yield* context.git.diffAll;
|
|
26
46
|
if (produced.trim().length === 0) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
// An empty diff is ambiguous: the task may be genuinely satisfied
|
|
48
|
+
// already, or the coder may simply have produced nothing. Ask
|
|
49
|
+
// explicitly instead of inferring from absence; a task that
|
|
50
|
+
// produces no changes and no confirmation fails rather than being
|
|
51
|
+
// silently marked complete.
|
|
52
|
+
const confirmation = yield* coder.ask([
|
|
53
|
+
"Your previous turn produced no file changes.",
|
|
54
|
+
`If the task "${task.title}" is already fully satisfied by the current state of the repository, reply with exactly TASK_ALREADY_SATISFIED and nothing else.`,
|
|
55
|
+
"Otherwise, implement the task now."
|
|
56
|
+
].join("\n"));
|
|
57
|
+
const afterConfirmation = yield* context.git.diffAll;
|
|
58
|
+
if (afterConfirmation.trim().length === 0) {
|
|
59
|
+
if (confirmation.includes("TASK_ALREADY_SATISFIED")) {
|
|
60
|
+
yield* context.events.publish(Info.make({
|
|
61
|
+
message: `task "${task.title}" confirmed already satisfied; skipping review and commit`
|
|
62
|
+
}));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
return yield* FlowAborted.make({
|
|
66
|
+
message: `task "${task.title}" produced no changes and did not confirm TASK_ALREADY_SATISFIED; failing instead of marking it complete`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
31
69
|
}
|
|
32
70
|
yield* reviewAndFixLoop({
|
|
33
71
|
reviewers: options.reviewers ?? minimalReviewers,
|
|
@@ -46,7 +84,10 @@ export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(fu
|
|
|
46
84
|
return yield* FlowAborted.make({
|
|
47
85
|
message: [
|
|
48
86
|
`task "${task.title}": the lint gate is still failing after review settled; refusing to commit`,
|
|
49
|
-
...gate.issues.map((issue) =>
|
|
87
|
+
...gate.issues.map((issue) => {
|
|
88
|
+
const detail = issue.description.length === 0 ? "" : `\n${issue.description.slice(-2000)}`;
|
|
89
|
+
return `- [${issue.severity}] ${issue.title}${detail}`;
|
|
90
|
+
})
|
|
50
91
|
].join("\n")
|
|
51
92
|
});
|
|
52
93
|
}
|
package/dist/Flow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Flow.js","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"Flow.js","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"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"}
|
package/dist/PlanExecution.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ import { type FlowEventsShape } from "./FlowEvents.ts";
|
|
|
3
3
|
import type { Plan, Task } from "./Plan.ts";
|
|
4
4
|
import type { PlanStoreShape } from "./Persistence.ts";
|
|
5
5
|
export declare const stage: <A, E, R>(events: FlowEventsShape, name: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
6
|
-
export declare const implementTaskLoop: <E, R>(store: PlanStoreShape, events: FlowEventsShape, planPath: string, plan: Plan, perTask: (task: Task) => Effect.Effect<void, E, R>) => Effect.Effect<Plan, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").BudgetExceeded | E, R>;
|
|
6
|
+
export declare const implementTaskLoop: <E, R>(store: PlanStoreShape, events: FlowEventsShape, planPath: string, plan: Plan, perTask: (task: Task, planSoFar: Plan) => Effect.Effect<void, E, R>) => Effect.Effect<Plan, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").BudgetExceeded | E, R>;
|
|
7
7
|
//# sourceMappingURL=PlanExecution.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanExecution.d.ts","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjG,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAKtD,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3B,QAAQ,eAAe,EACvB,MAAM,MAAM,EACZ,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAYrB,CAAA;AAEH,eAAO,MAAM,iBAAiB,GACjB,CAAC,EAAE,CAAC,gGAKG,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"PlanExecution.d.ts","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjG,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAKtD,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3B,QAAQ,eAAe,EACvB,MAAM,MAAM,EACZ,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAYrB,CAAA;AAEH,eAAO,MAAM,iBAAiB,GACjB,CAAC,EAAE,CAAC,gGAKG,IAAI,aAAa,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,kgBAYtE,CAAA"}
|
package/dist/PlanExecution.js
CHANGED
|
@@ -9,7 +9,7 @@ export const implementTaskLoop = Effect.fn("@llm4ts/flow/PlanExecution.implement
|
|
|
9
9
|
let current = plan;
|
|
10
10
|
for (const task of plan.tasks) {
|
|
11
11
|
if (!task.completed) {
|
|
12
|
-
yield* stage(events, task.title, perTask(task));
|
|
12
|
+
yield* stage(events, task.title, perTask(task, current));
|
|
13
13
|
current = current.complete(task.title);
|
|
14
14
|
yield* store.save(planPath, current);
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanExecution.js","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAA;AAKjG,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE,CAC9C,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAEpF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAuB,EACvB,IAAY,EACZ,MAA8B,EACN,EAAE,CAC1B,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EACtB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EACtE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,OAAO,CACZ,WAAW,CAAC,IAAI,CAAC;IACf,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC;CAC7B,CAAC,CACH,CACF,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,8CAA8C,CAAC,CACxF,QAAQ,CAAC,EACP,KAAqB,EACrB,MAAuB,EACvB,QAAgB,EAChB,IAAU,EACV,
|
|
1
|
+
{"version":3,"file":"PlanExecution.js","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAA;AAKjG,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE,CAC9C,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAEpF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAuB,EACvB,IAAY,EACZ,MAA8B,EACN,EAAE,CAC1B,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EACtB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EACtE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,OAAO,CACZ,WAAW,CAAC,IAAI,CAAC;IACf,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC;CAC7B,CAAC,CACH,CACF,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,8CAA8C,CAAC,CACxF,QAAQ,CAAC,EACP,KAAqB,EACrB,MAAuB,EACvB,QAAgB,EAChB,IAAU,EACV,OAAmE;IAEnE,IAAI,OAAO,GAAG,IAAI,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;YACxD,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACtC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CACF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/flow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Effect-native LLM workflow, persistence, review, and repository automation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"typescript"
|
|
77
77
|
],
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@llm4ts/core": "0.1.
|
|
79
|
+
"@llm4ts/core": "0.1.4"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"effect": "^4.0.0-beta.102"
|
package/src/Flow.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect"
|
|
2
2
|
import type { LlmServiceShape } from "@llm4ts/core/LlmService"
|
|
3
3
|
import { collect } from "@llm4ts/core/Streaming"
|
|
4
|
-
import { makeChat } from "./Chat.ts"
|
|
4
|
+
import { makeChat, type Chat } from "./Chat.ts"
|
|
5
5
|
import type { FlowContextShape } from "./FlowContext.ts"
|
|
6
6
|
import { FlowAborted, FlowLlmError, type FlowError } from "./FlowError.ts"
|
|
7
7
|
import { AssistantMessage, Info, type FlowEventsShape } from "./FlowEvents.ts"
|
|
@@ -31,6 +31,13 @@ export interface ImplementPlanOptions {
|
|
|
31
31
|
readonly planPath: string
|
|
32
32
|
readonly plan: Effect.Effect<Plan, FlowError>
|
|
33
33
|
readonly system?: string
|
|
34
|
+
/**
|
|
35
|
+
* When true, each task gets a fresh Chat (system prompt plus the plan's
|
|
36
|
+
* current render, showing prior tasks' completion status); that task's
|
|
37
|
+
* review-fix rounds share the same Chat. When false or omitted (default),
|
|
38
|
+
* one Chat is shared across every task in the plan.
|
|
39
|
+
*/
|
|
40
|
+
readonly chatPerTask?: boolean
|
|
34
41
|
readonly reviewers?: ReadonlyArray<Reviewer>
|
|
35
42
|
readonly commitMessage?: (plan: Plan, task: Task) => string
|
|
36
43
|
readonly checkoutBranch?: boolean
|
|
@@ -41,6 +48,9 @@ export interface ImplementPlanOptions {
|
|
|
41
48
|
|
|
42
49
|
const defaultCommitMessage = (plan: Plan, task: Task): string => `${plan.epicId}: ${task.title}`
|
|
43
50
|
|
|
51
|
+
const composeSystem = (base: string | undefined, note: string): string =>
|
|
52
|
+
[base, note].filter((part): part is string => part !== undefined && part.length > 0).join("\n\n")
|
|
53
|
+
|
|
44
54
|
export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(function* (
|
|
45
55
|
context: FlowContextShape,
|
|
46
56
|
options: ImplementPlanOptions
|
|
@@ -49,44 +59,92 @@ export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(fu
|
|
|
49
59
|
if (options.checkoutBranch !== false) {
|
|
50
60
|
yield* stage(context.events, "branch", context.git.checkoutOrCreate(plan.epicId))
|
|
51
61
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
let sharedCoder: Chat | undefined
|
|
63
|
+
if (options.chatPerTask !== true) {
|
|
64
|
+
sharedCoder = yield* makeChat(context.coder, {
|
|
65
|
+
...(options.system === undefined ? {} : { system: options.system })
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return yield* implementTaskLoop(
|
|
70
|
+
options.store,
|
|
71
|
+
context.events,
|
|
72
|
+
options.planPath,
|
|
73
|
+
plan,
|
|
74
|
+
(task, planSoFar) =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
// `sharedCoder`'s definedness mirrors `options.chatPerTask !== true`
|
|
77
|
+
// above: when it's set, every task reuses it; when it's undefined,
|
|
78
|
+
// chatPerTask is active and each task builds its own fresh Chat.
|
|
79
|
+
let coder: Chat
|
|
80
|
+
if (sharedCoder !== undefined) {
|
|
81
|
+
coder = sharedCoder
|
|
82
|
+
} else {
|
|
83
|
+
coder = yield* makeChat(context.coder, {
|
|
84
|
+
system: composeSystem(options.system, planSoFar.render)
|
|
63
85
|
})
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
message: [
|
|
83
|
-
`task "${task.title}": the lint gate is still failing after review settled; refusing to commit`,
|
|
84
|
-
...gate.issues.map((issue) => `- [${issue.severity}] ${issue.title}`)
|
|
86
|
+
}
|
|
87
|
+
// `plan.taskPrompt` deliberately reads the frozen `plan` captured at
|
|
88
|
+
// the top of this function: a task prompt only needs that task's own
|
|
89
|
+
// details. `planSoFar`, threaded through by implementTaskLoop, is the
|
|
90
|
+
// single source of truth for completion progress instead.
|
|
91
|
+
yield* coder.ask(plan.taskPrompt(task))
|
|
92
|
+
const produced = yield* context.git.diffAll
|
|
93
|
+
if (produced.trim().length === 0) {
|
|
94
|
+
// An empty diff is ambiguous: the task may be genuinely satisfied
|
|
95
|
+
// already, or the coder may simply have produced nothing. Ask
|
|
96
|
+
// explicitly instead of inferring from absence; a task that
|
|
97
|
+
// produces no changes and no confirmation fails rather than being
|
|
98
|
+
// silently marked complete.
|
|
99
|
+
const confirmation = yield* coder.ask(
|
|
100
|
+
[
|
|
101
|
+
"Your previous turn produced no file changes.",
|
|
102
|
+
`If the task "${task.title}" is already fully satisfied by the current state of the repository, reply with exactly TASK_ALREADY_SATISFIED and nothing else.`,
|
|
103
|
+
"Otherwise, implement the task now."
|
|
85
104
|
].join("\n")
|
|
86
|
-
|
|
105
|
+
)
|
|
106
|
+
const afterConfirmation = yield* context.git.diffAll
|
|
107
|
+
if (afterConfirmation.trim().length === 0) {
|
|
108
|
+
if (confirmation.includes("TASK_ALREADY_SATISFIED")) {
|
|
109
|
+
yield* context.events.publish(
|
|
110
|
+
Info.make({
|
|
111
|
+
message: `task "${task.title}" confirmed already satisfied; skipping review and commit`
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
return yield* FlowAborted.make({
|
|
117
|
+
message: `task "${task.title}" produced no changes and did not confirm TASK_ALREADY_SATISFIED; failing instead of marking it complete`
|
|
118
|
+
})
|
|
119
|
+
}
|
|
87
120
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
121
|
+
yield* reviewAndFixLoop({
|
|
122
|
+
reviewers: options.reviewers ?? minimalReviewers,
|
|
123
|
+
reviewerService: flowReviewer(context),
|
|
124
|
+
coder,
|
|
125
|
+
taskTitle: task.title,
|
|
126
|
+
currentDiff: context.git.diffAll,
|
|
127
|
+
events: context.events,
|
|
128
|
+
...(options.maxRounds === undefined ? {} : { maxRounds: options.maxRounds }),
|
|
129
|
+
...(options.lint === undefined ? {} : { lint: options.lint }),
|
|
130
|
+
...(options.format === undefined ? {} : { format: options.format })
|
|
131
|
+
})
|
|
132
|
+
if (options.lint !== undefined) {
|
|
133
|
+
const gate = yield* options.lint
|
|
134
|
+
if (!gate.isClean) {
|
|
135
|
+
return yield* FlowAborted.make({
|
|
136
|
+
message: [
|
|
137
|
+
`task "${task.title}": the lint gate is still failing after review settled; refusing to commit`,
|
|
138
|
+
...gate.issues.map((issue) => {
|
|
139
|
+
const detail =
|
|
140
|
+
issue.description.length === 0 ? "" : `\n${issue.description.slice(-2000)}`
|
|
141
|
+
return `- [${issue.severity}] ${issue.title}${detail}`
|
|
142
|
+
})
|
|
143
|
+
].join("\n")
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
yield* context.git.commitAll((options.commitMessage ?? defaultCommitMessage)(plan, task))
|
|
148
|
+
})
|
|
91
149
|
)
|
|
92
150
|
})
|
package/src/PlanExecution.ts
CHANGED
|
@@ -31,12 +31,12 @@ export const implementTaskLoop = Effect.fn("@llm4ts/flow/PlanExecution.implement
|
|
|
31
31
|
events: FlowEventsShape,
|
|
32
32
|
planPath: string,
|
|
33
33
|
plan: Plan,
|
|
34
|
-
perTask: (task: Task) => Effect.Effect<void, E, R>
|
|
34
|
+
perTask: (task: Task, planSoFar: Plan) => Effect.Effect<void, E, R>
|
|
35
35
|
): Effect.fn.Return<Plan, E | FlowError, R> {
|
|
36
36
|
let current = plan
|
|
37
37
|
for (const task of plan.tasks) {
|
|
38
38
|
if (!task.completed) {
|
|
39
|
-
yield* stage(events, task.title, perTask(task))
|
|
39
|
+
yield* stage(events, task.title, perTask(task, current))
|
|
40
40
|
current = current.complete(task.title)
|
|
41
41
|
yield* store.save(planPath, current)
|
|
42
42
|
}
|