ferix-code 0.0.2-beta.1 → 0.0.2-beta.10

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 (3) hide show
  1. package/dist/index.d.ts +688 -21
  2. package/dist/index.js +1938 -807
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -71,11 +71,12 @@ import { Command } from "commander";
71
71
  // package.json
72
72
  var package_default = {
73
73
  name: "ferix-code",
74
- version: "0.0.2-beta.0",
74
+ version: "0.0.2-beta.10",
75
75
  description: "Composable RALPH loops for AI coding agents - v2 with Effect",
76
76
  type: "module",
77
77
  bin: {
78
- "ferix-code": "./dist/index.js"
78
+ ferix: "dist/index.js",
79
+ "ferix-code": "dist/index.js"
79
80
  },
80
81
  files: [
81
82
  "dist"
@@ -85,13 +86,12 @@ var package_default = {
85
86
  dev: "tsup --watch",
86
87
  "check-types": "tsc --noEmit",
87
88
  test: "bun test",
88
- "test:watch": "bun test --watch"
89
+ "test:watch": "bun test --watch",
90
+ bump: "npm version prerelease --preid=beta --workspaces=false && bun run build && bun publish --tag beta"
89
91
  },
90
92
  dependencies: {
91
- "@effect/platform": "^0.72.0",
92
- "@effect/platform-node": "^0.66.0",
93
93
  commander: "^14.0.0",
94
- effect: "^3.12.0",
94
+ effect: "^3.19.15",
95
95
  "human-id": "^4.1.3",
96
96
  picocolors: "^1.1.1"
97
97
  },
@@ -118,7 +118,7 @@ var package_default = {
118
118
 
119
119
  // src/program.ts
120
120
  init_esm_shims();
121
- import { Effect as Effect17, Stream as Stream10 } from "effect";
121
+ import { Effect as Effect23, Stream as Stream10 } from "effect";
122
122
 
123
123
  // src/consumers/index.ts
124
124
  init_esm_shims();
@@ -126,7 +126,7 @@ init_esm_shims();
126
126
  // src/consumers/headless/consumer.ts
127
127
  init_esm_shims();
128
128
  import { Cause, Effect, Stream } from "effect";
129
- import pc9 from "picocolors";
129
+ import pc10 from "picocolors";
130
130
 
131
131
  // src/consumers/headless/formatters/index.ts
132
132
  init_esm_shims();
@@ -307,6 +307,17 @@ var taskCompletedFormatter = {
307
307
  headlessFormatterRegistry.register(tasksDefinedFormatter);
308
308
  headlessFormatterRegistry.register(taskCompletedFormatter);
309
309
 
310
+ // src/consumers/headless/formatters/worktree.ts
311
+ init_esm_shims();
312
+ import pc9 from "picocolors";
313
+ var worktreeCreatedFormatter = {
314
+ tag: "WorktreeCreated",
315
+ format: (event) => pc9.cyan(
316
+ `[WORKTREE] Branch: ${event.branchName} | Path: ${event.worktreePath}`
317
+ )
318
+ };
319
+ headlessFormatterRegistry.register(worktreeCreatedFormatter);
320
+
310
321
  // src/consumers/headless/formatters/index.ts
311
322
  function formatEvent(event) {
312
323
  return headlessFormatterRegistry.format(event);
@@ -315,37 +326,37 @@ function formatEvent(event) {
315
326
  // src/consumers/headless/consumer.ts
316
327
  function formatErrorToLines(err, lines) {
317
328
  if (err instanceof Error) {
318
- lines.push(pc9.red(` - ${err.name}: ${err.message}`));
329
+ lines.push(pc10.red(` - ${err.name}: ${err.message}`));
319
330
  if (err.stack) {
320
331
  lines.push("");
321
- lines.push(pc9.yellow("Stack trace:"));
332
+ lines.push(pc10.yellow("Stack trace:"));
322
333
  for (const stackLine of err.stack.split("\n").slice(1)) {
323
- lines.push(pc9.dim(` ${stackLine.trim()}`));
334
+ lines.push(pc10.dim(` ${stackLine.trim()}`));
324
335
  }
325
336
  }
326
337
  } else {
327
- lines.push(pc9.red(` - ${String(err)}`));
338
+ lines.push(pc10.red(` - ${String(err)}`));
328
339
  }
329
340
  }
330
341
  function formatCauseVerbose(cause) {
331
342
  const lines = [];
332
- const separator2 = pc9.red(
343
+ const separator2 = pc10.red(
333
344
  "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
334
345
  );
335
346
  const causeType = Cause.isFailure(cause) ? "Failure" : "Other";
336
347
  lines.push("");
337
348
  lines.push(separator2);
338
- lines.push(pc9.red(" ERROR"));
349
+ lines.push(pc10.red(" ERROR"));
339
350
  lines.push(separator2);
340
351
  lines.push("");
341
- lines.push(`${pc9.yellow("Type:")} ${causeType}`);
352
+ lines.push(`${pc10.yellow("Type:")} ${causeType}`);
342
353
  lines.push("");
343
- lines.push(pc9.yellow("Details:"));
354
+ lines.push(pc10.yellow("Details:"));
344
355
  lines.push(Cause.pretty(cause));
345
356
  const failures = Cause.failures(cause);
346
357
  if (failures.length > 0) {
347
358
  lines.push("");
348
- lines.push(pc9.yellow("Failures:"));
359
+ lines.push(pc10.yellow("Failures:"));
349
360
  for (const failure of failures) {
350
361
  formatErrorToLines(failure, lines);
351
362
  }
@@ -353,7 +364,7 @@ function formatCauseVerbose(cause) {
353
364
  const defects = Cause.defects(cause);
354
365
  if (defects.length > 0) {
355
366
  lines.push("");
356
- lines.push(pc9.yellow("Defects (unexpected errors):"));
367
+ lines.push(pc10.yellow("Defects (unexpected errors):"));
357
368
  for (const defect of defects) {
358
369
  formatErrorToLines(defect, lines);
359
370
  }
@@ -534,40 +545,40 @@ init_esm_shims();
534
545
 
535
546
  // src/consumers/tui/tags/primitives.ts
536
547
  init_esm_shims();
537
- import pc11 from "picocolors";
548
+ import pc12 from "picocolors";
538
549
 
539
550
  // src/consumers/tui/render/primitives.ts
540
551
  init_esm_shims();
541
- import pc10 from "picocolors";
552
+ import pc11 from "picocolors";
542
553
  var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
543
554
  var colors = {
544
- brand: pc10.magenta,
545
- success: pc10.green,
546
- warning: pc10.yellow,
547
- error: pc10.red,
548
- info: pc10.cyan,
549
- muted: pc10.dim,
550
- highlight: pc10.bold,
551
- brightWhite: (s) => pc10.bold(pc10.white(s)),
552
- brightMagenta: (s) => pc10.bold(pc10.magenta(s)),
553
- brightCyan: (s) => pc10.bold(pc10.cyan(s)),
554
- brightGreen: (s) => pc10.bold(pc10.green(s)),
555
- brightYellow: (s) => pc10.bold(pc10.yellow(s)),
556
- brightRed: (s) => pc10.bold(pc10.red(s))
555
+ brand: pc11.magenta,
556
+ success: pc11.green,
557
+ warning: pc11.yellow,
558
+ error: pc11.red,
559
+ info: pc11.cyan,
560
+ muted: pc11.dim,
561
+ highlight: pc11.bold,
562
+ brightWhite: (s) => pc11.bold(pc11.white(s)),
563
+ brightMagenta: (s) => pc11.bold(pc11.magenta(s)),
564
+ brightCyan: (s) => pc11.bold(pc11.cyan(s)),
565
+ brightGreen: (s) => pc11.bold(pc11.green(s)),
566
+ brightYellow: (s) => pc11.bold(pc11.yellow(s)),
567
+ brightRed: (s) => pc11.bold(pc11.red(s))
557
568
  };
558
569
  var toolColors = {
559
- Read: pc10.cyan,
560
- Edit: pc10.yellow,
561
- Write: pc10.green,
562
- Bash: pc10.magenta,
563
- Glob: pc10.blue,
564
- Grep: pc10.blue,
570
+ Read: pc11.cyan,
571
+ Edit: pc11.yellow,
572
+ Write: pc11.green,
573
+ Bash: pc11.magenta,
574
+ Glob: pc11.blue,
575
+ Grep: pc11.blue,
565
576
  Task: colors.brightWhite,
566
- WebFetch: pc10.cyan,
567
- WebSearch: pc10.blue
577
+ WebFetch: pc11.cyan,
578
+ WebSearch: pc11.blue
568
579
  };
569
580
  function getToolColor(tool) {
570
- return toolColors[tool] || pc10.white;
581
+ return toolColors[tool] || pc11.white;
571
582
  }
572
583
  var symbols = {
573
584
  diamond: "\u25C6",
@@ -594,24 +605,24 @@ var box = {
594
605
  };
595
606
  function topBorder(width) {
596
607
  const repeatCount = Math.max(0, width - 2);
597
- return pc10.cyan(
608
+ return pc11.cyan(
598
609
  `${box.topLeft}${box.horizontal.repeat(repeatCount)}${box.topRight}`
599
610
  );
600
611
  }
601
612
  function separator(width) {
602
613
  const repeatCount = Math.max(0, width - 2);
603
- return pc10.cyan(
614
+ return pc11.cyan(
604
615
  `${box.teeRight}${box.horizontal.repeat(repeatCount)}${box.teeLeft}`
605
616
  );
606
617
  }
607
618
  function borderedLine(content, width) {
608
619
  const stripped = stripAnsi(content);
609
620
  const padding = Math.max(0, width - stripped.length - 4);
610
- return `${pc10.cyan(box.vertical)} ${content}${" ".repeat(padding)} ${pc10.cyan(box.vertical)}`;
621
+ return `${pc11.cyan(box.vertical)} ${content}${" ".repeat(padding)} ${pc11.cyan(box.vertical)}`;
611
622
  }
612
623
  function emptyBorderedLine(width) {
613
624
  const repeatCount = Math.max(0, width - 2);
614
- return `${pc10.cyan(box.vertical)}${" ".repeat(repeatCount)}${pc10.cyan(box.vertical)}`;
625
+ return `${pc11.cyan(box.vertical)}${" ".repeat(repeatCount)}${pc11.cyan(box.vertical)}`;
615
626
  }
616
627
  function stripAnsi(str) {
617
628
  return str.replace(ANSI_PATTERN, "");
@@ -708,31 +719,31 @@ function taskListHeader(width) {
708
719
  }
709
720
  const label = ` ${symbols.diamond} TASKS ${symbols.diamond} `;
710
721
  const sideLen = Math.max(1, Math.floor((innerWidth - label.length) / 2));
711
- return `${pc11.cyan("\u250C")}${pc11.cyan(box.singleHorizontal.repeat(sideLen))}${colors.brand(label)}${pc11.cyan(box.singleHorizontal.repeat(sideLen))}`;
722
+ return `${pc12.cyan("\u250C")}${pc12.cyan(box.singleHorizontal.repeat(sideLen))}${colors.brand(label)}${pc12.cyan(box.singleHorizontal.repeat(sideLen))}`;
712
723
  }
713
724
  function taskListFooter() {
714
- return pc11.cyan("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
725
+ return pc12.cyan("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
715
726
  }
716
727
  function taskLine(id, description) {
717
- return `${pc11.cyan("\u2502")} ${colors.brightMagenta(`[${id}]`)} ${description}`;
728
+ return `${pc12.cyan("\u2502")} ${colors.brightMagenta(`[${id}]`)} ${description}`;
718
729
  }
719
730
  function taskDone(id) {
720
731
  return `${colors.success(symbols.checkmark)} ${colors.muted(`Task ${id} complete`)}`;
721
732
  }
722
733
  function phasesHeader(taskId) {
723
- return `${pc11.cyan("\u2502")} ${colors.muted(`Phases for task ${taskId}:`)}`;
734
+ return `${pc12.cyan("\u2502")} ${colors.muted(`Phases for task ${taskId}:`)}`;
724
735
  }
725
736
  function phaseLine(id, description) {
726
- return `${pc11.cyan("\u2502")} ${colors.muted(symbols.treeMiddle)} ${colors.muted(symbols.bulletEmpty)} ${colors.muted(`[${id}]`)} ${description}`;
737
+ return `${pc12.cyan("\u2502")} ${colors.muted(symbols.treeMiddle)} ${colors.muted(symbols.bulletEmpty)} ${colors.muted(`[${id}]`)} ${description}`;
727
738
  }
728
739
  function phaseStart(id) {
729
- return `${pc11.cyan("\u2502")} ${colors.warning(symbols.bulletFilled)} ${colors.muted(`Phase ${id} started`)}`;
740
+ return `${pc12.cyan("\u2502")} ${colors.warning(symbols.bulletFilled)} ${colors.muted(`Phase ${id} started`)}`;
730
741
  }
731
742
  function phaseDone(id) {
732
- return `${pc11.cyan("\u2502")} ${colors.success(symbols.checkmark)} ${colors.muted(`Phase ${id} complete`)}`;
743
+ return `${pc12.cyan("\u2502")} ${colors.success(symbols.checkmark)} ${colors.muted(`Phase ${id} complete`)}`;
733
744
  }
734
745
  function phaseFailed(id, reason) {
735
- return `${pc11.cyan("\u2502")} ${colors.error(symbols.cross)} ${colors.muted(`Phase ${id} failed:`)} ${colors.error(reason)}`;
746
+ return `${pc12.cyan("\u2502")} ${colors.error(symbols.cross)} ${colors.muted(`Phase ${id} failed:`)} ${colors.error(reason)}`;
736
747
  }
737
748
  function criterionPassed(id) {
738
749
  return `${colors.success(symbols.checkmark)} ${colors.muted(`Criterion ${id} passed`)}`;
@@ -768,10 +779,10 @@ function reviewFailedBanner(width) {
768
779
  );
769
780
  }
770
781
  function criteriaHeader(taskId) {
771
- return `${pc11.cyan("\u2502")} ${colors.muted(`Success criteria for task ${taskId}:`)}`;
782
+ return `${pc12.cyan("\u2502")} ${colors.muted(`Success criteria for task ${taskId}:`)}`;
772
783
  }
773
784
  function criterionLine(id, description) {
774
- return `${pc11.cyan("\u2502")} ${colors.muted(symbols.treeMiddle)} ${colors.muted(symbols.bulletEmpty)} ${colors.muted(`[${id}]`)} ${description}`;
785
+ return `${pc12.cyan("\u2502")} ${colors.muted(symbols.treeMiddle)} ${colors.muted(symbols.bulletEmpty)} ${colors.muted(`[${id}]`)} ${description}`;
775
786
  }
776
787
 
777
788
  // src/consumers/tui/tags/handlers/completion.ts
@@ -1044,11 +1055,11 @@ init_esm_shims();
1044
1055
 
1045
1056
  // src/consumers/tui/tools/registry.ts
1046
1057
  init_esm_shims();
1047
- import pc12 from "picocolors";
1048
- var brightWhite = (s) => pc12.bold(pc12.white(s));
1058
+ import pc13 from "picocolors";
1059
+ var brightWhite = (s) => pc13.bold(pc13.white(s));
1049
1060
  function createToolDisplayRegistry() {
1050
1061
  const configs = /* @__PURE__ */ new Map();
1051
- const defaultColor = pc12.white;
1062
+ const defaultColor = pc13.white;
1052
1063
  return {
1053
1064
  register(config) {
1054
1065
  configs.set(config.tool, config);
@@ -1085,32 +1096,32 @@ var toolDisplayRegistry = createToolDisplayRegistry();
1085
1096
  toolDisplayRegistry.register({
1086
1097
  tool: "Read",
1087
1098
  inputKey: "file_path",
1088
- color: pc12.cyan
1099
+ color: pc13.cyan
1089
1100
  });
1090
1101
  toolDisplayRegistry.register({
1091
1102
  tool: "Edit",
1092
1103
  inputKey: "file_path",
1093
- color: pc12.yellow
1104
+ color: pc13.yellow
1094
1105
  });
1095
1106
  toolDisplayRegistry.register({
1096
1107
  tool: "Write",
1097
1108
  inputKey: "file_path",
1098
- color: pc12.green
1109
+ color: pc13.green
1099
1110
  });
1100
1111
  toolDisplayRegistry.register({
1101
1112
  tool: "Bash",
1102
1113
  inputKey: "command",
1103
- color: pc12.magenta
1114
+ color: pc13.magenta
1104
1115
  });
1105
1116
  toolDisplayRegistry.register({
1106
1117
  tool: "Glob",
1107
1118
  inputKey: "pattern",
1108
- color: pc12.blue
1119
+ color: pc13.blue
1109
1120
  });
1110
1121
  toolDisplayRegistry.register({
1111
1122
  tool: "Grep",
1112
1123
  inputKey: "pattern",
1113
- color: pc12.blue
1124
+ color: pc13.blue
1114
1125
  });
1115
1126
  toolDisplayRegistry.register({
1116
1127
  tool: "Task",
@@ -1120,12 +1131,12 @@ toolDisplayRegistry.register({
1120
1131
  toolDisplayRegistry.register({
1121
1132
  tool: "WebFetch",
1122
1133
  inputKey: "url",
1123
- color: pc12.cyan
1134
+ color: pc13.cyan
1124
1135
  });
1125
1136
  toolDisplayRegistry.register({
1126
1137
  tool: "WebSearch",
1127
1138
  inputKey: "query",
1128
- color: pc12.blue
1139
+ color: pc13.blue
1129
1140
  });
1130
1141
 
1131
1142
  // src/consumers/tui/tools/index.ts
@@ -1305,6 +1316,34 @@ stateReducerRegistry.register(loopStartedReducer);
1305
1316
  stateReducerRegistry.register(loopCompletedReducer);
1306
1317
  stateReducerRegistry.register(loopFailedReducer);
1307
1318
 
1319
+ // src/consumers/tui/reducers/progress.ts
1320
+ init_esm_shims();
1321
+ var learningRecordedReducer = {
1322
+ tag: "LearningRecorded",
1323
+ reduce: (state, event) => {
1324
+ const category = event.category ? `[${event.category}] ` : "";
1325
+ const line = `Learning: ${category}${event.content}`;
1326
+ return appendOutput(state, `${line}
1327
+ `);
1328
+ }
1329
+ };
1330
+ var guardrailAddedReducer = {
1331
+ tag: "GuardrailAdded",
1332
+ reduce: (state, event) => {
1333
+ const severityIcon = event.severity === "critical" ? "[critical]" : "[warn]";
1334
+ const line = `${severityIcon} Guardrail: ${event.pattern}`;
1335
+ return appendOutput(state, `${line}
1336
+ `);
1337
+ }
1338
+ };
1339
+ var progressUpdatedReducer = {
1340
+ tag: "ProgressUpdated",
1341
+ reduce: (state) => state
1342
+ };
1343
+ stateReducerRegistry.register(learningRecordedReducer);
1344
+ stateReducerRegistry.register(guardrailAddedReducer);
1345
+ stateReducerRegistry.register(progressUpdatedReducer);
1346
+
1308
1347
  // src/consumers/tui/reducers/tasks.ts
1309
1348
  init_esm_shims();
1310
1349
  var tasksDefinedReducer = {
@@ -1385,6 +1424,21 @@ stateReducerRegistry.register(criterionPassedReducer);
1385
1424
  stateReducerRegistry.register(criterionFailedReducer);
1386
1425
  stateReducerRegistry.register(taskCompletedReducer);
1387
1426
 
1427
+ // src/consumers/tui/reducers/worktree.ts
1428
+ init_esm_shims();
1429
+ var worktreeCreatedReducer = {
1430
+ tag: "WorktreeCreated",
1431
+ reduce: (state, event) => appendOutput(
1432
+ state,
1433
+ `
1434
+ Worktree created
1435
+ Branch: ${event.branchName}
1436
+ Path: ${event.worktreePath}
1437
+ `
1438
+ )
1439
+ };
1440
+ stateReducerRegistry.register(worktreeCreatedReducer);
1441
+
1388
1442
  // src/consumers/tui/reducers/index.ts
1389
1443
  function reduce(state, event) {
1390
1444
  return stateReducerRegistry.reduce(state, event);
@@ -1463,7 +1517,7 @@ init_esm_shims();
1463
1517
 
1464
1518
  // src/consumers/tui/render/footer.ts
1465
1519
  init_esm_shims();
1466
- import pc13 from "picocolors";
1520
+ import pc14 from "picocolors";
1467
1521
  function hint(key, description) {
1468
1522
  return `${colors.brand(symbols.diamond)} ${colors.brightWhite(key)} ${colors.muted(description)}`;
1469
1523
  }
@@ -1512,7 +1566,7 @@ function renderFooter(state, width, outputHeight) {
1512
1566
  const content = hints.join(" ");
1513
1567
  const stripped = stripAnsi(content);
1514
1568
  const padding = Math.max(0, width - stripped.length - 4);
1515
- return `${pc13.cyan(box.bottomLeft)}${pc13.cyan(box.horizontal)} ${content}${pc13.cyan(box.horizontal.repeat(Math.max(1, padding)))}${pc13.cyan(box.bottomRight)}`;
1569
+ return `${pc14.cyan(box.bottomLeft)}${pc14.cyan(box.horizontal)} ${content}${pc14.cyan(box.horizontal.repeat(Math.max(1, padding)))}${pc14.cyan(box.bottomRight)}`;
1516
1570
  }
1517
1571
 
1518
1572
  // src/consumers/tui/render/status-bar.ts
@@ -1591,7 +1645,7 @@ init_esm_shims();
1591
1645
 
1592
1646
  // src/consumers/tui/render/task-detail.ts
1593
1647
  init_esm_shims();
1594
- import pc14 from "picocolors";
1648
+ import pc15 from "picocolors";
1595
1649
  function renderPhase(phase, isLast, width) {
1596
1650
  const lines = [];
1597
1651
  const prefix = isLast ? symbols.treeLast : symbols.treeMiddle;
@@ -1656,7 +1710,7 @@ function renderEmptyState(height, width) {
1656
1710
  return lines;
1657
1711
  }
1658
1712
  function renderTaskHeader(task, width, innerWidth) {
1659
- const sepLine = pc14.cyan(box.horizontal.repeat(innerWidth));
1713
+ const sepLine = pc15.cyan(box.horizontal.repeat(innerWidth));
1660
1714
  return [
1661
1715
  emptyBorderedLine(width),
1662
1716
  borderedLine(
@@ -1730,7 +1784,7 @@ function renderGitSection(state, width, innerWidth) {
1730
1784
  return [];
1731
1785
  }
1732
1786
  const lines = [];
1733
- const sepLine = pc14.cyan(box.horizontal.repeat(innerWidth));
1787
+ const sepLine = pc15.cyan(box.horizontal.repeat(innerWidth));
1734
1788
  const pushStatus = state.gitPushed ? colors.success(symbols.checkmark) : colors.muted("Not pushed");
1735
1789
  lines.push(borderedLine(sepLine, width));
1736
1790
  lines.push(
@@ -1809,7 +1863,7 @@ init_esm_shims();
1809
1863
 
1810
1864
  // src/consumers/tui/render/tasks-list.ts
1811
1865
  init_esm_shims();
1812
- import pc15 from "picocolors";
1866
+ import pc16 from "picocolors";
1813
1867
  function renderTaskLine(task, isSelected, width) {
1814
1868
  const innerWidth = width - 4;
1815
1869
  const parts = [];
@@ -1873,7 +1927,7 @@ function renderTasksListView(state, height, width) {
1873
1927
  lines.push(emptyBorderedLine(width));
1874
1928
  const headerText = `${colors.brand(symbols.diamond)} ${colors.brightWhite("TASKS")} ${colors.brand(symbols.diamond)}`;
1875
1929
  lines.push(borderedLine(headerText, width));
1876
- const sepLine = pc15.cyan(box.horizontal.repeat(innerWidth));
1930
+ const sepLine = pc16.cyan(box.horizontal.repeat(innerWidth));
1877
1931
  lines.push(borderedLine(sepLine, width));
1878
1932
  lines.push(emptyBorderedLine(width));
1879
1933
  const gitLines = renderGitInfo(state, width);
@@ -2189,6 +2243,7 @@ var ANSIOutput = class {
2189
2243
  init_esm_shims();
2190
2244
 
2191
2245
  // src/consumers/tui/consumer.ts
2246
+ var CLOCK_REFRESH_INTERVAL_MS = 1e3;
2192
2247
  function formatErrorToLines2(err, lines) {
2193
2248
  if (err instanceof Error) {
2194
2249
  lines.push(` - ${err.name}: ${err.message}`);
@@ -2248,6 +2303,17 @@ function createTUIConsumer() {
2248
2303
  const inputFiber = yield* Effect3.forkDaemon(
2249
2304
  runInputLoop(stateRef, output)
2250
2305
  );
2306
+ const clockFiber = yield* Effect3.forkDaemon(
2307
+ Effect3.forever(
2308
+ Effect3.gen(function* () {
2309
+ yield* Effect3.sleep(CLOCK_REFRESH_INTERVAL_MS);
2310
+ const state = yield* Ref2.get(stateRef);
2311
+ if (state.status === "running") {
2312
+ yield* Effect3.sync(() => safeRender(state, output));
2313
+ }
2314
+ })
2315
+ )
2316
+ );
2251
2317
  const processEvents = events.pipe(
2252
2318
  Stream3.runForEach(
2253
2319
  (event) => Effect3.gen(function* () {
@@ -2299,6 +2365,7 @@ ${errorText}
2299
2365
  })
2300
2366
  );
2301
2367
  }
2368
+ yield* Fiber.interrupt(clockFiber);
2302
2369
  unsubscribeResize();
2303
2370
  cleanup();
2304
2371
  })
@@ -2307,23 +2374,15 @@ ${errorText}
2307
2374
 
2308
2375
  // src/layers/index.ts
2309
2376
  init_esm_shims();
2310
- import { Layer as Layer8 } from "effect";
2311
-
2312
- // src/layers/llm/claude-cli.ts
2313
- init_esm_shims();
2314
- import { spawn } from "child_process";
2315
- import { Effect as Effect5, Layer, Stream as Stream5 } from "effect";
2316
-
2317
- // src/services/llm.ts
2318
- init_esm_shims();
2319
- import { Context } from "effect";
2320
- var LLM = class extends Context.Tag("@ferix/LLM")() {
2321
- };
2377
+ import { Layer as Layer14 } from "effect";
2322
2378
 
2323
- // src/layers/llm/stream.ts
2379
+ // src/layers/git/file-system.ts
2324
2380
  init_esm_shims();
2325
- import { createInterface } from "readline";
2326
- import { Effect as Effect4, Stream as Stream4 } from "effect";
2381
+ import { exec } from "child_process";
2382
+ import { access, mkdir, rm } from "fs/promises";
2383
+ import { join } from "path";
2384
+ import { promisify } from "util";
2385
+ import { Effect as Effect4, Layer } from "effect";
2327
2386
 
2328
2387
  // src/domain/errors.ts
2329
2388
  init_esm_shims();
@@ -2336,288 +2395,346 @@ var PlanStoreError = class extends Data.TaggedError("PlanStoreError") {
2336
2395
  };
2337
2396
  var SessionStoreError = class extends Data.TaggedError("SessionStoreError") {
2338
2397
  };
2398
+ var ProgressStoreError = class extends Data.TaggedError("ProgressStoreError") {
2399
+ };
2400
+ var GuardrailsStoreError = class extends Data.TaggedError(
2401
+ "GuardrailsStoreError"
2402
+ ) {
2403
+ };
2339
2404
  var OrchestratorError = class extends Data.TaggedError("OrchestratorError") {
2340
2405
  };
2406
+ var GitError = class extends Data.TaggedError("GitError") {
2407
+ };
2341
2408
 
2342
- // src/layers/llm/parsers.ts
2409
+ // src/services/git.ts
2343
2410
  init_esm_shims();
2344
- function parseJsonLine(line) {
2345
- if (!line.startsWith("{")) {
2346
- return null;
2347
- }
2348
- try {
2349
- return JSON.parse(line);
2350
- } catch {
2351
- return null;
2352
- }
2353
- }
2354
- function isTextContent(json) {
2355
- return typeof json === "object" && json !== null && "type" in json && typeof json.type === "string";
2356
- }
2357
- function isToolUse(json) {
2358
- return typeof json === "object" && json !== null && "type" in json && "content_block" in json;
2359
- }
2360
- function extractText(json) {
2361
- if (!isTextContent(json)) {
2362
- return null;
2363
- }
2364
- if (json.type === "content_block_delta") {
2365
- const delta = json;
2366
- if (delta.delta?.type === "text_delta" && delta.delta.text) {
2367
- return delta.delta.text;
2368
- }
2369
- }
2370
- if (json.type === "assistant" && json.message?.content) {
2371
- for (const block of json.message.content) {
2372
- if (block.type === "text" && block.text) {
2373
- return block.text;
2374
- }
2411
+ import { Context } from "effect";
2412
+ var Git = class extends Context.Tag("@ferix/Git")() {
2413
+ };
2414
+
2415
+ // src/layers/git/file-system.ts
2416
+ var execAsync = promisify(exec);
2417
+ var WORKTREES_DIR = ".ferix/worktrees";
2418
+ var BRANCH_PREFIX = "ferix";
2419
+ function getWorktreeDir(sessionId) {
2420
+ return join(process.cwd(), WORKTREES_DIR, sessionId);
2421
+ }
2422
+ function getBranchName(sessionId) {
2423
+ return `${BRANCH_PREFIX}/${sessionId}`;
2424
+ }
2425
+ function gitExec(command, cwd) {
2426
+ return Effect4.tryPromise({
2427
+ try: async () => {
2428
+ const { stdout } = await execAsync(command, { cwd });
2429
+ return stdout.trim();
2430
+ },
2431
+ catch: (error) => {
2432
+ const execError = error;
2433
+ return new GitError({
2434
+ message: execError.stderr || execError.message || String(error),
2435
+ operation: "status",
2436
+ cause: error
2437
+ });
2375
2438
  }
2376
- }
2377
- return null;
2439
+ });
2378
2440
  }
2379
- function isToolInputDelta(json) {
2380
- return typeof json === "object" && json !== null && "type" in json && json.type === "content_block_delta" && "delta" in json;
2441
+ function directoryExists(dirPath) {
2442
+ return Effect4.tryPromise({
2443
+ try: async () => {
2444
+ await access(dirPath);
2445
+ return true;
2446
+ },
2447
+ catch: () => new Error("Directory does not exist")
2448
+ }).pipe(Effect4.orElseSucceed(() => false));
2381
2449
  }
2382
- function extractToolInfo(json) {
2383
- if (typeof json === "object" && json !== null && "type" in json && json.type === "content_block_stop") {
2384
- return {
2385
- type: "end",
2386
- name: "unknown"
2387
- };
2388
- }
2389
- if (!isToolUse(json)) {
2390
- if (isToolInputDelta(json) && json.delta?.type === "input_json_delta" && json.delta.partial_json) {
2391
- return {
2392
- type: "input_delta",
2393
- name: "",
2394
- partialJson: json.delta.partial_json
2395
- };
2450
+ var make = {
2451
+ createWorktree: (sessionId, baseBranch) => Effect4.gen(function* () {
2452
+ const worktreeDir = getWorktreeDir(sessionId);
2453
+ const branchName = getBranchName(sessionId);
2454
+ const worktreesBase = join(process.cwd(), WORKTREES_DIR);
2455
+ yield* Effect4.tryPromise({
2456
+ try: () => mkdir(worktreesBase, { recursive: true }),
2457
+ catch: (error) => new GitError({
2458
+ message: `Failed to create worktrees directory: ${String(error)}`,
2459
+ operation: "createWorktree",
2460
+ cause: error
2461
+ })
2462
+ });
2463
+ const exists = yield* directoryExists(worktreeDir);
2464
+ if (exists) {
2465
+ return worktreeDir;
2396
2466
  }
2397
- return null;
2398
- }
2399
- if (json.type === "content_block_start" && json.content_block?.type === "tool_use") {
2400
- return {
2401
- type: "start",
2402
- name: json.content_block.name || "unknown"
2403
- };
2404
- }
2405
- return null;
2406
- }
2407
- function safeParseJson(jsonStr) {
2408
- try {
2409
- return JSON.parse(jsonStr);
2410
- } catch {
2411
- return null;
2412
- }
2413
- }
2414
- function unwrapStreamEvent(json) {
2415
- if (typeof json === "object" && json !== null && "type" in json && json.type === "stream_event" && "event" in json) {
2416
- return json.event;
2417
- }
2418
- return json;
2419
- }
2420
-
2421
- // src/layers/llm/stream.ts
2422
- function handleToolEvent(toolInfo, toolState, emit) {
2423
- if (toolInfo.type === "start") {
2424
- toolState.currentTool = toolInfo.name;
2425
- toolState.inputChunks.length = 0;
2426
- emit.single({ _tag: "ToolStart", tool: toolInfo.name });
2427
- return;
2428
- }
2429
- if (toolInfo.type === "input_delta" && toolInfo.partialJson) {
2430
- toolState.inputChunks.push(toolInfo.partialJson);
2431
- return;
2432
- }
2433
- if (toolInfo.type === "end" && toolState.currentTool) {
2434
- const inputJson = toolState.inputChunks.join("");
2435
- const input = safeParseJson(inputJson);
2436
- if (input !== null) {
2437
- emit.single({ _tag: "ToolUse", tool: toolState.currentTool, input });
2467
+ const baseRef = baseBranch || "HEAD";
2468
+ const command = `git worktree add "${worktreeDir}" -b "${branchName}" ${baseRef}`;
2469
+ yield* gitExec(command).pipe(
2470
+ Effect4.mapError(
2471
+ (error) => new GitError({
2472
+ message: `Failed to create worktree: ${error.message}`,
2473
+ operation: "createWorktree",
2474
+ cause: error
2475
+ })
2476
+ )
2477
+ );
2478
+ return worktreeDir;
2479
+ }),
2480
+ removeWorktree: (sessionId) => Effect4.gen(function* () {
2481
+ const worktreeDir = getWorktreeDir(sessionId);
2482
+ const branchName = getBranchName(sessionId);
2483
+ const exists = yield* directoryExists(worktreeDir);
2484
+ if (!exists) {
2485
+ return;
2438
2486
  }
2439
- emit.single({ _tag: "ToolEnd", tool: toolState.currentTool });
2440
- toolState.currentTool = "";
2441
- toolState.inputChunks.length = 0;
2442
- }
2443
- }
2444
- function processJsonLine(json, outputChunks, toolState, emit) {
2445
- const event = unwrapStreamEvent(json);
2446
- const text = extractText(event);
2447
- if (text) {
2448
- outputChunks.push(text);
2449
- emit.single({ _tag: "Text", text });
2450
- return;
2451
- }
2452
- const toolInfo = extractToolInfo(event);
2453
- if (toolInfo) {
2454
- handleToolEvent(toolInfo, toolState, emit);
2455
- }
2456
- }
2457
- function createEventStream(child) {
2458
- return Stream4.async((emit) => {
2459
- const outputChunks = [];
2460
- const toolState = { currentTool: "", inputChunks: [] };
2461
- const stdout = child.stdout;
2462
- if (!stdout) {
2463
- emit.fail(
2464
- new LLMError({ message: "Failed to get stdout from child process" })
2487
+ yield* gitExec(`git worktree remove "${worktreeDir}" --force`).pipe(
2488
+ Effect4.mapError(
2489
+ (error) => new GitError({
2490
+ message: `Failed to remove worktree: ${error.message}`,
2491
+ operation: "removeWorktree",
2492
+ cause: error
2493
+ })
2494
+ ),
2495
+ // If git worktree remove fails, try manual cleanup
2496
+ Effect4.catchAll(
2497
+ () => Effect4.tryPromise({
2498
+ try: () => rm(worktreeDir, { recursive: true, force: true }),
2499
+ catch: (error) => new GitError({
2500
+ message: `Failed to remove worktree directory: ${String(error)}`,
2501
+ operation: "removeWorktree",
2502
+ cause: error
2503
+ })
2504
+ })
2505
+ )
2506
+ );
2507
+ yield* gitExec(`git branch -D "${branchName}"`).pipe(
2508
+ Effect4.catchAll(() => Effect4.succeed(void 0))
2509
+ );
2510
+ yield* gitExec("git worktree prune").pipe(
2511
+ Effect4.catchAll(() => Effect4.succeed(void 0))
2512
+ );
2513
+ }),
2514
+ getWorktreePath: (sessionId) => Effect4.gen(function* () {
2515
+ const worktreeDir = getWorktreeDir(sessionId);
2516
+ const exists = yield* directoryExists(worktreeDir);
2517
+ return exists ? worktreeDir : void 0;
2518
+ }),
2519
+ commitChanges: (sessionId, message) => Effect4.gen(function* () {
2520
+ const worktreeDir = getWorktreeDir(sessionId);
2521
+ const exists = yield* directoryExists(worktreeDir);
2522
+ if (!exists) {
2523
+ return yield* Effect4.fail(
2524
+ new GitError({
2525
+ message: `Worktree not found for session: ${sessionId}`,
2526
+ operation: "commit"
2527
+ })
2465
2528
  );
2466
- return Effect4.void;
2467
2529
  }
2468
- const rl = createInterface({
2469
- input: stdout,
2470
- crlfDelay: Number.POSITIVE_INFINITY
2471
- });
2472
- rl.on("line", (line) => {
2473
- const json = parseJsonLine(line);
2474
- if (json) {
2475
- processJsonLine(json, outputChunks, toolState, emit);
2476
- }
2477
- });
2478
- child.stderr?.on("data", (data) => {
2479
- const text = data.toString().trim();
2480
- if (text) {
2481
- emit.single({ _tag: "Text", text: `[stderr] ${text}` });
2482
- }
2483
- });
2484
- child.on("close", (exitCode) => {
2485
- if (exitCode !== 0) {
2486
- emit.fail(
2487
- new LLMError({
2488
- message: `Claude CLI exited with code ${exitCode}`
2530
+ yield* gitExec("git add -A", worktreeDir).pipe(
2531
+ Effect4.mapError(
2532
+ (error) => new GitError({
2533
+ message: `Failed to stage changes: ${error.message}`,
2534
+ operation: "commit",
2535
+ cause: error
2536
+ })
2537
+ )
2538
+ );
2539
+ const status = yield* gitExec("git status --porcelain", worktreeDir).pipe(
2540
+ Effect4.catchAll(() => Effect4.succeed(""))
2541
+ );
2542
+ if (!status) {
2543
+ const head = yield* gitExec("git rev-parse HEAD", worktreeDir).pipe(
2544
+ Effect4.mapError(
2545
+ (error) => new GitError({
2546
+ message: `Failed to get HEAD: ${error.message}`,
2547
+ operation: "commit",
2548
+ cause: error
2489
2549
  })
2490
- );
2491
- } else {
2492
- const fullOutput = outputChunks.join("");
2493
- emit.single({ _tag: "Done", output: fullOutput });
2494
- emit.end();
2495
- }
2496
- });
2497
- child.on("error", (error) => {
2498
- emit.fail(
2499
- new LLMError({
2500
- message: error.message,
2550
+ )
2551
+ );
2552
+ return head;
2553
+ }
2554
+ const escapedMessage = message.replace(/"/g, '\\"');
2555
+ yield* gitExec(`git commit -m "${escapedMessage}"`, worktreeDir).pipe(
2556
+ Effect4.mapError(
2557
+ (error) => new GitError({
2558
+ message: `Failed to commit: ${error.message}`,
2559
+ operation: "commit",
2560
+ cause: error
2561
+ })
2562
+ )
2563
+ );
2564
+ const hash = yield* gitExec("git rev-parse HEAD", worktreeDir).pipe(
2565
+ Effect4.mapError(
2566
+ (error) => new GitError({
2567
+ message: `Failed to get commit hash: ${error.message}`,
2568
+ operation: "commit",
2501
2569
  cause: error
2502
2570
  })
2571
+ )
2572
+ );
2573
+ return hash;
2574
+ }),
2575
+ pushBranch: (sessionId) => Effect4.gen(function* () {
2576
+ const worktreeDir = getWorktreeDir(sessionId);
2577
+ const branchName = getBranchName(sessionId);
2578
+ const exists = yield* directoryExists(worktreeDir);
2579
+ if (!exists) {
2580
+ return yield* Effect4.fail(
2581
+ new GitError({
2582
+ message: `Worktree not found for session: ${sessionId}`,
2583
+ operation: "push"
2584
+ })
2503
2585
  );
2504
- });
2505
- return Effect4.sync(() => {
2506
- child.kill("SIGTERM");
2507
- });
2508
- });
2509
- }
2510
-
2511
- // src/layers/llm/claude-cli.ts
2512
- var make = {
2513
- execute: (prompt) => {
2514
- return Stream5.unwrap(
2515
- Effect5.sync(() => {
2516
- const child = spawn(
2517
- "claude",
2518
- [
2519
- "--permission-mode",
2520
- "acceptEdits",
2521
- "--output-format",
2522
- "stream-json",
2523
- "--verbose",
2524
- "--include-partial-messages",
2525
- "-p",
2526
- prompt
2527
- ],
2528
- {
2529
- stdio: ["inherit", "pipe", "pipe"],
2530
- env: {
2531
- ...process.env,
2532
- FORCE_COLOR: "1"
2533
- }
2534
- }
2535
- );
2536
- return createEventStream(child);
2537
- })
2586
+ }
2587
+ yield* gitExec(`git push -u origin "${branchName}"`, worktreeDir).pipe(
2588
+ Effect4.mapError(
2589
+ (error) => new GitError({
2590
+ message: `Failed to push branch: ${error.message}`,
2591
+ operation: "push",
2592
+ cause: error
2593
+ })
2594
+ )
2538
2595
  );
2539
- }
2596
+ }),
2597
+ createPR: (sessionId, title, body) => Effect4.gen(function* () {
2598
+ const worktreeDir = getWorktreeDir(sessionId);
2599
+ const exists = yield* directoryExists(worktreeDir);
2600
+ if (!exists) {
2601
+ return yield* Effect4.fail(
2602
+ new GitError({
2603
+ message: `Worktree not found for session: ${sessionId}`,
2604
+ operation: "createPR"
2605
+ })
2606
+ );
2607
+ }
2608
+ const escapedTitle = title.replace(/"/g, '\\"');
2609
+ const escapedBody = body.replace(/"/g, '\\"');
2610
+ const prUrl = yield* gitExec(
2611
+ `gh pr create --title "${escapedTitle}" --body "${escapedBody}"`,
2612
+ worktreeDir
2613
+ ).pipe(
2614
+ Effect4.mapError(
2615
+ (error) => new GitError({
2616
+ message: `Failed to create PR: ${error.message}`,
2617
+ operation: "createPR",
2618
+ cause: error
2619
+ })
2620
+ )
2621
+ );
2622
+ return prUrl;
2623
+ }),
2624
+ getBranchName
2540
2625
  };
2541
- var Live = Layer.succeed(LLM, make);
2542
- var ClaudeCLI = {
2626
+ var Live = Layer.succeed(Git, make);
2627
+ var FileSystemGit = {
2543
2628
  Live
2544
2629
  };
2545
2630
 
2546
- // src/layers/llm/mock.ts
2547
- init_esm_shims();
2548
- import { Effect as Effect6, Layer as Layer2, Schema as S2, Stream as Stream6 } from "effect";
2549
-
2550
- // src/domain/schemas/llm.ts
2631
+ // src/layers/git/memory.ts
2551
2632
  init_esm_shims();
2552
- import { Schema as S } from "effect";
2553
- var TextEventSchema = S.TaggedStruct("Text", {
2554
- text: S.String
2555
- });
2556
- var ToolStartEventSchema = S.TaggedStruct("ToolStart", {
2557
- tool: S.String
2558
- });
2559
- var ToolUseEventSchema = S.TaggedStruct("ToolUse", {
2560
- tool: S.String,
2561
- input: S.Unknown
2562
- });
2563
- var ToolEndEventSchema = S.TaggedStruct("ToolEnd", {
2564
- tool: S.String
2565
- });
2566
- var DoneEventSchema = S.TaggedStruct("Done", {
2567
- output: S.String
2568
- });
2569
- var LLMEventSchema = S.Union(
2570
- TextEventSchema,
2571
- ToolStartEventSchema,
2572
- ToolUseEventSchema,
2573
- ToolEndEventSchema,
2574
- DoneEventSchema
2575
- );
2576
- var decodeLLMEvent = S.decodeUnknown(LLMEventSchema);
2577
-
2578
- // src/layers/llm/mock.ts
2579
- var MockLLMConfigSchema = S2.Struct({
2580
- events: S2.Array(LLMEventSchema),
2581
- delayMs: S2.optional(S2.Number)
2582
- });
2583
- function createMockLLM(config) {
2633
+ import { Effect as Effect5, Layer as Layer2, Ref as Ref3 } from "effect";
2634
+ var BRANCH_PREFIX2 = "ferix";
2635
+ function getBranchName2(sessionId) {
2636
+ return `${BRANCH_PREFIX2}/${sessionId}`;
2637
+ }
2638
+ function createMemoryGitService(stateRef, commitCounterRef) {
2584
2639
  return {
2585
- execute: (_prompt) => {
2586
- const baseStream = Stream6.fromIterable(config.events);
2587
- if (config.delayMs !== void 0 && config.delayMs > 0) {
2588
- const delay = config.delayMs;
2589
- return baseStream.pipe(Stream6.tap(() => Effect6.sleep(delay)));
2640
+ createWorktree: (sessionId, _baseBranch) => Effect5.gen(function* () {
2641
+ const state = yield* Ref3.get(stateRef);
2642
+ const existing = state.get(sessionId);
2643
+ if (existing) {
2644
+ return existing.path;
2590
2645
  }
2591
- return baseStream;
2592
- }
2646
+ const path2 = `.ferix/worktrees/${sessionId}`;
2647
+ const branch = getBranchName2(sessionId);
2648
+ const worktree = {
2649
+ path: path2,
2650
+ branch,
2651
+ commits: []
2652
+ };
2653
+ state.set(sessionId, worktree);
2654
+ yield* Ref3.set(stateRef, state);
2655
+ return path2;
2656
+ }),
2657
+ removeWorktree: (sessionId) => Effect5.gen(function* () {
2658
+ const state = yield* Ref3.get(stateRef);
2659
+ state.delete(sessionId);
2660
+ yield* Ref3.set(stateRef, state);
2661
+ }),
2662
+ getWorktreePath: (sessionId) => Effect5.gen(function* () {
2663
+ const state = yield* Ref3.get(stateRef);
2664
+ const worktree = state.get(sessionId);
2665
+ return worktree?.path;
2666
+ }),
2667
+ commitChanges: (sessionId, message) => Effect5.gen(function* () {
2668
+ const state = yield* Ref3.get(stateRef);
2669
+ const worktree = state.get(sessionId);
2670
+ if (!worktree) {
2671
+ return yield* Effect5.fail(
2672
+ new GitError({
2673
+ message: `Worktree not found for session: ${sessionId}`,
2674
+ operation: "commit"
2675
+ })
2676
+ );
2677
+ }
2678
+ const counter = yield* Ref3.updateAndGet(commitCounterRef, (n) => n + 1);
2679
+ const hash = `test-commit-${counter}`;
2680
+ const updatedWorktree = {
2681
+ ...worktree,
2682
+ commits: [...worktree.commits, `${hash}: ${message}`]
2683
+ };
2684
+ state.set(sessionId, updatedWorktree);
2685
+ yield* Ref3.set(stateRef, state);
2686
+ return hash;
2687
+ }),
2688
+ pushBranch: (sessionId) => Effect5.gen(function* () {
2689
+ const state = yield* Ref3.get(stateRef);
2690
+ const worktree = state.get(sessionId);
2691
+ if (!worktree) {
2692
+ return yield* Effect5.fail(
2693
+ new GitError({
2694
+ message: `Worktree not found for session: ${sessionId}`,
2695
+ operation: "push"
2696
+ })
2697
+ );
2698
+ }
2699
+ }),
2700
+ createPR: (sessionId, title, _body) => Effect5.gen(function* () {
2701
+ const state = yield* Ref3.get(stateRef);
2702
+ const worktree = state.get(sessionId);
2703
+ if (!worktree) {
2704
+ return yield* Effect5.fail(
2705
+ new GitError({
2706
+ message: `Worktree not found for session: ${sessionId}`,
2707
+ operation: "createPR"
2708
+ })
2709
+ );
2710
+ }
2711
+ const slug = title.toLowerCase().replace(/\s+/g, "-").slice(0, 30);
2712
+ return `https://github.com/test/repo/pull/${slug}`;
2713
+ }),
2714
+ getBranchName: getBranchName2
2593
2715
  };
2594
2716
  }
2595
- var defaultMockEvents = [
2596
- { _tag: "Text", text: "Processing task...\n" },
2597
- { _tag: "ToolStart", tool: "Read" },
2598
- { _tag: "ToolEnd", tool: "Read" },
2599
- { _tag: "Text", text: "Task completed successfully.\n" },
2600
- {
2601
- _tag: "Done",
2602
- output: "Processing task...\nTask completed successfully.\n"
2603
- }
2604
- ];
2605
- var defaultMock = createMockLLM({ events: defaultMockEvents });
2606
- var Live2 = Layer2.succeed(LLM, defaultMock);
2607
- function layer(config) {
2608
- return Layer2.succeed(LLM, createMockLLM(config));
2717
+ function layer() {
2718
+ return Layer2.effect(
2719
+ Git,
2720
+ Effect5.gen(function* () {
2721
+ const stateRef = yield* Ref3.make(/* @__PURE__ */ new Map());
2722
+ const commitCounterRef = yield* Ref3.make(0);
2723
+ return createMemoryGitService(stateRef, commitCounterRef);
2724
+ })
2725
+ );
2609
2726
  }
2610
- var Mock = {
2727
+ var Live2 = layer();
2728
+ var MemoryGit = {
2611
2729
  Live: Live2,
2612
- layer,
2613
- createMockLLM
2730
+ layer
2614
2731
  };
2615
2732
 
2616
- // src/layers/plan/file-system.ts
2733
+ // src/layers/guardrails/file-system.ts
2617
2734
  init_esm_shims();
2618
- import { access, mkdir, readdir, readFile, writeFile } from "fs/promises";
2619
- import { join } from "path";
2620
- import { Effect as Effect7, Layer as Layer3 } from "effect";
2735
+ import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
2736
+ import { join as join2 } from "path";
2737
+ import { DateTime, Effect as Effect6, Layer as Layer3 } from "effect";
2621
2738
 
2622
2739
  // src/domain/index.ts
2623
2740
  init_esm_shims();
@@ -2627,55 +2744,55 @@ init_esm_shims();
2627
2744
 
2628
2745
  // src/domain/schemas/config.ts
2629
2746
  init_esm_shims();
2630
- import { Schema as S3 } from "effect";
2631
- var PhasePromptOverridesSchema = S3.Struct({
2632
- breakdown: S3.optional(S3.String),
2633
- planning: S3.optional(S3.String),
2634
- execution: S3.optional(S3.String),
2635
- check: S3.optional(S3.String),
2636
- verify: S3.optional(S3.String),
2637
- review: S3.optional(S3.String),
2638
- completion: S3.optional(S3.String)
2639
- });
2640
- var PromptConfigSchema = S3.Struct({
2641
- systemPrompt: S3.optional(S3.String),
2642
- phases: S3.optional(PhasePromptOverridesSchema),
2643
- additionalContext: S3.optional(S3.String)
2644
- });
2645
- var LoopConfigSchema = S3.Struct({
2646
- task: S3.String,
2647
- maxIterations: S3.Number,
2648
- verifyCommands: S3.Array(S3.String),
2649
- sessionId: S3.optional(S3.String),
2650
- branch: S3.optional(S3.String),
2651
- push: S3.optional(S3.Boolean),
2652
- pr: S3.optional(S3.Boolean),
2653
- verbose: S3.optional(S3.Boolean),
2654
- prompts: S3.optional(PromptConfigSchema)
2655
- });
2656
- var LoopSummarySchema = S3.Struct({
2657
- iterations: S3.Number,
2658
- success: S3.Boolean,
2659
- sessionId: S3.String,
2660
- completedTasks: S3.Array(S3.String),
2661
- durationMs: S3.Number
2662
- });
2663
- var LoopErrorSchema = S3.Struct({
2664
- message: S3.String,
2665
- phase: S3.String,
2666
- iteration: S3.optional(S3.Number)
2667
- });
2668
- var decodeLoopConfig = S3.decodeUnknown(LoopConfigSchema);
2747
+ import { Schema as S } from "effect";
2748
+ var PhasePromptOverridesSchema = S.Struct({
2749
+ breakdown: S.optional(S.String),
2750
+ planning: S.optional(S.String),
2751
+ execution: S.optional(S.String),
2752
+ check: S.optional(S.String),
2753
+ verify: S.optional(S.String),
2754
+ review: S.optional(S.String),
2755
+ completion: S.optional(S.String)
2756
+ });
2757
+ var PromptConfigSchema = S.Struct({
2758
+ systemPrompt: S.optional(S.String),
2759
+ phases: S.optional(PhasePromptOverridesSchema),
2760
+ additionalContext: S.optional(S.String)
2761
+ });
2762
+ var LoopConfigSchema = S.Struct({
2763
+ task: S.String,
2764
+ maxIterations: S.Number,
2765
+ verifyCommands: S.Array(S.String),
2766
+ sessionId: S.optional(S.String),
2767
+ branch: S.optional(S.String),
2768
+ push: S.optional(S.Boolean),
2769
+ pr: S.optional(S.Boolean),
2770
+ verbose: S.optional(S.Boolean),
2771
+ prompts: S.optional(PromptConfigSchema)
2772
+ });
2773
+ var LoopSummarySchema = S.Struct({
2774
+ iterations: S.Number,
2775
+ success: S.Boolean,
2776
+ sessionId: S.String,
2777
+ completedTasks: S.Array(S.String),
2778
+ durationMs: S.Number
2779
+ });
2780
+ var LoopErrorSchema = S.Struct({
2781
+ message: S.String,
2782
+ phase: S.String,
2783
+ iteration: S.optional(S.Number)
2784
+ });
2785
+ var decodeLoopConfig = S.decodeUnknown(LoopConfigSchema);
2669
2786
 
2670
2787
  // src/domain/schemas/events.ts
2671
2788
  init_esm_shims();
2672
- import { Schema as S6 } from "effect";
2789
+ import { Schema as S4 } from "effect";
2673
2790
 
2674
2791
  // src/domain/schemas/plan.ts
2675
2792
  init_esm_shims();
2676
- import { Brand, Schema as S4 } from "effect";
2793
+ import { Brand, Schema as S2 } from "effect";
2677
2794
  var PlanId = Brand.nominal();
2678
- var TaskStatusSchema = S4.Literal(
2795
+ var TaskStatusSchema = S2.Literal(
2679
2796
  "pending",
2680
2797
  "planning",
2681
2798
  "in_progress",
@@ -2683,114 +2800,114 @@ var TaskStatusSchema = S4.Literal(
2683
2800
  "failed",
2684
2801
  "skipped"
2685
2802
  );
2686
- var PhaseStatusSchema = S4.Literal(
2803
+ var PhaseStatusSchema = S2.Literal(
2687
2804
  "pending",
2688
2805
  "in_progress",
2689
2806
  "done",
2690
2807
  "failed"
2691
2808
  );
2692
- var CriterionStatusSchema = S4.Literal("pending", "passed", "failed");
2693
- var PhaseSchema = S4.Struct({
2694
- id: S4.String,
2695
- description: S4.String,
2809
+ var CriterionStatusSchema = S2.Literal("pending", "passed", "failed");
2810
+ var PhaseSchema = S2.Struct({
2811
+ id: S2.String,
2812
+ description: S2.String,
2696
2813
  status: PhaseStatusSchema
2697
2814
  });
2698
- var CriterionSchema = S4.Struct({
2699
- id: S4.String,
2700
- description: S4.String,
2815
+ var CriterionSchema = S2.Struct({
2816
+ id: S2.String,
2817
+ description: S2.String,
2701
2818
  status: CriterionStatusSchema,
2702
- failureReason: S4.optional(S4.String)
2819
+ failureReason: S2.optional(S2.String)
2703
2820
  });
2704
- var TaskSchema = S4.Struct({
2705
- id: S4.String,
2706
- title: S4.String,
2707
- description: S4.String,
2821
+ var TaskSchema = S2.Struct({
2822
+ id: S2.String,
2823
+ title: S2.String,
2824
+ description: S2.String,
2708
2825
  status: TaskStatusSchema,
2709
- phases: S4.Array(PhaseSchema),
2710
- criteria: S4.Array(CriterionSchema),
2711
- filesToModify: S4.Array(S4.String),
2712
- attempts: S4.Number,
2713
- completionNotes: S4.optional(S4.String)
2714
- });
2715
- var PlanDataSchema = S4.Struct({
2716
- sessionId: S4.String,
2717
- createdAt: S4.String,
2718
- originalTask: S4.String,
2719
- context: S4.optional(S4.String),
2720
- tasks: S4.Array(TaskSchema)
2721
- });
2722
- var PlanSchema = S4.Struct({
2723
- id: S4.String,
2724
- sessionId: S4.String,
2725
- createdAt: S4.String,
2726
- originalTask: S4.String,
2727
- context: S4.optional(S4.String),
2728
- tasks: S4.Array(TaskSchema)
2729
- });
2730
- var decodePlan = S4.decodeUnknown(PlanSchema);
2731
- var decodePlanData = S4.decodeUnknown(PlanDataSchema);
2826
+ phases: S2.Array(PhaseSchema),
2827
+ criteria: S2.Array(CriterionSchema),
2828
+ filesToModify: S2.Array(S2.String),
2829
+ attempts: S2.Number,
2830
+ completionNotes: S2.optional(S2.String)
2831
+ });
2832
+ var PlanDataSchema = S2.Struct({
2833
+ sessionId: S2.String,
2834
+ createdAt: S2.String,
2835
+ originalTask: S2.String,
2836
+ context: S2.optional(S2.String),
2837
+ tasks: S2.Array(TaskSchema)
2838
+ });
2839
+ var PlanSchema = S2.Struct({
2840
+ id: S2.String,
2841
+ sessionId: S2.String,
2842
+ createdAt: S2.String,
2843
+ originalTask: S2.String,
2844
+ context: S2.optional(S2.String),
2845
+ tasks: S2.Array(TaskSchema)
2846
+ });
2847
+ var decodePlan = S2.decodeUnknown(PlanSchema);
2848
+ var decodePlanData = S2.decodeUnknown(PlanDataSchema);
2732
2849
 
2733
2850
  // src/domain/schemas/shared.ts
2734
2851
  init_esm_shims();
2735
- import { Schema as S5 } from "effect";
2736
- var TaskBasicInfoSchema = S5.Struct({
2737
- id: S5.String,
2738
- title: S5.String,
2739
- description: S5.String
2852
+ import { Schema as S3 } from "effect";
2853
+ var TaskBasicInfoSchema = S3.Struct({
2854
+ id: S3.String,
2855
+ title: S3.String,
2856
+ description: S3.String
2740
2857
  });
2741
- var PhaseBasicInfoSchema = S5.Struct({
2742
- id: S5.String,
2743
- description: S5.String
2858
+ var PhaseBasicInfoSchema = S3.Struct({
2859
+ id: S3.String,
2860
+ description: S3.String
2744
2861
  });
2745
- var CriterionBasicInfoSchema = S5.Struct({
2746
- id: S5.String,
2747
- description: S5.String
2862
+ var CriterionBasicInfoSchema = S3.Struct({
2863
+ id: S3.String,
2864
+ description: S3.String
2748
2865
  });
2749
- var TasksDefinedDataSchema = S5.Struct({
2750
- tasks: S5.Array(TaskBasicInfoSchema)
2866
+ var TasksDefinedDataSchema = S3.Struct({
2867
+ tasks: S3.Array(TaskBasicInfoSchema)
2751
2868
  });
2752
- var PhasesDefinedDataSchema = S5.Struct({
2753
- taskId: S5.String,
2754
- phases: S5.Array(PhaseBasicInfoSchema)
2869
+ var PhasesDefinedDataSchema = S3.Struct({
2870
+ taskId: S3.String,
2871
+ phases: S3.Array(PhaseBasicInfoSchema)
2755
2872
  });
2756
- var CriteriaDefinedDataSchema = S5.Struct({
2757
- taskId: S5.String,
2758
- criteria: S5.Array(CriterionBasicInfoSchema)
2873
+ var CriteriaDefinedDataSchema = S3.Struct({
2874
+ taskId: S3.String,
2875
+ criteria: S3.Array(CriterionBasicInfoSchema)
2759
2876
  });
2760
- var PhaseIdDataSchema = S5.Struct({
2761
- phaseId: S5.String
2877
+ var PhaseIdDataSchema = S3.Struct({
2878
+ phaseId: S3.String
2762
2879
  });
2763
- var PhaseFailedDataSchema = S5.Struct({
2764
- phaseId: S5.String,
2765
- reason: S5.String
2880
+ var PhaseFailedDataSchema = S3.Struct({
2881
+ phaseId: S3.String,
2882
+ reason: S3.String
2766
2883
  });
2767
- var CriterionIdDataSchema = S5.Struct({
2768
- criterionId: S5.String
2884
+ var CriterionIdDataSchema = S3.Struct({
2885
+ criterionId: S3.String
2769
2886
  });
2770
- var CriterionFailedDataSchema = S5.Struct({
2771
- criterionId: S5.String,
2772
- reason: S5.String
2887
+ var CriterionFailedDataSchema = S3.Struct({
2888
+ criterionId: S3.String,
2889
+ reason: S3.String
2773
2890
  });
2774
- var ReviewCompleteDataSchema = S5.Struct({
2775
- changesMade: S5.Boolean
2891
+ var ReviewCompleteDataSchema = S3.Struct({
2892
+ changesMade: S3.Boolean
2776
2893
  });
2777
- var TaskCompleteSignalDataSchema = S5.Struct({
2778
- taskId: S5.String,
2779
- summary: S5.String,
2780
- filesModified: S5.Array(S5.String),
2781
- filesCreated: S5.Array(S5.String)
2894
+ var TaskCompleteSignalDataSchema = S3.Struct({
2895
+ taskId: S3.String,
2896
+ summary: S3.String,
2897
+ filesModified: S3.Array(S3.String),
2898
+ filesCreated: S3.Array(S3.String)
2782
2899
  });
2783
- var TaskCompleteDataSchema = S5.Struct({
2784
- taskId: S5.String,
2785
- summary: S5.String
2900
+ var TaskCompleteDataSchema = S3.Struct({
2901
+ taskId: S3.String,
2902
+ summary: S3.String
2786
2903
  });
2787
2904
 
2788
2905
  // src/domain/schemas/events.ts
2789
- var taggedEvent = (tag, fields) => S6.TaggedStruct(tag, fields);
2790
- var taggedFromData = (tag, dataSchema, extraFields = {}) => S6.TaggedStruct(tag, { ...dataSchema.fields, ...extraFields });
2906
+ var taggedEvent = (tag, fields) => S4.TaggedStruct(tag, fields);
2907
+ var taggedFromData = (tag, dataSchema, extraFields = {}) => S4.TaggedStruct(tag, { ...dataSchema.fields, ...extraFields });
2791
2908
  var LoopStartedEventSchema = taggedEvent("LoopStarted", {
2792
2909
  config: LoopConfigSchema,
2793
- timestamp: S6.Number
2910
+ timestamp: S4.Number
2794
2911
  });
2795
2912
  var LoopCompletedEventSchema = taggedEvent("LoopCompleted", {
2796
2913
  summary: LoopSummarySchema
@@ -2799,30 +2916,30 @@ var LoopFailedEventSchema = taggedEvent("LoopFailed", {
2799
2916
  error: LoopErrorSchema
2800
2917
  });
2801
2918
  var DiscoveryStartedEventSchema = taggedEvent("DiscoveryStarted", {
2802
- timestamp: S6.Number
2919
+ timestamp: S4.Number
2803
2920
  });
2804
2921
  var DiscoveryCompletedEventSchema = taggedEvent("DiscoveryCompleted", {
2805
- taskCount: S6.Number,
2806
- timestamp: S6.Number
2922
+ taskCount: S4.Number,
2923
+ timestamp: S4.Number
2807
2924
  });
2808
2925
  var IterationStartedEventSchema = taggedEvent("IterationStarted", {
2809
- iteration: S6.Number
2926
+ iteration: S4.Number
2810
2927
  });
2811
2928
  var IterationCompletedEventSchema = taggedEvent("IterationCompleted", {
2812
- iteration: S6.Number
2929
+ iteration: S4.Number
2813
2930
  });
2814
2931
  var LLMTextEventSchema = taggedEvent("LLMText", {
2815
- text: S6.String
2932
+ text: S4.String
2816
2933
  });
2817
2934
  var LLMToolStartEventSchema = taggedEvent("LLMToolStart", {
2818
- tool: S6.String
2935
+ tool: S4.String
2819
2936
  });
2820
2937
  var LLMToolUseEventSchema = taggedEvent("LLMToolUse", {
2821
- tool: S6.String,
2822
- input: S6.Unknown
2938
+ tool: S4.String,
2939
+ input: S4.Unknown
2823
2940
  });
2824
2941
  var LLMToolEndEventSchema = taggedEvent("LLMToolEnd", {
2825
- tool: S6.String
2942
+ tool: S4.String
2826
2943
  });
2827
2944
  var TasksDefinedEventSchema = taggedFromData(
2828
2945
  "TasksDefined",
@@ -2839,17 +2956,17 @@ var CriteriaDefinedEventSchema = taggedFromData(
2839
2956
  var PhaseStartedEventSchema = taggedFromData(
2840
2957
  "PhaseStarted",
2841
2958
  PhaseIdDataSchema,
2842
- { timestamp: S6.Number }
2959
+ { timestamp: S4.Number }
2843
2960
  );
2844
2961
  var PhaseCompletedEventSchema = taggedFromData(
2845
2962
  "PhaseCompleted",
2846
2963
  PhaseIdDataSchema,
2847
- { timestamp: S6.Number }
2964
+ { timestamp: S4.Number }
2848
2965
  );
2849
2966
  var PhaseFailedEventSchema = taggedFromData(
2850
2967
  "PhaseFailed",
2851
2968
  PhaseFailedDataSchema,
2852
- { timestamp: S6.Number }
2969
+ { timestamp: S4.Number }
2853
2970
  );
2854
2971
  var CriterionPassedEventSchema = taggedFromData(
2855
2972
  "CriterionPassed",
@@ -2859,9 +2976,9 @@ var CriterionFailedEventSchema = taggedFromData(
2859
2976
  "CriterionFailed",
2860
2977
  CriterionFailedDataSchema
2861
2978
  );
2862
- var CheckPassedEventSchema = S6.TaggedStruct("CheckPassed", {});
2979
+ var CheckPassedEventSchema = S4.TaggedStruct("CheckPassed", {});
2863
2980
  var CheckFailedEventSchema = taggedEvent("CheckFailed", {
2864
- failedCriteria: S6.Array(S6.String)
2981
+ failedCriteria: S4.Array(S4.String)
2865
2982
  });
2866
2983
  var ReviewCompleteEventSchema = taggedFromData(
2867
2984
  "ReviewComplete",
@@ -2870,7 +2987,7 @@ var ReviewCompleteEventSchema = taggedFromData(
2870
2987
  var TaskCompletedEventSchema = taggedFromData(
2871
2988
  "TaskCompleted",
2872
2989
  TaskCompleteDataSchema,
2873
- { timestamp: S6.Number }
2990
+ { timestamp: S4.Number }
2874
2991
  );
2875
2992
  var PlanCreatedEventSchema = taggedEvent("PlanCreated", {
2876
2993
  plan: PlanSchema
@@ -2879,11 +2996,43 @@ var PlanUpdatedEventSchema = taggedEvent("PlanUpdated", {
2879
2996
  plan: PlanSchema
2880
2997
  });
2881
2998
  var PlanUpdateFailedEventSchema = taggedEvent("PlanUpdateFailed", {
2882
- operation: S6.Literal("create", "update"),
2883
- error: S6.String,
2884
- planId: S6.optional(S6.String)
2999
+ operation: S4.Literal("create", "update"),
3000
+ error: S4.String,
3001
+ planId: S4.optional(S4.String)
3002
+ });
3003
+ var LearningRecordedEventSchema = taggedEvent("LearningRecorded", {
3004
+ iteration: S4.Number,
3005
+ content: S4.String,
3006
+ category: S4.optional(S4.Literal("success", "failure", "optimization")),
3007
+ timestamp: S4.Number
3008
+ });
3009
+ var GuardrailAddedEventSchema = taggedEvent("GuardrailAdded", {
3010
+ id: S4.String,
3011
+ iteration: S4.Number,
3012
+ pattern: S4.String,
3013
+ sign: S4.String,
3014
+ avoidance: S4.String,
3015
+ severity: S4.Literal("warning", "critical"),
3016
+ timestamp: S4.Number
3017
+ });
3018
+ var ProgressUpdatedEventSchema = taggedEvent("ProgressUpdated", {
3019
+ sessionId: S4.String,
3020
+ iteration: S4.Number,
3021
+ taskId: S4.String,
3022
+ action: S4.Literal("started", "completed", "failed", "learning"),
3023
+ timestamp: S4.Number
3024
+ });
3025
+ var WorktreeCreatedEventSchema = taggedEvent("WorktreeCreated", {
3026
+ sessionId: S4.String,
3027
+ worktreePath: S4.String,
3028
+ branchName: S4.String,
3029
+ timestamp: S4.Number
3030
+ });
3031
+ var WorktreeRemovedEventSchema = taggedEvent("WorktreeRemoved", {
3032
+ sessionId: S4.String,
3033
+ timestamp: S4.Number
2885
3034
  });
2886
- var DomainEventSchema = S6.Union(
3035
+ var DomainEventSchema = S4.Union(
2887
3036
  LoopStartedEventSchema,
2888
3037
  LoopCompletedEventSchema,
2889
3038
  LoopFailedEventSchema,
@@ -2909,7 +3058,12 @@ var DomainEventSchema = S6.Union(
2909
3058
  TaskCompletedEventSchema,
2910
3059
  PlanCreatedEventSchema,
2911
3060
  PlanUpdatedEventSchema,
2912
- PlanUpdateFailedEventSchema
3061
+ PlanUpdateFailedEventSchema,
3062
+ LearningRecordedEventSchema,
3063
+ GuardrailAddedEventSchema,
3064
+ ProgressUpdatedEventSchema,
3065
+ WorktreeCreatedEventSchema,
3066
+ WorktreeRemovedEventSchema
2913
3067
  );
2914
3068
  var DomainEventUtils = {
2915
3069
  isLLMEvent: (e) => e._tag.startsWith("LLM"),
@@ -2919,6 +3073,55 @@ var DomainEventUtils = {
2919
3073
  isDiscoveryEvent: (e) => e._tag.startsWith("Discovery")
2920
3074
  };
2921
3075
 
3076
+ // src/domain/schemas/guardrails.ts
3077
+ init_esm_shims();
3078
+ import { Schema as S5 } from "effect";
3079
+ var GuardrailSeveritySchema = S5.Literal("warning", "critical");
3080
+ var GuardrailSchema = S5.Struct({
3081
+ id: S5.String,
3082
+ createdAt: S5.String,
3083
+ iteration: S5.Number,
3084
+ pattern: S5.String,
3085
+ sign: S5.String,
3086
+ avoidance: S5.String,
3087
+ severity: GuardrailSeveritySchema
3088
+ });
3089
+ var GuardrailsFileSchema = S5.Struct({
3090
+ sessionId: S5.String,
3091
+ createdAt: S5.String,
3092
+ guardrails: S5.Array(GuardrailSchema)
3093
+ });
3094
+ var decodeGuardrail = S5.decodeUnknown(GuardrailSchema);
3095
+ var decodeGuardrailsFile = S5.decodeUnknown(GuardrailsFileSchema);
3096
+
3097
+ // src/domain/schemas/llm.ts
3098
+ init_esm_shims();
3099
+ import { Schema as S6 } from "effect";
3100
+ var TextEventSchema = S6.TaggedStruct("Text", {
3101
+ text: S6.String
3102
+ });
3103
+ var ToolStartEventSchema = S6.TaggedStruct("ToolStart", {
3104
+ tool: S6.String
3105
+ });
3106
+ var ToolUseEventSchema = S6.TaggedStruct("ToolUse", {
3107
+ tool: S6.String,
3108
+ input: S6.Unknown
3109
+ });
3110
+ var ToolEndEventSchema = S6.TaggedStruct("ToolEnd", {
3111
+ tool: S6.String
3112
+ });
3113
+ var DoneEventSchema = S6.TaggedStruct("Done", {
3114
+ output: S6.String
3115
+ });
3116
+ var LLMEventSchema = S6.Union(
3117
+ TextEventSchema,
3118
+ ToolStartEventSchema,
3119
+ ToolUseEventSchema,
3120
+ ToolEndEventSchema,
3121
+ DoneEventSchema
3122
+ );
3123
+ var decodeLLMEvent = S6.decodeUnknown(LLMEventSchema);
3124
+
2922
3125
  // src/domain/schemas/logger.ts
2923
3126
  init_esm_shims();
2924
3127
  import { Schema as S7 } from "effect";
@@ -2947,29 +3150,57 @@ var RunOptionsDataSchema = S8.Struct({
2947
3150
  consumer: S8.optional(ConsumerTypeSchema)
2948
3151
  });
2949
3152
 
2950
- // src/domain/schemas/session.ts
3153
+ // src/domain/schemas/progress.ts
2951
3154
  init_esm_shims();
2952
3155
  import { Schema as S9 } from "effect";
2953
- var SessionStatusSchema = S9.Literal(
3156
+ var ProgressActionSchema = S9.Literal(
3157
+ "started",
3158
+ "completed",
3159
+ "failed",
3160
+ "learning"
3161
+ );
3162
+ var ProgressEntrySchema = S9.Struct({
3163
+ iteration: S9.Number,
3164
+ timestamp: S9.String,
3165
+ taskId: S9.String,
3166
+ action: ProgressActionSchema,
3167
+ summary: S9.String,
3168
+ learnings: S9.optional(S9.Array(S9.String)),
3169
+ filesModified: S9.optional(S9.Array(S9.String))
3170
+ });
3171
+ var ProgressFileSchema = S9.Struct({
3172
+ sessionId: S9.String,
3173
+ createdAt: S9.String,
3174
+ entries: S9.Array(ProgressEntrySchema)
3175
+ });
3176
+ var decodeProgressEntry = S9.decodeUnknown(ProgressEntrySchema);
3177
+ var decodeProgressFile = S9.decodeUnknown(ProgressFileSchema);
3178
+
3179
+ // src/domain/schemas/session.ts
3180
+ init_esm_shims();
3181
+ import { Schema as S10 } from "effect";
3182
+ var SessionStatusSchema = S10.Literal(
2954
3183
  "active",
2955
3184
  "completed",
2956
3185
  "failed",
2957
3186
  "paused"
2958
3187
  );
2959
- var SessionSchema = S9.Struct({
2960
- id: S9.String,
2961
- createdAt: S9.String,
3188
+ var SessionSchema = S10.Struct({
3189
+ id: S10.String,
3190
+ createdAt: S10.String,
2962
3191
  status: SessionStatusSchema,
2963
- originalTask: S9.String,
2964
- completedTasks: S9.Array(S9.String),
2965
- currentTaskId: S9.optional(S9.String)
3192
+ originalTask: S10.String,
3193
+ completedTasks: S10.Array(S10.String),
3194
+ currentTaskId: S10.optional(S10.String),
3195
+ worktreePath: S10.optional(S10.String),
3196
+ branchName: S10.optional(S10.String)
2966
3197
  });
2967
- var decodeSession = S9.decodeUnknown(SessionSchema);
3198
+ var decodeSession = S10.decodeUnknown(SessionSchema);
2968
3199
 
2969
3200
  // src/domain/schemas/signals.ts
2970
3201
  init_esm_shims();
2971
- import { Schema as S10 } from "effect";
2972
- var taggedFromData2 = (tag, dataSchema) => S10.TaggedStruct(tag, dataSchema.fields);
3202
+ import { Schema as S11 } from "effect";
3203
+ var taggedFromData2 = (tag, dataSchema) => S11.TaggedStruct(tag, dataSchema.fields);
2973
3204
  var TasksDefinedSignalSchema = taggedFromData2(
2974
3205
  "TasksDefined",
2975
3206
  TasksDefinedDataSchema
@@ -3002,8 +3233,8 @@ var CriterionFailedSignalSchema = taggedFromData2(
3002
3233
  "CriterionFailed",
3003
3234
  CriterionFailedDataSchema
3004
3235
  );
3005
- var CheckPassedSignalSchema = S10.TaggedStruct("CheckPassed", {});
3006
- var CheckFailedSignalSchema = S10.TaggedStruct("CheckFailed", {});
3236
+ var CheckPassedSignalSchema = S11.TaggedStruct("CheckPassed", {});
3237
+ var CheckFailedSignalSchema = S11.TaggedStruct("CheckFailed", {});
3007
3238
  var ReviewCompleteSignalSchema = taggedFromData2(
3008
3239
  "ReviewComplete",
3009
3240
  ReviewCompleteDataSchema
@@ -3012,8 +3243,23 @@ var TaskCompleteSignalSchema = taggedFromData2(
3012
3243
  "TaskComplete",
3013
3244
  TaskCompleteSignalDataSchema
3014
3245
  );
3015
- var LoopCompleteSignalSchema = S10.TaggedStruct("LoopComplete", {});
3016
- var SignalSchema = S10.Union(
3246
+ var LoopCompleteSignalSchema = S11.TaggedStruct("LoopComplete", {});
3247
+ var LearningCategorySchema = S11.Literal(
3248
+ "success",
3249
+ "failure",
3250
+ "optimization"
3251
+ );
3252
+ var LearningSignalSchema = S11.TaggedStruct("Learning", {
3253
+ content: S11.String,
3254
+ category: S11.optional(LearningCategorySchema)
3255
+ });
3256
+ var GuardrailSignalSchema = S11.TaggedStruct("Guardrail", {
3257
+ pattern: S11.String,
3258
+ sign: S11.String,
3259
+ avoidance: S11.String,
3260
+ severity: S11.Literal("warning", "critical")
3261
+ });
3262
+ var SignalSchema = S11.Union(
3017
3263
  TasksDefinedSignalSchema,
3018
3264
  PhasesDefinedSignalSchema,
3019
3265
  CriteriaDefinedSignalSchema,
@@ -3026,26 +3272,28 @@ var SignalSchema = S10.Union(
3026
3272
  CheckFailedSignalSchema,
3027
3273
  ReviewCompleteSignalSchema,
3028
3274
  TaskCompleteSignalSchema,
3029
- LoopCompleteSignalSchema
3275
+ LoopCompleteSignalSchema,
3276
+ LearningSignalSchema,
3277
+ GuardrailSignalSchema
3030
3278
  );
3031
- var decodeSignal = S10.decodeUnknown(SignalSchema);
3032
- var decodeSignalSync = S10.decodeUnknownSync(SignalSchema);
3279
+ var decodeSignal = S11.decodeUnknown(SignalSchema);
3280
+ var decodeSignalSync = S11.decodeUnknownSync(SignalSchema);
3033
3281
 
3034
3282
  // src/domain/schemas/task-generation.ts
3035
3283
  init_esm_shims();
3036
- import { Schema as S11 } from "effect";
3037
- var GeneratedTaskStatusSchema = S11.Literal(
3284
+ import { Schema as S12 } from "effect";
3285
+ var GeneratedTaskStatusSchema = S12.Literal(
3038
3286
  "pending",
3039
3287
  "in_progress",
3040
3288
  "done",
3041
3289
  "failed"
3042
3290
  );
3043
- var GeneratedTaskSchema = S11.Struct({
3044
- id: S11.String,
3045
- title: S11.String,
3291
+ var GeneratedTaskSchema = S12.Struct({
3292
+ id: S12.String,
3293
+ title: S12.String,
3046
3294
  status: GeneratedTaskStatusSchema
3047
3295
  });
3048
- var GeneratedTaskListSchema = S11.Array(GeneratedTaskSchema);
3296
+ var GeneratedTaskListSchema = S12.Array(GeneratedTaskSchema);
3049
3297
  var STATUS_ICONS = {
3050
3298
  done: "[x]",
3051
3299
  in_progress: "[~]",
@@ -3104,15 +3352,15 @@ function parseTasksMd(content) {
3104
3352
 
3105
3353
  // src/domain/schemas/tui.ts
3106
3354
  init_esm_shims();
3107
- import { Schema as S12 } from "effect";
3108
- var ViewModeSchema = S12.Literal("logs", "tasks", "detail");
3109
- var LoopStatusSchema = S12.Literal(
3355
+ import { Schema as S13 } from "effect";
3356
+ var ViewModeSchema = S13.Literal("logs", "tasks", "detail");
3357
+ var LoopStatusSchema = S13.Literal(
3110
3358
  "idle",
3111
3359
  "running",
3112
3360
  "complete",
3113
3361
  "error"
3114
3362
  );
3115
- var ExecutionModeSchema = S12.Literal(
3363
+ var ExecutionModeSchema = S13.Literal(
3116
3364
  "idle",
3117
3365
  "discovery",
3118
3366
  "breakdown",
@@ -3122,98 +3370,566 @@ var ExecutionModeSchema = S12.Literal(
3122
3370
  "verifying",
3123
3371
  "reviewing"
3124
3372
  );
3125
- var TUIPhaseStatusSchema = S12.Literal(
3373
+ var TUIPhaseStatusSchema = S13.Literal(
3126
3374
  "pending",
3127
3375
  "in_progress",
3128
3376
  "done",
3129
3377
  "failed"
3130
3378
  );
3131
- var TUICriterionStatusSchema = S12.Literal(
3379
+ var TUICriterionStatusSchema = S13.Literal(
3132
3380
  "pending",
3133
3381
  "passed",
3134
3382
  "failed"
3135
3383
  );
3136
- var TUITaskStatusSchema = S12.Literal(
3384
+ var TUITaskStatusSchema = S13.Literal(
3137
3385
  "pending",
3138
3386
  "in_progress",
3139
3387
  "done",
3140
3388
  "failed"
3141
3389
  );
3142
- var TUIPhaseSchema = S12.Struct({
3143
- id: S12.String,
3144
- description: S12.String,
3390
+ var TUIPhaseSchema = S13.Struct({
3391
+ id: S13.String,
3392
+ description: S13.String,
3145
3393
  status: TUIPhaseStatusSchema,
3146
- startedAt: S12.optional(S12.Number),
3147
- completedAt: S12.optional(S12.Number)
3394
+ startedAt: S13.optional(S13.Number),
3395
+ completedAt: S13.optional(S13.Number)
3148
3396
  });
3149
- var TUICriterionSchema = S12.Struct({
3150
- id: S12.String,
3151
- description: S12.String,
3397
+ var TUICriterionSchema = S13.Struct({
3398
+ id: S13.String,
3399
+ description: S13.String,
3152
3400
  status: TUICriterionStatusSchema,
3153
- failureReason: S12.optional(S12.String)
3401
+ failureReason: S13.optional(S13.String)
3154
3402
  });
3155
- var TUITaskSchema = S12.Struct({
3156
- id: S12.String,
3157
- title: S12.String,
3403
+ var TUITaskSchema = S13.Struct({
3404
+ id: S13.String,
3405
+ title: S13.String,
3158
3406
  status: TUITaskStatusSchema,
3159
- phases: S12.Array(TUIPhaseSchema),
3160
- criteria: S12.Array(TUICriterionSchema),
3161
- startedAt: S12.optional(S12.Number),
3162
- completedAt: S12.optional(S12.Number)
3407
+ phases: S13.Array(TUIPhaseSchema),
3408
+ criteria: S13.Array(TUICriterionSchema),
3409
+ startedAt: S13.optional(S13.Number),
3410
+ completedAt: S13.optional(S13.Number)
3163
3411
  });
3164
- var TUIStateSchema = S12.Struct({
3412
+ var TUIStateSchema = S13.Struct({
3165
3413
  // Loop info
3166
- task: S12.String,
3167
- iteration: S12.Number,
3168
- maxIterations: S12.Number,
3414
+ task: S13.String,
3415
+ iteration: S13.Number,
3416
+ maxIterations: S13.Number,
3169
3417
  status: LoopStatusSchema,
3170
- startTime: S12.Number,
3418
+ startTime: S13.Number,
3171
3419
  // Discovery phase
3172
- discoveryInProgress: S12.Boolean,
3173
- discoveryCompleted: S12.Boolean,
3420
+ discoveryInProgress: S13.Boolean,
3421
+ discoveryCompleted: S13.Boolean,
3174
3422
  // Current activity
3175
3423
  executionMode: ExecutionModeSchema,
3176
- currentTool: S12.optional(S12.String),
3177
- currentTaskId: S12.optional(S12.String),
3424
+ currentTool: S13.optional(S13.String),
3425
+ currentTaskId: S13.optional(S13.String),
3178
3426
  // Output
3179
- outputLines: S12.Array(S12.String),
3180
- partialLine: S12.String,
3427
+ outputLines: S13.Array(S13.String),
3428
+ partialLine: S13.String,
3181
3429
  // Tasks
3182
- tasks: S12.Array(TUITaskSchema),
3430
+ tasks: S13.Array(TUITaskSchema),
3183
3431
  // Navigation
3184
3432
  viewMode: ViewModeSchema,
3185
- selectedTaskIndex: S12.Number,
3186
- scrollOffset: S12.Number,
3187
- userScrolled: S12.Boolean,
3433
+ selectedTaskIndex: S13.Number,
3434
+ scrollOffset: S13.Number,
3435
+ userScrolled: S13.Boolean,
3188
3436
  // Git
3189
- gitBranch: S12.optional(S12.String),
3190
- gitPushed: S12.Boolean,
3191
- prUrl: S12.optional(S12.String)
3437
+ gitBranch: S13.optional(S13.String),
3438
+ gitPushed: S13.Boolean,
3439
+ prUrl: S13.optional(S13.String)
3192
3440
  });
3193
3441
 
3442
+ // src/services/guardrails-store.ts
3443
+ init_esm_shims();
3444
+ import { Context as Context2 } from "effect";
3445
+ var GuardrailsStore = class extends Context2.Tag("@ferix/GuardrailsStore")() {
3446
+ };
3447
+
3448
+ // src/layers/guardrails/file-system.ts
3449
+ var PLANS_DIR = ".ferix/plans";
3450
+ function ensureDir(dirPath) {
3451
+ return Effect6.tryPromise({
3452
+ try: () => mkdir2(dirPath, { recursive: true }),
3453
+ catch: (error) => new GuardrailsStoreError({
3454
+ message: `Failed to create directory: ${dirPath}`,
3455
+ operation: "add",
3456
+ cause: error
3457
+ })
3458
+ }).pipe(Effect6.asVoid);
3459
+ }
3460
+ function getSessionDir(sessionId) {
3461
+ return join2(process.cwd(), PLANS_DIR, sessionId);
3462
+ }
3463
+ function getGuardrailsPath(sessionId) {
3464
+ return join2(getSessionDir(sessionId), "guardrails.json");
3465
+ }
3466
+ function serializeGuardrails(guardrails) {
3467
+ return JSON.stringify(guardrails, null, 2);
3468
+ }
3469
+ function deserializeGuardrails(json) {
3470
+ return Effect6.gen(function* () {
3471
+ const parsed = yield* Effect6.try({
3472
+ try: () => JSON.parse(json),
3473
+ catch: (error) => new GuardrailsStoreError({
3474
+ message: `Invalid JSON in guardrails file: ${String(error)}`,
3475
+ operation: "load",
3476
+ cause: error
3477
+ })
3478
+ });
3479
+ const validated = yield* decodeGuardrailsFile(parsed).pipe(
3480
+ Effect6.mapError(
3481
+ (error) => new GuardrailsStoreError({
3482
+ message: `Guardrails validation failed: ${String(error)}`,
3483
+ operation: "load",
3484
+ cause: error
3485
+ })
3486
+ )
3487
+ );
3488
+ return validated;
3489
+ });
3490
+ }
3491
+ function createEmptyGuardrails(sessionId, createdAt) {
3492
+ return {
3493
+ sessionId,
3494
+ createdAt,
3495
+ guardrails: []
3496
+ };
3497
+ }
3498
+ var make2 = {
3499
+ add: (sessionId, guardrail) => Effect6.gen(function* () {
3500
+ const sessionDir = getSessionDir(sessionId);
3501
+ yield* ensureDir(sessionDir);
3502
+ const guardrailsPath = getGuardrailsPath(sessionId);
3503
+ const existing = yield* Effect6.tryPromise({
3504
+ try: async () => {
3505
+ try {
3506
+ const content = await readFile(guardrailsPath, "utf-8");
3507
+ return content;
3508
+ } catch {
3509
+ return null;
3510
+ }
3511
+ },
3512
+ catch: (error) => new GuardrailsStoreError({
3513
+ message: `Failed to read guardrails file: ${guardrailsPath}`,
3514
+ operation: "add",
3515
+ cause: error
3516
+ })
3517
+ });
3518
+ let guardrails;
3519
+ if (existing) {
3520
+ guardrails = yield* deserializeGuardrails(existing).pipe(
3521
+ Effect6.mapError(
3522
+ (err) => new GuardrailsStoreError({
3523
+ message: err.message,
3524
+ operation: "add",
3525
+ cause: err
3526
+ })
3527
+ )
3528
+ );
3529
+ } else {
3530
+ const now = yield* DateTime.now;
3531
+ guardrails = createEmptyGuardrails(sessionId, DateTime.formatIso(now));
3532
+ }
3533
+ const updatedGuardrails = {
3534
+ ...guardrails,
3535
+ guardrails: [...guardrails.guardrails, guardrail]
3536
+ };
3537
+ yield* Effect6.tryPromise({
3538
+ try: () => writeFile(
3539
+ guardrailsPath,
3540
+ serializeGuardrails(updatedGuardrails),
3541
+ "utf-8"
3542
+ ),
3543
+ catch: (error) => new GuardrailsStoreError({
3544
+ message: `Failed to write guardrails file: ${guardrailsPath}`,
3545
+ operation: "add",
3546
+ cause: error
3547
+ })
3548
+ });
3549
+ }),
3550
+ load: (sessionId) => Effect6.gen(function* () {
3551
+ const guardrailsPath = getGuardrailsPath(sessionId);
3552
+ const content = yield* Effect6.tryPromise({
3553
+ try: async () => {
3554
+ try {
3555
+ return await readFile(guardrailsPath, "utf-8");
3556
+ } catch {
3557
+ return null;
3558
+ }
3559
+ },
3560
+ catch: (error) => new GuardrailsStoreError({
3561
+ message: `Failed to read guardrails file: ${guardrailsPath}`,
3562
+ operation: "load",
3563
+ cause: error
3564
+ })
3565
+ });
3566
+ if (content === null) {
3567
+ const now = yield* DateTime.now;
3568
+ return createEmptyGuardrails(sessionId, DateTime.formatIso(now));
3569
+ }
3570
+ return yield* deserializeGuardrails(content);
3571
+ }),
3572
+ getActive: (sessionId) => Effect6.gen(function* () {
3573
+ const guardrails = yield* make2.load(sessionId);
3574
+ return guardrails.guardrails;
3575
+ })
3576
+ };
3577
+ var Live3 = Layer3.succeed(GuardrailsStore, make2);
3578
+ var FileSystemGuardrails = {
3579
+ Live: Live3
3580
+ };
3581
+
3582
+ // src/layers/guardrails/memory.ts
3583
+ init_esm_shims();
3584
+ import { DateTime as DateTime2, Effect as Effect7, Layer as Layer4, Ref as Ref4 } from "effect";
3585
+ function createMemoryGuardrailsStore(stateRef) {
3586
+ return {
3587
+ add: (sessionId, guardrail) => Effect7.gen(function* () {
3588
+ const state = yield* Ref4.get(stateRef);
3589
+ let guardrails = state.get(sessionId);
3590
+ if (!guardrails) {
3591
+ const now = yield* DateTime2.now;
3592
+ guardrails = {
3593
+ sessionId,
3594
+ createdAt: DateTime2.formatIso(now),
3595
+ guardrails: []
3596
+ };
3597
+ }
3598
+ const updatedGuardrails = {
3599
+ ...guardrails,
3600
+ guardrails: [...guardrails.guardrails, guardrail]
3601
+ };
3602
+ state.set(sessionId, updatedGuardrails);
3603
+ yield* Ref4.set(stateRef, state);
3604
+ }),
3605
+ load: (sessionId) => Effect7.gen(function* () {
3606
+ const state = yield* Ref4.get(stateRef);
3607
+ const guardrails = state.get(sessionId);
3608
+ if (!guardrails) {
3609
+ const now = yield* DateTime2.now;
3610
+ return {
3611
+ sessionId,
3612
+ createdAt: DateTime2.formatIso(now),
3613
+ guardrails: []
3614
+ };
3615
+ }
3616
+ return guardrails;
3617
+ }),
3618
+ getActive: (sessionId) => Effect7.gen(function* () {
3619
+ const state = yield* Ref4.get(stateRef);
3620
+ const guardrails = state.get(sessionId);
3621
+ if (!guardrails) {
3622
+ return [];
3623
+ }
3624
+ return guardrails.guardrails;
3625
+ })
3626
+ };
3627
+ }
3628
+ function layer2() {
3629
+ return Layer4.effect(
3630
+ GuardrailsStore,
3631
+ Effect7.gen(function* () {
3632
+ const stateRef = yield* Ref4.make(/* @__PURE__ */ new Map());
3633
+ return createMemoryGuardrailsStore(stateRef);
3634
+ })
3635
+ );
3636
+ }
3637
+ var Live4 = layer2();
3638
+ var MemoryGuardrails = {
3639
+ Live: Live4,
3640
+ layer: layer2
3641
+ };
3642
+
3643
+ // src/layers/llm/claude-cli.ts
3644
+ init_esm_shims();
3645
+ import { spawn } from "child_process";
3646
+ import { Effect as Effect9, Layer as Layer5, Stream as Stream5 } from "effect";
3647
+
3648
+ // src/services/llm.ts
3649
+ init_esm_shims();
3650
+ import { Context as Context3 } from "effect";
3651
+ var LLM = class extends Context3.Tag("@ferix/LLM")() {
3652
+ };
3653
+
3654
+ // src/layers/llm/stream.ts
3655
+ init_esm_shims();
3656
+ import { createInterface } from "readline";
3657
+ import { Effect as Effect8, Stream as Stream4 } from "effect";
3658
+
3659
+ // src/layers/llm/parsers.ts
3660
+ init_esm_shims();
3661
+ function parseJsonLine(line) {
3662
+ if (!line.startsWith("{")) {
3663
+ return null;
3664
+ }
3665
+ try {
3666
+ return JSON.parse(line);
3667
+ } catch {
3668
+ return null;
3669
+ }
3670
+ }
3671
+ function isTextContent(json) {
3672
+ return typeof json === "object" && json !== null && "type" in json && typeof json.type === "string";
3673
+ }
3674
+ function isToolUse(json) {
3675
+ return typeof json === "object" && json !== null && "type" in json && "content_block" in json;
3676
+ }
3677
+ function extractText(json) {
3678
+ if (!isTextContent(json)) {
3679
+ return null;
3680
+ }
3681
+ if (json.type === "content_block_delta") {
3682
+ const delta = json;
3683
+ if (delta.delta?.type === "text_delta" && delta.delta.text) {
3684
+ return delta.delta.text;
3685
+ }
3686
+ }
3687
+ if (json.type === "assistant" && json.message?.content) {
3688
+ for (const block of json.message.content) {
3689
+ if (block.type === "text" && block.text) {
3690
+ return block.text;
3691
+ }
3692
+ }
3693
+ }
3694
+ return null;
3695
+ }
3696
+ function isToolInputDelta(json) {
3697
+ return typeof json === "object" && json !== null && "type" in json && json.type === "content_block_delta" && "delta" in json;
3698
+ }
3699
+ function extractToolInfo(json) {
3700
+ if (typeof json === "object" && json !== null && "type" in json && json.type === "content_block_stop") {
3701
+ return {
3702
+ type: "end",
3703
+ name: "unknown"
3704
+ };
3705
+ }
3706
+ if (!isToolUse(json)) {
3707
+ if (isToolInputDelta(json) && json.delta?.type === "input_json_delta" && json.delta.partial_json) {
3708
+ return {
3709
+ type: "input_delta",
3710
+ name: "",
3711
+ partialJson: json.delta.partial_json
3712
+ };
3713
+ }
3714
+ return null;
3715
+ }
3716
+ if (json.type === "content_block_start" && json.content_block?.type === "tool_use") {
3717
+ return {
3718
+ type: "start",
3719
+ name: json.content_block.name || "unknown"
3720
+ };
3721
+ }
3722
+ return null;
3723
+ }
3724
+ function safeParseJson(jsonStr) {
3725
+ try {
3726
+ return JSON.parse(jsonStr);
3727
+ } catch {
3728
+ return null;
3729
+ }
3730
+ }
3731
+ function unwrapStreamEvent(json) {
3732
+ if (typeof json === "object" && json !== null && "type" in json && json.type === "stream_event" && "event" in json) {
3733
+ return json.event;
3734
+ }
3735
+ return json;
3736
+ }
3737
+
3738
+ // src/layers/llm/stream.ts
3739
+ function handleToolEvent(toolInfo, toolState, emit) {
3740
+ if (toolInfo.type === "start") {
3741
+ toolState.currentTool = toolInfo.name;
3742
+ toolState.inputChunks.length = 0;
3743
+ emit.single({ _tag: "ToolStart", tool: toolInfo.name });
3744
+ return;
3745
+ }
3746
+ if (toolInfo.type === "input_delta" && toolInfo.partialJson) {
3747
+ toolState.inputChunks.push(toolInfo.partialJson);
3748
+ return;
3749
+ }
3750
+ if (toolInfo.type === "end" && toolState.currentTool) {
3751
+ const inputJson = toolState.inputChunks.join("");
3752
+ const input = safeParseJson(inputJson);
3753
+ if (input !== null) {
3754
+ emit.single({ _tag: "ToolUse", tool: toolState.currentTool, input });
3755
+ }
3756
+ emit.single({ _tag: "ToolEnd", tool: toolState.currentTool });
3757
+ toolState.currentTool = "";
3758
+ toolState.inputChunks.length = 0;
3759
+ }
3760
+ }
3761
+ function processJsonLine(json, outputChunks, toolState, emit) {
3762
+ const event = unwrapStreamEvent(json);
3763
+ const text = extractText(event);
3764
+ if (text) {
3765
+ outputChunks.push(text);
3766
+ emit.single({ _tag: "Text", text });
3767
+ return;
3768
+ }
3769
+ const toolInfo = extractToolInfo(event);
3770
+ if (toolInfo) {
3771
+ handleToolEvent(toolInfo, toolState, emit);
3772
+ }
3773
+ }
3774
+ function createEventStream(child) {
3775
+ return Stream4.async((emit) => {
3776
+ const outputChunks = [];
3777
+ const toolState = { currentTool: "", inputChunks: [] };
3778
+ const stdout = child.stdout;
3779
+ if (!stdout) {
3780
+ emit.fail(
3781
+ new LLMError({ message: "Failed to get stdout from child process" })
3782
+ );
3783
+ return Effect8.void;
3784
+ }
3785
+ const rl = createInterface({
3786
+ input: stdout,
3787
+ crlfDelay: Number.POSITIVE_INFINITY
3788
+ });
3789
+ rl.on("line", (line) => {
3790
+ const json = parseJsonLine(line);
3791
+ if (json) {
3792
+ processJsonLine(json, outputChunks, toolState, emit);
3793
+ }
3794
+ });
3795
+ child.stderr?.on("data", (data) => {
3796
+ const text = data.toString().trim();
3797
+ if (text) {
3798
+ emit.single({ _tag: "Text", text: `[stderr] ${text}` });
3799
+ }
3800
+ });
3801
+ child.on("close", (exitCode) => {
3802
+ if (exitCode !== 0) {
3803
+ emit.fail(
3804
+ new LLMError({
3805
+ message: `Claude CLI exited with code ${exitCode}`
3806
+ })
3807
+ );
3808
+ } else {
3809
+ const fullOutput = outputChunks.join("");
3810
+ emit.single({ _tag: "Done", output: fullOutput });
3811
+ emit.end();
3812
+ }
3813
+ });
3814
+ child.on("error", (error) => {
3815
+ emit.fail(
3816
+ new LLMError({
3817
+ message: error.message,
3818
+ cause: error
3819
+ })
3820
+ );
3821
+ });
3822
+ return Effect8.sync(() => {
3823
+ child.kill("SIGTERM");
3824
+ });
3825
+ });
3826
+ }
3827
+
3828
+ // src/layers/llm/claude-cli.ts
3829
+ var make3 = {
3830
+ execute: (prompt, options) => {
3831
+ return Stream5.unwrap(
3832
+ Effect9.sync(() => {
3833
+ const child = spawn(
3834
+ "claude",
3835
+ [
3836
+ "--permission-mode",
3837
+ "acceptEdits",
3838
+ "--output-format",
3839
+ "stream-json",
3840
+ "--verbose",
3841
+ "--include-partial-messages",
3842
+ "-p",
3843
+ prompt
3844
+ ],
3845
+ {
3846
+ stdio: ["inherit", "pipe", "pipe"],
3847
+ cwd: options?.cwd,
3848
+ env: {
3849
+ ...process.env,
3850
+ FORCE_COLOR: "1"
3851
+ }
3852
+ }
3853
+ );
3854
+ return createEventStream(child);
3855
+ })
3856
+ );
3857
+ }
3858
+ };
3859
+ var Live5 = Layer5.succeed(LLM, make3);
3860
+ var ClaudeCLI = {
3861
+ Live: Live5
3862
+ };
3863
+
3864
+ // src/layers/llm/mock.ts
3865
+ init_esm_shims();
3866
+ import { Effect as Effect10, Layer as Layer6, Schema as S14, Stream as Stream6 } from "effect";
3867
+ var MockLLMConfigSchema = S14.Struct({
3868
+ events: S14.Array(LLMEventSchema),
3869
+ delayMs: S14.optional(S14.Number)
3870
+ });
3871
+ function createMockLLM(config) {
3872
+ return {
3873
+ execute: (_prompt, _options) => {
3874
+ const baseStream = Stream6.fromIterable(config.events);
3875
+ if (config.delayMs !== void 0 && config.delayMs > 0) {
3876
+ const delay = config.delayMs;
3877
+ return baseStream.pipe(Stream6.tap(() => Effect10.sleep(delay)));
3878
+ }
3879
+ return baseStream;
3880
+ }
3881
+ };
3882
+ }
3883
+ var defaultMockEvents = [
3884
+ { _tag: "Text", text: "Processing task...\n" },
3885
+ { _tag: "ToolStart", tool: "Read" },
3886
+ { _tag: "ToolEnd", tool: "Read" },
3887
+ { _tag: "Text", text: "Task completed successfully.\n" },
3888
+ {
3889
+ _tag: "Done",
3890
+ output: "Processing task...\nTask completed successfully.\n"
3891
+ }
3892
+ ];
3893
+ var defaultMock = createMockLLM({ events: defaultMockEvents });
3894
+ var Live6 = Layer6.succeed(LLM, defaultMock);
3895
+ function layer3(config) {
3896
+ return Layer6.succeed(LLM, createMockLLM(config));
3897
+ }
3898
+ var Mock = {
3899
+ Live: Live6,
3900
+ layer: layer3,
3901
+ createMockLLM
3902
+ };
3903
+
3904
+ // src/layers/plan/file-system.ts
3905
+ init_esm_shims();
3906
+ import { access as access2, mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3907
+ import { join as join3 } from "path";
3908
+ import { Effect as Effect11, Layer as Layer7 } from "effect";
3909
+
3194
3910
  // src/services/plan-store.ts
3195
3911
  init_esm_shims();
3196
- import { Context as Context2 } from "effect";
3197
- var PlanStore = class extends Context2.Tag("@ferix/PlanStore")() {
3912
+ import { Context as Context4 } from "effect";
3913
+ var PlanStore = class extends Context4.Tag("@ferix/PlanStore")() {
3198
3914
  };
3199
3915
 
3200
3916
  // src/layers/plan/file-system.ts
3201
- var PLANS_DIR = ".ferix/plans";
3202
- function ensureDir(dirPath) {
3203
- return Effect7.tryPromise({
3204
- try: () => mkdir(dirPath, { recursive: true }),
3917
+ var PLANS_DIR2 = ".ferix/plans";
3918
+ function ensureDir2(dirPath) {
3919
+ return Effect11.tryPromise({
3920
+ try: () => mkdir3(dirPath, { recursive: true }),
3205
3921
  catch: (error) => new PlanStoreError({
3206
3922
  message: `Failed to create directory: ${dirPath}`,
3207
3923
  operation: "create",
3208
3924
  cause: error
3209
3925
  })
3210
- }).pipe(Effect7.asVoid);
3926
+ }).pipe(Effect11.asVoid);
3211
3927
  }
3212
- function getSessionDir(sessionId) {
3213
- return join(process.cwd(), PLANS_DIR, sessionId);
3928
+ function getSessionDir2(sessionId) {
3929
+ return join3(process.cwd(), PLANS_DIR2, sessionId);
3214
3930
  }
3215
3931
  function getPlanPath(sessionId, planId) {
3216
- return join(getSessionDir(sessionId), `${planId}.json`);
3932
+ return join3(getSessionDir2(sessionId), `${planId}.json`);
3217
3933
  }
3218
3934
  function generatePlanId(taskNumber) {
3219
3935
  return PlanId(`task-${taskNumber}`);
@@ -3222,8 +3938,8 @@ function serializePlan(plan) {
3222
3938
  return JSON.stringify(plan, null, 2);
3223
3939
  }
3224
3940
  function deserializePlan(json, planId) {
3225
- return Effect7.gen(function* () {
3226
- const parsed = yield* Effect7.try({
3941
+ return Effect11.gen(function* () {
3942
+ const parsed = yield* Effect11.try({
3227
3943
  try: () => JSON.parse(json),
3228
3944
  catch: (error) => new PlanStoreError({
3229
3945
  message: `Invalid JSON in plan file: ${String(error)}`,
@@ -3232,7 +3948,7 @@ function deserializePlan(json, planId) {
3232
3948
  })
3233
3949
  });
3234
3950
  const validated = yield* decodePlanData(parsed).pipe(
3235
- Effect7.mapError(
3951
+ Effect11.mapError(
3236
3952
  (error) => new PlanStoreError({
3237
3953
  message: `Plan validation failed: ${String(error)}`,
3238
3954
  operation: "load",
@@ -3246,11 +3962,11 @@ function deserializePlan(json, planId) {
3246
3962
  };
3247
3963
  });
3248
3964
  }
3249
- var make2 = {
3250
- create: (sessionId, plan) => Effect7.gen(function* () {
3251
- const sessionDir = getSessionDir(sessionId);
3252
- yield* ensureDir(sessionDir);
3253
- const existingPlans = yield* Effect7.tryPromise({
3965
+ var make4 = {
3966
+ create: (sessionId, plan) => Effect11.gen(function* () {
3967
+ const sessionDir = getSessionDir2(sessionId);
3968
+ yield* ensureDir2(sessionDir);
3969
+ const existingPlans = yield* Effect11.tryPromise({
3254
3970
  try: async () => {
3255
3971
  try {
3256
3972
  const files = await readdir(sessionDir);
@@ -3267,8 +3983,8 @@ var make2 = {
3267
3983
  const planId = generatePlanId(existingPlans + 1);
3268
3984
  const planPath = getPlanPath(sessionId, planId);
3269
3985
  const fullPlan = { ...plan, id: planId };
3270
- yield* Effect7.tryPromise({
3271
- try: () => writeFile(planPath, serializePlan(fullPlan), "utf-8"),
3986
+ yield* Effect11.tryPromise({
3987
+ try: () => writeFile2(planPath, serializePlan(fullPlan), "utf-8"),
3272
3988
  catch: (error) => new PlanStoreError({
3273
3989
  message: `Failed to write plan file: ${planPath}`,
3274
3990
  operation: "create",
@@ -3277,11 +3993,11 @@ var make2 = {
3277
3993
  });
3278
3994
  return planId;
3279
3995
  }),
3280
- load: (planId, sessionId) => Effect7.gen(function* () {
3996
+ load: (planId, sessionId) => Effect11.gen(function* () {
3281
3997
  if (sessionId) {
3282
3998
  const planPath = getPlanPath(sessionId, planId);
3283
- const content = yield* Effect7.tryPromise({
3284
- try: () => readFile(planPath, "utf-8"),
3999
+ const content = yield* Effect11.tryPromise({
4000
+ try: () => readFile2(planPath, "utf-8"),
3285
4001
  catch: (error) => new PlanStoreError({
3286
4002
  message: `Failed to read plan file: ${planPath}`,
3287
4003
  operation: "load",
@@ -3290,9 +4006,9 @@ var make2 = {
3290
4006
  });
3291
4007
  return yield* deserializePlan(content, planId);
3292
4008
  }
3293
- const sessionDirs = yield* Effect7.tryPromise({
4009
+ const sessionDirs = yield* Effect11.tryPromise({
3294
4010
  try: async () => {
3295
- const plansDir = join(process.cwd(), PLANS_DIR);
4011
+ const plansDir = join3(process.cwd(), PLANS_DIR2);
3296
4012
  const dirs = await readdir(plansDir);
3297
4013
  return dirs;
3298
4014
  },
@@ -3304,19 +4020,19 @@ var make2 = {
3304
4020
  });
3305
4021
  for (const sid of sessionDirs) {
3306
4022
  const planPath = getPlanPath(sid, planId);
3307
- const exists = yield* Effect7.tryPromise({
4023
+ const exists = yield* Effect11.tryPromise({
3308
4024
  try: async () => {
3309
- await access(planPath);
4025
+ await access2(planPath);
3310
4026
  return true;
3311
4027
  },
3312
4028
  catch: () => new PlanStoreError({
3313
4029
  message: "File not found",
3314
4030
  operation: "load"
3315
4031
  })
3316
- }).pipe(Effect7.orElseSucceed(() => false));
4032
+ }).pipe(Effect11.orElseSucceed(() => false));
3317
4033
  if (exists) {
3318
- const content = yield* Effect7.tryPromise({
3319
- try: () => readFile(planPath, "utf-8"),
4034
+ const content = yield* Effect11.tryPromise({
4035
+ try: () => readFile2(planPath, "utf-8"),
3320
4036
  catch: (error) => new PlanStoreError({
3321
4037
  message: `Failed to read plan file: ${planPath}`,
3322
4038
  operation: "load",
@@ -3326,17 +4042,17 @@ var make2 = {
3326
4042
  return yield* deserializePlan(content, planId);
3327
4043
  }
3328
4044
  }
3329
- return yield* Effect7.fail(
4045
+ return yield* Effect11.fail(
3330
4046
  new PlanStoreError({
3331
4047
  message: `Plan not found: ${planId}`,
3332
4048
  operation: "load"
3333
4049
  })
3334
4050
  );
3335
4051
  }),
3336
- update: (planId, plan) => Effect7.gen(function* () {
4052
+ update: (planId, plan) => Effect11.gen(function* () {
3337
4053
  const planPath = getPlanPath(plan.sessionId, planId);
3338
- yield* Effect7.tryPromise({
3339
- try: () => writeFile(planPath, serializePlan(plan), "utf-8"),
4054
+ yield* Effect11.tryPromise({
4055
+ try: () => writeFile2(planPath, serializePlan(plan), "utf-8"),
3340
4056
  catch: (error) => new PlanStoreError({
3341
4057
  message: `Failed to update plan file: ${planPath}`,
3342
4058
  operation: "update",
@@ -3344,9 +4060,9 @@ var make2 = {
3344
4060
  })
3345
4061
  });
3346
4062
  }),
3347
- list: (sessionId) => Effect7.gen(function* () {
3348
- const sessionDir = getSessionDir(sessionId);
3349
- const files = yield* Effect7.tryPromise({
4063
+ list: (sessionId) => Effect11.gen(function* () {
4064
+ const sessionDir = getSessionDir2(sessionId);
4065
+ const files = yield* Effect11.tryPromise({
3350
4066
  try: async () => {
3351
4067
  try {
3352
4068
  return await readdir(sessionDir);
@@ -3363,18 +4079,18 @@ var make2 = {
3363
4079
  return files.filter((f) => f.endsWith(".json")).map((f) => PlanId(f.replace(".json", "")));
3364
4080
  })
3365
4081
  };
3366
- var Live3 = Layer3.succeed(PlanStore, make2);
4082
+ var Live7 = Layer7.succeed(PlanStore, make4);
3367
4083
  var FileSystemPlan = {
3368
- Live: Live3
4084
+ Live: Live7
3369
4085
  };
3370
4086
 
3371
4087
  // src/layers/plan/memory.ts
3372
4088
  init_esm_shims();
3373
- import { Effect as Effect8, Layer as Layer4, Ref as Ref3 } from "effect";
4089
+ import { Effect as Effect12, Layer as Layer8, Ref as Ref5 } from "effect";
3374
4090
  function createMemoryPlanStore(stateRef) {
3375
4091
  return {
3376
- create: (sessionId, plan) => Effect8.gen(function* () {
3377
- const state = yield* Ref3.get(stateRef);
4092
+ create: (sessionId, plan) => Effect12.gen(function* () {
4093
+ const state = yield* Ref5.get(stateRef);
3378
4094
  if (!state.has(sessionId)) {
3379
4095
  state.set(sessionId, /* @__PURE__ */ new Map());
3380
4096
  }
@@ -3385,18 +4101,18 @@ function createMemoryPlanStore(stateRef) {
3385
4101
  const planId = PlanId(`task-${sessionPlans.size + 1}`);
3386
4102
  const fullPlan = { ...plan, id: planId };
3387
4103
  sessionPlans.set(planId, fullPlan);
3388
- yield* Ref3.set(stateRef, state);
4104
+ yield* Ref5.set(stateRef, state);
3389
4105
  return planId;
3390
4106
  }),
3391
- load: (planId, sessionId) => Effect8.gen(function* () {
3392
- const state = yield* Ref3.get(stateRef);
4107
+ load: (planId, sessionId) => Effect12.gen(function* () {
4108
+ const state = yield* Ref5.get(stateRef);
3393
4109
  if (sessionId) {
3394
4110
  const sessionPlans = state.get(sessionId);
3395
4111
  const plan = sessionPlans?.get(planId);
3396
4112
  if (plan) {
3397
4113
  return plan;
3398
4114
  }
3399
- return yield* Effect8.fail(
4115
+ return yield* Effect12.fail(
3400
4116
  new PlanStoreError({
3401
4117
  message: `Plan not found: ${planId}`,
3402
4118
  operation: "load"
@@ -3409,18 +4125,18 @@ function createMemoryPlanStore(stateRef) {
3409
4125
  return plan;
3410
4126
  }
3411
4127
  }
3412
- return yield* Effect8.fail(
4128
+ return yield* Effect12.fail(
3413
4129
  new PlanStoreError({
3414
4130
  message: `Plan not found: ${planId}`,
3415
4131
  operation: "load"
3416
4132
  })
3417
4133
  );
3418
4134
  }),
3419
- update: (planId, plan) => Effect8.gen(function* () {
3420
- const state = yield* Ref3.get(stateRef);
4135
+ update: (planId, plan) => Effect12.gen(function* () {
4136
+ const state = yield* Ref5.get(stateRef);
3421
4137
  const sessionPlans = state.get(plan.sessionId);
3422
4138
  if (!sessionPlans) {
3423
- return yield* Effect8.fail(
4139
+ return yield* Effect12.fail(
3424
4140
  new PlanStoreError({
3425
4141
  message: `Session not found: ${plan.sessionId}`,
3426
4142
  operation: "update"
@@ -3428,10 +4144,10 @@ function createMemoryPlanStore(stateRef) {
3428
4144
  );
3429
4145
  }
3430
4146
  sessionPlans.set(planId, plan);
3431
- yield* Ref3.set(stateRef, state);
4147
+ yield* Ref5.set(stateRef, state);
3432
4148
  }),
3433
- list: (sessionId) => Effect8.gen(function* () {
3434
- const state = yield* Ref3.get(stateRef);
4149
+ list: (sessionId) => Effect12.gen(function* () {
4150
+ const state = yield* Ref5.get(stateRef);
3435
4151
  const sessionPlans = state.get(sessionId);
3436
4152
  if (!sessionPlans) {
3437
4153
  return [];
@@ -3440,32 +4156,236 @@ function createMemoryPlanStore(stateRef) {
3440
4156
  })
3441
4157
  };
3442
4158
  }
3443
- function layer2() {
3444
- return Layer4.effect(
4159
+ function layer4() {
4160
+ return Layer8.effect(
3445
4161
  PlanStore,
3446
- Effect8.gen(function* () {
3447
- const stateRef = yield* Ref3.make(/* @__PURE__ */ new Map());
4162
+ Effect12.gen(function* () {
4163
+ const stateRef = yield* Ref5.make(/* @__PURE__ */ new Map());
3448
4164
  return createMemoryPlanStore(stateRef);
3449
4165
  })
3450
4166
  );
3451
4167
  }
3452
- var Live4 = layer2();
4168
+ var Live8 = layer4();
3453
4169
  var MemoryPlan = {
3454
- Live: Live4,
3455
- layer: layer2
4170
+ Live: Live8,
4171
+ layer: layer4
4172
+ };
4173
+
4174
+ // src/layers/progress/file-system.ts
4175
+ init_esm_shims();
4176
+ import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
4177
+ import { join as join4 } from "path";
4178
+ import { DateTime as DateTime3, Effect as Effect13, Layer as Layer9 } from "effect";
4179
+
4180
+ // src/services/progress-store.ts
4181
+ init_esm_shims();
4182
+ import { Context as Context5 } from "effect";
4183
+ var ProgressStore = class extends Context5.Tag("@ferix/ProgressStore")() {
4184
+ };
4185
+
4186
+ // src/layers/progress/file-system.ts
4187
+ var PLANS_DIR3 = ".ferix/plans";
4188
+ function ensureDir3(dirPath) {
4189
+ return Effect13.tryPromise({
4190
+ try: () => mkdir4(dirPath, { recursive: true }),
4191
+ catch: (error) => new ProgressStoreError({
4192
+ message: `Failed to create directory: ${dirPath}`,
4193
+ operation: "append",
4194
+ cause: error
4195
+ })
4196
+ }).pipe(Effect13.asVoid);
4197
+ }
4198
+ function getSessionDir3(sessionId) {
4199
+ return join4(process.cwd(), PLANS_DIR3, sessionId);
4200
+ }
4201
+ function getProgressPath(sessionId) {
4202
+ return join4(getSessionDir3(sessionId), "progress.json");
4203
+ }
4204
+ function serializeProgress(progress) {
4205
+ return JSON.stringify(progress, null, 2);
4206
+ }
4207
+ function deserializeProgress(json) {
4208
+ return Effect13.gen(function* () {
4209
+ const parsed = yield* Effect13.try({
4210
+ try: () => JSON.parse(json),
4211
+ catch: (error) => new ProgressStoreError({
4212
+ message: `Invalid JSON in progress file: ${String(error)}`,
4213
+ operation: "load",
4214
+ cause: error
4215
+ })
4216
+ });
4217
+ const validated = yield* decodeProgressFile(parsed).pipe(
4218
+ Effect13.mapError(
4219
+ (error) => new ProgressStoreError({
4220
+ message: `Progress validation failed: ${String(error)}`,
4221
+ operation: "load",
4222
+ cause: error
4223
+ })
4224
+ )
4225
+ );
4226
+ return validated;
4227
+ });
4228
+ }
4229
+ function createEmptyProgress(sessionId, createdAt) {
4230
+ return {
4231
+ sessionId,
4232
+ createdAt,
4233
+ entries: []
4234
+ };
4235
+ }
4236
+ var make5 = {
4237
+ append: (sessionId, entry) => Effect13.gen(function* () {
4238
+ const sessionDir = getSessionDir3(sessionId);
4239
+ yield* ensureDir3(sessionDir);
4240
+ const progressPath = getProgressPath(sessionId);
4241
+ const existing = yield* Effect13.tryPromise({
4242
+ try: async () => {
4243
+ try {
4244
+ const content = await readFile3(progressPath, "utf-8");
4245
+ return content;
4246
+ } catch {
4247
+ return null;
4248
+ }
4249
+ },
4250
+ catch: (error) => new ProgressStoreError({
4251
+ message: `Failed to read progress file: ${progressPath}`,
4252
+ operation: "append",
4253
+ cause: error
4254
+ })
4255
+ });
4256
+ let progress;
4257
+ if (existing) {
4258
+ progress = yield* deserializeProgress(existing).pipe(
4259
+ Effect13.mapError(
4260
+ (err) => new ProgressStoreError({
4261
+ message: err.message,
4262
+ operation: "append",
4263
+ cause: err
4264
+ })
4265
+ )
4266
+ );
4267
+ } else {
4268
+ const now = yield* DateTime3.now;
4269
+ progress = createEmptyProgress(sessionId, DateTime3.formatIso(now));
4270
+ }
4271
+ const updatedProgress = {
4272
+ ...progress,
4273
+ entries: [...progress.entries, entry]
4274
+ };
4275
+ yield* Effect13.tryPromise({
4276
+ try: () => writeFile3(progressPath, serializeProgress(updatedProgress), "utf-8"),
4277
+ catch: (error) => new ProgressStoreError({
4278
+ message: `Failed to write progress file: ${progressPath}`,
4279
+ operation: "append",
4280
+ cause: error
4281
+ })
4282
+ });
4283
+ }),
4284
+ load: (sessionId) => Effect13.gen(function* () {
4285
+ const progressPath = getProgressPath(sessionId);
4286
+ const content = yield* Effect13.tryPromise({
4287
+ try: async () => {
4288
+ try {
4289
+ return await readFile3(progressPath, "utf-8");
4290
+ } catch {
4291
+ return null;
4292
+ }
4293
+ },
4294
+ catch: (error) => new ProgressStoreError({
4295
+ message: `Failed to read progress file: ${progressPath}`,
4296
+ operation: "load",
4297
+ cause: error
4298
+ })
4299
+ });
4300
+ if (content === null) {
4301
+ const now = yield* DateTime3.now;
4302
+ return createEmptyProgress(sessionId, DateTime3.formatIso(now));
4303
+ }
4304
+ return yield* deserializeProgress(content);
4305
+ }),
4306
+ getRecent: (sessionId, count) => Effect13.gen(function* () {
4307
+ const progress = yield* make5.load(sessionId);
4308
+ const entries = progress.entries;
4309
+ return entries.slice(-count);
4310
+ })
4311
+ };
4312
+ var Live9 = Layer9.succeed(ProgressStore, make5);
4313
+ var FileSystemProgress = {
4314
+ Live: Live9
4315
+ };
4316
+
4317
+ // src/layers/progress/memory.ts
4318
+ init_esm_shims();
4319
+ import { DateTime as DateTime4, Effect as Effect14, Layer as Layer10, Ref as Ref6 } from "effect";
4320
+ function createMemoryProgressStore(stateRef) {
4321
+ return {
4322
+ append: (sessionId, entry) => Effect14.gen(function* () {
4323
+ const state = yield* Ref6.get(stateRef);
4324
+ let progress = state.get(sessionId);
4325
+ if (!progress) {
4326
+ const now = yield* DateTime4.now;
4327
+ progress = {
4328
+ sessionId,
4329
+ createdAt: DateTime4.formatIso(now),
4330
+ entries: []
4331
+ };
4332
+ }
4333
+ const updatedProgress = {
4334
+ ...progress,
4335
+ entries: [...progress.entries, entry]
4336
+ };
4337
+ state.set(sessionId, updatedProgress);
4338
+ yield* Ref6.set(stateRef, state);
4339
+ }),
4340
+ load: (sessionId) => Effect14.gen(function* () {
4341
+ const state = yield* Ref6.get(stateRef);
4342
+ const progress = state.get(sessionId);
4343
+ if (!progress) {
4344
+ const now = yield* DateTime4.now;
4345
+ return {
4346
+ sessionId,
4347
+ createdAt: DateTime4.formatIso(now),
4348
+ entries: []
4349
+ };
4350
+ }
4351
+ return progress;
4352
+ }),
4353
+ getRecent: (sessionId, count) => Effect14.gen(function* () {
4354
+ const state = yield* Ref6.get(stateRef);
4355
+ const progress = state.get(sessionId);
4356
+ if (!progress) {
4357
+ return [];
4358
+ }
4359
+ return progress.entries.slice(-count);
4360
+ })
4361
+ };
4362
+ }
4363
+ function layer5() {
4364
+ return Layer10.effect(
4365
+ ProgressStore,
4366
+ Effect14.gen(function* () {
4367
+ const stateRef = yield* Ref6.make(/* @__PURE__ */ new Map());
4368
+ return createMemoryProgressStore(stateRef);
4369
+ })
4370
+ );
4371
+ }
4372
+ var Live10 = layer5();
4373
+ var MemoryProgress = {
4374
+ Live: Live10,
4375
+ layer: layer5
3456
4376
  };
3457
4377
 
3458
4378
  // src/layers/session/file-system.ts
3459
4379
  init_esm_shims();
3460
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3461
- import { join as join2 } from "path";
3462
- import { DateTime, Effect as Effect9, Layer as Layer5 } from "effect";
4380
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
4381
+ import { join as join5 } from "path";
4382
+ import { DateTime as DateTime5, Effect as Effect15, Layer as Layer11 } from "effect";
3463
4383
  import { humanId } from "human-id";
3464
4384
 
3465
4385
  // src/services/session-store.ts
3466
4386
  init_esm_shims();
3467
- import { Context as Context3 } from "effect";
3468
- var SessionStore = class extends Context3.Tag("@ferix/SessionStore")() {
4387
+ import { Context as Context6 } from "effect";
4388
+ var SessionStore = class extends Context6.Tag("@ferix/SessionStore")() {
3469
4389
  };
3470
4390
 
3471
4391
  // src/layers/session/file-system.ts
@@ -3474,25 +4394,25 @@ function generateSessionId(timestampMs) {
3474
4394
  const id = humanId({ separator: "-", capitalize: false });
3475
4395
  return `${id}-${timestampMs}`;
3476
4396
  }
3477
- function ensureDir2(dirPath) {
3478
- return Effect9.tryPromise({
3479
- try: () => mkdir2(dirPath, { recursive: true }),
4397
+ function ensureDir4(dirPath) {
4398
+ return Effect15.tryPromise({
4399
+ try: () => mkdir5(dirPath, { recursive: true }),
3480
4400
  catch: (error) => new SessionStoreError({
3481
4401
  message: `Failed to create directory: ${dirPath}`,
3482
4402
  operation: "create",
3483
4403
  cause: error
3484
4404
  })
3485
- }).pipe(Effect9.asVoid);
4405
+ }).pipe(Effect15.asVoid);
3486
4406
  }
3487
4407
  function getSessionPath(sessionId) {
3488
- return join2(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
4408
+ return join5(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
3489
4409
  }
3490
4410
  function serializeSession(session) {
3491
4411
  return JSON.stringify(session, null, 2);
3492
4412
  }
3493
4413
  function deserializeSession(json) {
3494
- return Effect9.gen(function* () {
3495
- const parsed = yield* Effect9.try({
4414
+ return Effect15.gen(function* () {
4415
+ const parsed = yield* Effect15.try({
3496
4416
  try: () => JSON.parse(json),
3497
4417
  catch: (error) => new SessionStoreError({
3498
4418
  message: `Invalid JSON in session file: ${String(error)}`,
@@ -3501,7 +4421,7 @@ function deserializeSession(json) {
3501
4421
  })
3502
4422
  });
3503
4423
  const validated = yield* decodeSession(parsed).pipe(
3504
- Effect9.mapError(
4424
+ Effect15.mapError(
3505
4425
  (error) => new SessionStoreError({
3506
4426
  message: `Session validation failed: ${String(error)}`,
3507
4427
  operation: "get",
@@ -3512,23 +4432,23 @@ function deserializeSession(json) {
3512
4432
  return validated;
3513
4433
  });
3514
4434
  }
3515
- var make3 = {
3516
- create: (originalTask) => Effect9.gen(function* () {
3517
- const sessionsDir = join2(process.cwd(), SESSIONS_DIR);
3518
- yield* ensureDir2(sessionsDir);
3519
- const now = yield* DateTime.now;
3520
- const timestampMs = DateTime.toEpochMillis(now);
4435
+ var make6 = {
4436
+ create: (originalTask) => Effect15.gen(function* () {
4437
+ const sessionsDir = join5(process.cwd(), SESSIONS_DIR);
4438
+ yield* ensureDir4(sessionsDir);
4439
+ const now = yield* DateTime5.now;
4440
+ const timestampMs = DateTime5.toEpochMillis(now);
3521
4441
  const sessionId = generateSessionId(timestampMs);
3522
4442
  const session = {
3523
4443
  id: sessionId,
3524
- createdAt: DateTime.formatIso(now),
4444
+ createdAt: DateTime5.formatIso(now),
3525
4445
  status: "active",
3526
4446
  originalTask,
3527
4447
  completedTasks: []
3528
4448
  };
3529
4449
  const sessionPath = getSessionPath(sessionId);
3530
- yield* Effect9.tryPromise({
3531
- try: () => writeFile2(sessionPath, serializeSession(session), "utf-8"),
4450
+ yield* Effect15.tryPromise({
4451
+ try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
3532
4452
  catch: (error) => new SessionStoreError({
3533
4453
  message: `Failed to write session file: ${sessionPath}`,
3534
4454
  operation: "create",
@@ -3537,10 +4457,10 @@ var make3 = {
3537
4457
  });
3538
4458
  return session;
3539
4459
  }),
3540
- get: (sessionId) => Effect9.gen(function* () {
4460
+ get: (sessionId) => Effect15.gen(function* () {
3541
4461
  const sessionPath = getSessionPath(sessionId);
3542
- const content = yield* Effect9.tryPromise({
3543
- try: () => readFile2(sessionPath, "utf-8"),
4462
+ const content = yield* Effect15.tryPromise({
4463
+ try: () => readFile4(sessionPath, "utf-8"),
3544
4464
  catch: (error) => new SessionStoreError({
3545
4465
  message: `Failed to read session file: ${sessionPath}`,
3546
4466
  operation: "get",
@@ -3549,10 +4469,10 @@ var make3 = {
3549
4469
  });
3550
4470
  return yield* deserializeSession(content);
3551
4471
  }),
3552
- update: (sessionId, session) => Effect9.gen(function* () {
4472
+ update: (sessionId, session) => Effect15.gen(function* () {
3553
4473
  const sessionPath = getSessionPath(sessionId);
3554
- yield* Effect9.tryPromise({
3555
- try: () => writeFile2(sessionPath, serializeSession(session), "utf-8"),
4474
+ yield* Effect15.tryPromise({
4475
+ try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
3556
4476
  catch: (error) => new SessionStoreError({
3557
4477
  message: `Failed to update session file: ${sessionPath}`,
3558
4478
  operation: "update",
@@ -3561,37 +4481,37 @@ var make3 = {
3561
4481
  });
3562
4482
  })
3563
4483
  };
3564
- var Live5 = Layer5.succeed(SessionStore, make3);
4484
+ var Live11 = Layer11.succeed(SessionStore, make6);
3565
4485
  var FileSystemSession = {
3566
- Live: Live5
4486
+ Live: Live11
3567
4487
  };
3568
4488
 
3569
4489
  // src/layers/session/memory.ts
3570
4490
  init_esm_shims();
3571
- import { DateTime as DateTime2, Effect as Effect10, Layer as Layer6, Ref as Ref4 } from "effect";
4491
+ import { DateTime as DateTime6, Effect as Effect16, Layer as Layer12, Ref as Ref7 } from "effect";
3572
4492
  function createMemorySessionStore(stateRef, counterRef) {
3573
4493
  return {
3574
- create: (originalTask) => Effect10.gen(function* () {
3575
- const state = yield* Ref4.get(stateRef);
3576
- const counter = yield* Ref4.updateAndGet(counterRef, (n) => n + 1);
4494
+ create: (originalTask) => Effect16.gen(function* () {
4495
+ const state = yield* Ref7.get(stateRef);
4496
+ const counter = yield* Ref7.updateAndGet(counterRef, (n) => n + 1);
3577
4497
  const sessionId = `test-session-${counter}`;
3578
- const now = yield* DateTime2.now;
4498
+ const now = yield* DateTime6.now;
3579
4499
  const session = {
3580
4500
  id: sessionId,
3581
- createdAt: DateTime2.formatIso(now),
4501
+ createdAt: DateTime6.formatIso(now),
3582
4502
  status: "active",
3583
4503
  originalTask,
3584
4504
  completedTasks: []
3585
4505
  };
3586
4506
  state.set(sessionId, session);
3587
- yield* Ref4.set(stateRef, state);
4507
+ yield* Ref7.set(stateRef, state);
3588
4508
  return session;
3589
4509
  }),
3590
- get: (sessionId) => Effect10.gen(function* () {
3591
- const state = yield* Ref4.get(stateRef);
4510
+ get: (sessionId) => Effect16.gen(function* () {
4511
+ const state = yield* Ref7.get(stateRef);
3592
4512
  const session = state.get(sessionId);
3593
4513
  if (!session) {
3594
- return yield* Effect10.fail(
4514
+ return yield* Effect16.fail(
3595
4515
  new SessionStoreError({
3596
4516
  message: `Session not found: ${sessionId}`,
3597
4517
  operation: "get"
@@ -3600,10 +4520,10 @@ function createMemorySessionStore(stateRef, counterRef) {
3600
4520
  }
3601
4521
  return session;
3602
4522
  }),
3603
- update: (sessionId, session) => Effect10.gen(function* () {
3604
- const state = yield* Ref4.get(stateRef);
4523
+ update: (sessionId, session) => Effect16.gen(function* () {
4524
+ const state = yield* Ref7.get(stateRef);
3605
4525
  if (!state.has(sessionId)) {
3606
- return yield* Effect10.fail(
4526
+ return yield* Effect16.fail(
3607
4527
  new SessionStoreError({
3608
4528
  message: `Session not found: ${sessionId}`,
3609
4529
  operation: "update"
@@ -3611,34 +4531,34 @@ function createMemorySessionStore(stateRef, counterRef) {
3611
4531
  );
3612
4532
  }
3613
4533
  state.set(sessionId, session);
3614
- yield* Ref4.set(stateRef, state);
4534
+ yield* Ref7.set(stateRef, state);
3615
4535
  })
3616
4536
  };
3617
4537
  }
3618
- function layer3() {
3619
- return Layer6.effect(
4538
+ function layer6() {
4539
+ return Layer12.effect(
3620
4540
  SessionStore,
3621
- Effect10.gen(function* () {
3622
- const stateRef = yield* Ref4.make(/* @__PURE__ */ new Map());
3623
- const counterRef = yield* Ref4.make(0);
4541
+ Effect16.gen(function* () {
4542
+ const stateRef = yield* Ref7.make(/* @__PURE__ */ new Map());
4543
+ const counterRef = yield* Ref7.make(0);
3624
4544
  return createMemorySessionStore(stateRef, counterRef);
3625
4545
  })
3626
4546
  );
3627
4547
  }
3628
- var Live6 = layer3();
4548
+ var Live12 = layer6();
3629
4549
  var MemorySession = {
3630
- Live: Live6,
3631
- layer: layer3
4550
+ Live: Live12,
4551
+ layer: layer6
3632
4552
  };
3633
4553
 
3634
4554
  // src/layers/signal/ferix-parser.ts
3635
4555
  init_esm_shims();
3636
- import { Effect as Effect11, Layer as Layer7, Ref as Ref5 } from "effect";
4556
+ import { Effect as Effect17, Layer as Layer13, Ref as Ref8 } from "effect";
3637
4557
 
3638
4558
  // src/services/signal-parser.ts
3639
4559
  init_esm_shims();
3640
- import { Context as Context4 } from "effect";
3641
- var SignalParser = class extends Context4.Tag("@ferix/SignalParser")() {
4560
+ import { Context as Context7 } from "effect";
4561
+ var SignalParser = class extends Context7.Tag("@ferix/SignalParser")() {
3642
4562
  };
3643
4563
 
3644
4564
  // src/layers/signal/specs/index.ts
@@ -3646,7 +4566,7 @@ init_esm_shims();
3646
4566
 
3647
4567
  // src/layers/signal/specs/check.ts
3648
4568
  init_esm_shims();
3649
- import { Schema as S13 } from "effect";
4569
+ import { Schema as S15 } from "effect";
3650
4570
 
3651
4571
  // src/layers/signal/specs/registry.ts
3652
4572
  init_esm_shims();
@@ -3703,7 +4623,7 @@ var checkPassedSpec = {
3703
4623
  parse: (text) => {
3704
4624
  if (CHECK_PASSED.test(text)) {
3705
4625
  const raw = { _tag: "CheckPassed" };
3706
- const result = S13.decodeUnknownEither(CheckPassedSignalSchema)(raw);
4626
+ const result = S15.decodeUnknownEither(CheckPassedSignalSchema)(raw);
3707
4627
  if (result._tag === "Right") {
3708
4628
  return [result.right];
3709
4629
  }
@@ -3719,7 +4639,7 @@ var checkFailedSpec = {
3719
4639
  parse: (text) => {
3720
4640
  if (CHECK_FAILED.test(text)) {
3721
4641
  const raw = { _tag: "CheckFailed" };
3722
- const result = S13.decodeUnknownEither(CheckFailedSignalSchema)(raw);
4642
+ const result = S15.decodeUnknownEither(CheckFailedSignalSchema)(raw);
3723
4643
  if (result._tag === "Right") {
3724
4644
  return [result.right];
3725
4645
  }
@@ -3733,7 +4653,7 @@ signalSpecRegistry.register(checkFailedSpec);
3733
4653
 
3734
4654
  // src/layers/signal/specs/criteria.ts
3735
4655
  init_esm_shims();
3736
- import { Schema as S14 } from "effect";
4656
+ import { Schema as S16 } from "effect";
3737
4657
  var CRITERIA_BLOCK = /<ferix:criteria task="(\d+)">([\s\S]*?)<\/ferix:criteria>/g;
3738
4658
  var CRITERION = /<criterion id="([^"]+)">([^<]+)<\/criterion>/g;
3739
4659
  var CRITERION_PASSED = /<ferix:criterion-passed id="([\d.c]+)"\/>/g;
@@ -3764,7 +4684,7 @@ var criteriaDefinedSpec = {
3764
4684
  taskId: match[1],
3765
4685
  criteria
3766
4686
  };
3767
- const result = S14.decodeUnknownEither(CriteriaDefinedSignalSchema)(raw);
4687
+ const result = S16.decodeUnknownEither(CriteriaDefinedSignalSchema)(raw);
3768
4688
  if (result._tag === "Right") {
3769
4689
  signals.push(result.right);
3770
4690
  }
@@ -3783,7 +4703,7 @@ var criterionPassedSpec = {
3783
4703
  for (const m of text.matchAll(resetRegex(CRITERION_PASSED))) {
3784
4704
  if (m[1]) {
3785
4705
  const raw = { _tag: "CriterionPassed", criterionId: m[1] };
3786
- const result = S14.decodeUnknownEither(CriterionPassedSignalSchema)(raw);
4706
+ const result = S16.decodeUnknownEither(CriterionPassedSignalSchema)(raw);
3787
4707
  if (result._tag === "Right") {
3788
4708
  signals.push(result.right);
3789
4709
  }
@@ -3806,7 +4726,7 @@ var criterionFailedSpec = {
3806
4726
  criterionId: m[1],
3807
4727
  reason: m[2] || "Unknown reason"
3808
4728
  };
3809
- const result = S14.decodeUnknownEither(CriterionFailedSignalSchema)(raw);
4729
+ const result = S16.decodeUnknownEither(CriterionFailedSignalSchema)(raw);
3810
4730
  if (result._tag === "Right") {
3811
4731
  signals.push(result.right);
3812
4732
  }
@@ -3820,9 +4740,88 @@ signalSpecRegistry.register(criteriaDefinedSpec);
3820
4740
  signalSpecRegistry.register(criterionPassedSpec);
3821
4741
  signalSpecRegistry.register(criterionFailedSpec);
3822
4742
 
4743
+ // src/layers/signal/specs/guardrail.ts
4744
+ init_esm_shims();
4745
+ import { Schema as S17 } from "effect";
4746
+ var GUARDRAIL = /<ferix:guardrail\s+severity="(warning|critical)"\s*>([\s\S]*?)<\/ferix:guardrail>/g;
4747
+ var PATTERN = /<pattern>([\s\S]*?)<\/pattern>/;
4748
+ var SIGN = /<sign>([\s\S]*?)<\/sign>/;
4749
+ var AVOIDANCE = /<avoidance>([\s\S]*?)<\/avoidance>/;
4750
+ var guardrailSpec = {
4751
+ tag: "Guardrail",
4752
+ closingTag: "</ferix:guardrail>",
4753
+ schema: GuardrailSignalSchema,
4754
+ parse: (text) => {
4755
+ const signals = [];
4756
+ const matches = text.matchAll(GUARDRAIL);
4757
+ for (const match of matches) {
4758
+ const severity = match[1];
4759
+ const content = match[2] || "";
4760
+ const patternMatch = content.match(PATTERN);
4761
+ const signMatch = content.match(SIGN);
4762
+ const avoidanceMatch = content.match(AVOIDANCE);
4763
+ const pattern = patternMatch?.[1]?.trim() || "";
4764
+ const sign = signMatch?.[1]?.trim() || "";
4765
+ const avoidance = avoidanceMatch?.[1]?.trim() || "";
4766
+ if (!(pattern && sign && avoidance)) {
4767
+ continue;
4768
+ }
4769
+ const raw = {
4770
+ _tag: "Guardrail",
4771
+ pattern,
4772
+ sign,
4773
+ avoidance,
4774
+ severity
4775
+ };
4776
+ const result = S17.decodeUnknownEither(GuardrailSignalSchema)(raw);
4777
+ if (result._tag === "Right") {
4778
+ signals.push(result.right);
4779
+ }
4780
+ }
4781
+ return signals;
4782
+ },
4783
+ keyFields: (s) => s.pattern.slice(0, 50)
4784
+ };
4785
+ signalSpecRegistry.register(guardrailSpec);
4786
+
4787
+ // src/layers/signal/specs/learning.ts
4788
+ init_esm_shims();
4789
+ import { Schema as S18 } from "effect";
4790
+ var LEARNING = /<ferix:learning(?:\s+category="(success|failure|optimization)")?\s*>([\s\S]*?)<\/ferix:learning>/g;
4791
+ var learningSpec = {
4792
+ tag: "Learning",
4793
+ closingTag: "</ferix:learning>",
4794
+ schema: LearningSignalSchema,
4795
+ parse: (text) => {
4796
+ const signals = [];
4797
+ const matches = text.matchAll(LEARNING);
4798
+ for (const match of matches) {
4799
+ const category = match[1];
4800
+ const content = match[2]?.trim() || "";
4801
+ if (!content) {
4802
+ continue;
4803
+ }
4804
+ const raw = {
4805
+ _tag: "Learning",
4806
+ content
4807
+ };
4808
+ if (category) {
4809
+ raw.category = category;
4810
+ }
4811
+ const result = S18.decodeUnknownEither(LearningSignalSchema)(raw);
4812
+ if (result._tag === "Right") {
4813
+ signals.push(result.right);
4814
+ }
4815
+ }
4816
+ return signals;
4817
+ },
4818
+ keyFields: (s) => s.content.slice(0, 50)
4819
+ };
4820
+ signalSpecRegistry.register(learningSpec);
4821
+
3823
4822
  // src/layers/signal/specs/loop-complete.ts
3824
4823
  init_esm_shims();
3825
- import { Schema as S15 } from "effect";
4824
+ import { Schema as S19 } from "effect";
3826
4825
  var LOOP_COMPLETE = /<ferix:complete>/;
3827
4826
  var loopCompleteSpec = {
3828
4827
  tag: "LoopComplete",
@@ -3831,7 +4830,7 @@ var loopCompleteSpec = {
3831
4830
  parse: (text) => {
3832
4831
  if (LOOP_COMPLETE.test(text)) {
3833
4832
  const raw = { _tag: "LoopComplete" };
3834
- const result = S15.decodeUnknownEither(LoopCompleteSignalSchema)(raw);
4833
+ const result = S19.decodeUnknownEither(LoopCompleteSignalSchema)(raw);
3835
4834
  if (result._tag === "Right") {
3836
4835
  return [result.right];
3837
4836
  }
@@ -3844,7 +4843,7 @@ signalSpecRegistry.register(loopCompleteSpec);
3844
4843
 
3845
4844
  // src/layers/signal/specs/phases.ts
3846
4845
  init_esm_shims();
3847
- import { Schema as S16 } from "effect";
4846
+ import { Schema as S20 } from "effect";
3848
4847
  var PHASES_BLOCK = /<ferix:phases task="(\d+)">([\s\S]*?)<\/ferix:phases>/;
3849
4848
  var PHASE = /<phase id="([\d.]+)">([^<]+)<\/phase>/g;
3850
4849
  var PHASE_START = /<ferix:phase-start id="([\d.]+)"\/>/g;
@@ -3877,7 +4876,7 @@ var phasesDefinedSpec = {
3877
4876
  taskId: match[1],
3878
4877
  phases
3879
4878
  };
3880
- const result = S16.decodeUnknownEither(PhasesDefinedSignalSchema)(raw);
4879
+ const result = S20.decodeUnknownEither(PhasesDefinedSignalSchema)(raw);
3881
4880
  if (result._tag === "Left") {
3882
4881
  return [];
3883
4882
  }
@@ -3894,7 +4893,7 @@ var phaseStartedSpec = {
3894
4893
  for (const m of text.matchAll(resetRegex2(PHASE_START))) {
3895
4894
  if (m[1]) {
3896
4895
  const raw = { _tag: "PhaseStarted", phaseId: m[1] };
3897
- const result = S16.decodeUnknownEither(PhaseStartedSignalSchema)(raw);
4896
+ const result = S20.decodeUnknownEither(PhaseStartedSignalSchema)(raw);
3898
4897
  if (result._tag === "Right") {
3899
4898
  signals.push(result.right);
3900
4899
  }
@@ -3913,7 +4912,7 @@ var phaseCompletedSpec = {
3913
4912
  for (const m of text.matchAll(resetRegex2(PHASE_DONE))) {
3914
4913
  if (m[1]) {
3915
4914
  const raw = { _tag: "PhaseCompleted", phaseId: m[1] };
3916
- const result = S16.decodeUnknownEither(PhaseCompletedSignalSchema)(raw);
4915
+ const result = S20.decodeUnknownEither(PhaseCompletedSignalSchema)(raw);
3917
4916
  if (result._tag === "Right") {
3918
4917
  signals.push(result.right);
3919
4918
  }
@@ -3936,7 +4935,7 @@ var phaseFailedSpec = {
3936
4935
  phaseId: m[1],
3937
4936
  reason: m[2] || "Unknown reason"
3938
4937
  };
3939
- const result = S16.decodeUnknownEither(PhaseFailedSignalSchema)(raw);
4938
+ const result = S20.decodeUnknownEither(PhaseFailedSignalSchema)(raw);
3940
4939
  if (result._tag === "Right") {
3941
4940
  signals.push(result.right);
3942
4941
  }
@@ -3953,7 +4952,7 @@ signalSpecRegistry.register(phaseFailedSpec);
3953
4952
 
3954
4953
  // src/layers/signal/specs/review.ts
3955
4954
  init_esm_shims();
3956
- import { Schema as S17 } from "effect";
4955
+ import { Schema as S21 } from "effect";
3957
4956
  var REVIEW_COMPLETE = /<ferix:review-complete\/>/;
3958
4957
  var REVIEW_CHANGES = /<ferix:review-changes-made\/>/;
3959
4958
  var reviewCompleteSpec = {
@@ -3966,7 +4965,7 @@ var reviewCompleteSpec = {
3966
4965
  }
3967
4966
  const changesMade = REVIEW_CHANGES.test(text);
3968
4967
  const raw = { _tag: "ReviewComplete", changesMade };
3969
- const result = S17.decodeUnknownEither(ReviewCompleteSignalSchema)(raw);
4968
+ const result = S21.decodeUnknownEither(ReviewCompleteSignalSchema)(raw);
3970
4969
  if (result._tag === "Right") {
3971
4970
  return [result.right];
3972
4971
  }
@@ -3978,7 +4977,7 @@ signalSpecRegistry.register(reviewCompleteSpec);
3978
4977
 
3979
4978
  // src/layers/signal/specs/task-complete.ts
3980
4979
  init_esm_shims();
3981
- import { Schema as S18 } from "effect";
4980
+ import { Schema as S22 } from "effect";
3982
4981
  var TASK_COMPLETE = /<ferix:task-complete id="(\d+)">([\s\S]*?)<\/ferix:task-complete>/;
3983
4982
  var SUMMARY = /<summary>([\s\S]*?)<\/summary>/;
3984
4983
  var FILES_MODIFIED = /<files-modified>([\s\S]*?)<\/files-modified>/;
@@ -4009,7 +5008,7 @@ var taskCompleteSpec = {
4009
5008
  filesModified: parseFileList(filesModifiedMatch?.[1]),
4010
5009
  filesCreated: parseFileList(filesCreatedMatch?.[1])
4011
5010
  };
4012
- const result = S18.decodeUnknownEither(TaskCompleteSignalSchema)(raw);
5011
+ const result = S22.decodeUnknownEither(TaskCompleteSignalSchema)(raw);
4013
5012
  if (result._tag === "Right") {
4014
5013
  return [result.right];
4015
5014
  }
@@ -4021,7 +5020,7 @@ signalSpecRegistry.register(taskCompleteSpec);
4021
5020
 
4022
5021
  // src/layers/signal/specs/tasks.ts
4023
5022
  init_esm_shims();
4024
- import { Schema as S19 } from "effect";
5023
+ import { Schema as S23 } from "effect";
4025
5024
  var TASKS_BLOCK = /<ferix:tasks>([\s\S]*?)<\/ferix:tasks>/;
4026
5025
  var TASK = /<task id="(\d+)">([^<]+)<\/task>/g;
4027
5026
  function resetRegex3(pattern) {
@@ -4051,7 +5050,7 @@ var tasksDefinedSpec = {
4051
5050
  return [];
4052
5051
  }
4053
5052
  const raw = { _tag: "TasksDefined", tasks };
4054
- const result = S19.decodeUnknownEither(TasksDefinedSignalSchema)(raw);
5053
+ const result = S23.decodeUnknownEither(TasksDefinedSignalSchema)(raw);
4055
5054
  if (result._tag === "Left") {
4056
5055
  return [];
4057
5056
  }
@@ -4064,20 +5063,20 @@ signalSpecRegistry.register(tasksDefinedSpec);
4064
5063
  // src/layers/signal/ferix-parser.ts
4065
5064
  var MAX_BUFFER_SIZE = 1024 * 1024;
4066
5065
  function createAccumulatorImpl() {
4067
- return Effect11.gen(function* () {
4068
- const chunksRef = yield* Ref5.make([]);
4069
- const emittedRef = yield* Ref5.make(/* @__PURE__ */ new Set());
4070
- const feed = (text) => Effect11.gen(function* () {
4071
- const chunks = yield* Ref5.get(chunksRef);
5066
+ return Effect17.gen(function* () {
5067
+ const chunksRef = yield* Ref8.make([]);
5068
+ const emittedRef = yield* Ref8.make(/* @__PURE__ */ new Set());
5069
+ const feed = (text) => Effect17.gen(function* () {
5070
+ const chunks = yield* Ref8.get(chunksRef);
4072
5071
  chunks.push(text);
4073
5072
  const buffer = chunks.join("");
4074
5073
  if (buffer.length > MAX_BUFFER_SIZE) {
4075
- yield* Ref5.set(chunksRef, [
5074
+ yield* Ref8.set(chunksRef, [
4076
5075
  buffer.slice(buffer.length - MAX_BUFFER_SIZE)
4077
5076
  ]);
4078
5077
  }
4079
5078
  const signals = signalSpecRegistry.parseAll(buffer);
4080
- const emitted = yield* Ref5.get(emittedRef);
5079
+ const emitted = yield* Ref8.get(emittedRef);
4081
5080
  const newSignals = signals.filter((signal) => {
4082
5081
  const key = signalSpecRegistry.getSignalKey(signal);
4083
5082
  if (emitted.has(key)) {
@@ -4089,55 +5088,64 @@ function createAccumulatorImpl() {
4089
5088
  if (newSignals.length > 0) {
4090
5089
  const lastEndPos = signalSpecRegistry.findLastCompleteSignalEnd(buffer);
4091
5090
  if (lastEndPos > 0 && lastEndPos < buffer.length) {
4092
- yield* Ref5.set(chunksRef, [buffer.slice(lastEndPos)]);
5091
+ yield* Ref8.set(chunksRef, [buffer.slice(lastEndPos)]);
4093
5092
  }
4094
5093
  }
4095
- yield* Ref5.set(emittedRef, emitted);
5094
+ yield* Ref8.set(emittedRef, emitted);
4096
5095
  return newSignals;
4097
5096
  });
4098
- const flush = () => Effect11.gen(function* () {
4099
- const chunks = yield* Ref5.get(chunksRef);
5097
+ const flush = () => Effect17.gen(function* () {
5098
+ const chunks = yield* Ref8.get(chunksRef);
4100
5099
  const buffer = chunks.join("");
4101
- yield* Ref5.set(chunksRef, []);
4102
- const emitted = yield* Ref5.get(emittedRef);
5100
+ yield* Ref8.set(chunksRef, []);
5101
+ const emitted = yield* Ref8.get(emittedRef);
4103
5102
  const signals = signalSpecRegistry.parseAll(buffer);
4104
5103
  const result = signals.filter(
4105
5104
  (signal) => !emitted.has(signalSpecRegistry.getSignalKey(signal))
4106
5105
  );
4107
- yield* Ref5.set(emittedRef, /* @__PURE__ */ new Set());
5106
+ yield* Ref8.set(emittedRef, /* @__PURE__ */ new Set());
4108
5107
  return result;
4109
5108
  });
4110
5109
  return { feed, flush };
4111
5110
  });
4112
5111
  }
4113
- var make4 = {
4114
- parse: (text) => Effect11.succeed(signalSpecRegistry.parseAll(text)),
5112
+ var make7 = {
5113
+ parse: (text) => Effect17.succeed(signalSpecRegistry.parseAll(text)),
4115
5114
  createAccumulator: createAccumulatorImpl
4116
5115
  };
4117
- var Live7 = Layer7.succeed(SignalParser, make4);
5116
+ var Live13 = Layer13.succeed(SignalParser, make7);
4118
5117
  var FerixParser = {
4119
- Live: Live7
5118
+ Live: Live13
4120
5119
  };
4121
5120
 
4122
5121
  // src/layers/index.ts
4123
- var ProductionLayers = Layer8.mergeAll(
5122
+ var ProductionLayers = Layer14.mergeAll(
4124
5123
  ClaudeCLI.Live,
4125
5124
  FerixParser.Live,
4126
5125
  FileSystemPlan.Live,
4127
- FileSystemSession.Live
5126
+ FileSystemSession.Live,
5127
+ FileSystemProgress.Live,
5128
+ FileSystemGuardrails.Live,
5129
+ FileSystemGit.Live
4128
5130
  );
4129
- var TestLayers = Layer8.mergeAll(
5131
+ var TestLayers = Layer14.mergeAll(
4130
5132
  Mock.Live,
4131
5133
  FerixParser.Live,
4132
5134
  MemoryPlan.Live,
4133
- MemorySession.Live
5135
+ MemorySession.Live,
5136
+ MemoryProgress.Live,
5137
+ MemoryGuardrails.Live,
5138
+ MemoryGit.Live
4134
5139
  );
4135
5140
  function createTestLayers(events) {
4136
- return Layer8.mergeAll(
5141
+ return Layer14.mergeAll(
4137
5142
  Mock.layer({ events }),
4138
5143
  FerixParser.Live,
4139
5144
  MemoryPlan.layer(),
4140
- MemorySession.layer()
5145
+ MemorySession.layer(),
5146
+ MemoryProgress.layer(),
5147
+ MemoryGuardrails.layer(),
5148
+ MemoryGit.layer()
4141
5149
  );
4142
5150
  }
4143
5151
 
@@ -4146,42 +5154,42 @@ init_esm_shims();
4146
5154
 
4147
5155
  // src/orchestrator/loop.ts
4148
5156
  init_esm_shims();
4149
- import { DateTime as DateTime6, Effect as Effect16, Option, pipe as pipe3, Ref as Ref9, Stream as Stream9 } from "effect";
5157
+ import { DateTime as DateTime10, Effect as Effect22, Option, pipe as pipe3, Ref as Ref12, Stream as Stream9 } from "effect";
4150
5158
 
4151
5159
  // src/orchestrator/discovery.ts
4152
5160
  init_esm_shims();
4153
- import { DateTime as DateTime4, Effect as Effect14, pipe, Ref as Ref7, Stream as Stream7 } from "effect";
5161
+ import { DateTime as DateTime8, Effect as Effect20, pipe, Ref as Ref10, Stream as Stream7 } from "effect";
4154
5162
 
4155
5163
  // src/layers/plan/task-generation.ts
4156
5164
  init_esm_shims();
4157
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
4158
- import { join as join3 } from "path";
4159
- import { Effect as Effect12 } from "effect";
4160
- var PLANS_DIR2 = ".ferix/plans";
4161
- function ensureDir3(dirPath) {
4162
- return Effect12.tryPromise({
4163
- try: () => mkdir3(dirPath, { recursive: true }),
5165
+ import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
5166
+ import { join as join6 } from "path";
5167
+ import { Effect as Effect18 } from "effect";
5168
+ var PLANS_DIR4 = ".ferix/plans";
5169
+ function ensureDir5(dirPath) {
5170
+ return Effect18.tryPromise({
5171
+ try: () => mkdir6(dirPath, { recursive: true }),
4164
5172
  catch: (error) => new PlanStoreError({
4165
5173
  message: `Failed to create directory: ${dirPath}`,
4166
5174
  operation: "create",
4167
5175
  cause: error
4168
5176
  })
4169
- }).pipe(Effect12.asVoid);
5177
+ }).pipe(Effect18.asVoid);
4170
5178
  }
4171
- function getSessionDir2(sessionId) {
4172
- return join3(process.cwd(), PLANS_DIR2, sessionId);
5179
+ function getSessionDir4(sessionId) {
5180
+ return join6(process.cwd(), PLANS_DIR4, sessionId);
4173
5181
  }
4174
5182
  function getTasksMdPath(sessionId) {
4175
- return join3(getSessionDir2(sessionId), "tasks.md");
5183
+ return join6(getSessionDir4(sessionId), "tasks.md");
4176
5184
  }
4177
5185
  function writeTasksMd(sessionId, tasks) {
4178
- return Effect12.gen(function* () {
4179
- const sessionDir = getSessionDir2(sessionId);
4180
- yield* ensureDir3(sessionDir);
5186
+ return Effect18.gen(function* () {
5187
+ const sessionDir = getSessionDir4(sessionId);
5188
+ yield* ensureDir5(sessionDir);
4181
5189
  const tasksMdPath = getTasksMdPath(sessionId);
4182
5190
  const content = formatTasksMd(tasks);
4183
- yield* Effect12.tryPromise({
4184
- try: () => writeFile3(tasksMdPath, content, "utf-8"),
5191
+ yield* Effect18.tryPromise({
5192
+ try: () => writeFile5(tasksMdPath, content, "utf-8"),
4185
5193
  catch: (error) => new PlanStoreError({
4186
5194
  message: `Failed to write tasks.md: ${tasksMdPath}`,
4187
5195
  operation: "create",
@@ -4325,6 +5333,32 @@ eventMappingRegistry.registerSignalMapper({
4325
5333
  }
4326
5334
  })
4327
5335
  });
5336
+ eventMappingRegistry.registerSignalMapper({
5337
+ tag: "Learning",
5338
+ map: (signal, context) => ({
5339
+ _tag: "LearningRecorded",
5340
+ iteration: 0,
5341
+ // Will be overwritten by iteration stream
5342
+ content: signal.content,
5343
+ category: signal.category,
5344
+ timestamp: context.timestamp
5345
+ })
5346
+ });
5347
+ eventMappingRegistry.registerSignalMapper({
5348
+ tag: "Guardrail",
5349
+ map: (signal, context) => ({
5350
+ _tag: "GuardrailAdded",
5351
+ id: `gr-pending-${context.timestamp}`,
5352
+ // Temp ID, real one assigned in iteration
5353
+ iteration: 0,
5354
+ // Will be overwritten by iteration stream
5355
+ pattern: signal.pattern,
5356
+ sign: signal.sign,
5357
+ avoidance: signal.avoidance,
5358
+ severity: signal.severity,
5359
+ timestamp: context.timestamp
5360
+ })
5361
+ });
4328
5362
 
4329
5363
  // src/orchestrator/mapping/index.ts
4330
5364
  function mapLLMEventToDomain(event, context) {
@@ -4336,7 +5370,7 @@ function mapSignalToDomain(signal, context) {
4336
5370
 
4337
5371
  // src/orchestrator/plan-updates.ts
4338
5372
  init_esm_shims();
4339
- import { DateTime as DateTime3, Effect as Effect13, Ref as Ref6 } from "effect";
5373
+ import { DateTime as DateTime7, Effect as Effect19, Ref as Ref9 } from "effect";
4340
5374
 
4341
5375
  // src/orchestrator/plan-updates/index.ts
4342
5376
  init_esm_shims();
@@ -4548,6 +5582,20 @@ planUpdateRegistry.register({
4548
5582
  } : void 0
4549
5583
  });
4550
5584
 
5585
+ // src/orchestrator/plan-updates/handlers/guardrail.ts
5586
+ init_esm_shims();
5587
+ planUpdateRegistry.register({
5588
+ tag: "Guardrail",
5589
+ handle: (_signal, _currentPlan, _context) => void 0
5590
+ });
5591
+
5592
+ // src/orchestrator/plan-updates/handlers/learning.ts
5593
+ init_esm_shims();
5594
+ planUpdateRegistry.register({
5595
+ tag: "Learning",
5596
+ handle: (_signal, _currentPlan, _context) => void 0
5597
+ });
5598
+
4551
5599
  // src/orchestrator/plan-updates/handlers/phase-lifecycle.ts
4552
5600
  init_esm_shims();
4553
5601
  planUpdateRegistry.register({
@@ -4627,9 +5675,9 @@ function persistPlanUpdate(planStore, plan, operation) {
4627
5675
  tasks: plan.tasks
4628
5676
  }) : planStore.update(plan.id, plan);
4629
5677
  return storeOp.pipe(
4630
- Effect13.map(() => null),
4631
- Effect13.catchAll(
4632
- (error) => Effect13.succeed({
5678
+ Effect19.map(() => null),
5679
+ Effect19.catchAll(
5680
+ (error) => Effect19.succeed({
4633
5681
  _tag: "PlanUpdateFailed",
4634
5682
  operation,
4635
5683
  error: error.message,
@@ -4639,10 +5687,10 @@ function persistPlanUpdate(planStore, plan, operation) {
4639
5687
  );
4640
5688
  }
4641
5689
  function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessionId, originalTask) {
4642
- return Effect13.gen(function* () {
4643
- const currentPlan = yield* Ref6.get(currentPlanRef);
4644
- const now = yield* DateTime3.now;
4645
- const timestamp = DateTime3.formatIso(now);
5690
+ return Effect19.gen(function* () {
5691
+ const currentPlan = yield* Ref9.get(currentPlanRef);
5692
+ const now = yield* DateTime7.now;
5693
+ const timestamp = DateTime7.formatIso(now);
4646
5694
  const updateResult = computePlanUpdate(signal, currentPlan, {
4647
5695
  sessionId,
4648
5696
  originalTask,
@@ -4652,8 +5700,8 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
4652
5700
  return [];
4653
5701
  }
4654
5702
  const { plan, operation, eventTag } = updateResult;
4655
- yield* Ref6.set(currentPlanRef, plan);
4656
- yield* Ref6.update(persistenceStateRef, (state) => ({
5703
+ yield* Ref9.set(currentPlanRef, plan);
5704
+ yield* Ref9.update(persistenceStateRef, (state) => ({
4657
5705
  dirty: true,
4658
5706
  pendingOperation: state.pendingOperation === "create" ? "create" : operation
4659
5707
  }));
@@ -4661,12 +5709,12 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
4661
5709
  });
4662
5710
  }
4663
5711
  function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
4664
- return Effect13.gen(function* () {
4665
- const state = yield* Ref6.get(persistenceStateRef);
5712
+ return Effect19.gen(function* () {
5713
+ const state = yield* Ref9.get(persistenceStateRef);
4666
5714
  if (!(state.dirty && state.pendingOperation)) {
4667
5715
  return [];
4668
5716
  }
4669
- const plan = yield* Ref6.get(currentPlanRef);
5717
+ const plan = yield* Ref9.get(currentPlanRef);
4670
5718
  if (!plan) {
4671
5719
  return [];
4672
5720
  }
@@ -4679,7 +5727,7 @@ function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
4679
5727
  if (failureEvent) {
4680
5728
  events.push(failureEvent);
4681
5729
  }
4682
- yield* Ref6.set(persistenceStateRef, {
5730
+ yield* Ref9.set(persistenceStateRef, {
4683
5731
  dirty: false,
4684
5732
  pendingOperation: null
4685
5733
  });
@@ -4738,9 +5786,12 @@ Use these XML-like tags to communicate structured information:
4738
5786
  <files-created>new-file.ts</files-created>
4739
5787
  </ferix:task-complete>
4740
5788
 
4741
- ### Loop Completion
5789
+ ### Loop Completion (use ONLY when ALL tasks are done)
4742
5790
  <ferix:complete>
4743
5791
 
5792
+ \u26A0\uFE0F IMPORTANT: Only emit <ferix:complete> after ALL tasks in the plan are complete.
5793
+ After completing a single task, emit <ferix:task-complete> and continue to the next task.
5794
+
4744
5795
  IMPORTANT: Always emit signals on their own lines, never inside markdown code blocks.`;
4745
5796
  var DISCOVERY_SYSTEM_PROMPT = `You are in the DISCOVERY phase of a ralph loop - an iterative AI coding workflow.
4746
5797
 
@@ -4781,14 +5832,17 @@ Review the code for quality:
4781
5832
  - When done, emit <ferix:review-complete/>`;
4782
5833
  var DEFAULT_COMPLETION_PROMPT = `## Completion
4783
5834
 
4784
- When the task is complete, emit:
5835
+ When you finish the CURRENT task, emit:
4785
5836
  <ferix:task-complete id="N">
4786
5837
  <summary>What was accomplished</summary>
4787
5838
  <files-modified>list of modified files</files-modified>
4788
5839
  <files-created>list of new files</files-created>
4789
5840
  </ferix:task-complete>
4790
5841
 
4791
- When ALL tasks are complete, emit <ferix:complete>`;
5842
+ After emitting task-complete, CONTINUE working on the next pending task.
5843
+
5844
+ \u26A0\uFE0F ONLY emit <ferix:complete> when ALL tasks in the plan are done.
5845
+ Do NOT emit <ferix:complete> after completing just one task - continue to the next task instead.`;
4792
5846
  function getPhasePrompt(phase, prompts, defaultPrompt) {
4793
5847
  return prompts?.phases?.[phase] ?? defaultPrompt;
4794
5848
  }
@@ -4929,12 +5983,12 @@ Begin.`);
4929
5983
 
4930
5984
  // src/orchestrator/discovery.ts
4931
5985
  function processTextSignals(signalParser, text, context) {
4932
- return Effect14.gen(function* () {
5986
+ return Effect20.gen(function* () {
4933
5987
  const events = [];
4934
5988
  const parsedSignals = [];
4935
5989
  const signals = yield* signalParser.parse(text).pipe(
4936
- Effect14.tapError(
4937
- (error) => Effect14.logDebug(
5990
+ Effect20.tapError(
5991
+ (error) => Effect20.logDebug(
4938
5992
  "Signal parsing failed, continuing with empty signals",
4939
5993
  {
4940
5994
  error: String(error),
@@ -4942,7 +5996,7 @@ function processTextSignals(signalParser, text, context) {
4942
5996
  }
4943
5997
  )
4944
5998
  ),
4945
- Effect14.orElseSucceed(() => [])
5999
+ Effect20.orElseSucceed(() => [])
4946
6000
  );
4947
6001
  for (const signal of signals) {
4948
6002
  events.push(mapSignalToDomain(signal, context));
@@ -4952,7 +6006,7 @@ function processTextSignals(signalParser, text, context) {
4952
6006
  });
4953
6007
  }
4954
6008
  function processLLMEvent(signalParser, llmEvent, context) {
4955
- return Effect14.gen(function* () {
6009
+ return Effect20.gen(function* () {
4956
6010
  const domainEvent = mapLLMEventToDomain(llmEvent, context);
4957
6011
  const events = [domainEvent];
4958
6012
  const allSignals = [];
@@ -4995,12 +6049,12 @@ function planTasksToGeneratedTasks(plan) {
4995
6049
  status: mapTaskStatus(task.status)
4996
6050
  }));
4997
6051
  }
4998
- function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId) {
6052
+ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId, worktreePath) {
4999
6053
  return Stream7.unwrap(
5000
- Effect14.gen(function* () {
5001
- const startTimeUtc = yield* DateTime4.now;
5002
- const startTime = DateTime4.toEpochMillis(startTimeUtc);
5003
- const persistenceStateRef = yield* Ref7.make({
6054
+ Effect20.gen(function* () {
6055
+ const startTimeUtc = yield* DateTime8.now;
6056
+ const startTime = DateTime8.toEpochMillis(startTimeUtc);
6057
+ const persistenceStateRef = yield* Ref10.make({
5004
6058
  dirty: false,
5005
6059
  pendingOperation: null
5006
6060
  });
@@ -5014,7 +6068,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5014
6068
  input: {}
5015
6069
  };
5016
6070
  const prompt = buildDiscoveryPrompt(config);
5017
- const llmStream = llm.execute(prompt).pipe(
6071
+ const llmStream = llm.execute(prompt, worktreePath ? { cwd: worktreePath } : void 0).pipe(
5018
6072
  Stream7.mapError(
5019
6073
  (e) => new OrchestratorError({
5020
6074
  message: `LLM execution failed during discovery: ${String(e)}`,
@@ -5024,10 +6078,10 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5024
6078
  ),
5025
6079
  Stream7.flatMap(
5026
6080
  (llmEvent) => Stream7.unwrap(
5027
- Effect14.gen(function* () {
5028
- const now = yield* DateTime4.now;
6081
+ Effect20.gen(function* () {
6082
+ const now = yield* DateTime8.now;
5029
6083
  const context = {
5030
- timestamp: DateTime4.toEpochMillis(now)
6084
+ timestamp: DateTime8.toEpochMillis(now)
5031
6085
  };
5032
6086
  const result = yield* processLLMEvent(
5033
6087
  signalParser,
@@ -5061,27 +6115,27 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5061
6115
  )
5062
6116
  );
5063
6117
  const completionStream = Stream7.fromEffect(
5064
- Effect14.gen(function* () {
6118
+ Effect20.gen(function* () {
5065
6119
  const persistEvents = yield* flushPlanPersistence(
5066
6120
  planStore,
5067
6121
  currentPlanRef,
5068
6122
  persistenceStateRef
5069
6123
  );
5070
- const plan = yield* Ref7.get(currentPlanRef);
6124
+ const plan = yield* Ref10.get(currentPlanRef);
5071
6125
  const taskCount = plan?.tasks.length ?? 0;
5072
6126
  if (plan && plan.tasks.length > 0) {
5073
6127
  const generatedTasks = planTasksToGeneratedTasks(plan);
5074
6128
  yield* writeTasksMd(sessionId, generatedTasks).pipe(
5075
- Effect14.tapError(
5076
- (error) => Effect14.logDebug("Failed to write tasks.md, continuing", {
6129
+ Effect20.tapError(
6130
+ (error) => Effect20.logDebug("Failed to write tasks.md, continuing", {
5077
6131
  error: String(error)
5078
6132
  })
5079
6133
  ),
5080
- Effect14.orElseSucceed(() => void 0)
6134
+ Effect20.orElseSucceed(() => void 0)
5081
6135
  );
5082
6136
  }
5083
- const endTimeUtc = yield* DateTime4.now;
5084
- const endTime = DateTime4.toEpochMillis(endTimeUtc);
6137
+ const endTimeUtc = yield* DateTime8.now;
6138
+ const endTime = DateTime8.toEpochMillis(endTimeUtc);
5085
6139
  const discoveryCompleted = {
5086
6140
  _tag: "DiscoveryCompleted",
5087
6141
  taskCount,
@@ -5102,15 +6156,15 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5102
6156
 
5103
6157
  // src/orchestrator/iteration.ts
5104
6158
  init_esm_shims();
5105
- import { DateTime as DateTime5, Effect as Effect15, pipe as pipe2, Ref as Ref8, Stream as Stream8 } from "effect";
6159
+ import { DateTime as DateTime9, Effect as Effect21, pipe as pipe2, Ref as Ref11, Stream as Stream8 } from "effect";
5106
6160
  function processTextSignals2(signalParser, text, context) {
5107
- return Effect15.gen(function* () {
6161
+ return Effect21.gen(function* () {
5108
6162
  const events = [];
5109
6163
  let completed = false;
5110
6164
  const parsedSignals = [];
5111
6165
  const signals = yield* signalParser.parse(text).pipe(
5112
- Effect15.tapError(
5113
- (error) => Effect15.logDebug(
6166
+ Effect21.tapError(
6167
+ (error) => Effect21.logDebug(
5114
6168
  "Signal parsing failed, continuing with empty signals",
5115
6169
  {
5116
6170
  error: String(error),
@@ -5118,7 +6172,7 @@ function processTextSignals2(signalParser, text, context) {
5118
6172
  }
5119
6173
  )
5120
6174
  ),
5121
- Effect15.orElseSucceed(() => [])
6175
+ Effect21.orElseSucceed(() => [])
5122
6176
  );
5123
6177
  for (const signal of signals) {
5124
6178
  events.push(mapSignalToDomain(signal, context));
@@ -5131,7 +6185,7 @@ function processTextSignals2(signalParser, text, context) {
5131
6185
  });
5132
6186
  }
5133
6187
  function processLLMEvent2(signalParser, llmEvent, context) {
5134
- return Effect15.gen(function* () {
6188
+ return Effect21.gen(function* () {
5135
6189
  const domainEvent = mapLLMEventToDomain(llmEvent, context);
5136
6190
  const events = [domainEvent];
5137
6191
  let completed = false;
@@ -5162,11 +6216,11 @@ function processLLMEvent2(signalParser, llmEvent, context) {
5162
6216
  return { events, completed, signals: allSignals };
5163
6217
  });
5164
6218
  }
5165
- function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId) {
6219
+ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId, worktreePath) {
5166
6220
  return Stream8.unwrap(
5167
- Effect15.gen(function* () {
5168
- const currentPlan = yield* Ref8.get(currentPlanRef);
5169
- const persistenceStateRef = yield* Ref8.make({
6221
+ Effect21.gen(function* () {
6222
+ const currentPlan = yield* Ref11.get(currentPlanRef);
6223
+ const persistenceStateRef = yield* Ref11.make({
5170
6224
  dirty: false,
5171
6225
  pendingOperation: null
5172
6226
  });
@@ -5174,7 +6228,10 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5174
6228
  _tag: "IterationStarted",
5175
6229
  iteration
5176
6230
  };
5177
- const llmStream = llm.execute(buildPrompt(config, iteration, currentPlan)).pipe(
6231
+ const llmStream = llm.execute(
6232
+ buildPrompt(config, iteration, currentPlan),
6233
+ worktreePath ? { cwd: worktreePath } : void 0
6234
+ ).pipe(
5178
6235
  Stream8.mapError(
5179
6236
  (e) => new OrchestratorError({
5180
6237
  message: `LLM execution failed: ${String(e)}`,
@@ -5184,10 +6241,10 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5184
6241
  ),
5185
6242
  Stream8.flatMap(
5186
6243
  (llmEvent) => Stream8.unwrap(
5187
- Effect15.gen(function* () {
5188
- const now = yield* DateTime5.now;
6244
+ Effect21.gen(function* () {
6245
+ const now = yield* DateTime9.now;
5189
6246
  const context = {
5190
- timestamp: DateTime5.toEpochMillis(now)
6247
+ timestamp: DateTime9.toEpochMillis(now)
5191
6248
  };
5192
6249
  const result = yield* processLLMEvent2(
5193
6250
  signalParser,
@@ -5206,7 +6263,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5206
6263
  events.push(...planEvents);
5207
6264
  }
5208
6265
  if (result.completed) {
5209
- yield* Ref8.set(loopCompletedRef, true);
6266
+ yield* Ref11.set(loopCompletedRef, true);
5210
6267
  }
5211
6268
  return Stream8.fromIterable(events);
5212
6269
  })
@@ -5225,7 +6282,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5225
6282
  )
5226
6283
  );
5227
6284
  const completionStream = Stream8.fromEffect(
5228
- Effect15.gen(function* () {
6285
+ Effect21.gen(function* () {
5229
6286
  const persistEvents = yield* flushPlanPersistence(
5230
6287
  planStore,
5231
6288
  currentPlanRef,
@@ -5250,13 +6307,14 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5250
6307
  // src/orchestrator/loop.ts
5251
6308
  function runLoop(config) {
5252
6309
  return Stream9.unwrap(
5253
- Effect16.gen(function* () {
6310
+ Effect22.gen(function* () {
5254
6311
  const llm = yield* LLM;
5255
6312
  const signalParser = yield* SignalParser;
5256
6313
  const sessionStore = yield* SessionStore;
5257
6314
  const planStore = yield* PlanStore;
6315
+ const git = yield* Git;
5258
6316
  const session = yield* sessionStore.create(config.task).pipe(
5259
- Effect16.mapError(
6317
+ Effect22.mapError(
5260
6318
  (e) => new OrchestratorError({
5261
6319
  message: `Failed to create session: ${e.message}`,
5262
6320
  phase: "setup",
@@ -5264,10 +6322,39 @@ function runLoop(config) {
5264
6322
  })
5265
6323
  )
5266
6324
  );
5267
- const startTimeUtc = yield* DateTime6.now;
5268
- const startTime = DateTime6.toEpochMillis(startTimeUtc);
5269
- const loopCompletedRef = yield* Ref9.make(false);
5270
- const currentPlanRef = yield* Ref9.make(void 0);
6325
+ const worktreePath = yield* git.createWorktree(session.id).pipe(
6326
+ Effect22.mapError(
6327
+ (e) => new OrchestratorError({
6328
+ message: `Failed to create worktree: ${e.message}`,
6329
+ phase: "setup",
6330
+ cause: e
6331
+ })
6332
+ )
6333
+ );
6334
+ const branchName = git.getBranchName(session.id);
6335
+ yield* sessionStore.update(session.id, {
6336
+ ...session,
6337
+ worktreePath,
6338
+ branchName
6339
+ }).pipe(
6340
+ Effect22.tapError(
6341
+ (error) => Effect22.logDebug("Failed to update session with worktree info", {
6342
+ error: String(error)
6343
+ })
6344
+ ),
6345
+ Effect22.orElseSucceed(() => void 0)
6346
+ );
6347
+ const startTimeUtc = yield* DateTime10.now;
6348
+ const startTime = DateTime10.toEpochMillis(startTimeUtc);
6349
+ const worktreeCreated = {
6350
+ _tag: "WorktreeCreated",
6351
+ sessionId: session.id,
6352
+ worktreePath,
6353
+ branchName,
6354
+ timestamp: startTime
6355
+ };
6356
+ const loopCompletedRef = yield* Ref12.make(false);
6357
+ const currentPlanRef = yield* Ref12.make(void 0);
5271
6358
  const maxIterations = config.maxIterations === 0 ? Number.POSITIVE_INFINITY : config.maxIterations;
5272
6359
  const loopStarted = {
5273
6360
  _tag: "LoopStarted",
@@ -5280,12 +6367,13 @@ function runLoop(config) {
5280
6367
  planStore,
5281
6368
  currentPlanRef,
5282
6369
  config,
5283
- session.id
6370
+ session.id,
6371
+ worktreePath
5284
6372
  );
5285
6373
  const iterationsStream = Stream9.unfoldEffect(
5286
6374
  1,
5287
- (iteration) => Effect16.gen(function* () {
5288
- const completed = yield* Ref9.get(loopCompletedRef);
6375
+ (iteration) => Effect22.gen(function* () {
6376
+ const completed = yield* Ref12.get(loopCompletedRef);
5289
6377
  if (completed || iteration > maxIterations) {
5290
6378
  return Option.none();
5291
6379
  }
@@ -5301,27 +6389,31 @@ function runLoop(config) {
5301
6389
  loopCompletedRef,
5302
6390
  config,
5303
6391
  iteration,
5304
- session.id
6392
+ session.id,
6393
+ worktreePath
5305
6394
  )
5306
6395
  )
5307
6396
  );
5308
6397
  const completionStream = createCompletionStream(
5309
6398
  sessionStore,
6399
+ git,
5310
6400
  session,
5311
6401
  config,
5312
6402
  startTime,
5313
- loopCompletedRef
6403
+ loopCompletedRef,
6404
+ worktreePath
5314
6405
  );
5315
6406
  return pipe3(
5316
6407
  Stream9.succeed(loopStarted),
6408
+ Stream9.concat(Stream9.succeed(worktreeCreated)),
5317
6409
  Stream9.concat(discoveryStream),
5318
6410
  Stream9.concat(iterationsStream),
5319
6411
  Stream9.concat(completionStream)
5320
6412
  );
5321
6413
  }).pipe(
5322
6414
  // Also catch setup errors (e.g., session creation failure)
5323
- Effect16.catchAll(
5324
- (error) => Effect16.succeed(
6415
+ Effect22.catchAll(
6416
+ (error) => Effect22.succeed(
5325
6417
  Stream9.succeed({
5326
6418
  _tag: "LoopFailed",
5327
6419
  error: {
@@ -5334,12 +6426,21 @@ function runLoop(config) {
5334
6426
  )
5335
6427
  );
5336
6428
  }
5337
- function createCompletionStream(sessionStore, session, config, startTime, loopCompletedRef) {
5338
- return Stream9.fromEffect(
5339
- Effect16.gen(function* () {
5340
- const endTimeUtc = yield* DateTime6.now;
5341
- const durationMs = DateTime6.toEpochMillis(endTimeUtc) - startTime;
5342
- const completed = yield* Ref9.get(loopCompletedRef);
6429
+ function createCompletionStream(sessionStore, git, session, config, startTime, loopCompletedRef, _worktreePath) {
6430
+ return Stream9.unwrap(
6431
+ Effect22.gen(function* () {
6432
+ const endTimeUtc = yield* DateTime10.now;
6433
+ const durationMs = DateTime10.toEpochMillis(endTimeUtc) - startTime;
6434
+ const completed = yield* Ref12.get(loopCompletedRef);
6435
+ yield* git.commitChanges(session.id, `feat: complete session ${session.id}`).pipe(
6436
+ Effect22.tapError(
6437
+ (error) => Effect22.logDebug("Final commit failed, continuing", {
6438
+ sessionId: session.id,
6439
+ error: String(error)
6440
+ })
6441
+ ),
6442
+ Effect22.orElseSucceed(() => void 0)
6443
+ );
5343
6444
  const summary = {
5344
6445
  iterations: config.maxIterations,
5345
6446
  success: completed,
@@ -5351,16 +6452,16 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
5351
6452
  ...session,
5352
6453
  status: completed ? "completed" : "paused"
5353
6454
  }).pipe(
5354
- Effect16.tapError(
5355
- (error) => Effect16.logDebug("Session update failed, continuing", {
6455
+ Effect22.tapError(
6456
+ (error) => Effect22.logDebug("Session update failed, continuing", {
5356
6457
  sessionId: session.id,
5357
6458
  error: String(error)
5358
6459
  })
5359
6460
  ),
5360
- Effect16.orElseSucceed(() => void 0)
6461
+ Effect22.orElseSucceed(() => void 0)
5361
6462
  );
5362
- const event = { _tag: "LoopCompleted", summary };
5363
- return event;
6463
+ const loopCompleted = { _tag: "LoopCompleted", summary };
6464
+ return Stream9.succeed(loopCompleted);
5364
6465
  })
5365
6466
  );
5366
6467
  }
@@ -5369,7 +6470,7 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
5369
6470
  function run(options) {
5370
6471
  const { config, consumer: consumerType = "headless", onEvent } = options;
5371
6472
  const events = runLoop(config);
5372
- const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect17.sync(() => onEvent(event)))) : events;
6473
+ const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
5373
6474
  const eventsWithLayers = eventsWithCallback.pipe(
5374
6475
  Stream10.provideLayer(ProductionLayers)
5375
6476
  );
@@ -5382,7 +6483,7 @@ function run(options) {
5382
6483
  function runTest(options, mockEvents) {
5383
6484
  const { config, onEvent } = options;
5384
6485
  const events = runLoop(config);
5385
- const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect17.sync(() => onEvent(event)))) : events;
6486
+ const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
5386
6487
  const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
5387
6488
  const eventsWithLayers = eventsWithCallback.pipe(Stream10.provideLayer(layers));
5388
6489
  return eventsWithLayers.pipe(Stream10.runDrain);
@@ -5390,11 +6491,11 @@ function runTest(options, mockEvents) {
5390
6491
  function collectEvents(config, mockEvents) {
5391
6492
  const events = runLoop(config);
5392
6493
  const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
5393
- return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(Effect17.map((chunk) => Array.from(chunk)));
6494
+ return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(Effect23.map((chunk) => Array.from(chunk)));
5394
6495
  }
5395
6496
  function main(config) {
5396
6497
  const consumerType = process.stdout.isTTY ? "tui" : "headless";
5397
- return run({ config, consumer: consumerType }).pipe(Effect17.runPromise);
6498
+ return run({ config, consumer: consumerType }).pipe(Effect23.runPromise);
5398
6499
  }
5399
6500
 
5400
6501
  // src/services/index.ts
@@ -5402,7 +6503,7 @@ init_esm_shims();
5402
6503
 
5403
6504
  // src/index.ts
5404
6505
  var program = new Command();
5405
- program.name("ferix-code").description("Composable RALPH loops for AI coding agents").version(package_default.version).argument("<task>", "Task description or path to PRD file").option("-i, --iterations <n>", "Maximum iterations", "1").option("-v, --verify <commands...>", "Verification commands").option("--branch <name>", "Git branch to create").option("--push", "Push branch after completion").option("--pr", "Create PR after pushing").action(async (task, options) => {
6506
+ program.name("ferix-code").description("Composable RALPH loops for AI coding agents").version(package_default.version, "-v, --version", "Output the version number").argument("<task>", "Task description or path to PRD file").option("-i, --iterations <n>", "Maximum iterations", "1").option("-c, --verify <commands...>", "Verification commands to run").option("--branch <name>", "Git branch to create").option("--push", "Push branch after completion").option("--pr", "Create PR after pushing").action(async (task, options) => {
5406
6507
  const config = {
5407
6508
  task,
5408
6509
  maxIterations: Number.parseInt(options.iterations, 10),
@@ -5447,11 +6548,23 @@ export {
5447
6548
  ExecutionModeSchema,
5448
6549
  FerixParser,
5449
6550
  FileLoggerConfigSchema,
6551
+ FileSystemGit,
6552
+ FileSystemGuardrails,
5450
6553
  FileSystemPlan,
6554
+ FileSystemProgress,
5451
6555
  FileSystemSession,
5452
6556
  GeneratedTaskListSchema,
5453
6557
  GeneratedTaskSchema,
5454
6558
  GeneratedTaskStatusSchema,
6559
+ Git,
6560
+ GitError,
6561
+ GuardrailAddedEventSchema,
6562
+ GuardrailSchema,
6563
+ GuardrailSeveritySchema,
6564
+ GuardrailSignalSchema,
6565
+ GuardrailsFileSchema,
6566
+ GuardrailsStore,
6567
+ GuardrailsStoreError,
5455
6568
  IterationCompletedEventSchema,
5456
6569
  IterationStartedEventSchema,
5457
6570
  LLM,
@@ -5461,6 +6574,9 @@ export {
5461
6574
  LLMToolEndEventSchema,
5462
6575
  LLMToolStartEventSchema,
5463
6576
  LLMToolUseEventSchema,
6577
+ LearningCategorySchema,
6578
+ LearningRecordedEventSchema,
6579
+ LearningSignalSchema,
5464
6580
  LogEntrySchema,
5465
6581
  LogLevelSchema,
5466
6582
  LoopCompleteSignalSchema,
@@ -5471,7 +6587,10 @@ export {
5471
6587
  LoopStartedEventSchema,
5472
6588
  LoopStatusSchema,
5473
6589
  LoopSummarySchema,
6590
+ MemoryGit,
6591
+ MemoryGuardrails,
5474
6592
  MemoryPlan,
6593
+ MemoryProgress,
5475
6594
  MemorySession,
5476
6595
  Mock,
5477
6596
  Mock as MockLLM,
@@ -5501,6 +6620,12 @@ export {
5501
6620
  PlanUpdateFailedEventSchema,
5502
6621
  PlanUpdatedEventSchema,
5503
6622
  ProductionLayers,
6623
+ ProgressActionSchema,
6624
+ ProgressEntrySchema,
6625
+ ProgressFileSchema,
6626
+ ProgressStore,
6627
+ ProgressStoreError,
6628
+ ProgressUpdatedEventSchema,
5504
6629
  PromptConfigSchema,
5505
6630
  ReviewCompleteDataSchema,
5506
6631
  ReviewCompleteEventSchema,
@@ -5535,15 +6660,21 @@ export {
5535
6660
  ToolStartEventSchema,
5536
6661
  ToolUseEventSchema,
5537
6662
  ViewModeSchema,
6663
+ WorktreeCreatedEventSchema,
6664
+ WorktreeRemovedEventSchema,
5538
6665
  buildPrompt,
5539
6666
  collectEvents,
5540
6667
  createHeadlessConsumer,
5541
6668
  createTUIConsumer,
5542
6669
  createTestLayers,
6670
+ decodeGuardrail,
6671
+ decodeGuardrailsFile,
5543
6672
  decodeLLMEvent,
5544
6673
  decodeLoopConfig,
5545
6674
  decodePlan,
5546
6675
  decodePlanData,
6676
+ decodeProgressEntry,
6677
+ decodeProgressFile,
5547
6678
  decodeSession,
5548
6679
  decodeSignal,
5549
6680
  decodeSignalSync,