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

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 +1961 -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.11",
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, copyFile, mkdir, rm } from "fs/promises";
2383
+ import { dirname, 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,369 @@ 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;
2378
- }
2379
- function isToolInputDelta(json) {
2380
- return typeof json === "object" && json !== null && "type" in json && json.type === "content_block_delta" && "delta" in json;
2439
+ });
2381
2440
  }
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
- };
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));
2449
+ }
2450
+ function copyUntrackedFiles(worktreeDir) {
2451
+ return Effect4.gen(function* () {
2452
+ const untrackedOutput = yield* gitExec(
2453
+ "git ls-files --others --exclude-standard"
2454
+ ).pipe(Effect4.catchAll(() => Effect4.succeed("")));
2455
+ const untrackedFiles = untrackedOutput.split("\n").filter((f) => f.length > 0).filter((f) => !f.startsWith(".ferix/"));
2456
+ for (const file of untrackedFiles) {
2457
+ const srcPath = join(process.cwd(), file);
2458
+ const destPath = join(worktreeDir, file);
2459
+ yield* Effect4.tryPromise({
2460
+ try: async () => {
2461
+ await mkdir(dirname(destPath), { recursive: true });
2462
+ await copyFile(srcPath, destPath);
2463
+ },
2464
+ catch: () => new GitError({
2465
+ message: `Failed to copy untracked file: ${file}`,
2466
+ operation: "createWorktree"
2467
+ })
2468
+ }).pipe(Effect4.catchAll(() => Effect4.succeed(void 0)));
2396
2469
  }
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;
2470
+ });
2419
2471
  }
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 });
2472
+ var make = {
2473
+ createWorktree: (sessionId, baseBranch) => Effect4.gen(function* () {
2474
+ const worktreeDir = getWorktreeDir(sessionId);
2475
+ const branchName = getBranchName(sessionId);
2476
+ const worktreesBase = join(process.cwd(), WORKTREES_DIR);
2477
+ yield* Effect4.tryPromise({
2478
+ try: () => mkdir(worktreesBase, { recursive: true }),
2479
+ catch: (error) => new GitError({
2480
+ message: `Failed to create worktrees directory: ${String(error)}`,
2481
+ operation: "createWorktree",
2482
+ cause: error
2483
+ })
2484
+ });
2485
+ const exists = yield* directoryExists(worktreeDir);
2486
+ if (exists) {
2487
+ return worktreeDir;
2438
2488
  }
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" })
2489
+ const baseRef = baseBranch || "HEAD";
2490
+ const command = `git worktree add "${worktreeDir}" -b "${branchName}" ${baseRef}`;
2491
+ yield* gitExec(command).pipe(
2492
+ Effect4.mapError(
2493
+ (error) => new GitError({
2494
+ message: `Failed to create worktree: ${error.message}`,
2495
+ operation: "createWorktree",
2496
+ cause: error
2497
+ })
2498
+ )
2499
+ );
2500
+ yield* copyUntrackedFiles(worktreeDir);
2501
+ return worktreeDir;
2502
+ }),
2503
+ removeWorktree: (sessionId) => Effect4.gen(function* () {
2504
+ const worktreeDir = getWorktreeDir(sessionId);
2505
+ const branchName = getBranchName(sessionId);
2506
+ const exists = yield* directoryExists(worktreeDir);
2507
+ if (!exists) {
2508
+ return;
2509
+ }
2510
+ yield* gitExec(`git worktree remove "${worktreeDir}" --force`).pipe(
2511
+ Effect4.mapError(
2512
+ (error) => new GitError({
2513
+ message: `Failed to remove worktree: ${error.message}`,
2514
+ operation: "removeWorktree",
2515
+ cause: error
2516
+ })
2517
+ ),
2518
+ // If git worktree remove fails, try manual cleanup
2519
+ Effect4.catchAll(
2520
+ () => Effect4.tryPromise({
2521
+ try: () => rm(worktreeDir, { recursive: true, force: true }),
2522
+ catch: (error) => new GitError({
2523
+ message: `Failed to remove worktree directory: ${String(error)}`,
2524
+ operation: "removeWorktree",
2525
+ cause: error
2526
+ })
2527
+ })
2528
+ )
2529
+ );
2530
+ yield* gitExec(`git branch -D "${branchName}"`).pipe(
2531
+ Effect4.catchAll(() => Effect4.succeed(void 0))
2532
+ );
2533
+ yield* gitExec("git worktree prune").pipe(
2534
+ Effect4.catchAll(() => Effect4.succeed(void 0))
2535
+ );
2536
+ }),
2537
+ getWorktreePath: (sessionId) => Effect4.gen(function* () {
2538
+ const worktreeDir = getWorktreeDir(sessionId);
2539
+ const exists = yield* directoryExists(worktreeDir);
2540
+ return exists ? worktreeDir : void 0;
2541
+ }),
2542
+ commitChanges: (sessionId, message) => Effect4.gen(function* () {
2543
+ const worktreeDir = getWorktreeDir(sessionId);
2544
+ const exists = yield* directoryExists(worktreeDir);
2545
+ if (!exists) {
2546
+ return yield* Effect4.fail(
2547
+ new GitError({
2548
+ message: `Worktree not found for session: ${sessionId}`,
2549
+ operation: "commit"
2550
+ })
2465
2551
  );
2466
- return Effect4.void;
2467
2552
  }
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}`
2553
+ yield* gitExec("git add -A", worktreeDir).pipe(
2554
+ Effect4.mapError(
2555
+ (error) => new GitError({
2556
+ message: `Failed to stage changes: ${error.message}`,
2557
+ operation: "commit",
2558
+ cause: error
2559
+ })
2560
+ )
2561
+ );
2562
+ const status = yield* gitExec("git status --porcelain", worktreeDir).pipe(
2563
+ Effect4.catchAll(() => Effect4.succeed(""))
2564
+ );
2565
+ if (!status) {
2566
+ const head = yield* gitExec("git rev-parse HEAD", worktreeDir).pipe(
2567
+ Effect4.mapError(
2568
+ (error) => new GitError({
2569
+ message: `Failed to get HEAD: ${error.message}`,
2570
+ operation: "commit",
2571
+ cause: error
2489
2572
  })
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,
2573
+ )
2574
+ );
2575
+ return head;
2576
+ }
2577
+ const escapedMessage = message.replace(/"/g, '\\"');
2578
+ yield* gitExec(`git commit -m "${escapedMessage}"`, worktreeDir).pipe(
2579
+ Effect4.mapError(
2580
+ (error) => new GitError({
2581
+ message: `Failed to commit: ${error.message}`,
2582
+ operation: "commit",
2501
2583
  cause: error
2502
2584
  })
2585
+ )
2586
+ );
2587
+ const hash = yield* gitExec("git rev-parse HEAD", worktreeDir).pipe(
2588
+ Effect4.mapError(
2589
+ (error) => new GitError({
2590
+ message: `Failed to get commit hash: ${error.message}`,
2591
+ operation: "commit",
2592
+ cause: error
2593
+ })
2594
+ )
2595
+ );
2596
+ return hash;
2597
+ }),
2598
+ pushBranch: (sessionId) => Effect4.gen(function* () {
2599
+ const worktreeDir = getWorktreeDir(sessionId);
2600
+ const branchName = getBranchName(sessionId);
2601
+ const exists = yield* directoryExists(worktreeDir);
2602
+ if (!exists) {
2603
+ return yield* Effect4.fail(
2604
+ new GitError({
2605
+ message: `Worktree not found for session: ${sessionId}`,
2606
+ operation: "push"
2607
+ })
2503
2608
  );
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
- })
2609
+ }
2610
+ yield* gitExec(`git push -u origin "${branchName}"`, worktreeDir).pipe(
2611
+ Effect4.mapError(
2612
+ (error) => new GitError({
2613
+ message: `Failed to push branch: ${error.message}`,
2614
+ operation: "push",
2615
+ cause: error
2616
+ })
2617
+ )
2538
2618
  );
2539
- }
2619
+ }),
2620
+ createPR: (sessionId, title, body) => Effect4.gen(function* () {
2621
+ const worktreeDir = getWorktreeDir(sessionId);
2622
+ const exists = yield* directoryExists(worktreeDir);
2623
+ if (!exists) {
2624
+ return yield* Effect4.fail(
2625
+ new GitError({
2626
+ message: `Worktree not found for session: ${sessionId}`,
2627
+ operation: "createPR"
2628
+ })
2629
+ );
2630
+ }
2631
+ const escapedTitle = title.replace(/"/g, '\\"');
2632
+ const escapedBody = body.replace(/"/g, '\\"');
2633
+ const prUrl = yield* gitExec(
2634
+ `gh pr create --title "${escapedTitle}" --body "${escapedBody}"`,
2635
+ worktreeDir
2636
+ ).pipe(
2637
+ Effect4.mapError(
2638
+ (error) => new GitError({
2639
+ message: `Failed to create PR: ${error.message}`,
2640
+ operation: "createPR",
2641
+ cause: error
2642
+ })
2643
+ )
2644
+ );
2645
+ return prUrl;
2646
+ }),
2647
+ getBranchName
2540
2648
  };
2541
- var Live = Layer.succeed(LLM, make);
2542
- var ClaudeCLI = {
2649
+ var Live = Layer.succeed(Git, make);
2650
+ var FileSystemGit = {
2543
2651
  Live
2544
2652
  };
2545
2653
 
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
2654
+ // src/layers/git/memory.ts
2551
2655
  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) {
2656
+ import { Effect as Effect5, Layer as Layer2, Ref as Ref3 } from "effect";
2657
+ var BRANCH_PREFIX2 = "ferix";
2658
+ function getBranchName2(sessionId) {
2659
+ return `${BRANCH_PREFIX2}/${sessionId}`;
2660
+ }
2661
+ function createMemoryGitService(stateRef, commitCounterRef) {
2584
2662
  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)));
2663
+ createWorktree: (sessionId, _baseBranch) => Effect5.gen(function* () {
2664
+ const state = yield* Ref3.get(stateRef);
2665
+ const existing = state.get(sessionId);
2666
+ if (existing) {
2667
+ return existing.path;
2590
2668
  }
2591
- return baseStream;
2592
- }
2669
+ const path2 = `.ferix/worktrees/${sessionId}`;
2670
+ const branch = getBranchName2(sessionId);
2671
+ const worktree = {
2672
+ path: path2,
2673
+ branch,
2674
+ commits: []
2675
+ };
2676
+ state.set(sessionId, worktree);
2677
+ yield* Ref3.set(stateRef, state);
2678
+ return path2;
2679
+ }),
2680
+ removeWorktree: (sessionId) => Effect5.gen(function* () {
2681
+ const state = yield* Ref3.get(stateRef);
2682
+ state.delete(sessionId);
2683
+ yield* Ref3.set(stateRef, state);
2684
+ }),
2685
+ getWorktreePath: (sessionId) => Effect5.gen(function* () {
2686
+ const state = yield* Ref3.get(stateRef);
2687
+ const worktree = state.get(sessionId);
2688
+ return worktree?.path;
2689
+ }),
2690
+ commitChanges: (sessionId, message) => Effect5.gen(function* () {
2691
+ const state = yield* Ref3.get(stateRef);
2692
+ const worktree = state.get(sessionId);
2693
+ if (!worktree) {
2694
+ return yield* Effect5.fail(
2695
+ new GitError({
2696
+ message: `Worktree not found for session: ${sessionId}`,
2697
+ operation: "commit"
2698
+ })
2699
+ );
2700
+ }
2701
+ const counter = yield* Ref3.updateAndGet(commitCounterRef, (n) => n + 1);
2702
+ const hash = `test-commit-${counter}`;
2703
+ const updatedWorktree = {
2704
+ ...worktree,
2705
+ commits: [...worktree.commits, `${hash}: ${message}`]
2706
+ };
2707
+ state.set(sessionId, updatedWorktree);
2708
+ yield* Ref3.set(stateRef, state);
2709
+ return hash;
2710
+ }),
2711
+ pushBranch: (sessionId) => Effect5.gen(function* () {
2712
+ const state = yield* Ref3.get(stateRef);
2713
+ const worktree = state.get(sessionId);
2714
+ if (!worktree) {
2715
+ return yield* Effect5.fail(
2716
+ new GitError({
2717
+ message: `Worktree not found for session: ${sessionId}`,
2718
+ operation: "push"
2719
+ })
2720
+ );
2721
+ }
2722
+ }),
2723
+ createPR: (sessionId, title, _body) => Effect5.gen(function* () {
2724
+ const state = yield* Ref3.get(stateRef);
2725
+ const worktree = state.get(sessionId);
2726
+ if (!worktree) {
2727
+ return yield* Effect5.fail(
2728
+ new GitError({
2729
+ message: `Worktree not found for session: ${sessionId}`,
2730
+ operation: "createPR"
2731
+ })
2732
+ );
2733
+ }
2734
+ const slug = title.toLowerCase().replace(/\s+/g, "-").slice(0, 30);
2735
+ return `https://github.com/test/repo/pull/${slug}`;
2736
+ }),
2737
+ getBranchName: getBranchName2
2593
2738
  };
2594
2739
  }
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));
2740
+ function layer() {
2741
+ return Layer2.effect(
2742
+ Git,
2743
+ Effect5.gen(function* () {
2744
+ const stateRef = yield* Ref3.make(/* @__PURE__ */ new Map());
2745
+ const commitCounterRef = yield* Ref3.make(0);
2746
+ return createMemoryGitService(stateRef, commitCounterRef);
2747
+ })
2748
+ );
2609
2749
  }
2610
- var Mock = {
2750
+ var Live2 = layer();
2751
+ var MemoryGit = {
2611
2752
  Live: Live2,
2612
- layer,
2613
- createMockLLM
2753
+ layer
2614
2754
  };
2615
2755
 
2616
- // src/layers/plan/file-system.ts
2756
+ // src/layers/guardrails/file-system.ts
2617
2757
  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";
2758
+ import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
2759
+ import { join as join2 } from "path";
2760
+ import { DateTime, Effect as Effect6, Layer as Layer3 } from "effect";
2621
2761
 
2622
2762
  // src/domain/index.ts
2623
2763
  init_esm_shims();
@@ -2627,55 +2767,55 @@ init_esm_shims();
2627
2767
 
2628
2768
  // src/domain/schemas/config.ts
2629
2769
  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);
2770
+ import { Schema as S } from "effect";
2771
+ var PhasePromptOverridesSchema = S.Struct({
2772
+ breakdown: S.optional(S.String),
2773
+ planning: S.optional(S.String),
2774
+ execution: S.optional(S.String),
2775
+ check: S.optional(S.String),
2776
+ verify: S.optional(S.String),
2777
+ review: S.optional(S.String),
2778
+ completion: S.optional(S.String)
2779
+ });
2780
+ var PromptConfigSchema = S.Struct({
2781
+ systemPrompt: S.optional(S.String),
2782
+ phases: S.optional(PhasePromptOverridesSchema),
2783
+ additionalContext: S.optional(S.String)
2784
+ });
2785
+ var LoopConfigSchema = S.Struct({
2786
+ task: S.String,
2787
+ maxIterations: S.Number,
2788
+ verifyCommands: S.Array(S.String),
2789
+ sessionId: S.optional(S.String),
2790
+ branch: S.optional(S.String),
2791
+ push: S.optional(S.Boolean),
2792
+ pr: S.optional(S.Boolean),
2793
+ verbose: S.optional(S.Boolean),
2794
+ prompts: S.optional(PromptConfigSchema)
2795
+ });
2796
+ var LoopSummarySchema = S.Struct({
2797
+ iterations: S.Number,
2798
+ success: S.Boolean,
2799
+ sessionId: S.String,
2800
+ completedTasks: S.Array(S.String),
2801
+ durationMs: S.Number
2802
+ });
2803
+ var LoopErrorSchema = S.Struct({
2804
+ message: S.String,
2805
+ phase: S.String,
2806
+ iteration: S.optional(S.Number)
2807
+ });
2808
+ var decodeLoopConfig = S.decodeUnknown(LoopConfigSchema);
2669
2809
 
2670
2810
  // src/domain/schemas/events.ts
2671
2811
  init_esm_shims();
2672
- import { Schema as S6 } from "effect";
2812
+ import { Schema as S4 } from "effect";
2673
2813
 
2674
2814
  // src/domain/schemas/plan.ts
2675
2815
  init_esm_shims();
2676
- import { Brand, Schema as S4 } from "effect";
2816
+ import { Brand, Schema as S2 } from "effect";
2677
2817
  var PlanId = Brand.nominal();
2678
- var TaskStatusSchema = S4.Literal(
2818
+ var TaskStatusSchema = S2.Literal(
2679
2819
  "pending",
2680
2820
  "planning",
2681
2821
  "in_progress",
@@ -2683,114 +2823,114 @@ var TaskStatusSchema = S4.Literal(
2683
2823
  "failed",
2684
2824
  "skipped"
2685
2825
  );
2686
- var PhaseStatusSchema = S4.Literal(
2826
+ var PhaseStatusSchema = S2.Literal(
2687
2827
  "pending",
2688
2828
  "in_progress",
2689
2829
  "done",
2690
2830
  "failed"
2691
2831
  );
2692
- var CriterionStatusSchema = S4.Literal("pending", "passed", "failed");
2693
- var PhaseSchema = S4.Struct({
2694
- id: S4.String,
2695
- description: S4.String,
2832
+ var CriterionStatusSchema = S2.Literal("pending", "passed", "failed");
2833
+ var PhaseSchema = S2.Struct({
2834
+ id: S2.String,
2835
+ description: S2.String,
2696
2836
  status: PhaseStatusSchema
2697
2837
  });
2698
- var CriterionSchema = S4.Struct({
2699
- id: S4.String,
2700
- description: S4.String,
2838
+ var CriterionSchema = S2.Struct({
2839
+ id: S2.String,
2840
+ description: S2.String,
2701
2841
  status: CriterionStatusSchema,
2702
- failureReason: S4.optional(S4.String)
2842
+ failureReason: S2.optional(S2.String)
2703
2843
  });
2704
- var TaskSchema = S4.Struct({
2705
- id: S4.String,
2706
- title: S4.String,
2707
- description: S4.String,
2844
+ var TaskSchema = S2.Struct({
2845
+ id: S2.String,
2846
+ title: S2.String,
2847
+ description: S2.String,
2708
2848
  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);
2849
+ phases: S2.Array(PhaseSchema),
2850
+ criteria: S2.Array(CriterionSchema),
2851
+ filesToModify: S2.Array(S2.String),
2852
+ attempts: S2.Number,
2853
+ completionNotes: S2.optional(S2.String)
2854
+ });
2855
+ var PlanDataSchema = S2.Struct({
2856
+ sessionId: S2.String,
2857
+ createdAt: S2.String,
2858
+ originalTask: S2.String,
2859
+ context: S2.optional(S2.String),
2860
+ tasks: S2.Array(TaskSchema)
2861
+ });
2862
+ var PlanSchema = S2.Struct({
2863
+ id: S2.String,
2864
+ sessionId: S2.String,
2865
+ createdAt: S2.String,
2866
+ originalTask: S2.String,
2867
+ context: S2.optional(S2.String),
2868
+ tasks: S2.Array(TaskSchema)
2869
+ });
2870
+ var decodePlan = S2.decodeUnknown(PlanSchema);
2871
+ var decodePlanData = S2.decodeUnknown(PlanDataSchema);
2732
2872
 
2733
2873
  // src/domain/schemas/shared.ts
2734
2874
  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
2875
+ import { Schema as S3 } from "effect";
2876
+ var TaskBasicInfoSchema = S3.Struct({
2877
+ id: S3.String,
2878
+ title: S3.String,
2879
+ description: S3.String
2740
2880
  });
2741
- var PhaseBasicInfoSchema = S5.Struct({
2742
- id: S5.String,
2743
- description: S5.String
2881
+ var PhaseBasicInfoSchema = S3.Struct({
2882
+ id: S3.String,
2883
+ description: S3.String
2744
2884
  });
2745
- var CriterionBasicInfoSchema = S5.Struct({
2746
- id: S5.String,
2747
- description: S5.String
2885
+ var CriterionBasicInfoSchema = S3.Struct({
2886
+ id: S3.String,
2887
+ description: S3.String
2748
2888
  });
2749
- var TasksDefinedDataSchema = S5.Struct({
2750
- tasks: S5.Array(TaskBasicInfoSchema)
2889
+ var TasksDefinedDataSchema = S3.Struct({
2890
+ tasks: S3.Array(TaskBasicInfoSchema)
2751
2891
  });
2752
- var PhasesDefinedDataSchema = S5.Struct({
2753
- taskId: S5.String,
2754
- phases: S5.Array(PhaseBasicInfoSchema)
2892
+ var PhasesDefinedDataSchema = S3.Struct({
2893
+ taskId: S3.String,
2894
+ phases: S3.Array(PhaseBasicInfoSchema)
2755
2895
  });
2756
- var CriteriaDefinedDataSchema = S5.Struct({
2757
- taskId: S5.String,
2758
- criteria: S5.Array(CriterionBasicInfoSchema)
2896
+ var CriteriaDefinedDataSchema = S3.Struct({
2897
+ taskId: S3.String,
2898
+ criteria: S3.Array(CriterionBasicInfoSchema)
2759
2899
  });
2760
- var PhaseIdDataSchema = S5.Struct({
2761
- phaseId: S5.String
2900
+ var PhaseIdDataSchema = S3.Struct({
2901
+ phaseId: S3.String
2762
2902
  });
2763
- var PhaseFailedDataSchema = S5.Struct({
2764
- phaseId: S5.String,
2765
- reason: S5.String
2903
+ var PhaseFailedDataSchema = S3.Struct({
2904
+ phaseId: S3.String,
2905
+ reason: S3.String
2766
2906
  });
2767
- var CriterionIdDataSchema = S5.Struct({
2768
- criterionId: S5.String
2907
+ var CriterionIdDataSchema = S3.Struct({
2908
+ criterionId: S3.String
2769
2909
  });
2770
- var CriterionFailedDataSchema = S5.Struct({
2771
- criterionId: S5.String,
2772
- reason: S5.String
2910
+ var CriterionFailedDataSchema = S3.Struct({
2911
+ criterionId: S3.String,
2912
+ reason: S3.String
2773
2913
  });
2774
- var ReviewCompleteDataSchema = S5.Struct({
2775
- changesMade: S5.Boolean
2914
+ var ReviewCompleteDataSchema = S3.Struct({
2915
+ changesMade: S3.Boolean
2776
2916
  });
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)
2917
+ var TaskCompleteSignalDataSchema = S3.Struct({
2918
+ taskId: S3.String,
2919
+ summary: S3.String,
2920
+ filesModified: S3.Array(S3.String),
2921
+ filesCreated: S3.Array(S3.String)
2782
2922
  });
2783
- var TaskCompleteDataSchema = S5.Struct({
2784
- taskId: S5.String,
2785
- summary: S5.String
2923
+ var TaskCompleteDataSchema = S3.Struct({
2924
+ taskId: S3.String,
2925
+ summary: S3.String
2786
2926
  });
2787
2927
 
2788
2928
  // 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 });
2929
+ var taggedEvent = (tag, fields) => S4.TaggedStruct(tag, fields);
2930
+ var taggedFromData = (tag, dataSchema, extraFields = {}) => S4.TaggedStruct(tag, { ...dataSchema.fields, ...extraFields });
2791
2931
  var LoopStartedEventSchema = taggedEvent("LoopStarted", {
2792
2932
  config: LoopConfigSchema,
2793
- timestamp: S6.Number
2933
+ timestamp: S4.Number
2794
2934
  });
2795
2935
  var LoopCompletedEventSchema = taggedEvent("LoopCompleted", {
2796
2936
  summary: LoopSummarySchema
@@ -2799,30 +2939,30 @@ var LoopFailedEventSchema = taggedEvent("LoopFailed", {
2799
2939
  error: LoopErrorSchema
2800
2940
  });
2801
2941
  var DiscoveryStartedEventSchema = taggedEvent("DiscoveryStarted", {
2802
- timestamp: S6.Number
2942
+ timestamp: S4.Number
2803
2943
  });
2804
2944
  var DiscoveryCompletedEventSchema = taggedEvent("DiscoveryCompleted", {
2805
- taskCount: S6.Number,
2806
- timestamp: S6.Number
2945
+ taskCount: S4.Number,
2946
+ timestamp: S4.Number
2807
2947
  });
2808
2948
  var IterationStartedEventSchema = taggedEvent("IterationStarted", {
2809
- iteration: S6.Number
2949
+ iteration: S4.Number
2810
2950
  });
2811
2951
  var IterationCompletedEventSchema = taggedEvent("IterationCompleted", {
2812
- iteration: S6.Number
2952
+ iteration: S4.Number
2813
2953
  });
2814
2954
  var LLMTextEventSchema = taggedEvent("LLMText", {
2815
- text: S6.String
2955
+ text: S4.String
2816
2956
  });
2817
2957
  var LLMToolStartEventSchema = taggedEvent("LLMToolStart", {
2818
- tool: S6.String
2958
+ tool: S4.String
2819
2959
  });
2820
2960
  var LLMToolUseEventSchema = taggedEvent("LLMToolUse", {
2821
- tool: S6.String,
2822
- input: S6.Unknown
2961
+ tool: S4.String,
2962
+ input: S4.Unknown
2823
2963
  });
2824
2964
  var LLMToolEndEventSchema = taggedEvent("LLMToolEnd", {
2825
- tool: S6.String
2965
+ tool: S4.String
2826
2966
  });
2827
2967
  var TasksDefinedEventSchema = taggedFromData(
2828
2968
  "TasksDefined",
@@ -2839,17 +2979,17 @@ var CriteriaDefinedEventSchema = taggedFromData(
2839
2979
  var PhaseStartedEventSchema = taggedFromData(
2840
2980
  "PhaseStarted",
2841
2981
  PhaseIdDataSchema,
2842
- { timestamp: S6.Number }
2982
+ { timestamp: S4.Number }
2843
2983
  );
2844
2984
  var PhaseCompletedEventSchema = taggedFromData(
2845
2985
  "PhaseCompleted",
2846
2986
  PhaseIdDataSchema,
2847
- { timestamp: S6.Number }
2987
+ { timestamp: S4.Number }
2848
2988
  );
2849
2989
  var PhaseFailedEventSchema = taggedFromData(
2850
2990
  "PhaseFailed",
2851
2991
  PhaseFailedDataSchema,
2852
- { timestamp: S6.Number }
2992
+ { timestamp: S4.Number }
2853
2993
  );
2854
2994
  var CriterionPassedEventSchema = taggedFromData(
2855
2995
  "CriterionPassed",
@@ -2859,9 +2999,9 @@ var CriterionFailedEventSchema = taggedFromData(
2859
2999
  "CriterionFailed",
2860
3000
  CriterionFailedDataSchema
2861
3001
  );
2862
- var CheckPassedEventSchema = S6.TaggedStruct("CheckPassed", {});
3002
+ var CheckPassedEventSchema = S4.TaggedStruct("CheckPassed", {});
2863
3003
  var CheckFailedEventSchema = taggedEvent("CheckFailed", {
2864
- failedCriteria: S6.Array(S6.String)
3004
+ failedCriteria: S4.Array(S4.String)
2865
3005
  });
2866
3006
  var ReviewCompleteEventSchema = taggedFromData(
2867
3007
  "ReviewComplete",
@@ -2870,7 +3010,7 @@ var ReviewCompleteEventSchema = taggedFromData(
2870
3010
  var TaskCompletedEventSchema = taggedFromData(
2871
3011
  "TaskCompleted",
2872
3012
  TaskCompleteDataSchema,
2873
- { timestamp: S6.Number }
3013
+ { timestamp: S4.Number }
2874
3014
  );
2875
3015
  var PlanCreatedEventSchema = taggedEvent("PlanCreated", {
2876
3016
  plan: PlanSchema
@@ -2879,11 +3019,43 @@ var PlanUpdatedEventSchema = taggedEvent("PlanUpdated", {
2879
3019
  plan: PlanSchema
2880
3020
  });
2881
3021
  var PlanUpdateFailedEventSchema = taggedEvent("PlanUpdateFailed", {
2882
- operation: S6.Literal("create", "update"),
2883
- error: S6.String,
2884
- planId: S6.optional(S6.String)
3022
+ operation: S4.Literal("create", "update"),
3023
+ error: S4.String,
3024
+ planId: S4.optional(S4.String)
3025
+ });
3026
+ var LearningRecordedEventSchema = taggedEvent("LearningRecorded", {
3027
+ iteration: S4.Number,
3028
+ content: S4.String,
3029
+ category: S4.optional(S4.Literal("success", "failure", "optimization")),
3030
+ timestamp: S4.Number
3031
+ });
3032
+ var GuardrailAddedEventSchema = taggedEvent("GuardrailAdded", {
3033
+ id: S4.String,
3034
+ iteration: S4.Number,
3035
+ pattern: S4.String,
3036
+ sign: S4.String,
3037
+ avoidance: S4.String,
3038
+ severity: S4.Literal("warning", "critical"),
3039
+ timestamp: S4.Number
3040
+ });
3041
+ var ProgressUpdatedEventSchema = taggedEvent("ProgressUpdated", {
3042
+ sessionId: S4.String,
3043
+ iteration: S4.Number,
3044
+ taskId: S4.String,
3045
+ action: S4.Literal("started", "completed", "failed", "learning"),
3046
+ timestamp: S4.Number
3047
+ });
3048
+ var WorktreeCreatedEventSchema = taggedEvent("WorktreeCreated", {
3049
+ sessionId: S4.String,
3050
+ worktreePath: S4.String,
3051
+ branchName: S4.String,
3052
+ timestamp: S4.Number
3053
+ });
3054
+ var WorktreeRemovedEventSchema = taggedEvent("WorktreeRemoved", {
3055
+ sessionId: S4.String,
3056
+ timestamp: S4.Number
2885
3057
  });
2886
- var DomainEventSchema = S6.Union(
3058
+ var DomainEventSchema = S4.Union(
2887
3059
  LoopStartedEventSchema,
2888
3060
  LoopCompletedEventSchema,
2889
3061
  LoopFailedEventSchema,
@@ -2909,7 +3081,12 @@ var DomainEventSchema = S6.Union(
2909
3081
  TaskCompletedEventSchema,
2910
3082
  PlanCreatedEventSchema,
2911
3083
  PlanUpdatedEventSchema,
2912
- PlanUpdateFailedEventSchema
3084
+ PlanUpdateFailedEventSchema,
3085
+ LearningRecordedEventSchema,
3086
+ GuardrailAddedEventSchema,
3087
+ ProgressUpdatedEventSchema,
3088
+ WorktreeCreatedEventSchema,
3089
+ WorktreeRemovedEventSchema
2913
3090
  );
2914
3091
  var DomainEventUtils = {
2915
3092
  isLLMEvent: (e) => e._tag.startsWith("LLM"),
@@ -2919,6 +3096,55 @@ var DomainEventUtils = {
2919
3096
  isDiscoveryEvent: (e) => e._tag.startsWith("Discovery")
2920
3097
  };
2921
3098
 
3099
+ // src/domain/schemas/guardrails.ts
3100
+ init_esm_shims();
3101
+ import { Schema as S5 } from "effect";
3102
+ var GuardrailSeveritySchema = S5.Literal("warning", "critical");
3103
+ var GuardrailSchema = S5.Struct({
3104
+ id: S5.String,
3105
+ createdAt: S5.String,
3106
+ iteration: S5.Number,
3107
+ pattern: S5.String,
3108
+ sign: S5.String,
3109
+ avoidance: S5.String,
3110
+ severity: GuardrailSeveritySchema
3111
+ });
3112
+ var GuardrailsFileSchema = S5.Struct({
3113
+ sessionId: S5.String,
3114
+ createdAt: S5.String,
3115
+ guardrails: S5.Array(GuardrailSchema)
3116
+ });
3117
+ var decodeGuardrail = S5.decodeUnknown(GuardrailSchema);
3118
+ var decodeGuardrailsFile = S5.decodeUnknown(GuardrailsFileSchema);
3119
+
3120
+ // src/domain/schemas/llm.ts
3121
+ init_esm_shims();
3122
+ import { Schema as S6 } from "effect";
3123
+ var TextEventSchema = S6.TaggedStruct("Text", {
3124
+ text: S6.String
3125
+ });
3126
+ var ToolStartEventSchema = S6.TaggedStruct("ToolStart", {
3127
+ tool: S6.String
3128
+ });
3129
+ var ToolUseEventSchema = S6.TaggedStruct("ToolUse", {
3130
+ tool: S6.String,
3131
+ input: S6.Unknown
3132
+ });
3133
+ var ToolEndEventSchema = S6.TaggedStruct("ToolEnd", {
3134
+ tool: S6.String
3135
+ });
3136
+ var DoneEventSchema = S6.TaggedStruct("Done", {
3137
+ output: S6.String
3138
+ });
3139
+ var LLMEventSchema = S6.Union(
3140
+ TextEventSchema,
3141
+ ToolStartEventSchema,
3142
+ ToolUseEventSchema,
3143
+ ToolEndEventSchema,
3144
+ DoneEventSchema
3145
+ );
3146
+ var decodeLLMEvent = S6.decodeUnknown(LLMEventSchema);
3147
+
2922
3148
  // src/domain/schemas/logger.ts
2923
3149
  init_esm_shims();
2924
3150
  import { Schema as S7 } from "effect";
@@ -2947,29 +3173,57 @@ var RunOptionsDataSchema = S8.Struct({
2947
3173
  consumer: S8.optional(ConsumerTypeSchema)
2948
3174
  });
2949
3175
 
2950
- // src/domain/schemas/session.ts
3176
+ // src/domain/schemas/progress.ts
2951
3177
  init_esm_shims();
2952
3178
  import { Schema as S9 } from "effect";
2953
- var SessionStatusSchema = S9.Literal(
3179
+ var ProgressActionSchema = S9.Literal(
3180
+ "started",
3181
+ "completed",
3182
+ "failed",
3183
+ "learning"
3184
+ );
3185
+ var ProgressEntrySchema = S9.Struct({
3186
+ iteration: S9.Number,
3187
+ timestamp: S9.String,
3188
+ taskId: S9.String,
3189
+ action: ProgressActionSchema,
3190
+ summary: S9.String,
3191
+ learnings: S9.optional(S9.Array(S9.String)),
3192
+ filesModified: S9.optional(S9.Array(S9.String))
3193
+ });
3194
+ var ProgressFileSchema = S9.Struct({
3195
+ sessionId: S9.String,
3196
+ createdAt: S9.String,
3197
+ entries: S9.Array(ProgressEntrySchema)
3198
+ });
3199
+ var decodeProgressEntry = S9.decodeUnknown(ProgressEntrySchema);
3200
+ var decodeProgressFile = S9.decodeUnknown(ProgressFileSchema);
3201
+
3202
+ // src/domain/schemas/session.ts
3203
+ init_esm_shims();
3204
+ import { Schema as S10 } from "effect";
3205
+ var SessionStatusSchema = S10.Literal(
2954
3206
  "active",
2955
3207
  "completed",
2956
3208
  "failed",
2957
3209
  "paused"
2958
3210
  );
2959
- var SessionSchema = S9.Struct({
2960
- id: S9.String,
2961
- createdAt: S9.String,
3211
+ var SessionSchema = S10.Struct({
3212
+ id: S10.String,
3213
+ createdAt: S10.String,
2962
3214
  status: SessionStatusSchema,
2963
- originalTask: S9.String,
2964
- completedTasks: S9.Array(S9.String),
2965
- currentTaskId: S9.optional(S9.String)
3215
+ originalTask: S10.String,
3216
+ completedTasks: S10.Array(S10.String),
3217
+ currentTaskId: S10.optional(S10.String),
3218
+ worktreePath: S10.optional(S10.String),
3219
+ branchName: S10.optional(S10.String)
2966
3220
  });
2967
- var decodeSession = S9.decodeUnknown(SessionSchema);
3221
+ var decodeSession = S10.decodeUnknown(SessionSchema);
2968
3222
 
2969
3223
  // src/domain/schemas/signals.ts
2970
3224
  init_esm_shims();
2971
- import { Schema as S10 } from "effect";
2972
- var taggedFromData2 = (tag, dataSchema) => S10.TaggedStruct(tag, dataSchema.fields);
3225
+ import { Schema as S11 } from "effect";
3226
+ var taggedFromData2 = (tag, dataSchema) => S11.TaggedStruct(tag, dataSchema.fields);
2973
3227
  var TasksDefinedSignalSchema = taggedFromData2(
2974
3228
  "TasksDefined",
2975
3229
  TasksDefinedDataSchema
@@ -3002,8 +3256,8 @@ var CriterionFailedSignalSchema = taggedFromData2(
3002
3256
  "CriterionFailed",
3003
3257
  CriterionFailedDataSchema
3004
3258
  );
3005
- var CheckPassedSignalSchema = S10.TaggedStruct("CheckPassed", {});
3006
- var CheckFailedSignalSchema = S10.TaggedStruct("CheckFailed", {});
3259
+ var CheckPassedSignalSchema = S11.TaggedStruct("CheckPassed", {});
3260
+ var CheckFailedSignalSchema = S11.TaggedStruct("CheckFailed", {});
3007
3261
  var ReviewCompleteSignalSchema = taggedFromData2(
3008
3262
  "ReviewComplete",
3009
3263
  ReviewCompleteDataSchema
@@ -3012,8 +3266,23 @@ var TaskCompleteSignalSchema = taggedFromData2(
3012
3266
  "TaskComplete",
3013
3267
  TaskCompleteSignalDataSchema
3014
3268
  );
3015
- var LoopCompleteSignalSchema = S10.TaggedStruct("LoopComplete", {});
3016
- var SignalSchema = S10.Union(
3269
+ var LoopCompleteSignalSchema = S11.TaggedStruct("LoopComplete", {});
3270
+ var LearningCategorySchema = S11.Literal(
3271
+ "success",
3272
+ "failure",
3273
+ "optimization"
3274
+ );
3275
+ var LearningSignalSchema = S11.TaggedStruct("Learning", {
3276
+ content: S11.String,
3277
+ category: S11.optional(LearningCategorySchema)
3278
+ });
3279
+ var GuardrailSignalSchema = S11.TaggedStruct("Guardrail", {
3280
+ pattern: S11.String,
3281
+ sign: S11.String,
3282
+ avoidance: S11.String,
3283
+ severity: S11.Literal("warning", "critical")
3284
+ });
3285
+ var SignalSchema = S11.Union(
3017
3286
  TasksDefinedSignalSchema,
3018
3287
  PhasesDefinedSignalSchema,
3019
3288
  CriteriaDefinedSignalSchema,
@@ -3026,26 +3295,28 @@ var SignalSchema = S10.Union(
3026
3295
  CheckFailedSignalSchema,
3027
3296
  ReviewCompleteSignalSchema,
3028
3297
  TaskCompleteSignalSchema,
3029
- LoopCompleteSignalSchema
3298
+ LoopCompleteSignalSchema,
3299
+ LearningSignalSchema,
3300
+ GuardrailSignalSchema
3030
3301
  );
3031
- var decodeSignal = S10.decodeUnknown(SignalSchema);
3032
- var decodeSignalSync = S10.decodeUnknownSync(SignalSchema);
3302
+ var decodeSignal = S11.decodeUnknown(SignalSchema);
3303
+ var decodeSignalSync = S11.decodeUnknownSync(SignalSchema);
3033
3304
 
3034
3305
  // src/domain/schemas/task-generation.ts
3035
3306
  init_esm_shims();
3036
- import { Schema as S11 } from "effect";
3037
- var GeneratedTaskStatusSchema = S11.Literal(
3307
+ import { Schema as S12 } from "effect";
3308
+ var GeneratedTaskStatusSchema = S12.Literal(
3038
3309
  "pending",
3039
3310
  "in_progress",
3040
3311
  "done",
3041
3312
  "failed"
3042
3313
  );
3043
- var GeneratedTaskSchema = S11.Struct({
3044
- id: S11.String,
3045
- title: S11.String,
3314
+ var GeneratedTaskSchema = S12.Struct({
3315
+ id: S12.String,
3316
+ title: S12.String,
3046
3317
  status: GeneratedTaskStatusSchema
3047
3318
  });
3048
- var GeneratedTaskListSchema = S11.Array(GeneratedTaskSchema);
3319
+ var GeneratedTaskListSchema = S12.Array(GeneratedTaskSchema);
3049
3320
  var STATUS_ICONS = {
3050
3321
  done: "[x]",
3051
3322
  in_progress: "[~]",
@@ -3104,15 +3375,15 @@ function parseTasksMd(content) {
3104
3375
 
3105
3376
  // src/domain/schemas/tui.ts
3106
3377
  init_esm_shims();
3107
- import { Schema as S12 } from "effect";
3108
- var ViewModeSchema = S12.Literal("logs", "tasks", "detail");
3109
- var LoopStatusSchema = S12.Literal(
3378
+ import { Schema as S13 } from "effect";
3379
+ var ViewModeSchema = S13.Literal("logs", "tasks", "detail");
3380
+ var LoopStatusSchema = S13.Literal(
3110
3381
  "idle",
3111
3382
  "running",
3112
3383
  "complete",
3113
3384
  "error"
3114
3385
  );
3115
- var ExecutionModeSchema = S12.Literal(
3386
+ var ExecutionModeSchema = S13.Literal(
3116
3387
  "idle",
3117
3388
  "discovery",
3118
3389
  "breakdown",
@@ -3122,98 +3393,566 @@ var ExecutionModeSchema = S12.Literal(
3122
3393
  "verifying",
3123
3394
  "reviewing"
3124
3395
  );
3125
- var TUIPhaseStatusSchema = S12.Literal(
3396
+ var TUIPhaseStatusSchema = S13.Literal(
3126
3397
  "pending",
3127
3398
  "in_progress",
3128
3399
  "done",
3129
3400
  "failed"
3130
3401
  );
3131
- var TUICriterionStatusSchema = S12.Literal(
3402
+ var TUICriterionStatusSchema = S13.Literal(
3132
3403
  "pending",
3133
3404
  "passed",
3134
3405
  "failed"
3135
3406
  );
3136
- var TUITaskStatusSchema = S12.Literal(
3407
+ var TUITaskStatusSchema = S13.Literal(
3137
3408
  "pending",
3138
3409
  "in_progress",
3139
3410
  "done",
3140
3411
  "failed"
3141
3412
  );
3142
- var TUIPhaseSchema = S12.Struct({
3143
- id: S12.String,
3144
- description: S12.String,
3413
+ var TUIPhaseSchema = S13.Struct({
3414
+ id: S13.String,
3415
+ description: S13.String,
3145
3416
  status: TUIPhaseStatusSchema,
3146
- startedAt: S12.optional(S12.Number),
3147
- completedAt: S12.optional(S12.Number)
3417
+ startedAt: S13.optional(S13.Number),
3418
+ completedAt: S13.optional(S13.Number)
3148
3419
  });
3149
- var TUICriterionSchema = S12.Struct({
3150
- id: S12.String,
3151
- description: S12.String,
3420
+ var TUICriterionSchema = S13.Struct({
3421
+ id: S13.String,
3422
+ description: S13.String,
3152
3423
  status: TUICriterionStatusSchema,
3153
- failureReason: S12.optional(S12.String)
3424
+ failureReason: S13.optional(S13.String)
3154
3425
  });
3155
- var TUITaskSchema = S12.Struct({
3156
- id: S12.String,
3157
- title: S12.String,
3426
+ var TUITaskSchema = S13.Struct({
3427
+ id: S13.String,
3428
+ title: S13.String,
3158
3429
  status: TUITaskStatusSchema,
3159
- phases: S12.Array(TUIPhaseSchema),
3160
- criteria: S12.Array(TUICriterionSchema),
3161
- startedAt: S12.optional(S12.Number),
3162
- completedAt: S12.optional(S12.Number)
3430
+ phases: S13.Array(TUIPhaseSchema),
3431
+ criteria: S13.Array(TUICriterionSchema),
3432
+ startedAt: S13.optional(S13.Number),
3433
+ completedAt: S13.optional(S13.Number)
3163
3434
  });
3164
- var TUIStateSchema = S12.Struct({
3435
+ var TUIStateSchema = S13.Struct({
3165
3436
  // Loop info
3166
- task: S12.String,
3167
- iteration: S12.Number,
3168
- maxIterations: S12.Number,
3437
+ task: S13.String,
3438
+ iteration: S13.Number,
3439
+ maxIterations: S13.Number,
3169
3440
  status: LoopStatusSchema,
3170
- startTime: S12.Number,
3441
+ startTime: S13.Number,
3171
3442
  // Discovery phase
3172
- discoveryInProgress: S12.Boolean,
3173
- discoveryCompleted: S12.Boolean,
3443
+ discoveryInProgress: S13.Boolean,
3444
+ discoveryCompleted: S13.Boolean,
3174
3445
  // Current activity
3175
3446
  executionMode: ExecutionModeSchema,
3176
- currentTool: S12.optional(S12.String),
3177
- currentTaskId: S12.optional(S12.String),
3447
+ currentTool: S13.optional(S13.String),
3448
+ currentTaskId: S13.optional(S13.String),
3178
3449
  // Output
3179
- outputLines: S12.Array(S12.String),
3180
- partialLine: S12.String,
3450
+ outputLines: S13.Array(S13.String),
3451
+ partialLine: S13.String,
3181
3452
  // Tasks
3182
- tasks: S12.Array(TUITaskSchema),
3453
+ tasks: S13.Array(TUITaskSchema),
3183
3454
  // Navigation
3184
3455
  viewMode: ViewModeSchema,
3185
- selectedTaskIndex: S12.Number,
3186
- scrollOffset: S12.Number,
3187
- userScrolled: S12.Boolean,
3456
+ selectedTaskIndex: S13.Number,
3457
+ scrollOffset: S13.Number,
3458
+ userScrolled: S13.Boolean,
3188
3459
  // Git
3189
- gitBranch: S12.optional(S12.String),
3190
- gitPushed: S12.Boolean,
3191
- prUrl: S12.optional(S12.String)
3460
+ gitBranch: S13.optional(S13.String),
3461
+ gitPushed: S13.Boolean,
3462
+ prUrl: S13.optional(S13.String)
3192
3463
  });
3193
3464
 
3465
+ // src/services/guardrails-store.ts
3466
+ init_esm_shims();
3467
+ import { Context as Context2 } from "effect";
3468
+ var GuardrailsStore = class extends Context2.Tag("@ferix/GuardrailsStore")() {
3469
+ };
3470
+
3471
+ // src/layers/guardrails/file-system.ts
3472
+ var PLANS_DIR = ".ferix/plans";
3473
+ function ensureDir(dirPath) {
3474
+ return Effect6.tryPromise({
3475
+ try: () => mkdir2(dirPath, { recursive: true }),
3476
+ catch: (error) => new GuardrailsStoreError({
3477
+ message: `Failed to create directory: ${dirPath}`,
3478
+ operation: "add",
3479
+ cause: error
3480
+ })
3481
+ }).pipe(Effect6.asVoid);
3482
+ }
3483
+ function getSessionDir(sessionId) {
3484
+ return join2(process.cwd(), PLANS_DIR, sessionId);
3485
+ }
3486
+ function getGuardrailsPath(sessionId) {
3487
+ return join2(getSessionDir(sessionId), "guardrails.json");
3488
+ }
3489
+ function serializeGuardrails(guardrails) {
3490
+ return JSON.stringify(guardrails, null, 2);
3491
+ }
3492
+ function deserializeGuardrails(json) {
3493
+ return Effect6.gen(function* () {
3494
+ const parsed = yield* Effect6.try({
3495
+ try: () => JSON.parse(json),
3496
+ catch: (error) => new GuardrailsStoreError({
3497
+ message: `Invalid JSON in guardrails file: ${String(error)}`,
3498
+ operation: "load",
3499
+ cause: error
3500
+ })
3501
+ });
3502
+ const validated = yield* decodeGuardrailsFile(parsed).pipe(
3503
+ Effect6.mapError(
3504
+ (error) => new GuardrailsStoreError({
3505
+ message: `Guardrails validation failed: ${String(error)}`,
3506
+ operation: "load",
3507
+ cause: error
3508
+ })
3509
+ )
3510
+ );
3511
+ return validated;
3512
+ });
3513
+ }
3514
+ function createEmptyGuardrails(sessionId, createdAt) {
3515
+ return {
3516
+ sessionId,
3517
+ createdAt,
3518
+ guardrails: []
3519
+ };
3520
+ }
3521
+ var make2 = {
3522
+ add: (sessionId, guardrail) => Effect6.gen(function* () {
3523
+ const sessionDir = getSessionDir(sessionId);
3524
+ yield* ensureDir(sessionDir);
3525
+ const guardrailsPath = getGuardrailsPath(sessionId);
3526
+ const existing = yield* Effect6.tryPromise({
3527
+ try: async () => {
3528
+ try {
3529
+ const content = await readFile(guardrailsPath, "utf-8");
3530
+ return content;
3531
+ } catch {
3532
+ return null;
3533
+ }
3534
+ },
3535
+ catch: (error) => new GuardrailsStoreError({
3536
+ message: `Failed to read guardrails file: ${guardrailsPath}`,
3537
+ operation: "add",
3538
+ cause: error
3539
+ })
3540
+ });
3541
+ let guardrails;
3542
+ if (existing) {
3543
+ guardrails = yield* deserializeGuardrails(existing).pipe(
3544
+ Effect6.mapError(
3545
+ (err) => new GuardrailsStoreError({
3546
+ message: err.message,
3547
+ operation: "add",
3548
+ cause: err
3549
+ })
3550
+ )
3551
+ );
3552
+ } else {
3553
+ const now = yield* DateTime.now;
3554
+ guardrails = createEmptyGuardrails(sessionId, DateTime.formatIso(now));
3555
+ }
3556
+ const updatedGuardrails = {
3557
+ ...guardrails,
3558
+ guardrails: [...guardrails.guardrails, guardrail]
3559
+ };
3560
+ yield* Effect6.tryPromise({
3561
+ try: () => writeFile(
3562
+ guardrailsPath,
3563
+ serializeGuardrails(updatedGuardrails),
3564
+ "utf-8"
3565
+ ),
3566
+ catch: (error) => new GuardrailsStoreError({
3567
+ message: `Failed to write guardrails file: ${guardrailsPath}`,
3568
+ operation: "add",
3569
+ cause: error
3570
+ })
3571
+ });
3572
+ }),
3573
+ load: (sessionId) => Effect6.gen(function* () {
3574
+ const guardrailsPath = getGuardrailsPath(sessionId);
3575
+ const content = yield* Effect6.tryPromise({
3576
+ try: async () => {
3577
+ try {
3578
+ return await readFile(guardrailsPath, "utf-8");
3579
+ } catch {
3580
+ return null;
3581
+ }
3582
+ },
3583
+ catch: (error) => new GuardrailsStoreError({
3584
+ message: `Failed to read guardrails file: ${guardrailsPath}`,
3585
+ operation: "load",
3586
+ cause: error
3587
+ })
3588
+ });
3589
+ if (content === null) {
3590
+ const now = yield* DateTime.now;
3591
+ return createEmptyGuardrails(sessionId, DateTime.formatIso(now));
3592
+ }
3593
+ return yield* deserializeGuardrails(content);
3594
+ }),
3595
+ getActive: (sessionId) => Effect6.gen(function* () {
3596
+ const guardrails = yield* make2.load(sessionId);
3597
+ return guardrails.guardrails;
3598
+ })
3599
+ };
3600
+ var Live3 = Layer3.succeed(GuardrailsStore, make2);
3601
+ var FileSystemGuardrails = {
3602
+ Live: Live3
3603
+ };
3604
+
3605
+ // src/layers/guardrails/memory.ts
3606
+ init_esm_shims();
3607
+ import { DateTime as DateTime2, Effect as Effect7, Layer as Layer4, Ref as Ref4 } from "effect";
3608
+ function createMemoryGuardrailsStore(stateRef) {
3609
+ return {
3610
+ add: (sessionId, guardrail) => Effect7.gen(function* () {
3611
+ const state = yield* Ref4.get(stateRef);
3612
+ let guardrails = state.get(sessionId);
3613
+ if (!guardrails) {
3614
+ const now = yield* DateTime2.now;
3615
+ guardrails = {
3616
+ sessionId,
3617
+ createdAt: DateTime2.formatIso(now),
3618
+ guardrails: []
3619
+ };
3620
+ }
3621
+ const updatedGuardrails = {
3622
+ ...guardrails,
3623
+ guardrails: [...guardrails.guardrails, guardrail]
3624
+ };
3625
+ state.set(sessionId, updatedGuardrails);
3626
+ yield* Ref4.set(stateRef, state);
3627
+ }),
3628
+ load: (sessionId) => Effect7.gen(function* () {
3629
+ const state = yield* Ref4.get(stateRef);
3630
+ const guardrails = state.get(sessionId);
3631
+ if (!guardrails) {
3632
+ const now = yield* DateTime2.now;
3633
+ return {
3634
+ sessionId,
3635
+ createdAt: DateTime2.formatIso(now),
3636
+ guardrails: []
3637
+ };
3638
+ }
3639
+ return guardrails;
3640
+ }),
3641
+ getActive: (sessionId) => Effect7.gen(function* () {
3642
+ const state = yield* Ref4.get(stateRef);
3643
+ const guardrails = state.get(sessionId);
3644
+ if (!guardrails) {
3645
+ return [];
3646
+ }
3647
+ return guardrails.guardrails;
3648
+ })
3649
+ };
3650
+ }
3651
+ function layer2() {
3652
+ return Layer4.effect(
3653
+ GuardrailsStore,
3654
+ Effect7.gen(function* () {
3655
+ const stateRef = yield* Ref4.make(/* @__PURE__ */ new Map());
3656
+ return createMemoryGuardrailsStore(stateRef);
3657
+ })
3658
+ );
3659
+ }
3660
+ var Live4 = layer2();
3661
+ var MemoryGuardrails = {
3662
+ Live: Live4,
3663
+ layer: layer2
3664
+ };
3665
+
3666
+ // src/layers/llm/claude-cli.ts
3667
+ init_esm_shims();
3668
+ import { spawn } from "child_process";
3669
+ import { Effect as Effect9, Layer as Layer5, Stream as Stream5 } from "effect";
3670
+
3671
+ // src/services/llm.ts
3672
+ init_esm_shims();
3673
+ import { Context as Context3 } from "effect";
3674
+ var LLM = class extends Context3.Tag("@ferix/LLM")() {
3675
+ };
3676
+
3677
+ // src/layers/llm/stream.ts
3678
+ init_esm_shims();
3679
+ import { createInterface } from "readline";
3680
+ import { Effect as Effect8, Stream as Stream4 } from "effect";
3681
+
3682
+ // src/layers/llm/parsers.ts
3683
+ init_esm_shims();
3684
+ function parseJsonLine(line) {
3685
+ if (!line.startsWith("{")) {
3686
+ return null;
3687
+ }
3688
+ try {
3689
+ return JSON.parse(line);
3690
+ } catch {
3691
+ return null;
3692
+ }
3693
+ }
3694
+ function isTextContent(json) {
3695
+ return typeof json === "object" && json !== null && "type" in json && typeof json.type === "string";
3696
+ }
3697
+ function isToolUse(json) {
3698
+ return typeof json === "object" && json !== null && "type" in json && "content_block" in json;
3699
+ }
3700
+ function extractText(json) {
3701
+ if (!isTextContent(json)) {
3702
+ return null;
3703
+ }
3704
+ if (json.type === "content_block_delta") {
3705
+ const delta = json;
3706
+ if (delta.delta?.type === "text_delta" && delta.delta.text) {
3707
+ return delta.delta.text;
3708
+ }
3709
+ }
3710
+ if (json.type === "assistant" && json.message?.content) {
3711
+ for (const block of json.message.content) {
3712
+ if (block.type === "text" && block.text) {
3713
+ return block.text;
3714
+ }
3715
+ }
3716
+ }
3717
+ return null;
3718
+ }
3719
+ function isToolInputDelta(json) {
3720
+ return typeof json === "object" && json !== null && "type" in json && json.type === "content_block_delta" && "delta" in json;
3721
+ }
3722
+ function extractToolInfo(json) {
3723
+ if (typeof json === "object" && json !== null && "type" in json && json.type === "content_block_stop") {
3724
+ return {
3725
+ type: "end",
3726
+ name: "unknown"
3727
+ };
3728
+ }
3729
+ if (!isToolUse(json)) {
3730
+ if (isToolInputDelta(json) && json.delta?.type === "input_json_delta" && json.delta.partial_json) {
3731
+ return {
3732
+ type: "input_delta",
3733
+ name: "",
3734
+ partialJson: json.delta.partial_json
3735
+ };
3736
+ }
3737
+ return null;
3738
+ }
3739
+ if (json.type === "content_block_start" && json.content_block?.type === "tool_use") {
3740
+ return {
3741
+ type: "start",
3742
+ name: json.content_block.name || "unknown"
3743
+ };
3744
+ }
3745
+ return null;
3746
+ }
3747
+ function safeParseJson(jsonStr) {
3748
+ try {
3749
+ return JSON.parse(jsonStr);
3750
+ } catch {
3751
+ return null;
3752
+ }
3753
+ }
3754
+ function unwrapStreamEvent(json) {
3755
+ if (typeof json === "object" && json !== null && "type" in json && json.type === "stream_event" && "event" in json) {
3756
+ return json.event;
3757
+ }
3758
+ return json;
3759
+ }
3760
+
3761
+ // src/layers/llm/stream.ts
3762
+ function handleToolEvent(toolInfo, toolState, emit) {
3763
+ if (toolInfo.type === "start") {
3764
+ toolState.currentTool = toolInfo.name;
3765
+ toolState.inputChunks.length = 0;
3766
+ emit.single({ _tag: "ToolStart", tool: toolInfo.name });
3767
+ return;
3768
+ }
3769
+ if (toolInfo.type === "input_delta" && toolInfo.partialJson) {
3770
+ toolState.inputChunks.push(toolInfo.partialJson);
3771
+ return;
3772
+ }
3773
+ if (toolInfo.type === "end" && toolState.currentTool) {
3774
+ const inputJson = toolState.inputChunks.join("");
3775
+ const input = safeParseJson(inputJson);
3776
+ if (input !== null) {
3777
+ emit.single({ _tag: "ToolUse", tool: toolState.currentTool, input });
3778
+ }
3779
+ emit.single({ _tag: "ToolEnd", tool: toolState.currentTool });
3780
+ toolState.currentTool = "";
3781
+ toolState.inputChunks.length = 0;
3782
+ }
3783
+ }
3784
+ function processJsonLine(json, outputChunks, toolState, emit) {
3785
+ const event = unwrapStreamEvent(json);
3786
+ const text = extractText(event);
3787
+ if (text) {
3788
+ outputChunks.push(text);
3789
+ emit.single({ _tag: "Text", text });
3790
+ return;
3791
+ }
3792
+ const toolInfo = extractToolInfo(event);
3793
+ if (toolInfo) {
3794
+ handleToolEvent(toolInfo, toolState, emit);
3795
+ }
3796
+ }
3797
+ function createEventStream(child) {
3798
+ return Stream4.async((emit) => {
3799
+ const outputChunks = [];
3800
+ const toolState = { currentTool: "", inputChunks: [] };
3801
+ const stdout = child.stdout;
3802
+ if (!stdout) {
3803
+ emit.fail(
3804
+ new LLMError({ message: "Failed to get stdout from child process" })
3805
+ );
3806
+ return Effect8.void;
3807
+ }
3808
+ const rl = createInterface({
3809
+ input: stdout,
3810
+ crlfDelay: Number.POSITIVE_INFINITY
3811
+ });
3812
+ rl.on("line", (line) => {
3813
+ const json = parseJsonLine(line);
3814
+ if (json) {
3815
+ processJsonLine(json, outputChunks, toolState, emit);
3816
+ }
3817
+ });
3818
+ child.stderr?.on("data", (data) => {
3819
+ const text = data.toString().trim();
3820
+ if (text) {
3821
+ emit.single({ _tag: "Text", text: `[stderr] ${text}` });
3822
+ }
3823
+ });
3824
+ child.on("close", (exitCode) => {
3825
+ if (exitCode !== 0) {
3826
+ emit.fail(
3827
+ new LLMError({
3828
+ message: `Claude CLI exited with code ${exitCode}`
3829
+ })
3830
+ );
3831
+ } else {
3832
+ const fullOutput = outputChunks.join("");
3833
+ emit.single({ _tag: "Done", output: fullOutput });
3834
+ emit.end();
3835
+ }
3836
+ });
3837
+ child.on("error", (error) => {
3838
+ emit.fail(
3839
+ new LLMError({
3840
+ message: error.message,
3841
+ cause: error
3842
+ })
3843
+ );
3844
+ });
3845
+ return Effect8.sync(() => {
3846
+ child.kill("SIGTERM");
3847
+ });
3848
+ });
3849
+ }
3850
+
3851
+ // src/layers/llm/claude-cli.ts
3852
+ var make3 = {
3853
+ execute: (prompt, options) => {
3854
+ return Stream5.unwrap(
3855
+ Effect9.sync(() => {
3856
+ const child = spawn(
3857
+ "claude",
3858
+ [
3859
+ "--permission-mode",
3860
+ "acceptEdits",
3861
+ "--output-format",
3862
+ "stream-json",
3863
+ "--verbose",
3864
+ "--include-partial-messages",
3865
+ "-p",
3866
+ prompt
3867
+ ],
3868
+ {
3869
+ stdio: ["inherit", "pipe", "pipe"],
3870
+ cwd: options?.cwd,
3871
+ env: {
3872
+ ...process.env,
3873
+ FORCE_COLOR: "1"
3874
+ }
3875
+ }
3876
+ );
3877
+ return createEventStream(child);
3878
+ })
3879
+ );
3880
+ }
3881
+ };
3882
+ var Live5 = Layer5.succeed(LLM, make3);
3883
+ var ClaudeCLI = {
3884
+ Live: Live5
3885
+ };
3886
+
3887
+ // src/layers/llm/mock.ts
3888
+ init_esm_shims();
3889
+ import { Effect as Effect10, Layer as Layer6, Schema as S14, Stream as Stream6 } from "effect";
3890
+ var MockLLMConfigSchema = S14.Struct({
3891
+ events: S14.Array(LLMEventSchema),
3892
+ delayMs: S14.optional(S14.Number)
3893
+ });
3894
+ function createMockLLM(config) {
3895
+ return {
3896
+ execute: (_prompt, _options) => {
3897
+ const baseStream = Stream6.fromIterable(config.events);
3898
+ if (config.delayMs !== void 0 && config.delayMs > 0) {
3899
+ const delay = config.delayMs;
3900
+ return baseStream.pipe(Stream6.tap(() => Effect10.sleep(delay)));
3901
+ }
3902
+ return baseStream;
3903
+ }
3904
+ };
3905
+ }
3906
+ var defaultMockEvents = [
3907
+ { _tag: "Text", text: "Processing task...\n" },
3908
+ { _tag: "ToolStart", tool: "Read" },
3909
+ { _tag: "ToolEnd", tool: "Read" },
3910
+ { _tag: "Text", text: "Task completed successfully.\n" },
3911
+ {
3912
+ _tag: "Done",
3913
+ output: "Processing task...\nTask completed successfully.\n"
3914
+ }
3915
+ ];
3916
+ var defaultMock = createMockLLM({ events: defaultMockEvents });
3917
+ var Live6 = Layer6.succeed(LLM, defaultMock);
3918
+ function layer3(config) {
3919
+ return Layer6.succeed(LLM, createMockLLM(config));
3920
+ }
3921
+ var Mock = {
3922
+ Live: Live6,
3923
+ layer: layer3,
3924
+ createMockLLM
3925
+ };
3926
+
3927
+ // src/layers/plan/file-system.ts
3928
+ init_esm_shims();
3929
+ import { access as access2, mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3930
+ import { join as join3 } from "path";
3931
+ import { Effect as Effect11, Layer as Layer7 } from "effect";
3932
+
3194
3933
  // src/services/plan-store.ts
3195
3934
  init_esm_shims();
3196
- import { Context as Context2 } from "effect";
3197
- var PlanStore = class extends Context2.Tag("@ferix/PlanStore")() {
3935
+ import { Context as Context4 } from "effect";
3936
+ var PlanStore = class extends Context4.Tag("@ferix/PlanStore")() {
3198
3937
  };
3199
3938
 
3200
3939
  // 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 }),
3940
+ var PLANS_DIR2 = ".ferix/plans";
3941
+ function ensureDir2(dirPath) {
3942
+ return Effect11.tryPromise({
3943
+ try: () => mkdir3(dirPath, { recursive: true }),
3205
3944
  catch: (error) => new PlanStoreError({
3206
3945
  message: `Failed to create directory: ${dirPath}`,
3207
3946
  operation: "create",
3208
3947
  cause: error
3209
3948
  })
3210
- }).pipe(Effect7.asVoid);
3949
+ }).pipe(Effect11.asVoid);
3211
3950
  }
3212
- function getSessionDir(sessionId) {
3213
- return join(process.cwd(), PLANS_DIR, sessionId);
3951
+ function getSessionDir2(sessionId) {
3952
+ return join3(process.cwd(), PLANS_DIR2, sessionId);
3214
3953
  }
3215
3954
  function getPlanPath(sessionId, planId) {
3216
- return join(getSessionDir(sessionId), `${planId}.json`);
3955
+ return join3(getSessionDir2(sessionId), `${planId}.json`);
3217
3956
  }
3218
3957
  function generatePlanId(taskNumber) {
3219
3958
  return PlanId(`task-${taskNumber}`);
@@ -3222,8 +3961,8 @@ function serializePlan(plan) {
3222
3961
  return JSON.stringify(plan, null, 2);
3223
3962
  }
3224
3963
  function deserializePlan(json, planId) {
3225
- return Effect7.gen(function* () {
3226
- const parsed = yield* Effect7.try({
3964
+ return Effect11.gen(function* () {
3965
+ const parsed = yield* Effect11.try({
3227
3966
  try: () => JSON.parse(json),
3228
3967
  catch: (error) => new PlanStoreError({
3229
3968
  message: `Invalid JSON in plan file: ${String(error)}`,
@@ -3232,7 +3971,7 @@ function deserializePlan(json, planId) {
3232
3971
  })
3233
3972
  });
3234
3973
  const validated = yield* decodePlanData(parsed).pipe(
3235
- Effect7.mapError(
3974
+ Effect11.mapError(
3236
3975
  (error) => new PlanStoreError({
3237
3976
  message: `Plan validation failed: ${String(error)}`,
3238
3977
  operation: "load",
@@ -3246,11 +3985,11 @@ function deserializePlan(json, planId) {
3246
3985
  };
3247
3986
  });
3248
3987
  }
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({
3988
+ var make4 = {
3989
+ create: (sessionId, plan) => Effect11.gen(function* () {
3990
+ const sessionDir = getSessionDir2(sessionId);
3991
+ yield* ensureDir2(sessionDir);
3992
+ const existingPlans = yield* Effect11.tryPromise({
3254
3993
  try: async () => {
3255
3994
  try {
3256
3995
  const files = await readdir(sessionDir);
@@ -3267,8 +4006,8 @@ var make2 = {
3267
4006
  const planId = generatePlanId(existingPlans + 1);
3268
4007
  const planPath = getPlanPath(sessionId, planId);
3269
4008
  const fullPlan = { ...plan, id: planId };
3270
- yield* Effect7.tryPromise({
3271
- try: () => writeFile(planPath, serializePlan(fullPlan), "utf-8"),
4009
+ yield* Effect11.tryPromise({
4010
+ try: () => writeFile2(planPath, serializePlan(fullPlan), "utf-8"),
3272
4011
  catch: (error) => new PlanStoreError({
3273
4012
  message: `Failed to write plan file: ${planPath}`,
3274
4013
  operation: "create",
@@ -3277,11 +4016,11 @@ var make2 = {
3277
4016
  });
3278
4017
  return planId;
3279
4018
  }),
3280
- load: (planId, sessionId) => Effect7.gen(function* () {
4019
+ load: (planId, sessionId) => Effect11.gen(function* () {
3281
4020
  if (sessionId) {
3282
4021
  const planPath = getPlanPath(sessionId, planId);
3283
- const content = yield* Effect7.tryPromise({
3284
- try: () => readFile(planPath, "utf-8"),
4022
+ const content = yield* Effect11.tryPromise({
4023
+ try: () => readFile2(planPath, "utf-8"),
3285
4024
  catch: (error) => new PlanStoreError({
3286
4025
  message: `Failed to read plan file: ${planPath}`,
3287
4026
  operation: "load",
@@ -3290,9 +4029,9 @@ var make2 = {
3290
4029
  });
3291
4030
  return yield* deserializePlan(content, planId);
3292
4031
  }
3293
- const sessionDirs = yield* Effect7.tryPromise({
4032
+ const sessionDirs = yield* Effect11.tryPromise({
3294
4033
  try: async () => {
3295
- const plansDir = join(process.cwd(), PLANS_DIR);
4034
+ const plansDir = join3(process.cwd(), PLANS_DIR2);
3296
4035
  const dirs = await readdir(plansDir);
3297
4036
  return dirs;
3298
4037
  },
@@ -3304,19 +4043,19 @@ var make2 = {
3304
4043
  });
3305
4044
  for (const sid of sessionDirs) {
3306
4045
  const planPath = getPlanPath(sid, planId);
3307
- const exists = yield* Effect7.tryPromise({
4046
+ const exists = yield* Effect11.tryPromise({
3308
4047
  try: async () => {
3309
- await access(planPath);
4048
+ await access2(planPath);
3310
4049
  return true;
3311
4050
  },
3312
4051
  catch: () => new PlanStoreError({
3313
4052
  message: "File not found",
3314
4053
  operation: "load"
3315
4054
  })
3316
- }).pipe(Effect7.orElseSucceed(() => false));
4055
+ }).pipe(Effect11.orElseSucceed(() => false));
3317
4056
  if (exists) {
3318
- const content = yield* Effect7.tryPromise({
3319
- try: () => readFile(planPath, "utf-8"),
4057
+ const content = yield* Effect11.tryPromise({
4058
+ try: () => readFile2(planPath, "utf-8"),
3320
4059
  catch: (error) => new PlanStoreError({
3321
4060
  message: `Failed to read plan file: ${planPath}`,
3322
4061
  operation: "load",
@@ -3326,17 +4065,17 @@ var make2 = {
3326
4065
  return yield* deserializePlan(content, planId);
3327
4066
  }
3328
4067
  }
3329
- return yield* Effect7.fail(
4068
+ return yield* Effect11.fail(
3330
4069
  new PlanStoreError({
3331
4070
  message: `Plan not found: ${planId}`,
3332
4071
  operation: "load"
3333
4072
  })
3334
4073
  );
3335
4074
  }),
3336
- update: (planId, plan) => Effect7.gen(function* () {
4075
+ update: (planId, plan) => Effect11.gen(function* () {
3337
4076
  const planPath = getPlanPath(plan.sessionId, planId);
3338
- yield* Effect7.tryPromise({
3339
- try: () => writeFile(planPath, serializePlan(plan), "utf-8"),
4077
+ yield* Effect11.tryPromise({
4078
+ try: () => writeFile2(planPath, serializePlan(plan), "utf-8"),
3340
4079
  catch: (error) => new PlanStoreError({
3341
4080
  message: `Failed to update plan file: ${planPath}`,
3342
4081
  operation: "update",
@@ -3344,9 +4083,9 @@ var make2 = {
3344
4083
  })
3345
4084
  });
3346
4085
  }),
3347
- list: (sessionId) => Effect7.gen(function* () {
3348
- const sessionDir = getSessionDir(sessionId);
3349
- const files = yield* Effect7.tryPromise({
4086
+ list: (sessionId) => Effect11.gen(function* () {
4087
+ const sessionDir = getSessionDir2(sessionId);
4088
+ const files = yield* Effect11.tryPromise({
3350
4089
  try: async () => {
3351
4090
  try {
3352
4091
  return await readdir(sessionDir);
@@ -3363,18 +4102,18 @@ var make2 = {
3363
4102
  return files.filter((f) => f.endsWith(".json")).map((f) => PlanId(f.replace(".json", "")));
3364
4103
  })
3365
4104
  };
3366
- var Live3 = Layer3.succeed(PlanStore, make2);
4105
+ var Live7 = Layer7.succeed(PlanStore, make4);
3367
4106
  var FileSystemPlan = {
3368
- Live: Live3
4107
+ Live: Live7
3369
4108
  };
3370
4109
 
3371
4110
  // src/layers/plan/memory.ts
3372
4111
  init_esm_shims();
3373
- import { Effect as Effect8, Layer as Layer4, Ref as Ref3 } from "effect";
4112
+ import { Effect as Effect12, Layer as Layer8, Ref as Ref5 } from "effect";
3374
4113
  function createMemoryPlanStore(stateRef) {
3375
4114
  return {
3376
- create: (sessionId, plan) => Effect8.gen(function* () {
3377
- const state = yield* Ref3.get(stateRef);
4115
+ create: (sessionId, plan) => Effect12.gen(function* () {
4116
+ const state = yield* Ref5.get(stateRef);
3378
4117
  if (!state.has(sessionId)) {
3379
4118
  state.set(sessionId, /* @__PURE__ */ new Map());
3380
4119
  }
@@ -3385,18 +4124,18 @@ function createMemoryPlanStore(stateRef) {
3385
4124
  const planId = PlanId(`task-${sessionPlans.size + 1}`);
3386
4125
  const fullPlan = { ...plan, id: planId };
3387
4126
  sessionPlans.set(planId, fullPlan);
3388
- yield* Ref3.set(stateRef, state);
4127
+ yield* Ref5.set(stateRef, state);
3389
4128
  return planId;
3390
4129
  }),
3391
- load: (planId, sessionId) => Effect8.gen(function* () {
3392
- const state = yield* Ref3.get(stateRef);
4130
+ load: (planId, sessionId) => Effect12.gen(function* () {
4131
+ const state = yield* Ref5.get(stateRef);
3393
4132
  if (sessionId) {
3394
4133
  const sessionPlans = state.get(sessionId);
3395
4134
  const plan = sessionPlans?.get(planId);
3396
4135
  if (plan) {
3397
4136
  return plan;
3398
4137
  }
3399
- return yield* Effect8.fail(
4138
+ return yield* Effect12.fail(
3400
4139
  new PlanStoreError({
3401
4140
  message: `Plan not found: ${planId}`,
3402
4141
  operation: "load"
@@ -3409,18 +4148,18 @@ function createMemoryPlanStore(stateRef) {
3409
4148
  return plan;
3410
4149
  }
3411
4150
  }
3412
- return yield* Effect8.fail(
4151
+ return yield* Effect12.fail(
3413
4152
  new PlanStoreError({
3414
4153
  message: `Plan not found: ${planId}`,
3415
4154
  operation: "load"
3416
4155
  })
3417
4156
  );
3418
4157
  }),
3419
- update: (planId, plan) => Effect8.gen(function* () {
3420
- const state = yield* Ref3.get(stateRef);
4158
+ update: (planId, plan) => Effect12.gen(function* () {
4159
+ const state = yield* Ref5.get(stateRef);
3421
4160
  const sessionPlans = state.get(plan.sessionId);
3422
4161
  if (!sessionPlans) {
3423
- return yield* Effect8.fail(
4162
+ return yield* Effect12.fail(
3424
4163
  new PlanStoreError({
3425
4164
  message: `Session not found: ${plan.sessionId}`,
3426
4165
  operation: "update"
@@ -3428,10 +4167,10 @@ function createMemoryPlanStore(stateRef) {
3428
4167
  );
3429
4168
  }
3430
4169
  sessionPlans.set(planId, plan);
3431
- yield* Ref3.set(stateRef, state);
4170
+ yield* Ref5.set(stateRef, state);
3432
4171
  }),
3433
- list: (sessionId) => Effect8.gen(function* () {
3434
- const state = yield* Ref3.get(stateRef);
4172
+ list: (sessionId) => Effect12.gen(function* () {
4173
+ const state = yield* Ref5.get(stateRef);
3435
4174
  const sessionPlans = state.get(sessionId);
3436
4175
  if (!sessionPlans) {
3437
4176
  return [];
@@ -3440,32 +4179,236 @@ function createMemoryPlanStore(stateRef) {
3440
4179
  })
3441
4180
  };
3442
4181
  }
3443
- function layer2() {
3444
- return Layer4.effect(
4182
+ function layer4() {
4183
+ return Layer8.effect(
3445
4184
  PlanStore,
3446
- Effect8.gen(function* () {
3447
- const stateRef = yield* Ref3.make(/* @__PURE__ */ new Map());
4185
+ Effect12.gen(function* () {
4186
+ const stateRef = yield* Ref5.make(/* @__PURE__ */ new Map());
3448
4187
  return createMemoryPlanStore(stateRef);
3449
4188
  })
3450
4189
  );
3451
4190
  }
3452
- var Live4 = layer2();
4191
+ var Live8 = layer4();
3453
4192
  var MemoryPlan = {
3454
- Live: Live4,
3455
- layer: layer2
4193
+ Live: Live8,
4194
+ layer: layer4
4195
+ };
4196
+
4197
+ // src/layers/progress/file-system.ts
4198
+ init_esm_shims();
4199
+ import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
4200
+ import { join as join4 } from "path";
4201
+ import { DateTime as DateTime3, Effect as Effect13, Layer as Layer9 } from "effect";
4202
+
4203
+ // src/services/progress-store.ts
4204
+ init_esm_shims();
4205
+ import { Context as Context5 } from "effect";
4206
+ var ProgressStore = class extends Context5.Tag("@ferix/ProgressStore")() {
4207
+ };
4208
+
4209
+ // src/layers/progress/file-system.ts
4210
+ var PLANS_DIR3 = ".ferix/plans";
4211
+ function ensureDir3(dirPath) {
4212
+ return Effect13.tryPromise({
4213
+ try: () => mkdir4(dirPath, { recursive: true }),
4214
+ catch: (error) => new ProgressStoreError({
4215
+ message: `Failed to create directory: ${dirPath}`,
4216
+ operation: "append",
4217
+ cause: error
4218
+ })
4219
+ }).pipe(Effect13.asVoid);
4220
+ }
4221
+ function getSessionDir3(sessionId) {
4222
+ return join4(process.cwd(), PLANS_DIR3, sessionId);
4223
+ }
4224
+ function getProgressPath(sessionId) {
4225
+ return join4(getSessionDir3(sessionId), "progress.json");
4226
+ }
4227
+ function serializeProgress(progress) {
4228
+ return JSON.stringify(progress, null, 2);
4229
+ }
4230
+ function deserializeProgress(json) {
4231
+ return Effect13.gen(function* () {
4232
+ const parsed = yield* Effect13.try({
4233
+ try: () => JSON.parse(json),
4234
+ catch: (error) => new ProgressStoreError({
4235
+ message: `Invalid JSON in progress file: ${String(error)}`,
4236
+ operation: "load",
4237
+ cause: error
4238
+ })
4239
+ });
4240
+ const validated = yield* decodeProgressFile(parsed).pipe(
4241
+ Effect13.mapError(
4242
+ (error) => new ProgressStoreError({
4243
+ message: `Progress validation failed: ${String(error)}`,
4244
+ operation: "load",
4245
+ cause: error
4246
+ })
4247
+ )
4248
+ );
4249
+ return validated;
4250
+ });
4251
+ }
4252
+ function createEmptyProgress(sessionId, createdAt) {
4253
+ return {
4254
+ sessionId,
4255
+ createdAt,
4256
+ entries: []
4257
+ };
4258
+ }
4259
+ var make5 = {
4260
+ append: (sessionId, entry) => Effect13.gen(function* () {
4261
+ const sessionDir = getSessionDir3(sessionId);
4262
+ yield* ensureDir3(sessionDir);
4263
+ const progressPath = getProgressPath(sessionId);
4264
+ const existing = yield* Effect13.tryPromise({
4265
+ try: async () => {
4266
+ try {
4267
+ const content = await readFile3(progressPath, "utf-8");
4268
+ return content;
4269
+ } catch {
4270
+ return null;
4271
+ }
4272
+ },
4273
+ catch: (error) => new ProgressStoreError({
4274
+ message: `Failed to read progress file: ${progressPath}`,
4275
+ operation: "append",
4276
+ cause: error
4277
+ })
4278
+ });
4279
+ let progress;
4280
+ if (existing) {
4281
+ progress = yield* deserializeProgress(existing).pipe(
4282
+ Effect13.mapError(
4283
+ (err) => new ProgressStoreError({
4284
+ message: err.message,
4285
+ operation: "append",
4286
+ cause: err
4287
+ })
4288
+ )
4289
+ );
4290
+ } else {
4291
+ const now = yield* DateTime3.now;
4292
+ progress = createEmptyProgress(sessionId, DateTime3.formatIso(now));
4293
+ }
4294
+ const updatedProgress = {
4295
+ ...progress,
4296
+ entries: [...progress.entries, entry]
4297
+ };
4298
+ yield* Effect13.tryPromise({
4299
+ try: () => writeFile3(progressPath, serializeProgress(updatedProgress), "utf-8"),
4300
+ catch: (error) => new ProgressStoreError({
4301
+ message: `Failed to write progress file: ${progressPath}`,
4302
+ operation: "append",
4303
+ cause: error
4304
+ })
4305
+ });
4306
+ }),
4307
+ load: (sessionId) => Effect13.gen(function* () {
4308
+ const progressPath = getProgressPath(sessionId);
4309
+ const content = yield* Effect13.tryPromise({
4310
+ try: async () => {
4311
+ try {
4312
+ return await readFile3(progressPath, "utf-8");
4313
+ } catch {
4314
+ return null;
4315
+ }
4316
+ },
4317
+ catch: (error) => new ProgressStoreError({
4318
+ message: `Failed to read progress file: ${progressPath}`,
4319
+ operation: "load",
4320
+ cause: error
4321
+ })
4322
+ });
4323
+ if (content === null) {
4324
+ const now = yield* DateTime3.now;
4325
+ return createEmptyProgress(sessionId, DateTime3.formatIso(now));
4326
+ }
4327
+ return yield* deserializeProgress(content);
4328
+ }),
4329
+ getRecent: (sessionId, count) => Effect13.gen(function* () {
4330
+ const progress = yield* make5.load(sessionId);
4331
+ const entries = progress.entries;
4332
+ return entries.slice(-count);
4333
+ })
4334
+ };
4335
+ var Live9 = Layer9.succeed(ProgressStore, make5);
4336
+ var FileSystemProgress = {
4337
+ Live: Live9
4338
+ };
4339
+
4340
+ // src/layers/progress/memory.ts
4341
+ init_esm_shims();
4342
+ import { DateTime as DateTime4, Effect as Effect14, Layer as Layer10, Ref as Ref6 } from "effect";
4343
+ function createMemoryProgressStore(stateRef) {
4344
+ return {
4345
+ append: (sessionId, entry) => Effect14.gen(function* () {
4346
+ const state = yield* Ref6.get(stateRef);
4347
+ let progress = state.get(sessionId);
4348
+ if (!progress) {
4349
+ const now = yield* DateTime4.now;
4350
+ progress = {
4351
+ sessionId,
4352
+ createdAt: DateTime4.formatIso(now),
4353
+ entries: []
4354
+ };
4355
+ }
4356
+ const updatedProgress = {
4357
+ ...progress,
4358
+ entries: [...progress.entries, entry]
4359
+ };
4360
+ state.set(sessionId, updatedProgress);
4361
+ yield* Ref6.set(stateRef, state);
4362
+ }),
4363
+ load: (sessionId) => Effect14.gen(function* () {
4364
+ const state = yield* Ref6.get(stateRef);
4365
+ const progress = state.get(sessionId);
4366
+ if (!progress) {
4367
+ const now = yield* DateTime4.now;
4368
+ return {
4369
+ sessionId,
4370
+ createdAt: DateTime4.formatIso(now),
4371
+ entries: []
4372
+ };
4373
+ }
4374
+ return progress;
4375
+ }),
4376
+ getRecent: (sessionId, count) => Effect14.gen(function* () {
4377
+ const state = yield* Ref6.get(stateRef);
4378
+ const progress = state.get(sessionId);
4379
+ if (!progress) {
4380
+ return [];
4381
+ }
4382
+ return progress.entries.slice(-count);
4383
+ })
4384
+ };
4385
+ }
4386
+ function layer5() {
4387
+ return Layer10.effect(
4388
+ ProgressStore,
4389
+ Effect14.gen(function* () {
4390
+ const stateRef = yield* Ref6.make(/* @__PURE__ */ new Map());
4391
+ return createMemoryProgressStore(stateRef);
4392
+ })
4393
+ );
4394
+ }
4395
+ var Live10 = layer5();
4396
+ var MemoryProgress = {
4397
+ Live: Live10,
4398
+ layer: layer5
3456
4399
  };
3457
4400
 
3458
4401
  // src/layers/session/file-system.ts
3459
4402
  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";
4403
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
4404
+ import { join as join5 } from "path";
4405
+ import { DateTime as DateTime5, Effect as Effect15, Layer as Layer11 } from "effect";
3463
4406
  import { humanId } from "human-id";
3464
4407
 
3465
4408
  // src/services/session-store.ts
3466
4409
  init_esm_shims();
3467
- import { Context as Context3 } from "effect";
3468
- var SessionStore = class extends Context3.Tag("@ferix/SessionStore")() {
4410
+ import { Context as Context6 } from "effect";
4411
+ var SessionStore = class extends Context6.Tag("@ferix/SessionStore")() {
3469
4412
  };
3470
4413
 
3471
4414
  // src/layers/session/file-system.ts
@@ -3474,25 +4417,25 @@ function generateSessionId(timestampMs) {
3474
4417
  const id = humanId({ separator: "-", capitalize: false });
3475
4418
  return `${id}-${timestampMs}`;
3476
4419
  }
3477
- function ensureDir2(dirPath) {
3478
- return Effect9.tryPromise({
3479
- try: () => mkdir2(dirPath, { recursive: true }),
4420
+ function ensureDir4(dirPath) {
4421
+ return Effect15.tryPromise({
4422
+ try: () => mkdir5(dirPath, { recursive: true }),
3480
4423
  catch: (error) => new SessionStoreError({
3481
4424
  message: `Failed to create directory: ${dirPath}`,
3482
4425
  operation: "create",
3483
4426
  cause: error
3484
4427
  })
3485
- }).pipe(Effect9.asVoid);
4428
+ }).pipe(Effect15.asVoid);
3486
4429
  }
3487
4430
  function getSessionPath(sessionId) {
3488
- return join2(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
4431
+ return join5(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
3489
4432
  }
3490
4433
  function serializeSession(session) {
3491
4434
  return JSON.stringify(session, null, 2);
3492
4435
  }
3493
4436
  function deserializeSession(json) {
3494
- return Effect9.gen(function* () {
3495
- const parsed = yield* Effect9.try({
4437
+ return Effect15.gen(function* () {
4438
+ const parsed = yield* Effect15.try({
3496
4439
  try: () => JSON.parse(json),
3497
4440
  catch: (error) => new SessionStoreError({
3498
4441
  message: `Invalid JSON in session file: ${String(error)}`,
@@ -3501,7 +4444,7 @@ function deserializeSession(json) {
3501
4444
  })
3502
4445
  });
3503
4446
  const validated = yield* decodeSession(parsed).pipe(
3504
- Effect9.mapError(
4447
+ Effect15.mapError(
3505
4448
  (error) => new SessionStoreError({
3506
4449
  message: `Session validation failed: ${String(error)}`,
3507
4450
  operation: "get",
@@ -3512,23 +4455,23 @@ function deserializeSession(json) {
3512
4455
  return validated;
3513
4456
  });
3514
4457
  }
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);
4458
+ var make6 = {
4459
+ create: (originalTask) => Effect15.gen(function* () {
4460
+ const sessionsDir = join5(process.cwd(), SESSIONS_DIR);
4461
+ yield* ensureDir4(sessionsDir);
4462
+ const now = yield* DateTime5.now;
4463
+ const timestampMs = DateTime5.toEpochMillis(now);
3521
4464
  const sessionId = generateSessionId(timestampMs);
3522
4465
  const session = {
3523
4466
  id: sessionId,
3524
- createdAt: DateTime.formatIso(now),
4467
+ createdAt: DateTime5.formatIso(now),
3525
4468
  status: "active",
3526
4469
  originalTask,
3527
4470
  completedTasks: []
3528
4471
  };
3529
4472
  const sessionPath = getSessionPath(sessionId);
3530
- yield* Effect9.tryPromise({
3531
- try: () => writeFile2(sessionPath, serializeSession(session), "utf-8"),
4473
+ yield* Effect15.tryPromise({
4474
+ try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
3532
4475
  catch: (error) => new SessionStoreError({
3533
4476
  message: `Failed to write session file: ${sessionPath}`,
3534
4477
  operation: "create",
@@ -3537,10 +4480,10 @@ var make3 = {
3537
4480
  });
3538
4481
  return session;
3539
4482
  }),
3540
- get: (sessionId) => Effect9.gen(function* () {
4483
+ get: (sessionId) => Effect15.gen(function* () {
3541
4484
  const sessionPath = getSessionPath(sessionId);
3542
- const content = yield* Effect9.tryPromise({
3543
- try: () => readFile2(sessionPath, "utf-8"),
4485
+ const content = yield* Effect15.tryPromise({
4486
+ try: () => readFile4(sessionPath, "utf-8"),
3544
4487
  catch: (error) => new SessionStoreError({
3545
4488
  message: `Failed to read session file: ${sessionPath}`,
3546
4489
  operation: "get",
@@ -3549,10 +4492,10 @@ var make3 = {
3549
4492
  });
3550
4493
  return yield* deserializeSession(content);
3551
4494
  }),
3552
- update: (sessionId, session) => Effect9.gen(function* () {
4495
+ update: (sessionId, session) => Effect15.gen(function* () {
3553
4496
  const sessionPath = getSessionPath(sessionId);
3554
- yield* Effect9.tryPromise({
3555
- try: () => writeFile2(sessionPath, serializeSession(session), "utf-8"),
4497
+ yield* Effect15.tryPromise({
4498
+ try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
3556
4499
  catch: (error) => new SessionStoreError({
3557
4500
  message: `Failed to update session file: ${sessionPath}`,
3558
4501
  operation: "update",
@@ -3561,37 +4504,37 @@ var make3 = {
3561
4504
  });
3562
4505
  })
3563
4506
  };
3564
- var Live5 = Layer5.succeed(SessionStore, make3);
4507
+ var Live11 = Layer11.succeed(SessionStore, make6);
3565
4508
  var FileSystemSession = {
3566
- Live: Live5
4509
+ Live: Live11
3567
4510
  };
3568
4511
 
3569
4512
  // src/layers/session/memory.ts
3570
4513
  init_esm_shims();
3571
- import { DateTime as DateTime2, Effect as Effect10, Layer as Layer6, Ref as Ref4 } from "effect";
4514
+ import { DateTime as DateTime6, Effect as Effect16, Layer as Layer12, Ref as Ref7 } from "effect";
3572
4515
  function createMemorySessionStore(stateRef, counterRef) {
3573
4516
  return {
3574
- create: (originalTask) => Effect10.gen(function* () {
3575
- const state = yield* Ref4.get(stateRef);
3576
- const counter = yield* Ref4.updateAndGet(counterRef, (n) => n + 1);
4517
+ create: (originalTask) => Effect16.gen(function* () {
4518
+ const state = yield* Ref7.get(stateRef);
4519
+ const counter = yield* Ref7.updateAndGet(counterRef, (n) => n + 1);
3577
4520
  const sessionId = `test-session-${counter}`;
3578
- const now = yield* DateTime2.now;
4521
+ const now = yield* DateTime6.now;
3579
4522
  const session = {
3580
4523
  id: sessionId,
3581
- createdAt: DateTime2.formatIso(now),
4524
+ createdAt: DateTime6.formatIso(now),
3582
4525
  status: "active",
3583
4526
  originalTask,
3584
4527
  completedTasks: []
3585
4528
  };
3586
4529
  state.set(sessionId, session);
3587
- yield* Ref4.set(stateRef, state);
4530
+ yield* Ref7.set(stateRef, state);
3588
4531
  return session;
3589
4532
  }),
3590
- get: (sessionId) => Effect10.gen(function* () {
3591
- const state = yield* Ref4.get(stateRef);
4533
+ get: (sessionId) => Effect16.gen(function* () {
4534
+ const state = yield* Ref7.get(stateRef);
3592
4535
  const session = state.get(sessionId);
3593
4536
  if (!session) {
3594
- return yield* Effect10.fail(
4537
+ return yield* Effect16.fail(
3595
4538
  new SessionStoreError({
3596
4539
  message: `Session not found: ${sessionId}`,
3597
4540
  operation: "get"
@@ -3600,10 +4543,10 @@ function createMemorySessionStore(stateRef, counterRef) {
3600
4543
  }
3601
4544
  return session;
3602
4545
  }),
3603
- update: (sessionId, session) => Effect10.gen(function* () {
3604
- const state = yield* Ref4.get(stateRef);
4546
+ update: (sessionId, session) => Effect16.gen(function* () {
4547
+ const state = yield* Ref7.get(stateRef);
3605
4548
  if (!state.has(sessionId)) {
3606
- return yield* Effect10.fail(
4549
+ return yield* Effect16.fail(
3607
4550
  new SessionStoreError({
3608
4551
  message: `Session not found: ${sessionId}`,
3609
4552
  operation: "update"
@@ -3611,34 +4554,34 @@ function createMemorySessionStore(stateRef, counterRef) {
3611
4554
  );
3612
4555
  }
3613
4556
  state.set(sessionId, session);
3614
- yield* Ref4.set(stateRef, state);
4557
+ yield* Ref7.set(stateRef, state);
3615
4558
  })
3616
4559
  };
3617
4560
  }
3618
- function layer3() {
3619
- return Layer6.effect(
4561
+ function layer6() {
4562
+ return Layer12.effect(
3620
4563
  SessionStore,
3621
- Effect10.gen(function* () {
3622
- const stateRef = yield* Ref4.make(/* @__PURE__ */ new Map());
3623
- const counterRef = yield* Ref4.make(0);
4564
+ Effect16.gen(function* () {
4565
+ const stateRef = yield* Ref7.make(/* @__PURE__ */ new Map());
4566
+ const counterRef = yield* Ref7.make(0);
3624
4567
  return createMemorySessionStore(stateRef, counterRef);
3625
4568
  })
3626
4569
  );
3627
4570
  }
3628
- var Live6 = layer3();
4571
+ var Live12 = layer6();
3629
4572
  var MemorySession = {
3630
- Live: Live6,
3631
- layer: layer3
4573
+ Live: Live12,
4574
+ layer: layer6
3632
4575
  };
3633
4576
 
3634
4577
  // src/layers/signal/ferix-parser.ts
3635
4578
  init_esm_shims();
3636
- import { Effect as Effect11, Layer as Layer7, Ref as Ref5 } from "effect";
4579
+ import { Effect as Effect17, Layer as Layer13, Ref as Ref8 } from "effect";
3637
4580
 
3638
4581
  // src/services/signal-parser.ts
3639
4582
  init_esm_shims();
3640
- import { Context as Context4 } from "effect";
3641
- var SignalParser = class extends Context4.Tag("@ferix/SignalParser")() {
4583
+ import { Context as Context7 } from "effect";
4584
+ var SignalParser = class extends Context7.Tag("@ferix/SignalParser")() {
3642
4585
  };
3643
4586
 
3644
4587
  // src/layers/signal/specs/index.ts
@@ -3646,7 +4589,7 @@ init_esm_shims();
3646
4589
 
3647
4590
  // src/layers/signal/specs/check.ts
3648
4591
  init_esm_shims();
3649
- import { Schema as S13 } from "effect";
4592
+ import { Schema as S15 } from "effect";
3650
4593
 
3651
4594
  // src/layers/signal/specs/registry.ts
3652
4595
  init_esm_shims();
@@ -3703,7 +4646,7 @@ var checkPassedSpec = {
3703
4646
  parse: (text) => {
3704
4647
  if (CHECK_PASSED.test(text)) {
3705
4648
  const raw = { _tag: "CheckPassed" };
3706
- const result = S13.decodeUnknownEither(CheckPassedSignalSchema)(raw);
4649
+ const result = S15.decodeUnknownEither(CheckPassedSignalSchema)(raw);
3707
4650
  if (result._tag === "Right") {
3708
4651
  return [result.right];
3709
4652
  }
@@ -3719,7 +4662,7 @@ var checkFailedSpec = {
3719
4662
  parse: (text) => {
3720
4663
  if (CHECK_FAILED.test(text)) {
3721
4664
  const raw = { _tag: "CheckFailed" };
3722
- const result = S13.decodeUnknownEither(CheckFailedSignalSchema)(raw);
4665
+ const result = S15.decodeUnknownEither(CheckFailedSignalSchema)(raw);
3723
4666
  if (result._tag === "Right") {
3724
4667
  return [result.right];
3725
4668
  }
@@ -3733,7 +4676,7 @@ signalSpecRegistry.register(checkFailedSpec);
3733
4676
 
3734
4677
  // src/layers/signal/specs/criteria.ts
3735
4678
  init_esm_shims();
3736
- import { Schema as S14 } from "effect";
4679
+ import { Schema as S16 } from "effect";
3737
4680
  var CRITERIA_BLOCK = /<ferix:criteria task="(\d+)">([\s\S]*?)<\/ferix:criteria>/g;
3738
4681
  var CRITERION = /<criterion id="([^"]+)">([^<]+)<\/criterion>/g;
3739
4682
  var CRITERION_PASSED = /<ferix:criterion-passed id="([\d.c]+)"\/>/g;
@@ -3764,7 +4707,7 @@ var criteriaDefinedSpec = {
3764
4707
  taskId: match[1],
3765
4708
  criteria
3766
4709
  };
3767
- const result = S14.decodeUnknownEither(CriteriaDefinedSignalSchema)(raw);
4710
+ const result = S16.decodeUnknownEither(CriteriaDefinedSignalSchema)(raw);
3768
4711
  if (result._tag === "Right") {
3769
4712
  signals.push(result.right);
3770
4713
  }
@@ -3783,7 +4726,7 @@ var criterionPassedSpec = {
3783
4726
  for (const m of text.matchAll(resetRegex(CRITERION_PASSED))) {
3784
4727
  if (m[1]) {
3785
4728
  const raw = { _tag: "CriterionPassed", criterionId: m[1] };
3786
- const result = S14.decodeUnknownEither(CriterionPassedSignalSchema)(raw);
4729
+ const result = S16.decodeUnknownEither(CriterionPassedSignalSchema)(raw);
3787
4730
  if (result._tag === "Right") {
3788
4731
  signals.push(result.right);
3789
4732
  }
@@ -3806,7 +4749,7 @@ var criterionFailedSpec = {
3806
4749
  criterionId: m[1],
3807
4750
  reason: m[2] || "Unknown reason"
3808
4751
  };
3809
- const result = S14.decodeUnknownEither(CriterionFailedSignalSchema)(raw);
4752
+ const result = S16.decodeUnknownEither(CriterionFailedSignalSchema)(raw);
3810
4753
  if (result._tag === "Right") {
3811
4754
  signals.push(result.right);
3812
4755
  }
@@ -3820,9 +4763,88 @@ signalSpecRegistry.register(criteriaDefinedSpec);
3820
4763
  signalSpecRegistry.register(criterionPassedSpec);
3821
4764
  signalSpecRegistry.register(criterionFailedSpec);
3822
4765
 
4766
+ // src/layers/signal/specs/guardrail.ts
4767
+ init_esm_shims();
4768
+ import { Schema as S17 } from "effect";
4769
+ var GUARDRAIL = /<ferix:guardrail\s+severity="(warning|critical)"\s*>([\s\S]*?)<\/ferix:guardrail>/g;
4770
+ var PATTERN = /<pattern>([\s\S]*?)<\/pattern>/;
4771
+ var SIGN = /<sign>([\s\S]*?)<\/sign>/;
4772
+ var AVOIDANCE = /<avoidance>([\s\S]*?)<\/avoidance>/;
4773
+ var guardrailSpec = {
4774
+ tag: "Guardrail",
4775
+ closingTag: "</ferix:guardrail>",
4776
+ schema: GuardrailSignalSchema,
4777
+ parse: (text) => {
4778
+ const signals = [];
4779
+ const matches = text.matchAll(GUARDRAIL);
4780
+ for (const match of matches) {
4781
+ const severity = match[1];
4782
+ const content = match[2] || "";
4783
+ const patternMatch = content.match(PATTERN);
4784
+ const signMatch = content.match(SIGN);
4785
+ const avoidanceMatch = content.match(AVOIDANCE);
4786
+ const pattern = patternMatch?.[1]?.trim() || "";
4787
+ const sign = signMatch?.[1]?.trim() || "";
4788
+ const avoidance = avoidanceMatch?.[1]?.trim() || "";
4789
+ if (!(pattern && sign && avoidance)) {
4790
+ continue;
4791
+ }
4792
+ const raw = {
4793
+ _tag: "Guardrail",
4794
+ pattern,
4795
+ sign,
4796
+ avoidance,
4797
+ severity
4798
+ };
4799
+ const result = S17.decodeUnknownEither(GuardrailSignalSchema)(raw);
4800
+ if (result._tag === "Right") {
4801
+ signals.push(result.right);
4802
+ }
4803
+ }
4804
+ return signals;
4805
+ },
4806
+ keyFields: (s) => s.pattern.slice(0, 50)
4807
+ };
4808
+ signalSpecRegistry.register(guardrailSpec);
4809
+
4810
+ // src/layers/signal/specs/learning.ts
4811
+ init_esm_shims();
4812
+ import { Schema as S18 } from "effect";
4813
+ var LEARNING = /<ferix:learning(?:\s+category="(success|failure|optimization)")?\s*>([\s\S]*?)<\/ferix:learning>/g;
4814
+ var learningSpec = {
4815
+ tag: "Learning",
4816
+ closingTag: "</ferix:learning>",
4817
+ schema: LearningSignalSchema,
4818
+ parse: (text) => {
4819
+ const signals = [];
4820
+ const matches = text.matchAll(LEARNING);
4821
+ for (const match of matches) {
4822
+ const category = match[1];
4823
+ const content = match[2]?.trim() || "";
4824
+ if (!content) {
4825
+ continue;
4826
+ }
4827
+ const raw = {
4828
+ _tag: "Learning",
4829
+ content
4830
+ };
4831
+ if (category) {
4832
+ raw.category = category;
4833
+ }
4834
+ const result = S18.decodeUnknownEither(LearningSignalSchema)(raw);
4835
+ if (result._tag === "Right") {
4836
+ signals.push(result.right);
4837
+ }
4838
+ }
4839
+ return signals;
4840
+ },
4841
+ keyFields: (s) => s.content.slice(0, 50)
4842
+ };
4843
+ signalSpecRegistry.register(learningSpec);
4844
+
3823
4845
  // src/layers/signal/specs/loop-complete.ts
3824
4846
  init_esm_shims();
3825
- import { Schema as S15 } from "effect";
4847
+ import { Schema as S19 } from "effect";
3826
4848
  var LOOP_COMPLETE = /<ferix:complete>/;
3827
4849
  var loopCompleteSpec = {
3828
4850
  tag: "LoopComplete",
@@ -3831,7 +4853,7 @@ var loopCompleteSpec = {
3831
4853
  parse: (text) => {
3832
4854
  if (LOOP_COMPLETE.test(text)) {
3833
4855
  const raw = { _tag: "LoopComplete" };
3834
- const result = S15.decodeUnknownEither(LoopCompleteSignalSchema)(raw);
4856
+ const result = S19.decodeUnknownEither(LoopCompleteSignalSchema)(raw);
3835
4857
  if (result._tag === "Right") {
3836
4858
  return [result.right];
3837
4859
  }
@@ -3844,7 +4866,7 @@ signalSpecRegistry.register(loopCompleteSpec);
3844
4866
 
3845
4867
  // src/layers/signal/specs/phases.ts
3846
4868
  init_esm_shims();
3847
- import { Schema as S16 } from "effect";
4869
+ import { Schema as S20 } from "effect";
3848
4870
  var PHASES_BLOCK = /<ferix:phases task="(\d+)">([\s\S]*?)<\/ferix:phases>/;
3849
4871
  var PHASE = /<phase id="([\d.]+)">([^<]+)<\/phase>/g;
3850
4872
  var PHASE_START = /<ferix:phase-start id="([\d.]+)"\/>/g;
@@ -3877,7 +4899,7 @@ var phasesDefinedSpec = {
3877
4899
  taskId: match[1],
3878
4900
  phases
3879
4901
  };
3880
- const result = S16.decodeUnknownEither(PhasesDefinedSignalSchema)(raw);
4902
+ const result = S20.decodeUnknownEither(PhasesDefinedSignalSchema)(raw);
3881
4903
  if (result._tag === "Left") {
3882
4904
  return [];
3883
4905
  }
@@ -3894,7 +4916,7 @@ var phaseStartedSpec = {
3894
4916
  for (const m of text.matchAll(resetRegex2(PHASE_START))) {
3895
4917
  if (m[1]) {
3896
4918
  const raw = { _tag: "PhaseStarted", phaseId: m[1] };
3897
- const result = S16.decodeUnknownEither(PhaseStartedSignalSchema)(raw);
4919
+ const result = S20.decodeUnknownEither(PhaseStartedSignalSchema)(raw);
3898
4920
  if (result._tag === "Right") {
3899
4921
  signals.push(result.right);
3900
4922
  }
@@ -3913,7 +4935,7 @@ var phaseCompletedSpec = {
3913
4935
  for (const m of text.matchAll(resetRegex2(PHASE_DONE))) {
3914
4936
  if (m[1]) {
3915
4937
  const raw = { _tag: "PhaseCompleted", phaseId: m[1] };
3916
- const result = S16.decodeUnknownEither(PhaseCompletedSignalSchema)(raw);
4938
+ const result = S20.decodeUnknownEither(PhaseCompletedSignalSchema)(raw);
3917
4939
  if (result._tag === "Right") {
3918
4940
  signals.push(result.right);
3919
4941
  }
@@ -3936,7 +4958,7 @@ var phaseFailedSpec = {
3936
4958
  phaseId: m[1],
3937
4959
  reason: m[2] || "Unknown reason"
3938
4960
  };
3939
- const result = S16.decodeUnknownEither(PhaseFailedSignalSchema)(raw);
4961
+ const result = S20.decodeUnknownEither(PhaseFailedSignalSchema)(raw);
3940
4962
  if (result._tag === "Right") {
3941
4963
  signals.push(result.right);
3942
4964
  }
@@ -3953,7 +4975,7 @@ signalSpecRegistry.register(phaseFailedSpec);
3953
4975
 
3954
4976
  // src/layers/signal/specs/review.ts
3955
4977
  init_esm_shims();
3956
- import { Schema as S17 } from "effect";
4978
+ import { Schema as S21 } from "effect";
3957
4979
  var REVIEW_COMPLETE = /<ferix:review-complete\/>/;
3958
4980
  var REVIEW_CHANGES = /<ferix:review-changes-made\/>/;
3959
4981
  var reviewCompleteSpec = {
@@ -3966,7 +4988,7 @@ var reviewCompleteSpec = {
3966
4988
  }
3967
4989
  const changesMade = REVIEW_CHANGES.test(text);
3968
4990
  const raw = { _tag: "ReviewComplete", changesMade };
3969
- const result = S17.decodeUnknownEither(ReviewCompleteSignalSchema)(raw);
4991
+ const result = S21.decodeUnknownEither(ReviewCompleteSignalSchema)(raw);
3970
4992
  if (result._tag === "Right") {
3971
4993
  return [result.right];
3972
4994
  }
@@ -3978,7 +5000,7 @@ signalSpecRegistry.register(reviewCompleteSpec);
3978
5000
 
3979
5001
  // src/layers/signal/specs/task-complete.ts
3980
5002
  init_esm_shims();
3981
- import { Schema as S18 } from "effect";
5003
+ import { Schema as S22 } from "effect";
3982
5004
  var TASK_COMPLETE = /<ferix:task-complete id="(\d+)">([\s\S]*?)<\/ferix:task-complete>/;
3983
5005
  var SUMMARY = /<summary>([\s\S]*?)<\/summary>/;
3984
5006
  var FILES_MODIFIED = /<files-modified>([\s\S]*?)<\/files-modified>/;
@@ -4009,7 +5031,7 @@ var taskCompleteSpec = {
4009
5031
  filesModified: parseFileList(filesModifiedMatch?.[1]),
4010
5032
  filesCreated: parseFileList(filesCreatedMatch?.[1])
4011
5033
  };
4012
- const result = S18.decodeUnknownEither(TaskCompleteSignalSchema)(raw);
5034
+ const result = S22.decodeUnknownEither(TaskCompleteSignalSchema)(raw);
4013
5035
  if (result._tag === "Right") {
4014
5036
  return [result.right];
4015
5037
  }
@@ -4021,7 +5043,7 @@ signalSpecRegistry.register(taskCompleteSpec);
4021
5043
 
4022
5044
  // src/layers/signal/specs/tasks.ts
4023
5045
  init_esm_shims();
4024
- import { Schema as S19 } from "effect";
5046
+ import { Schema as S23 } from "effect";
4025
5047
  var TASKS_BLOCK = /<ferix:tasks>([\s\S]*?)<\/ferix:tasks>/;
4026
5048
  var TASK = /<task id="(\d+)">([^<]+)<\/task>/g;
4027
5049
  function resetRegex3(pattern) {
@@ -4051,7 +5073,7 @@ var tasksDefinedSpec = {
4051
5073
  return [];
4052
5074
  }
4053
5075
  const raw = { _tag: "TasksDefined", tasks };
4054
- const result = S19.decodeUnknownEither(TasksDefinedSignalSchema)(raw);
5076
+ const result = S23.decodeUnknownEither(TasksDefinedSignalSchema)(raw);
4055
5077
  if (result._tag === "Left") {
4056
5078
  return [];
4057
5079
  }
@@ -4064,20 +5086,20 @@ signalSpecRegistry.register(tasksDefinedSpec);
4064
5086
  // src/layers/signal/ferix-parser.ts
4065
5087
  var MAX_BUFFER_SIZE = 1024 * 1024;
4066
5088
  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);
5089
+ return Effect17.gen(function* () {
5090
+ const chunksRef = yield* Ref8.make([]);
5091
+ const emittedRef = yield* Ref8.make(/* @__PURE__ */ new Set());
5092
+ const feed = (text) => Effect17.gen(function* () {
5093
+ const chunks = yield* Ref8.get(chunksRef);
4072
5094
  chunks.push(text);
4073
5095
  const buffer = chunks.join("");
4074
5096
  if (buffer.length > MAX_BUFFER_SIZE) {
4075
- yield* Ref5.set(chunksRef, [
5097
+ yield* Ref8.set(chunksRef, [
4076
5098
  buffer.slice(buffer.length - MAX_BUFFER_SIZE)
4077
5099
  ]);
4078
5100
  }
4079
5101
  const signals = signalSpecRegistry.parseAll(buffer);
4080
- const emitted = yield* Ref5.get(emittedRef);
5102
+ const emitted = yield* Ref8.get(emittedRef);
4081
5103
  const newSignals = signals.filter((signal) => {
4082
5104
  const key = signalSpecRegistry.getSignalKey(signal);
4083
5105
  if (emitted.has(key)) {
@@ -4089,55 +5111,64 @@ function createAccumulatorImpl() {
4089
5111
  if (newSignals.length > 0) {
4090
5112
  const lastEndPos = signalSpecRegistry.findLastCompleteSignalEnd(buffer);
4091
5113
  if (lastEndPos > 0 && lastEndPos < buffer.length) {
4092
- yield* Ref5.set(chunksRef, [buffer.slice(lastEndPos)]);
5114
+ yield* Ref8.set(chunksRef, [buffer.slice(lastEndPos)]);
4093
5115
  }
4094
5116
  }
4095
- yield* Ref5.set(emittedRef, emitted);
5117
+ yield* Ref8.set(emittedRef, emitted);
4096
5118
  return newSignals;
4097
5119
  });
4098
- const flush = () => Effect11.gen(function* () {
4099
- const chunks = yield* Ref5.get(chunksRef);
5120
+ const flush = () => Effect17.gen(function* () {
5121
+ const chunks = yield* Ref8.get(chunksRef);
4100
5122
  const buffer = chunks.join("");
4101
- yield* Ref5.set(chunksRef, []);
4102
- const emitted = yield* Ref5.get(emittedRef);
5123
+ yield* Ref8.set(chunksRef, []);
5124
+ const emitted = yield* Ref8.get(emittedRef);
4103
5125
  const signals = signalSpecRegistry.parseAll(buffer);
4104
5126
  const result = signals.filter(
4105
5127
  (signal) => !emitted.has(signalSpecRegistry.getSignalKey(signal))
4106
5128
  );
4107
- yield* Ref5.set(emittedRef, /* @__PURE__ */ new Set());
5129
+ yield* Ref8.set(emittedRef, /* @__PURE__ */ new Set());
4108
5130
  return result;
4109
5131
  });
4110
5132
  return { feed, flush };
4111
5133
  });
4112
5134
  }
4113
- var make4 = {
4114
- parse: (text) => Effect11.succeed(signalSpecRegistry.parseAll(text)),
5135
+ var make7 = {
5136
+ parse: (text) => Effect17.succeed(signalSpecRegistry.parseAll(text)),
4115
5137
  createAccumulator: createAccumulatorImpl
4116
5138
  };
4117
- var Live7 = Layer7.succeed(SignalParser, make4);
5139
+ var Live13 = Layer13.succeed(SignalParser, make7);
4118
5140
  var FerixParser = {
4119
- Live: Live7
5141
+ Live: Live13
4120
5142
  };
4121
5143
 
4122
5144
  // src/layers/index.ts
4123
- var ProductionLayers = Layer8.mergeAll(
5145
+ var ProductionLayers = Layer14.mergeAll(
4124
5146
  ClaudeCLI.Live,
4125
5147
  FerixParser.Live,
4126
5148
  FileSystemPlan.Live,
4127
- FileSystemSession.Live
5149
+ FileSystemSession.Live,
5150
+ FileSystemProgress.Live,
5151
+ FileSystemGuardrails.Live,
5152
+ FileSystemGit.Live
4128
5153
  );
4129
- var TestLayers = Layer8.mergeAll(
5154
+ var TestLayers = Layer14.mergeAll(
4130
5155
  Mock.Live,
4131
5156
  FerixParser.Live,
4132
5157
  MemoryPlan.Live,
4133
- MemorySession.Live
5158
+ MemorySession.Live,
5159
+ MemoryProgress.Live,
5160
+ MemoryGuardrails.Live,
5161
+ MemoryGit.Live
4134
5162
  );
4135
5163
  function createTestLayers(events) {
4136
- return Layer8.mergeAll(
5164
+ return Layer14.mergeAll(
4137
5165
  Mock.layer({ events }),
4138
5166
  FerixParser.Live,
4139
5167
  MemoryPlan.layer(),
4140
- MemorySession.layer()
5168
+ MemorySession.layer(),
5169
+ MemoryProgress.layer(),
5170
+ MemoryGuardrails.layer(),
5171
+ MemoryGit.layer()
4141
5172
  );
4142
5173
  }
4143
5174
 
@@ -4146,42 +5177,42 @@ init_esm_shims();
4146
5177
 
4147
5178
  // src/orchestrator/loop.ts
4148
5179
  init_esm_shims();
4149
- import { DateTime as DateTime6, Effect as Effect16, Option, pipe as pipe3, Ref as Ref9, Stream as Stream9 } from "effect";
5180
+ import { DateTime as DateTime10, Effect as Effect22, Option, pipe as pipe3, Ref as Ref12, Stream as Stream9 } from "effect";
4150
5181
 
4151
5182
  // src/orchestrator/discovery.ts
4152
5183
  init_esm_shims();
4153
- import { DateTime as DateTime4, Effect as Effect14, pipe, Ref as Ref7, Stream as Stream7 } from "effect";
5184
+ import { DateTime as DateTime8, Effect as Effect20, pipe, Ref as Ref10, Stream as Stream7 } from "effect";
4154
5185
 
4155
5186
  // src/layers/plan/task-generation.ts
4156
5187
  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 }),
5188
+ import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
5189
+ import { join as join6 } from "path";
5190
+ import { Effect as Effect18 } from "effect";
5191
+ var PLANS_DIR4 = ".ferix/plans";
5192
+ function ensureDir5(dirPath) {
5193
+ return Effect18.tryPromise({
5194
+ try: () => mkdir6(dirPath, { recursive: true }),
4164
5195
  catch: (error) => new PlanStoreError({
4165
5196
  message: `Failed to create directory: ${dirPath}`,
4166
5197
  operation: "create",
4167
5198
  cause: error
4168
5199
  })
4169
- }).pipe(Effect12.asVoid);
5200
+ }).pipe(Effect18.asVoid);
4170
5201
  }
4171
- function getSessionDir2(sessionId) {
4172
- return join3(process.cwd(), PLANS_DIR2, sessionId);
5202
+ function getSessionDir4(sessionId) {
5203
+ return join6(process.cwd(), PLANS_DIR4, sessionId);
4173
5204
  }
4174
5205
  function getTasksMdPath(sessionId) {
4175
- return join3(getSessionDir2(sessionId), "tasks.md");
5206
+ return join6(getSessionDir4(sessionId), "tasks.md");
4176
5207
  }
4177
5208
  function writeTasksMd(sessionId, tasks) {
4178
- return Effect12.gen(function* () {
4179
- const sessionDir = getSessionDir2(sessionId);
4180
- yield* ensureDir3(sessionDir);
5209
+ return Effect18.gen(function* () {
5210
+ const sessionDir = getSessionDir4(sessionId);
5211
+ yield* ensureDir5(sessionDir);
4181
5212
  const tasksMdPath = getTasksMdPath(sessionId);
4182
5213
  const content = formatTasksMd(tasks);
4183
- yield* Effect12.tryPromise({
4184
- try: () => writeFile3(tasksMdPath, content, "utf-8"),
5214
+ yield* Effect18.tryPromise({
5215
+ try: () => writeFile5(tasksMdPath, content, "utf-8"),
4185
5216
  catch: (error) => new PlanStoreError({
4186
5217
  message: `Failed to write tasks.md: ${tasksMdPath}`,
4187
5218
  operation: "create",
@@ -4325,6 +5356,32 @@ eventMappingRegistry.registerSignalMapper({
4325
5356
  }
4326
5357
  })
4327
5358
  });
5359
+ eventMappingRegistry.registerSignalMapper({
5360
+ tag: "Learning",
5361
+ map: (signal, context) => ({
5362
+ _tag: "LearningRecorded",
5363
+ iteration: 0,
5364
+ // Will be overwritten by iteration stream
5365
+ content: signal.content,
5366
+ category: signal.category,
5367
+ timestamp: context.timestamp
5368
+ })
5369
+ });
5370
+ eventMappingRegistry.registerSignalMapper({
5371
+ tag: "Guardrail",
5372
+ map: (signal, context) => ({
5373
+ _tag: "GuardrailAdded",
5374
+ id: `gr-pending-${context.timestamp}`,
5375
+ // Temp ID, real one assigned in iteration
5376
+ iteration: 0,
5377
+ // Will be overwritten by iteration stream
5378
+ pattern: signal.pattern,
5379
+ sign: signal.sign,
5380
+ avoidance: signal.avoidance,
5381
+ severity: signal.severity,
5382
+ timestamp: context.timestamp
5383
+ })
5384
+ });
4328
5385
 
4329
5386
  // src/orchestrator/mapping/index.ts
4330
5387
  function mapLLMEventToDomain(event, context) {
@@ -4336,7 +5393,7 @@ function mapSignalToDomain(signal, context) {
4336
5393
 
4337
5394
  // src/orchestrator/plan-updates.ts
4338
5395
  init_esm_shims();
4339
- import { DateTime as DateTime3, Effect as Effect13, Ref as Ref6 } from "effect";
5396
+ import { DateTime as DateTime7, Effect as Effect19, Ref as Ref9 } from "effect";
4340
5397
 
4341
5398
  // src/orchestrator/plan-updates/index.ts
4342
5399
  init_esm_shims();
@@ -4548,6 +5605,20 @@ planUpdateRegistry.register({
4548
5605
  } : void 0
4549
5606
  });
4550
5607
 
5608
+ // src/orchestrator/plan-updates/handlers/guardrail.ts
5609
+ init_esm_shims();
5610
+ planUpdateRegistry.register({
5611
+ tag: "Guardrail",
5612
+ handle: (_signal, _currentPlan, _context) => void 0
5613
+ });
5614
+
5615
+ // src/orchestrator/plan-updates/handlers/learning.ts
5616
+ init_esm_shims();
5617
+ planUpdateRegistry.register({
5618
+ tag: "Learning",
5619
+ handle: (_signal, _currentPlan, _context) => void 0
5620
+ });
5621
+
4551
5622
  // src/orchestrator/plan-updates/handlers/phase-lifecycle.ts
4552
5623
  init_esm_shims();
4553
5624
  planUpdateRegistry.register({
@@ -4627,9 +5698,9 @@ function persistPlanUpdate(planStore, plan, operation) {
4627
5698
  tasks: plan.tasks
4628
5699
  }) : planStore.update(plan.id, plan);
4629
5700
  return storeOp.pipe(
4630
- Effect13.map(() => null),
4631
- Effect13.catchAll(
4632
- (error) => Effect13.succeed({
5701
+ Effect19.map(() => null),
5702
+ Effect19.catchAll(
5703
+ (error) => Effect19.succeed({
4633
5704
  _tag: "PlanUpdateFailed",
4634
5705
  operation,
4635
5706
  error: error.message,
@@ -4639,10 +5710,10 @@ function persistPlanUpdate(planStore, plan, operation) {
4639
5710
  );
4640
5711
  }
4641
5712
  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);
5713
+ return Effect19.gen(function* () {
5714
+ const currentPlan = yield* Ref9.get(currentPlanRef);
5715
+ const now = yield* DateTime7.now;
5716
+ const timestamp = DateTime7.formatIso(now);
4646
5717
  const updateResult = computePlanUpdate(signal, currentPlan, {
4647
5718
  sessionId,
4648
5719
  originalTask,
@@ -4652,8 +5723,8 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
4652
5723
  return [];
4653
5724
  }
4654
5725
  const { plan, operation, eventTag } = updateResult;
4655
- yield* Ref6.set(currentPlanRef, plan);
4656
- yield* Ref6.update(persistenceStateRef, (state) => ({
5726
+ yield* Ref9.set(currentPlanRef, plan);
5727
+ yield* Ref9.update(persistenceStateRef, (state) => ({
4657
5728
  dirty: true,
4658
5729
  pendingOperation: state.pendingOperation === "create" ? "create" : operation
4659
5730
  }));
@@ -4661,12 +5732,12 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
4661
5732
  });
4662
5733
  }
4663
5734
  function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
4664
- return Effect13.gen(function* () {
4665
- const state = yield* Ref6.get(persistenceStateRef);
5735
+ return Effect19.gen(function* () {
5736
+ const state = yield* Ref9.get(persistenceStateRef);
4666
5737
  if (!(state.dirty && state.pendingOperation)) {
4667
5738
  return [];
4668
5739
  }
4669
- const plan = yield* Ref6.get(currentPlanRef);
5740
+ const plan = yield* Ref9.get(currentPlanRef);
4670
5741
  if (!plan) {
4671
5742
  return [];
4672
5743
  }
@@ -4679,7 +5750,7 @@ function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
4679
5750
  if (failureEvent) {
4680
5751
  events.push(failureEvent);
4681
5752
  }
4682
- yield* Ref6.set(persistenceStateRef, {
5753
+ yield* Ref9.set(persistenceStateRef, {
4683
5754
  dirty: false,
4684
5755
  pendingOperation: null
4685
5756
  });
@@ -4738,9 +5809,12 @@ Use these XML-like tags to communicate structured information:
4738
5809
  <files-created>new-file.ts</files-created>
4739
5810
  </ferix:task-complete>
4740
5811
 
4741
- ### Loop Completion
5812
+ ### Loop Completion (use ONLY when ALL tasks are done)
4742
5813
  <ferix:complete>
4743
5814
 
5815
+ \u26A0\uFE0F IMPORTANT: Only emit <ferix:complete> after ALL tasks in the plan are complete.
5816
+ After completing a single task, emit <ferix:task-complete> and continue to the next task.
5817
+
4744
5818
  IMPORTANT: Always emit signals on their own lines, never inside markdown code blocks.`;
4745
5819
  var DISCOVERY_SYSTEM_PROMPT = `You are in the DISCOVERY phase of a ralph loop - an iterative AI coding workflow.
4746
5820
 
@@ -4781,14 +5855,17 @@ Review the code for quality:
4781
5855
  - When done, emit <ferix:review-complete/>`;
4782
5856
  var DEFAULT_COMPLETION_PROMPT = `## Completion
4783
5857
 
4784
- When the task is complete, emit:
5858
+ When you finish the CURRENT task, emit:
4785
5859
  <ferix:task-complete id="N">
4786
5860
  <summary>What was accomplished</summary>
4787
5861
  <files-modified>list of modified files</files-modified>
4788
5862
  <files-created>list of new files</files-created>
4789
5863
  </ferix:task-complete>
4790
5864
 
4791
- When ALL tasks are complete, emit <ferix:complete>`;
5865
+ After emitting task-complete, CONTINUE working on the next pending task.
5866
+
5867
+ \u26A0\uFE0F ONLY emit <ferix:complete> when ALL tasks in the plan are done.
5868
+ Do NOT emit <ferix:complete> after completing just one task - continue to the next task instead.`;
4792
5869
  function getPhasePrompt(phase, prompts, defaultPrompt) {
4793
5870
  return prompts?.phases?.[phase] ?? defaultPrompt;
4794
5871
  }
@@ -4929,12 +6006,12 @@ Begin.`);
4929
6006
 
4930
6007
  // src/orchestrator/discovery.ts
4931
6008
  function processTextSignals(signalParser, text, context) {
4932
- return Effect14.gen(function* () {
6009
+ return Effect20.gen(function* () {
4933
6010
  const events = [];
4934
6011
  const parsedSignals = [];
4935
6012
  const signals = yield* signalParser.parse(text).pipe(
4936
- Effect14.tapError(
4937
- (error) => Effect14.logDebug(
6013
+ Effect20.tapError(
6014
+ (error) => Effect20.logDebug(
4938
6015
  "Signal parsing failed, continuing with empty signals",
4939
6016
  {
4940
6017
  error: String(error),
@@ -4942,7 +6019,7 @@ function processTextSignals(signalParser, text, context) {
4942
6019
  }
4943
6020
  )
4944
6021
  ),
4945
- Effect14.orElseSucceed(() => [])
6022
+ Effect20.orElseSucceed(() => [])
4946
6023
  );
4947
6024
  for (const signal of signals) {
4948
6025
  events.push(mapSignalToDomain(signal, context));
@@ -4952,7 +6029,7 @@ function processTextSignals(signalParser, text, context) {
4952
6029
  });
4953
6030
  }
4954
6031
  function processLLMEvent(signalParser, llmEvent, context) {
4955
- return Effect14.gen(function* () {
6032
+ return Effect20.gen(function* () {
4956
6033
  const domainEvent = mapLLMEventToDomain(llmEvent, context);
4957
6034
  const events = [domainEvent];
4958
6035
  const allSignals = [];
@@ -4995,12 +6072,12 @@ function planTasksToGeneratedTasks(plan) {
4995
6072
  status: mapTaskStatus(task.status)
4996
6073
  }));
4997
6074
  }
4998
- function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId) {
6075
+ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId, worktreePath) {
4999
6076
  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({
6077
+ Effect20.gen(function* () {
6078
+ const startTimeUtc = yield* DateTime8.now;
6079
+ const startTime = DateTime8.toEpochMillis(startTimeUtc);
6080
+ const persistenceStateRef = yield* Ref10.make({
5004
6081
  dirty: false,
5005
6082
  pendingOperation: null
5006
6083
  });
@@ -5014,7 +6091,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5014
6091
  input: {}
5015
6092
  };
5016
6093
  const prompt = buildDiscoveryPrompt(config);
5017
- const llmStream = llm.execute(prompt).pipe(
6094
+ const llmStream = llm.execute(prompt, worktreePath ? { cwd: worktreePath } : void 0).pipe(
5018
6095
  Stream7.mapError(
5019
6096
  (e) => new OrchestratorError({
5020
6097
  message: `LLM execution failed during discovery: ${String(e)}`,
@@ -5024,10 +6101,10 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5024
6101
  ),
5025
6102
  Stream7.flatMap(
5026
6103
  (llmEvent) => Stream7.unwrap(
5027
- Effect14.gen(function* () {
5028
- const now = yield* DateTime4.now;
6104
+ Effect20.gen(function* () {
6105
+ const now = yield* DateTime8.now;
5029
6106
  const context = {
5030
- timestamp: DateTime4.toEpochMillis(now)
6107
+ timestamp: DateTime8.toEpochMillis(now)
5031
6108
  };
5032
6109
  const result = yield* processLLMEvent(
5033
6110
  signalParser,
@@ -5061,27 +6138,27 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5061
6138
  )
5062
6139
  );
5063
6140
  const completionStream = Stream7.fromEffect(
5064
- Effect14.gen(function* () {
6141
+ Effect20.gen(function* () {
5065
6142
  const persistEvents = yield* flushPlanPersistence(
5066
6143
  planStore,
5067
6144
  currentPlanRef,
5068
6145
  persistenceStateRef
5069
6146
  );
5070
- const plan = yield* Ref7.get(currentPlanRef);
6147
+ const plan = yield* Ref10.get(currentPlanRef);
5071
6148
  const taskCount = plan?.tasks.length ?? 0;
5072
6149
  if (plan && plan.tasks.length > 0) {
5073
6150
  const generatedTasks = planTasksToGeneratedTasks(plan);
5074
6151
  yield* writeTasksMd(sessionId, generatedTasks).pipe(
5075
- Effect14.tapError(
5076
- (error) => Effect14.logDebug("Failed to write tasks.md, continuing", {
6152
+ Effect20.tapError(
6153
+ (error) => Effect20.logDebug("Failed to write tasks.md, continuing", {
5077
6154
  error: String(error)
5078
6155
  })
5079
6156
  ),
5080
- Effect14.orElseSucceed(() => void 0)
6157
+ Effect20.orElseSucceed(() => void 0)
5081
6158
  );
5082
6159
  }
5083
- const endTimeUtc = yield* DateTime4.now;
5084
- const endTime = DateTime4.toEpochMillis(endTimeUtc);
6160
+ const endTimeUtc = yield* DateTime8.now;
6161
+ const endTime = DateTime8.toEpochMillis(endTimeUtc);
5085
6162
  const discoveryCompleted = {
5086
6163
  _tag: "DiscoveryCompleted",
5087
6164
  taskCount,
@@ -5102,15 +6179,15 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
5102
6179
 
5103
6180
  // src/orchestrator/iteration.ts
5104
6181
  init_esm_shims();
5105
- import { DateTime as DateTime5, Effect as Effect15, pipe as pipe2, Ref as Ref8, Stream as Stream8 } from "effect";
6182
+ import { DateTime as DateTime9, Effect as Effect21, pipe as pipe2, Ref as Ref11, Stream as Stream8 } from "effect";
5106
6183
  function processTextSignals2(signalParser, text, context) {
5107
- return Effect15.gen(function* () {
6184
+ return Effect21.gen(function* () {
5108
6185
  const events = [];
5109
6186
  let completed = false;
5110
6187
  const parsedSignals = [];
5111
6188
  const signals = yield* signalParser.parse(text).pipe(
5112
- Effect15.tapError(
5113
- (error) => Effect15.logDebug(
6189
+ Effect21.tapError(
6190
+ (error) => Effect21.logDebug(
5114
6191
  "Signal parsing failed, continuing with empty signals",
5115
6192
  {
5116
6193
  error: String(error),
@@ -5118,7 +6195,7 @@ function processTextSignals2(signalParser, text, context) {
5118
6195
  }
5119
6196
  )
5120
6197
  ),
5121
- Effect15.orElseSucceed(() => [])
6198
+ Effect21.orElseSucceed(() => [])
5122
6199
  );
5123
6200
  for (const signal of signals) {
5124
6201
  events.push(mapSignalToDomain(signal, context));
@@ -5131,7 +6208,7 @@ function processTextSignals2(signalParser, text, context) {
5131
6208
  });
5132
6209
  }
5133
6210
  function processLLMEvent2(signalParser, llmEvent, context) {
5134
- return Effect15.gen(function* () {
6211
+ return Effect21.gen(function* () {
5135
6212
  const domainEvent = mapLLMEventToDomain(llmEvent, context);
5136
6213
  const events = [domainEvent];
5137
6214
  let completed = false;
@@ -5162,11 +6239,11 @@ function processLLMEvent2(signalParser, llmEvent, context) {
5162
6239
  return { events, completed, signals: allSignals };
5163
6240
  });
5164
6241
  }
5165
- function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId) {
6242
+ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId, worktreePath) {
5166
6243
  return Stream8.unwrap(
5167
- Effect15.gen(function* () {
5168
- const currentPlan = yield* Ref8.get(currentPlanRef);
5169
- const persistenceStateRef = yield* Ref8.make({
6244
+ Effect21.gen(function* () {
6245
+ const currentPlan = yield* Ref11.get(currentPlanRef);
6246
+ const persistenceStateRef = yield* Ref11.make({
5170
6247
  dirty: false,
5171
6248
  pendingOperation: null
5172
6249
  });
@@ -5174,7 +6251,10 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5174
6251
  _tag: "IterationStarted",
5175
6252
  iteration
5176
6253
  };
5177
- const llmStream = llm.execute(buildPrompt(config, iteration, currentPlan)).pipe(
6254
+ const llmStream = llm.execute(
6255
+ buildPrompt(config, iteration, currentPlan),
6256
+ worktreePath ? { cwd: worktreePath } : void 0
6257
+ ).pipe(
5178
6258
  Stream8.mapError(
5179
6259
  (e) => new OrchestratorError({
5180
6260
  message: `LLM execution failed: ${String(e)}`,
@@ -5184,10 +6264,10 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5184
6264
  ),
5185
6265
  Stream8.flatMap(
5186
6266
  (llmEvent) => Stream8.unwrap(
5187
- Effect15.gen(function* () {
5188
- const now = yield* DateTime5.now;
6267
+ Effect21.gen(function* () {
6268
+ const now = yield* DateTime9.now;
5189
6269
  const context = {
5190
- timestamp: DateTime5.toEpochMillis(now)
6270
+ timestamp: DateTime9.toEpochMillis(now)
5191
6271
  };
5192
6272
  const result = yield* processLLMEvent2(
5193
6273
  signalParser,
@@ -5206,7 +6286,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5206
6286
  events.push(...planEvents);
5207
6287
  }
5208
6288
  if (result.completed) {
5209
- yield* Ref8.set(loopCompletedRef, true);
6289
+ yield* Ref11.set(loopCompletedRef, true);
5210
6290
  }
5211
6291
  return Stream8.fromIterable(events);
5212
6292
  })
@@ -5225,7 +6305,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5225
6305
  )
5226
6306
  );
5227
6307
  const completionStream = Stream8.fromEffect(
5228
- Effect15.gen(function* () {
6308
+ Effect21.gen(function* () {
5229
6309
  const persistEvents = yield* flushPlanPersistence(
5230
6310
  planStore,
5231
6311
  currentPlanRef,
@@ -5250,13 +6330,14 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
5250
6330
  // src/orchestrator/loop.ts
5251
6331
  function runLoop(config) {
5252
6332
  return Stream9.unwrap(
5253
- Effect16.gen(function* () {
6333
+ Effect22.gen(function* () {
5254
6334
  const llm = yield* LLM;
5255
6335
  const signalParser = yield* SignalParser;
5256
6336
  const sessionStore = yield* SessionStore;
5257
6337
  const planStore = yield* PlanStore;
6338
+ const git = yield* Git;
5258
6339
  const session = yield* sessionStore.create(config.task).pipe(
5259
- Effect16.mapError(
6340
+ Effect22.mapError(
5260
6341
  (e) => new OrchestratorError({
5261
6342
  message: `Failed to create session: ${e.message}`,
5262
6343
  phase: "setup",
@@ -5264,10 +6345,39 @@ function runLoop(config) {
5264
6345
  })
5265
6346
  )
5266
6347
  );
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);
6348
+ const worktreePath = yield* git.createWorktree(session.id).pipe(
6349
+ Effect22.mapError(
6350
+ (e) => new OrchestratorError({
6351
+ message: `Failed to create worktree: ${e.message}`,
6352
+ phase: "setup",
6353
+ cause: e
6354
+ })
6355
+ )
6356
+ );
6357
+ const branchName = git.getBranchName(session.id);
6358
+ yield* sessionStore.update(session.id, {
6359
+ ...session,
6360
+ worktreePath,
6361
+ branchName
6362
+ }).pipe(
6363
+ Effect22.tapError(
6364
+ (error) => Effect22.logDebug("Failed to update session with worktree info", {
6365
+ error: String(error)
6366
+ })
6367
+ ),
6368
+ Effect22.orElseSucceed(() => void 0)
6369
+ );
6370
+ const startTimeUtc = yield* DateTime10.now;
6371
+ const startTime = DateTime10.toEpochMillis(startTimeUtc);
6372
+ const worktreeCreated = {
6373
+ _tag: "WorktreeCreated",
6374
+ sessionId: session.id,
6375
+ worktreePath,
6376
+ branchName,
6377
+ timestamp: startTime
6378
+ };
6379
+ const loopCompletedRef = yield* Ref12.make(false);
6380
+ const currentPlanRef = yield* Ref12.make(void 0);
5271
6381
  const maxIterations = config.maxIterations === 0 ? Number.POSITIVE_INFINITY : config.maxIterations;
5272
6382
  const loopStarted = {
5273
6383
  _tag: "LoopStarted",
@@ -5280,12 +6390,13 @@ function runLoop(config) {
5280
6390
  planStore,
5281
6391
  currentPlanRef,
5282
6392
  config,
5283
- session.id
6393
+ session.id,
6394
+ worktreePath
5284
6395
  );
5285
6396
  const iterationsStream = Stream9.unfoldEffect(
5286
6397
  1,
5287
- (iteration) => Effect16.gen(function* () {
5288
- const completed = yield* Ref9.get(loopCompletedRef);
6398
+ (iteration) => Effect22.gen(function* () {
6399
+ const completed = yield* Ref12.get(loopCompletedRef);
5289
6400
  if (completed || iteration > maxIterations) {
5290
6401
  return Option.none();
5291
6402
  }
@@ -5301,27 +6412,31 @@ function runLoop(config) {
5301
6412
  loopCompletedRef,
5302
6413
  config,
5303
6414
  iteration,
5304
- session.id
6415
+ session.id,
6416
+ worktreePath
5305
6417
  )
5306
6418
  )
5307
6419
  );
5308
6420
  const completionStream = createCompletionStream(
5309
6421
  sessionStore,
6422
+ git,
5310
6423
  session,
5311
6424
  config,
5312
6425
  startTime,
5313
- loopCompletedRef
6426
+ loopCompletedRef,
6427
+ worktreePath
5314
6428
  );
5315
6429
  return pipe3(
5316
6430
  Stream9.succeed(loopStarted),
6431
+ Stream9.concat(Stream9.succeed(worktreeCreated)),
5317
6432
  Stream9.concat(discoveryStream),
5318
6433
  Stream9.concat(iterationsStream),
5319
6434
  Stream9.concat(completionStream)
5320
6435
  );
5321
6436
  }).pipe(
5322
6437
  // Also catch setup errors (e.g., session creation failure)
5323
- Effect16.catchAll(
5324
- (error) => Effect16.succeed(
6438
+ Effect22.catchAll(
6439
+ (error) => Effect22.succeed(
5325
6440
  Stream9.succeed({
5326
6441
  _tag: "LoopFailed",
5327
6442
  error: {
@@ -5334,12 +6449,21 @@ function runLoop(config) {
5334
6449
  )
5335
6450
  );
5336
6451
  }
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);
6452
+ function createCompletionStream(sessionStore, git, session, config, startTime, loopCompletedRef, _worktreePath) {
6453
+ return Stream9.unwrap(
6454
+ Effect22.gen(function* () {
6455
+ const endTimeUtc = yield* DateTime10.now;
6456
+ const durationMs = DateTime10.toEpochMillis(endTimeUtc) - startTime;
6457
+ const completed = yield* Ref12.get(loopCompletedRef);
6458
+ yield* git.commitChanges(session.id, `feat: complete session ${session.id}`).pipe(
6459
+ Effect22.tapError(
6460
+ (error) => Effect22.logDebug("Final commit failed, continuing", {
6461
+ sessionId: session.id,
6462
+ error: String(error)
6463
+ })
6464
+ ),
6465
+ Effect22.orElseSucceed(() => void 0)
6466
+ );
5343
6467
  const summary = {
5344
6468
  iterations: config.maxIterations,
5345
6469
  success: completed,
@@ -5351,16 +6475,16 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
5351
6475
  ...session,
5352
6476
  status: completed ? "completed" : "paused"
5353
6477
  }).pipe(
5354
- Effect16.tapError(
5355
- (error) => Effect16.logDebug("Session update failed, continuing", {
6478
+ Effect22.tapError(
6479
+ (error) => Effect22.logDebug("Session update failed, continuing", {
5356
6480
  sessionId: session.id,
5357
6481
  error: String(error)
5358
6482
  })
5359
6483
  ),
5360
- Effect16.orElseSucceed(() => void 0)
6484
+ Effect22.orElseSucceed(() => void 0)
5361
6485
  );
5362
- const event = { _tag: "LoopCompleted", summary };
5363
- return event;
6486
+ const loopCompleted = { _tag: "LoopCompleted", summary };
6487
+ return Stream9.succeed(loopCompleted);
5364
6488
  })
5365
6489
  );
5366
6490
  }
@@ -5369,7 +6493,7 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
5369
6493
  function run(options) {
5370
6494
  const { config, consumer: consumerType = "headless", onEvent } = options;
5371
6495
  const events = runLoop(config);
5372
- const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect17.sync(() => onEvent(event)))) : events;
6496
+ const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
5373
6497
  const eventsWithLayers = eventsWithCallback.pipe(
5374
6498
  Stream10.provideLayer(ProductionLayers)
5375
6499
  );
@@ -5382,7 +6506,7 @@ function run(options) {
5382
6506
  function runTest(options, mockEvents) {
5383
6507
  const { config, onEvent } = options;
5384
6508
  const events = runLoop(config);
5385
- const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect17.sync(() => onEvent(event)))) : events;
6509
+ const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
5386
6510
  const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
5387
6511
  const eventsWithLayers = eventsWithCallback.pipe(Stream10.provideLayer(layers));
5388
6512
  return eventsWithLayers.pipe(Stream10.runDrain);
@@ -5390,11 +6514,11 @@ function runTest(options, mockEvents) {
5390
6514
  function collectEvents(config, mockEvents) {
5391
6515
  const events = runLoop(config);
5392
6516
  const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
5393
- return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(Effect17.map((chunk) => Array.from(chunk)));
6517
+ return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(Effect23.map((chunk) => Array.from(chunk)));
5394
6518
  }
5395
6519
  function main(config) {
5396
6520
  const consumerType = process.stdout.isTTY ? "tui" : "headless";
5397
- return run({ config, consumer: consumerType }).pipe(Effect17.runPromise);
6521
+ return run({ config, consumer: consumerType }).pipe(Effect23.runPromise);
5398
6522
  }
5399
6523
 
5400
6524
  // src/services/index.ts
@@ -5402,7 +6526,7 @@ init_esm_shims();
5402
6526
 
5403
6527
  // src/index.ts
5404
6528
  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) => {
6529
+ 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
6530
  const config = {
5407
6531
  task,
5408
6532
  maxIterations: Number.parseInt(options.iterations, 10),
@@ -5447,11 +6571,23 @@ export {
5447
6571
  ExecutionModeSchema,
5448
6572
  FerixParser,
5449
6573
  FileLoggerConfigSchema,
6574
+ FileSystemGit,
6575
+ FileSystemGuardrails,
5450
6576
  FileSystemPlan,
6577
+ FileSystemProgress,
5451
6578
  FileSystemSession,
5452
6579
  GeneratedTaskListSchema,
5453
6580
  GeneratedTaskSchema,
5454
6581
  GeneratedTaskStatusSchema,
6582
+ Git,
6583
+ GitError,
6584
+ GuardrailAddedEventSchema,
6585
+ GuardrailSchema,
6586
+ GuardrailSeveritySchema,
6587
+ GuardrailSignalSchema,
6588
+ GuardrailsFileSchema,
6589
+ GuardrailsStore,
6590
+ GuardrailsStoreError,
5455
6591
  IterationCompletedEventSchema,
5456
6592
  IterationStartedEventSchema,
5457
6593
  LLM,
@@ -5461,6 +6597,9 @@ export {
5461
6597
  LLMToolEndEventSchema,
5462
6598
  LLMToolStartEventSchema,
5463
6599
  LLMToolUseEventSchema,
6600
+ LearningCategorySchema,
6601
+ LearningRecordedEventSchema,
6602
+ LearningSignalSchema,
5464
6603
  LogEntrySchema,
5465
6604
  LogLevelSchema,
5466
6605
  LoopCompleteSignalSchema,
@@ -5471,7 +6610,10 @@ export {
5471
6610
  LoopStartedEventSchema,
5472
6611
  LoopStatusSchema,
5473
6612
  LoopSummarySchema,
6613
+ MemoryGit,
6614
+ MemoryGuardrails,
5474
6615
  MemoryPlan,
6616
+ MemoryProgress,
5475
6617
  MemorySession,
5476
6618
  Mock,
5477
6619
  Mock as MockLLM,
@@ -5501,6 +6643,12 @@ export {
5501
6643
  PlanUpdateFailedEventSchema,
5502
6644
  PlanUpdatedEventSchema,
5503
6645
  ProductionLayers,
6646
+ ProgressActionSchema,
6647
+ ProgressEntrySchema,
6648
+ ProgressFileSchema,
6649
+ ProgressStore,
6650
+ ProgressStoreError,
6651
+ ProgressUpdatedEventSchema,
5504
6652
  PromptConfigSchema,
5505
6653
  ReviewCompleteDataSchema,
5506
6654
  ReviewCompleteEventSchema,
@@ -5535,15 +6683,21 @@ export {
5535
6683
  ToolStartEventSchema,
5536
6684
  ToolUseEventSchema,
5537
6685
  ViewModeSchema,
6686
+ WorktreeCreatedEventSchema,
6687
+ WorktreeRemovedEventSchema,
5538
6688
  buildPrompt,
5539
6689
  collectEvents,
5540
6690
  createHeadlessConsumer,
5541
6691
  createTUIConsumer,
5542
6692
  createTestLayers,
6693
+ decodeGuardrail,
6694
+ decodeGuardrailsFile,
5543
6695
  decodeLLMEvent,
5544
6696
  decodeLoopConfig,
5545
6697
  decodePlan,
5546
6698
  decodePlanData,
6699
+ decodeProgressEntry,
6700
+ decodeProgressFile,
5547
6701
  decodeSession,
5548
6702
  decodeSignal,
5549
6703
  decodeSignalSync,