@llm4ts/flow 0.15.0 → 0.16.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.
Files changed (55) hide show
  1. package/dist/BenchReport.d.ts +2 -2
  2. package/dist/BenchReport.d.ts.map +1 -1
  3. package/dist/CostLedger.d.ts +2 -2
  4. package/dist/CostLedger.d.ts.map +1 -1
  5. package/dist/Equiv.d.ts +3 -3
  6. package/dist/Equiv.d.ts.map +1 -1
  7. package/dist/Flow.d.ts +1 -1
  8. package/dist/Flow.d.ts.map +1 -1
  9. package/dist/FlowContext.d.ts +10 -0
  10. package/dist/FlowContext.d.ts.map +1 -1
  11. package/dist/FlowContext.js.map +1 -1
  12. package/dist/FlowError.d.ts +42 -1
  13. package/dist/FlowError.d.ts.map +1 -1
  14. package/dist/FlowError.js +60 -1
  15. package/dist/FlowError.js.map +1 -1
  16. package/dist/GitTool.d.ts +15 -1
  17. package/dist/GitTool.d.ts.map +1 -1
  18. package/dist/GitTool.js +30 -2
  19. package/dist/GitTool.js.map +1 -1
  20. package/dist/Perimeter.d.ts +13 -0
  21. package/dist/Perimeter.d.ts.map +1 -0
  22. package/dist/Perimeter.js +34 -0
  23. package/dist/Perimeter.js.map +1 -0
  24. package/dist/Persistence.d.ts +2 -2
  25. package/dist/Persistence.d.ts.map +1 -1
  26. package/dist/PlanExecution.d.ts +1 -1
  27. package/dist/PlanExecution.d.ts.map +1 -1
  28. package/dist/ProgramJudge.d.ts +1 -1
  29. package/dist/ProgramJudge.d.ts.map +1 -1
  30. package/dist/Replay.d.ts +2 -2
  31. package/dist/Replay.d.ts.map +1 -1
  32. package/dist/Review.d.ts +2 -2
  33. package/dist/Review.d.ts.map +1 -1
  34. package/dist/ReviewCache.d.ts +1 -1
  35. package/dist/ReviewCache.d.ts.map +1 -1
  36. package/dist/Stories.d.ts +109 -0
  37. package/dist/Stories.d.ts.map +1 -0
  38. package/dist/Stories.js +432 -0
  39. package/dist/Stories.js.map +1 -0
  40. package/dist/StoryPlan.d.ts +81 -0
  41. package/dist/StoryPlan.d.ts.map +1 -0
  42. package/dist/StoryPlan.js +242 -0
  43. package/dist/StoryPlan.js.map +1 -0
  44. package/dist/TransientRetry.d.ts +18 -0
  45. package/dist/TransientRetry.d.ts.map +1 -1
  46. package/dist/TransientRetry.js +35 -3
  47. package/dist/TransientRetry.js.map +1 -1
  48. package/package.json +5 -2
  49. package/src/FlowContext.ts +10 -0
  50. package/src/FlowError.ts +74 -1
  51. package/src/GitTool.ts +74 -4
  52. package/src/Perimeter.ts +49 -0
  53. package/src/Stories.ts +670 -0
  54. package/src/StoryPlan.ts +353 -0
  55. package/src/TransientRetry.ts +76 -7
package/dist/Replay.d.ts CHANGED
@@ -22,8 +22,8 @@ export declare class ReplayFailure extends ReplayFailure_base {
22
22
  export declare const ReplayTurn: Schema.Union<readonly [typeof ReplaySuccess, typeof ReplayFailure]>;
23
23
  export type ReplayTurn = typeof ReplayTurn.Type;
24
24
  export declare const segmentReplayTurns: (lines: ReadonlyArray<TraceLine>) => ReadonlyArray<ReplayTurn>;
25
- export declare const readTrace: (files: PlainFileStoreShape, path: string) => Effect.Effect<readonly TraceLine[], import("./FlowError.ts").PersistenceError | PlanParseError | 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded, never>;
25
+ export declare const readTrace: (files: PlainFileStoreShape, path: string) => Effect.Effect<readonly TraceLine[], import("./FlowError.ts").PersistenceError | PlanParseError | 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed, never>;
26
26
  export declare const makeReplayConnector: (turns: readonly (ReplaySuccess | ReplayFailure)[]) => Effect.Effect<LlmServiceShape, never, never>;
27
- export declare const replayFromTrace: (files: PlainFileStoreShape, path: string) => Effect.Effect<LlmServiceShape, import("./FlowError.ts").PersistenceError | PlanParseError | 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded, never>;
27
+ export declare const replayFromTrace: (files: PlainFileStoreShape, path: string) => Effect.Effect<LlmServiceShape, import("./FlowError.ts").PersistenceError | PlanParseError | 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed, never>;
28
28
  export {};
29
29
  //# sourceMappingURL=Replay.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Replay.d.ts","sourceRoot":"","sources":["../src/Replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAY,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE1D,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAkB,MAAM,gBAAgB,CAAA;AACzF,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3D,eAAO,MAAM,kBAAkB,IAAI,CAAA;;;;;;AAEnC,qBAAa,aAAc,SAAQ,kBAIjC;CAAG;;;;;AAEL,qBAAa,aAAc,SAAQ,kBAGjC;CAAG;AAEL,eAAO,MAAM,UAAU,qEAA+C,CAAA;AACtE,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AA8C/C,eAAO,MAAM,kBAAkB,GAAI,OAAO,aAAa,CAAC,SAAS,CAAC,KAAG,aAAa,CAAC,UAAU,CAgE5F,CAAA;AAID,eAAO,MAAM,SAAS,qjBAqCpB,CAAA;AAEF,eAAO,MAAM,mBAAmB,qGA6E9B,CAAA;AAEF,eAAO,MAAM,eAAe,gjBAM1B,CAAA"}
1
+ {"version":3,"file":"Replay.d.ts","sourceRoot":"","sources":["../src/Replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAY,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE1D,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAkB,MAAM,gBAAgB,CAAA;AACzF,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3D,eAAO,MAAM,kBAAkB,IAAI,CAAA;;;;;;AAEnC,qBAAa,aAAc,SAAQ,kBAIjC;CAAG;;;;;AAEL,qBAAa,aAAc,SAAQ,kBAGjC;CAAG;AAEL,eAAO,MAAM,UAAU,qEAA+C,CAAA;AACtE,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AA8C/C,eAAO,MAAM,kBAAkB,GAAI,OAAO,aAAa,CAAC,SAAS,CAAC,KAAG,aAAa,CAAC,UAAU,CAgE5F,CAAA;AAID,eAAO,MAAM,SAAS,4wBAqCpB,CAAA;AAEF,eAAO,MAAM,mBAAmB,qGA6E9B,CAAA;AAEF,eAAO,MAAM,eAAe,uwBAM1B,CAAA"}
package/dist/Review.d.ts CHANGED
@@ -51,7 +51,7 @@ export declare const llmDriven: (picker: LlmServiceShape) => ReviewerSelector;
51
51
  export declare const reviewPrompt: (task: string, diff: string) => string;
52
52
  export declare const fixPrompt: (result: ReviewResult) => string;
53
53
  export declare const mergeReviewResults: (results: ReadonlyArray<ReviewResult>) => ReviewResult;
54
- export declare const lintCommand: (process: ProcessExecutorShape, events: FlowEventsShape, command: readonly string[], workDir: string) => Effect.Effect<ReviewResult, 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 | ProcessError | FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded, never>;
54
+ export declare const lintCommand: (process: ProcessExecutorShape, events: FlowEventsShape, command: readonly string[], workDir: string) => Effect.Effect<ReviewResult, 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 | ProcessError | FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed, never>;
55
55
  export interface ReviewAndFixOptions {
56
56
  readonly reviewers: ReadonlyArray<Reviewer>;
57
57
  readonly reviewerService: LlmServiceShape;
@@ -66,6 +66,6 @@ export interface ReviewAndFixOptions {
66
66
  readonly parallelism?: number;
67
67
  readonly format?: Effect.Effect<void, FlowError>;
68
68
  }
69
- export declare const reviewAndFixLoop: (options: ReviewAndFixOptions) => Effect.Effect<ReviewResult, 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 | ProcessError | FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded, never>;
69
+ export declare const reviewAndFixLoop: (options: ReviewAndFixOptions) => Effect.Effect<ReviewResult, 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 | ProcessError | FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed, never>;
70
70
  export {};
71
71
  //# sourceMappingURL=Review.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Review.d.ts","sourceRoot":"","sources":["../src/Review.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Review.d.ts","sourceRoot":"","sources":["../src/Review.ts"],"names":[],"mappings":"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"}
@@ -2,5 +2,5 @@ import * as Effect from "effect/Effect";
2
2
  import { PersistenceError } from "./FlowError.ts";
3
3
  import type { PlainFileStoreShape } from "./Persistence.ts";
4
4
  import { ReviewResult } from "./Review.ts";
5
- export declare const cachedReview: <R>(files: PlainFileStoreShape, path: string, fingerprint: string, evaluate: Effect.Effect<ReviewResult, 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded, R>) => Effect.Effect<ReviewResult, 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded, R>;
5
+ export declare const cachedReview: <R>(files: PlainFileStoreShape, path: string, fingerprint: string, evaluate: Effect.Effect<ReviewResult, 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed, R>) => Effect.Effect<ReviewResult, 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").StoryFailed, R>;
6
6
  //# sourceMappingURL=ReviewCache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewCache.d.ts","sourceRoot":"","sources":["../src/ReviewCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,gBAAgB,EAAkB,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAa1C,eAAO,MAAM,YAAY,GAA2D,CAAC,onCA2BnF,CAAA"}
1
+ {"version":3,"file":"ReviewCache.d.ts","sourceRoot":"","sources":["../src/ReviewCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,gBAAgB,EAAkB,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAa1C,eAAO,MAAM,YAAY,GAA2D,CAAC,kiDA2BnF,CAAA"}
@@ -0,0 +1,109 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Ref from "effect/Ref";
3
+ import * as Schema from "effect/Schema";
4
+ import type * as Scope from "effect/Scope";
5
+ import type { LlmServiceShape } from "@llm4ts/core/LlmService";
6
+ import type { TokenUsage } from "@llm4ts/core/Models";
7
+ import { type BoardSyncShape } from "./BoardSync.ts";
8
+ import type { FlowContextShape } from "./FlowContext.ts";
9
+ import { MissingDependency, StoryFailed, type FlowError } from "./FlowError.ts";
10
+ import { type PlainFileStoreShape } from "./Persistence.ts";
11
+ import type { Plan } from "./Plan.ts";
12
+ import type { ReviewResult } from "./Review.ts";
13
+ import type { Reviewer } from "./Reviewer.ts";
14
+ import { type Story, type StoryPlan } from "./StoryPlan.ts";
15
+ /** A flow context rooted in a story's worktree, plus that story's own usage totals. */
16
+ export interface StorySeats {
17
+ readonly context: FlowContextShape;
18
+ /** Running usage of this story's seats — estimates where the backend reports none. */
19
+ readonly totals?: Effect.Effect<TokenUsage | undefined>;
20
+ }
21
+ export declare const StoryStateVersion = 1;
22
+ export declare const StoryStatus: Schema.Literals<readonly ["started", "merged", "failed"]>;
23
+ export type StoryStatus = typeof StoryStatus.Type;
24
+ declare const StoryState_base: Schema.Class<StoryState, Schema.Struct<{
25
+ readonly id: Schema.String;
26
+ /** `storyHash` of the plan entry the branch was created from. */
27
+ readonly hash: Schema.String;
28
+ readonly branch: Schema.String;
29
+ readonly worktree: Schema.String;
30
+ readonly status: Schema.Literals<readonly ["started", "merged", "failed"]>;
31
+ }>, {}>;
32
+ /** What the executor remembers about a story between runs. */
33
+ export declare class StoryState extends StoryState_base {
34
+ }
35
+ export declare const EpicReportVersion = 1;
36
+ export declare const OutcomeStatus: Schema.Literals<readonly ["done", "failed", "skipped"]>;
37
+ export type OutcomeStatus = typeof OutcomeStatus.Type;
38
+ declare const StoryOutcome_base: Schema.Class<StoryOutcome, Schema.Struct<{
39
+ readonly id: Schema.String;
40
+ readonly title: Schema.String;
41
+ readonly status: Schema.Literals<readonly ["done", "failed", "skipped"]>;
42
+ readonly branch: Schema.optionalKey<Schema.String>;
43
+ /** Failure or skip reason. */
44
+ readonly reason: Schema.optionalKey<Schema.String>;
45
+ readonly judge: Schema.optionalKey<Schema.String>;
46
+ /** ESTIMATES, never measurements (ADR 0012). */
47
+ readonly estimatedTokens: Schema.optionalKey<Schema.Int>;
48
+ readonly estimatedCostUsd: Schema.optionalKey<Schema.Number>;
49
+ }>, {}>;
50
+ export declare class StoryOutcome extends StoryOutcome_base {
51
+ }
52
+ declare const EpicReport_base: Schema.Class<EpicReport, Schema.Struct<{
53
+ readonly epicId: Schema.String;
54
+ readonly epicBranch: Schema.String;
55
+ /** Always true: the figures come from character-count estimates. */
56
+ readonly estimated: Schema.Boolean;
57
+ readonly stories: Schema.$Array<typeof StoryOutcome>;
58
+ }>, {}>;
59
+ export declare class EpicReport extends EpicReport_base {
60
+ count(status: OutcomeStatus): number;
61
+ }
62
+ export declare const renderEpicReport: (report: EpicReport) => string;
63
+ export declare const blockedOnSentinel = "BLOCKED_ON:";
64
+ /** The text after the sentinel, if the coder ended a turn with it. */
65
+ export declare const blockedOnIn: (text: string) => string | undefined;
66
+ /** The hard rules every story coder receives; the perimeter check enforces them afterwards. */
67
+ export declare const perimeterRules: (story: Story) => string;
68
+ export declare const storyPrompt: (story: Story) => string;
69
+ export declare const storyTaskPlanInstructions: (story: Story) => string;
70
+ /** The coder plans its own tasks from the story — the orchestrator split the epic, not the story. */
71
+ export declare const defaultPlanTasks: (seats: StorySeats, story: Story, prompt: string) => Effect.Effect<Plan, FlowError>;
72
+ /**
73
+ * A coder whose turns are watched for the `BLOCKED_ON:` sentinel. The story
74
+ * loop reads the ref after the coder stops, so a blocked turn ends the story
75
+ * as a typed `MissingDependency` instead of an empty-diff abort.
76
+ */
77
+ export declare const watchForBlockedOn: (service: LlmServiceShape, blocked: Ref.Ref<string | undefined>) => LlmServiceShape;
78
+ export interface StoriesOptions {
79
+ readonly plan: StoryPlan;
80
+ readonly files: PlainFileStoreShape;
81
+ /** The executor's own durable state: story states, task plans, the report. */
82
+ readonly stateDir: string;
83
+ /** Where story worktrees are created (`<worktreeRoot>/<story-id>`). */
84
+ readonly worktreeRoot: string;
85
+ /** Default `epic/<epicId>`. */
86
+ readonly epicBranch?: string;
87
+ /** Seats rooted in a worktree; the executor never resolves seats itself. */
88
+ readonly contextFor: (workDir: string) => Effect.Effect<StorySeats, FlowError, Scope.Scope>;
89
+ readonly board: BoardSyncShape;
90
+ /** The target's gates, run in a worktree per task and on the epic checkout after each merge. */
91
+ readonly gates: (workDir: string) => Effect.Effect<ReviewResult, FlowError>;
92
+ /** Story-level judge over the branch's diff against the epic branch; omit to skip. */
93
+ readonly judge?: (story: Story, diff: string) => Effect.Effect<ReviewResult, FlowError>;
94
+ /** Judge attempts, each but the last followed by one coder feedback round. Default 2. */
95
+ readonly judgeRounds?: number;
96
+ /** Extra system context per story (house rules, shared read-only excerpts). */
97
+ readonly system?: (story: Story) => Effect.Effect<string, FlowError>;
98
+ /** How a story's task plan is produced. Default: the story's own coder plans it. */
99
+ readonly planTasks?: (seats: StorySeats, story: Story, prompt: string) => Effect.Effect<Plan, FlowError>;
100
+ /** Stories implemented at once. Default 3. */
101
+ readonly concurrency?: number;
102
+ /** Stop the epic at the first failed story instead of skipping its dependents. */
103
+ readonly failFast?: boolean;
104
+ readonly reviewers?: ReadonlyArray<Reviewer>;
105
+ readonly maxRounds?: number;
106
+ }
107
+ export declare const implementStoriesFlow: (context: FlowContextShape, options: StoriesOptions) => Effect.Effect<EpicReport, 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").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | MissingDependency | import("./FlowError.ts").MergeConflict | StoryFailed, never>;
108
+ export {};
109
+ //# sourceMappingURL=Stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stories.d.ts","sourceRoot":"","sources":["../src/Stories.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,432 @@
1
+ // The parallel story executor (ADR 0013). One story = one worktree on its
2
+ // own branch, created only once every predecessor has merged into the epic
3
+ // branch; the inner loop is the unchanged `implementPlanFlow`; a story
4
+ // merges back only after its judge and perimeter checks pass, and the
5
+ // target's gates run on the epic branch after every merge. Scheduling,
6
+ // gating, resume and failure policy live here; seats come from `contextFor`.
7
+ import * as Effect from "effect/Effect";
8
+ import * as Queue from "effect/Queue";
9
+ import * as Ref from "effect/Ref";
10
+ import * as Schema from "effect/Schema";
11
+ import * as Semaphore from "effect/Semaphore";
12
+ import * as Stream from "effect/Stream";
13
+ import { BoardItem } from "./BoardSync.js";
14
+ import { makeChat } from "./Chat.js";
15
+ import { implementPlanFlow } from "./Flow.js";
16
+ import { describeFlowError, MissingDependency, StoryFailed } from "./FlowError.js";
17
+ import { Info } from "./FlowEvents.js";
18
+ import { enforcePerimeter } from "./Perimeter.js";
19
+ import { loadVersioned, makePlanStore, saveVersioned } from "./Persistence.js";
20
+ import { stage } from "./PlanExecution.js";
21
+ import { planFrom } from "./Planner.js";
22
+ import { dependentsOf, readyStories, storyHash, topologicalWaves, validateStoryPlan } from "./StoryPlan.js";
23
+ const join = (root, path) => `${root.replace(/[\\/]+$/, "")}/${path.replace(/^[\\/]+/, "")}`;
24
+ // ---- Durable state ----------------------------------------------------------
25
+ export const StoryStateVersion = 1;
26
+ export const StoryStatus = Schema.Literals(["started", "merged", "failed"]);
27
+ /** What the executor remembers about a story between runs. */
28
+ export class StoryState extends Schema.Class("StoryState")({
29
+ id: Schema.String,
30
+ /** `storyHash` of the plan entry the branch was created from. */
31
+ hash: Schema.String,
32
+ branch: Schema.String,
33
+ worktree: Schema.String,
34
+ status: StoryStatus
35
+ }) {
36
+ }
37
+ export const EpicReportVersion = 1;
38
+ export const OutcomeStatus = Schema.Literals(["done", "failed", "skipped"]);
39
+ export class StoryOutcome extends Schema.Class("StoryOutcome")({
40
+ id: Schema.String,
41
+ title: Schema.String,
42
+ status: OutcomeStatus,
43
+ branch: Schema.optionalKey(Schema.String),
44
+ /** Failure or skip reason. */
45
+ reason: Schema.optionalKey(Schema.String),
46
+ judge: Schema.optionalKey(Schema.String),
47
+ /** ESTIMATES, never measurements (ADR 0012). */
48
+ estimatedTokens: Schema.optionalKey(Schema.Int),
49
+ estimatedCostUsd: Schema.optionalKey(Schema.Number)
50
+ }) {
51
+ }
52
+ export class EpicReport extends Schema.Class("EpicReport")({
53
+ epicId: Schema.String,
54
+ epicBranch: Schema.String,
55
+ /** Always true: the figures come from character-count estimates. */
56
+ estimated: Schema.Boolean,
57
+ stories: Schema.Array(StoryOutcome)
58
+ }) {
59
+ count(status) {
60
+ return this.stories.filter((story) => story.status === status).length;
61
+ }
62
+ }
63
+ const money = (value) => `~$${value.toFixed(2)}`;
64
+ export const renderEpicReport = (report) => {
65
+ const lines = [
66
+ `# Epic report: ${report.epicId}`,
67
+ "",
68
+ "> Token and cost figures are ESTIMATES from character counts (ADR 0012):",
69
+ "> the CLI seats report no usage. They are not measurements.",
70
+ "",
71
+ `- Epic branch: \`${report.epicBranch}\``,
72
+ `- Stories: ${report.stories.length} (done ${report.count("done")}, failed ${report.count("failed")}, skipped ${report.count("skipped")})`,
73
+ "",
74
+ "| Story | Status | Branch | Est. tokens | Est. cost | Note |",
75
+ "| --- | --- | --- | --- | --- | --- |"
76
+ ];
77
+ for (const story of report.stories) {
78
+ const note = story.reason ?? story.judge ?? "";
79
+ lines.push(`| ${story.id} | ${story.status} | ${story.branch === undefined ? "—" : `\`${story.branch}\``} | ${story.estimatedTokens === undefined ? "—" : `~${story.estimatedTokens}`} | ${story.estimatedCostUsd === undefined ? "—" : money(story.estimatedCostUsd)} | ${note.replace(/\|/g, "\\|").replace(/\s+/g, " ")} |`);
80
+ }
81
+ const tokens = report.stories.flatMap((story) => story.estimatedTokens === undefined ? [] : [story.estimatedTokens]);
82
+ const cost = report.stories.flatMap((story) => story.estimatedCostUsd === undefined ? [] : [story.estimatedCostUsd]);
83
+ lines.push("");
84
+ if (tokens.length > 0) {
85
+ lines.push(`- Estimated tokens: ~${tokens.reduce((sum, value) => sum + value, 0)}`);
86
+ }
87
+ if (cost.length > 0) {
88
+ lines.push(`- Estimated cost: ${money(cost.reduce((sum, value) => sum + value, 0))}`);
89
+ }
90
+ lines.push("");
91
+ return lines.join("\n");
92
+ };
93
+ // ---- Prompts ----------------------------------------------------------------
94
+ export const blockedOnSentinel = "BLOCKED_ON:";
95
+ const blockedPattern = /BLOCKED_ON:\s*(.+)/;
96
+ /** The text after the sentinel, if the coder ended a turn with it. */
97
+ export const blockedOnIn = (text) => {
98
+ const match = blockedPattern.exec(text);
99
+ const need = match?.[1]?.trim();
100
+ return need === undefined || need.length === 0 ? undefined : need;
101
+ };
102
+ const bullets = (items) => items.length === 0 ? "- (none)" : items.map((item) => `- ${item}`).join("\n");
103
+ /** The hard rules every story coder receives; the perimeter check enforces them afterwards. */
104
+ export const perimeterRules = (story) => [
105
+ `You are implementing ONE story of a larger epic: "${story.title}" (id: ${story.id}).`,
106
+ "",
107
+ "Paths you own — create and change files only here:",
108
+ bullets(story.owned),
109
+ "",
110
+ "Shared paths — read them for conventions, NEVER modify them:",
111
+ bullets(story.sharedReadOnly),
112
+ "",
113
+ "What this story must provide for other stories:",
114
+ bullets(story.provides),
115
+ "",
116
+ "Rules:",
117
+ "- Do not touch any path outside the owned list; a change there fails the story.",
118
+ "- If the story needs something that does not exist and is not yours to build, do not",
119
+ ` build it. Reply with exactly \`${blockedOnSentinel} <what you need and which path>\` and stop.`,
120
+ "- Everything you provide must be implemented completely: other stories depend on it."
121
+ ].join("\n");
122
+ export const storyPrompt = (story) => [`Story: ${story.title}`, "", story.description.trim()].join("\n");
123
+ export const storyTaskPlanInstructions = (story) => [
124
+ "You are planning the implementation of ONE story inside a larger epic. Break the story",
125
+ "into an ordered list of small, independently verifiable tasks, each described by its",
126
+ "observable outcome. Every task must stay inside the story's owned paths.",
127
+ `Use exactly this epicId: "${story.id}".`,
128
+ 'Respond only with JSON: {"epicId":"' + story.id + '","tasks":',
129
+ '[{"title":"...","description":"...","completed":false}]}'
130
+ ].join("\n");
131
+ /** The coder plans its own tasks from the story — the orchestrator split the epic, not the story. */
132
+ export const defaultPlanTasks = (seats, story, prompt) => planFrom(seats.context.coder, prompt, storyTaskPlanInstructions(story));
133
+ // ---- BLOCKED_ON detection ---------------------------------------------------
134
+ const watchStream = (stream, blocked) => Stream.unwrap(Effect.gen(function* () {
135
+ const text = yield* Ref.make("");
136
+ const tapped = stream.pipe(Stream.tap((chunk) => Ref.update(text, (current) => current + chunk.delta)));
137
+ const check = Stream.fromEffect(Effect.gen(function* () {
138
+ const need = blockedOnIn(yield* Ref.get(text));
139
+ if (need !== undefined) {
140
+ yield* Ref.set(blocked, need);
141
+ }
142
+ })).pipe(Stream.drain);
143
+ return Stream.concat(tapped, check);
144
+ }));
145
+ /**
146
+ * A coder whose turns are watched for the `BLOCKED_ON:` sentinel. The story
147
+ * loop reads the ref after the coder stops, so a blocked turn ends the story
148
+ * as a typed `MissingDependency` instead of an empty-diff abort.
149
+ */
150
+ export const watchForBlockedOn = (service, blocked) => ({
151
+ ...service,
152
+ executeStream: (prompt) => watchStream(service.executeStream(prompt), blocked),
153
+ executeStreamWithHistory: (messages) => watchStream(service.executeStreamWithHistory(messages), blocked)
154
+ });
155
+ const issueLines = (result) => result.issues.map((issue) => `- ${issue.title}: ${issue.description}`).join("\n");
156
+ const failed = (story, reason) => StoryFailed.make({ story: story.id, reason });
157
+ // ---- The executor -------------------------------------------------------------
158
+ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(function* (context, options) {
159
+ const plan = yield* validateStoryPlan(options.plan);
160
+ const { files, board } = options;
161
+ const events = context.events;
162
+ const epicBranch = options.epicBranch ?? `epic/${plan.epicId}`;
163
+ const concurrency = Math.max(1, options.concurrency ?? 3);
164
+ const judgeRounds = Math.max(1, options.judgeRounds ?? 2);
165
+ const statePath = (story) => join(options.stateDir, `stories/${story.id}.json`);
166
+ const planPath = (story) => join(options.stateDir, `stories/${story.id}.plan.md`);
167
+ yield* stage(events, "epic branch", context.git.checkoutOrCreate(epicBranch));
168
+ const waves = topologicalWaves(plan);
169
+ const waveOf = (id) => {
170
+ const index = waves.findIndex((wave) => wave.includes(id));
171
+ return index < 0 ? undefined : `${index + 1}`;
172
+ };
173
+ yield* board.plan(plan.stories.map((story) => {
174
+ const wave = waveOf(story.id);
175
+ return BoardItem.make({
176
+ id: story.id,
177
+ title: story.title,
178
+ status: "planned",
179
+ ...(wave === undefined ? {} : { wave })
180
+ });
181
+ }));
182
+ const mergeLock = yield* Semaphore.make(1);
183
+ /** Merge the story branch into the epic branch and re-gate the epic head, one story at a time. */
184
+ const integrate = (story, branch) => mergeLock.withPermit(Effect.gen(function* () {
185
+ const checkpoint = yield* context.git.checkpoint;
186
+ yield* context.git.merge(branch, `${plan.epicId}: merge story ${story.id}`);
187
+ const gate = yield* options.gates(context.workDir);
188
+ if (!gate.isClean) {
189
+ // Never leave a red epic head for the next story to inherit.
190
+ yield* context.git.rollback(checkpoint);
191
+ return yield* failed(story, `epic gates failed after merging; merge undone:\n${issueLines(gate)}`);
192
+ }
193
+ yield* events.publish(Info.make({ message: `story ${story.id}: merged into ${epicBranch}` }));
194
+ }));
195
+ /** The worktree and branch for a story, honouring the hash-guarded resume rules. */
196
+ const prepareWorktree = Effect.fn("@llm4ts/flow/Stories.prepareWorktree")(function* (story) {
197
+ const hash = storyHash(story);
198
+ const branch = `story/${plan.epicId}/${story.id}`;
199
+ const worktree = join(options.worktreeRoot, story.id);
200
+ const path = statePath(story);
201
+ let stored = yield* loadVersioned(files, path, StoryStateVersion, StoryState);
202
+ if (stored !== undefined && stored.hash !== hash) {
203
+ yield* events.publish(Info.make({
204
+ message: `story ${story.id}: plan entry changed since its branch was created; starting over`
205
+ }));
206
+ // The old worktree may already be gone (an operator cleaned up); that is
207
+ // not a failure of this run, so removal errors are reported and dropped.
208
+ yield* context.git
209
+ .removeWorktree(stored.worktree, true)
210
+ .pipe(Effect.catch((error) => events.publish(Info.make({ message: `story ${story.id}: ${describeFlowError(error)}` }))));
211
+ if (yield* context.git.branchExists(stored.branch)) {
212
+ yield* context.git.deleteBranch(stored.branch);
213
+ }
214
+ stored = undefined;
215
+ }
216
+ if (stored !== undefined && stored.status === "merged") {
217
+ return { state: stored, alreadyMerged: true };
218
+ }
219
+ if (stored === undefined) {
220
+ yield* context.git.addWorktreeNewBranch(worktree, branch, epicBranch);
221
+ const state = StoryState.make({ id: story.id, hash, branch, worktree, status: "started" });
222
+ yield* saveVersioned(files, path, StoryStateVersion, StoryState, state);
223
+ return { state, alreadyMerged: false };
224
+ }
225
+ // Resume: the branch exists; the worktree may not (a worktree has a
226
+ // `.git` FILE at its root, which is how its presence is checked).
227
+ const marker = yield* files.read(join(stored.worktree, ".git"));
228
+ if (marker === undefined) {
229
+ yield* context.git.addWorktree(stored.worktree, stored.branch);
230
+ }
231
+ yield* events.publish(Info.make({ message: `story ${story.id}: resuming ${stored.branch}` }));
232
+ return { state: stored, alreadyMerged: false };
233
+ });
234
+ /** Everything that happens inside a story's worktree: plan, implement, judge, perimeter. */
235
+ const implementStory = Effect.fn("@llm4ts/flow/Stories.implementStory")(function* (story, state) {
236
+ const seats = yield* options.contextFor(state.worktree);
237
+ const blocked = yield* Ref.make(undefined);
238
+ const storyContext = {
239
+ ...seats.context,
240
+ coder: watchForBlockedOn(seats.context.coder, blocked)
241
+ };
242
+ const watchedSeats = { ...seats, context: storyContext };
243
+ const extra = options.system === undefined ? undefined : yield* options.system(story);
244
+ const system = [perimeterRules(story), extra]
245
+ .filter((part) => part !== undefined && part.trim().length > 0)
246
+ .join("\n\n");
247
+ const prompt = storyPrompt(story);
248
+ const blockedOr = (effect) => effect.pipe(Effect.catch((error) => Effect.flatMap(Ref.get(blocked), (need) => need === undefined
249
+ ? Effect.fail(error)
250
+ : Effect.fail(MissingDependency.make({ story: story.id, need })))), Effect.tap(() => Effect.flatMap(Ref.get(blocked), (need) => need === undefined
251
+ ? Effect.void
252
+ : Effect.fail(MissingDependency.make({ story: story.id, need })))));
253
+ yield* blockedOr(implementPlanFlow(storyContext, {
254
+ store: makePlanStore(files),
255
+ planPath: planPath(story),
256
+ plan: (options.planTasks ?? defaultPlanTasks)(watchedSeats, story, prompt),
257
+ system,
258
+ chatPerTask: true,
259
+ checkoutBranch: false,
260
+ lint: options.gates(state.worktree),
261
+ ...(options.reviewers === undefined ? {} : { reviewers: options.reviewers }),
262
+ ...(options.maxRounds === undefined ? {} : { maxRounds: options.maxRounds })
263
+ }));
264
+ let judgeNote;
265
+ if (options.judge !== undefined) {
266
+ const judge = options.judge;
267
+ for (let round = 1; round <= judgeRounds; round += 1) {
268
+ const diff = yield* storyContext.git.diffVsBase(epicBranch);
269
+ const verdict = yield* judge(story, diff);
270
+ if (verdict.isClean) {
271
+ judgeNote = `judge cleared (round ${round})`;
272
+ break;
273
+ }
274
+ if (round >= judgeRounds) {
275
+ return yield* failed(story, `judge not cleared after ${judgeRounds} round(s):\n${issueLines(verdict)}`);
276
+ }
277
+ const feedback = yield* makeChat(storyContext.coder, {
278
+ system,
279
+ events,
280
+ agent: "coder"
281
+ });
282
+ yield* blockedOr(feedback.ask([
283
+ `The story "${story.title}" scored below the bar. Close these gaps without`,
284
+ "weakening any test and without leaving your owned paths, then stop:",
285
+ issueLines(verdict)
286
+ ].join("\n")));
287
+ const regated = yield* options.gates(state.worktree);
288
+ if (!regated.isClean) {
289
+ return yield* failed(story, `gates broke while addressing judge feedback:\n${issueLines(regated)}`);
290
+ }
291
+ yield* storyContext.git.commitAll(`${story.id}: address judge feedback`);
292
+ }
293
+ }
294
+ const changed = yield* storyContext.git.changedFilesVsBase(epicBranch);
295
+ yield* enforcePerimeter(changed, story);
296
+ const totals = seats.totals === undefined ? undefined : yield* seats.totals;
297
+ return { judge: judgeNote, totals };
298
+ });
299
+ const runStory = Effect.fn("@llm4ts/flow/Stories.runStory")(function* (story) {
300
+ yield* board.start(story.id);
301
+ const { state, alreadyMerged } = yield* prepareWorktree(story);
302
+ if (alreadyMerged) {
303
+ yield* events.publish(Info.make({ message: `story ${story.id}: already merged; skipping` }));
304
+ return StoryOutcome.make({
305
+ id: story.id,
306
+ title: story.title,
307
+ status: "done",
308
+ branch: state.branch,
309
+ judge: "merged on a previous run"
310
+ });
311
+ }
312
+ const result = yield* Effect.scoped(implementStory(story, state));
313
+ yield* integrate(story, state.branch);
314
+ yield* saveVersioned(files, statePath(story), StoryStateVersion, StoryState, StoryState.make({ ...state, status: "merged" }));
315
+ return StoryOutcome.make({
316
+ id: story.id,
317
+ title: story.title,
318
+ status: "done",
319
+ branch: state.branch,
320
+ ...(result.judge === undefined ? {} : { judge: result.judge }),
321
+ ...(result.totals === undefined ? {} : { estimatedTokens: result.totals.total }),
322
+ ...(result.totals?.costUsd === undefined ? {} : { estimatedCostUsd: result.totals.costUsd })
323
+ });
324
+ });
325
+ /** A story's run as a completion — failures become outcomes, never fiber deaths. */
326
+ const attempt = (story) => stage(events, `story ${story.id}`, runStory(story)).pipe(Effect.map((outcome) => ({ story, outcome })), Effect.catch((error) => Effect.gen(function* () {
327
+ const reason = describeFlowError(error);
328
+ const path = statePath(story);
329
+ const stored = yield* loadVersioned(files, path, StoryStateVersion, StoryState).pipe(Effect.catch(() => Effect.succeed(undefined)));
330
+ if (stored !== undefined && stored.status !== "merged") {
331
+ yield* saveVersioned(files, path, StoryStateVersion, StoryState, StoryState.make({ ...stored, status: "failed" })).pipe(Effect.ignore);
332
+ }
333
+ return {
334
+ story,
335
+ outcome: StoryOutcome.make({
336
+ id: story.id,
337
+ title: story.title,
338
+ status: "failed",
339
+ reason,
340
+ ...(stored === undefined ? {} : { branch: stored.branch })
341
+ })
342
+ };
343
+ })));
344
+ const outcomes = yield* Ref.make([]);
345
+ const running = yield* Ref.make(new Set());
346
+ const completions = yield* Queue.unbounded();
347
+ const progress = Effect.gen(function* () {
348
+ const known = yield* Ref.get(outcomes);
349
+ const byStatus = (status) => new Set(known.filter((outcome) => outcome.status === status).map((outcome) => outcome.id));
350
+ return {
351
+ done: byStatus("done"),
352
+ failed: byStatus("failed"),
353
+ skipped: byStatus("skipped"),
354
+ running: yield* Ref.get(running)
355
+ };
356
+ });
357
+ const record = (outcome) => Ref.update(outcomes, (current) => [...current, outcome]);
358
+ const skipDependents = (story) => Effect.gen(function* () {
359
+ const current = yield* progress;
360
+ for (const id of dependentsOf(plan, story.id)) {
361
+ const dependent = plan.story(id);
362
+ if (dependent === undefined ||
363
+ current.done.has(id) ||
364
+ current.failed.has(id) ||
365
+ current.skipped.has(id)) {
366
+ continue;
367
+ }
368
+ const reason = `blocked by ${story.id}`;
369
+ yield* record(StoryOutcome.make({ id, title: dependent.title, status: "skipped", reason }));
370
+ yield* board.skip(id, reason);
371
+ }
372
+ });
373
+ yield* Effect.scoped(Effect.gen(function* () {
374
+ while (true) {
375
+ const current = yield* progress;
376
+ const ready = readyStories(plan, current);
377
+ const slots = concurrency - current.running.size;
378
+ for (const story of ready.slice(0, Math.max(0, slots))) {
379
+ yield* Ref.update(running, (set) => new Set([...set, story.id]));
380
+ yield* Effect.forkScoped(attempt(story).pipe(Effect.flatMap((completion) => Queue.offer(completions, completion))));
381
+ }
382
+ if ((yield* Ref.get(running)).size === 0) {
383
+ break;
384
+ }
385
+ const completion = yield* Queue.take(completions);
386
+ yield* Ref.update(running, (set) => {
387
+ const next = new Set(set);
388
+ next.delete(completion.story.id);
389
+ return next;
390
+ });
391
+ yield* record(completion.outcome);
392
+ const outcome = completion.outcome;
393
+ if (outcome.status === "done") {
394
+ yield* board.complete(outcome.id, {
395
+ ...(outcome.branch === undefined ? {} : { branch: outcome.branch }),
396
+ ...(outcome.judge === undefined ? {} : { detail: outcome.judge }),
397
+ ...(outcome.estimatedTokens === undefined
398
+ ? {}
399
+ : { estimatedTokens: outcome.estimatedTokens }),
400
+ ...(outcome.estimatedCostUsd === undefined
401
+ ? {}
402
+ : { estimatedCostUsd: outcome.estimatedCostUsd })
403
+ });
404
+ }
405
+ else {
406
+ yield* board.fail(outcome.id, outcome.reason ?? "failed");
407
+ if (options.failFast === true) {
408
+ return yield* StoryFailed.make({
409
+ story: outcome.id,
410
+ reason: outcome.reason ?? "failed"
411
+ });
412
+ }
413
+ yield* skipDependents(completion.story);
414
+ }
415
+ }
416
+ }));
417
+ const recorded = yield* Ref.get(outcomes);
418
+ const ordered = plan.stories.flatMap((story) => {
419
+ const outcome = recorded.find((candidate) => candidate.id === story.id);
420
+ return outcome === undefined ? [] : [outcome];
421
+ });
422
+ const report = EpicReport.make({
423
+ epicId: plan.epicId,
424
+ epicBranch,
425
+ estimated: true,
426
+ stories: ordered
427
+ });
428
+ yield* saveVersioned(files, join(options.stateDir, "report.json"), EpicReportVersion, EpicReport, report);
429
+ yield* files.writeAtomic(join(options.stateDir, "report.md"), renderEpicReport(report));
430
+ return report;
431
+ });
432
+ //# sourceMappingURL=Stories.js.map