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.
- package/dist/index.d.ts +688 -21
- package/dist/index.js +1961 -807
- 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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
329
|
+
lines.push(pc10.red(` - ${err.name}: ${err.message}`));
|
|
319
330
|
if (err.stack) {
|
|
320
331
|
lines.push("");
|
|
321
|
-
lines.push(
|
|
332
|
+
lines.push(pc10.yellow("Stack trace:"));
|
|
322
333
|
for (const stackLine of err.stack.split("\n").slice(1)) {
|
|
323
|
-
lines.push(
|
|
334
|
+
lines.push(pc10.dim(` ${stackLine.trim()}`));
|
|
324
335
|
}
|
|
325
336
|
}
|
|
326
337
|
} else {
|
|
327
|
-
lines.push(
|
|
338
|
+
lines.push(pc10.red(` - ${String(err)}`));
|
|
328
339
|
}
|
|
329
340
|
}
|
|
330
341
|
function formatCauseVerbose(cause) {
|
|
331
342
|
const lines = [];
|
|
332
|
-
const separator2 =
|
|
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(
|
|
349
|
+
lines.push(pc10.red(" ERROR"));
|
|
339
350
|
lines.push(separator2);
|
|
340
351
|
lines.push("");
|
|
341
|
-
lines.push(`${
|
|
352
|
+
lines.push(`${pc10.yellow("Type:")} ${causeType}`);
|
|
342
353
|
lines.push("");
|
|
343
|
-
lines.push(
|
|
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(
|
|
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(
|
|
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
|
|
548
|
+
import pc12 from "picocolors";
|
|
538
549
|
|
|
539
550
|
// src/consumers/tui/render/primitives.ts
|
|
540
551
|
init_esm_shims();
|
|
541
|
-
import
|
|
552
|
+
import pc11 from "picocolors";
|
|
542
553
|
var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
543
554
|
var colors = {
|
|
544
|
-
brand:
|
|
545
|
-
success:
|
|
546
|
-
warning:
|
|
547
|
-
error:
|
|
548
|
-
info:
|
|
549
|
-
muted:
|
|
550
|
-
highlight:
|
|
551
|
-
brightWhite: (s) =>
|
|
552
|
-
brightMagenta: (s) =>
|
|
553
|
-
brightCyan: (s) =>
|
|
554
|
-
brightGreen: (s) =>
|
|
555
|
-
brightYellow: (s) =>
|
|
556
|
-
brightRed: (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:
|
|
560
|
-
Edit:
|
|
561
|
-
Write:
|
|
562
|
-
Bash:
|
|
563
|
-
Glob:
|
|
564
|
-
Grep:
|
|
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:
|
|
567
|
-
WebSearch:
|
|
577
|
+
WebFetch: pc11.cyan,
|
|
578
|
+
WebSearch: pc11.blue
|
|
568
579
|
};
|
|
569
580
|
function getToolColor(tool) {
|
|
570
|
-
return toolColors[tool] ||
|
|
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
|
|
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
|
|
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 `${
|
|
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 `${
|
|
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 `${
|
|
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
|
|
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 `${
|
|
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 `${
|
|
734
|
+
return `${pc12.cyan("\u2502")} ${colors.muted(`Phases for task ${taskId}:`)}`;
|
|
724
735
|
}
|
|
725
736
|
function phaseLine(id, description) {
|
|
726
|
-
return `${
|
|
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 `${
|
|
740
|
+
return `${pc12.cyan("\u2502")} ${colors.warning(symbols.bulletFilled)} ${colors.muted(`Phase ${id} started`)}`;
|
|
730
741
|
}
|
|
731
742
|
function phaseDone(id) {
|
|
732
|
-
return `${
|
|
743
|
+
return `${pc12.cyan("\u2502")} ${colors.success(symbols.checkmark)} ${colors.muted(`Phase ${id} complete`)}`;
|
|
733
744
|
}
|
|
734
745
|
function phaseFailed(id, reason) {
|
|
735
|
-
return `${
|
|
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 `${
|
|
782
|
+
return `${pc12.cyan("\u2502")} ${colors.muted(`Success criteria for task ${taskId}:`)}`;
|
|
772
783
|
}
|
|
773
784
|
function criterionLine(id, description) {
|
|
774
|
-
return `${
|
|
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
|
|
1048
|
-
var brightWhite = (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 =
|
|
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:
|
|
1099
|
+
color: pc13.cyan
|
|
1089
1100
|
});
|
|
1090
1101
|
toolDisplayRegistry.register({
|
|
1091
1102
|
tool: "Edit",
|
|
1092
1103
|
inputKey: "file_path",
|
|
1093
|
-
color:
|
|
1104
|
+
color: pc13.yellow
|
|
1094
1105
|
});
|
|
1095
1106
|
toolDisplayRegistry.register({
|
|
1096
1107
|
tool: "Write",
|
|
1097
1108
|
inputKey: "file_path",
|
|
1098
|
-
color:
|
|
1109
|
+
color: pc13.green
|
|
1099
1110
|
});
|
|
1100
1111
|
toolDisplayRegistry.register({
|
|
1101
1112
|
tool: "Bash",
|
|
1102
1113
|
inputKey: "command",
|
|
1103
|
-
color:
|
|
1114
|
+
color: pc13.magenta
|
|
1104
1115
|
});
|
|
1105
1116
|
toolDisplayRegistry.register({
|
|
1106
1117
|
tool: "Glob",
|
|
1107
1118
|
inputKey: "pattern",
|
|
1108
|
-
color:
|
|
1119
|
+
color: pc13.blue
|
|
1109
1120
|
});
|
|
1110
1121
|
toolDisplayRegistry.register({
|
|
1111
1122
|
tool: "Grep",
|
|
1112
1123
|
inputKey: "pattern",
|
|
1113
|
-
color:
|
|
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:
|
|
1134
|
+
color: pc13.cyan
|
|
1124
1135
|
});
|
|
1125
1136
|
toolDisplayRegistry.register({
|
|
1126
1137
|
tool: "WebSearch",
|
|
1127
1138
|
inputKey: "query",
|
|
1128
|
-
color:
|
|
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
|
|
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 `${
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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/
|
|
2379
|
+
// src/layers/git/file-system.ts
|
|
2324
2380
|
init_esm_shims();
|
|
2325
|
-
import {
|
|
2326
|
-
import {
|
|
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/
|
|
2409
|
+
// src/services/git.ts
|
|
2343
2410
|
init_esm_shims();
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
}
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
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
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
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
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
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
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
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
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
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(
|
|
2542
|
-
var
|
|
2649
|
+
var Live = Layer.succeed(Git, make);
|
|
2650
|
+
var FileSystemGit = {
|
|
2543
2651
|
Live
|
|
2544
2652
|
};
|
|
2545
2653
|
|
|
2546
|
-
// src/layers/
|
|
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 {
|
|
2553
|
-
var
|
|
2554
|
-
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
|
|
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
|
-
|
|
2586
|
-
const
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
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
|
|
2750
|
+
var Live2 = layer();
|
|
2751
|
+
var MemoryGit = {
|
|
2611
2752
|
Live: Live2,
|
|
2612
|
-
layer
|
|
2613
|
-
createMockLLM
|
|
2753
|
+
layer
|
|
2614
2754
|
};
|
|
2615
2755
|
|
|
2616
|
-
// src/layers/
|
|
2756
|
+
// src/layers/guardrails/file-system.ts
|
|
2617
2757
|
init_esm_shims();
|
|
2618
|
-
import {
|
|
2619
|
-
import { join } from "path";
|
|
2620
|
-
import { Effect as
|
|
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
|
|
2631
|
-
var PhasePromptOverridesSchema =
|
|
2632
|
-
breakdown:
|
|
2633
|
-
planning:
|
|
2634
|
-
execution:
|
|
2635
|
-
check:
|
|
2636
|
-
verify:
|
|
2637
|
-
review:
|
|
2638
|
-
completion:
|
|
2639
|
-
});
|
|
2640
|
-
var PromptConfigSchema =
|
|
2641
|
-
systemPrompt:
|
|
2642
|
-
phases:
|
|
2643
|
-
additionalContext:
|
|
2644
|
-
});
|
|
2645
|
-
var LoopConfigSchema =
|
|
2646
|
-
task:
|
|
2647
|
-
maxIterations:
|
|
2648
|
-
verifyCommands:
|
|
2649
|
-
sessionId:
|
|
2650
|
-
branch:
|
|
2651
|
-
push:
|
|
2652
|
-
pr:
|
|
2653
|
-
verbose:
|
|
2654
|
-
prompts:
|
|
2655
|
-
});
|
|
2656
|
-
var LoopSummarySchema =
|
|
2657
|
-
iterations:
|
|
2658
|
-
success:
|
|
2659
|
-
sessionId:
|
|
2660
|
-
completedTasks:
|
|
2661
|
-
durationMs:
|
|
2662
|
-
});
|
|
2663
|
-
var LoopErrorSchema =
|
|
2664
|
-
message:
|
|
2665
|
-
phase:
|
|
2666
|
-
iteration:
|
|
2667
|
-
});
|
|
2668
|
-
var decodeLoopConfig =
|
|
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
|
|
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
|
|
2816
|
+
import { Brand, Schema as S2 } from "effect";
|
|
2677
2817
|
var PlanId = Brand.nominal();
|
|
2678
|
-
var TaskStatusSchema =
|
|
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 =
|
|
2826
|
+
var PhaseStatusSchema = S2.Literal(
|
|
2687
2827
|
"pending",
|
|
2688
2828
|
"in_progress",
|
|
2689
2829
|
"done",
|
|
2690
2830
|
"failed"
|
|
2691
2831
|
);
|
|
2692
|
-
var CriterionStatusSchema =
|
|
2693
|
-
var PhaseSchema =
|
|
2694
|
-
id:
|
|
2695
|
-
description:
|
|
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 =
|
|
2699
|
-
id:
|
|
2700
|
-
description:
|
|
2838
|
+
var CriterionSchema = S2.Struct({
|
|
2839
|
+
id: S2.String,
|
|
2840
|
+
description: S2.String,
|
|
2701
2841
|
status: CriterionStatusSchema,
|
|
2702
|
-
failureReason:
|
|
2842
|
+
failureReason: S2.optional(S2.String)
|
|
2703
2843
|
});
|
|
2704
|
-
var TaskSchema =
|
|
2705
|
-
id:
|
|
2706
|
-
title:
|
|
2707
|
-
description:
|
|
2844
|
+
var TaskSchema = S2.Struct({
|
|
2845
|
+
id: S2.String,
|
|
2846
|
+
title: S2.String,
|
|
2847
|
+
description: S2.String,
|
|
2708
2848
|
status: TaskStatusSchema,
|
|
2709
|
-
phases:
|
|
2710
|
-
criteria:
|
|
2711
|
-
filesToModify:
|
|
2712
|
-
attempts:
|
|
2713
|
-
completionNotes:
|
|
2714
|
-
});
|
|
2715
|
-
var PlanDataSchema =
|
|
2716
|
-
sessionId:
|
|
2717
|
-
createdAt:
|
|
2718
|
-
originalTask:
|
|
2719
|
-
context:
|
|
2720
|
-
tasks:
|
|
2721
|
-
});
|
|
2722
|
-
var PlanSchema =
|
|
2723
|
-
id:
|
|
2724
|
-
sessionId:
|
|
2725
|
-
createdAt:
|
|
2726
|
-
originalTask:
|
|
2727
|
-
context:
|
|
2728
|
-
tasks:
|
|
2729
|
-
});
|
|
2730
|
-
var decodePlan =
|
|
2731
|
-
var decodePlanData =
|
|
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
|
|
2736
|
-
var TaskBasicInfoSchema =
|
|
2737
|
-
id:
|
|
2738
|
-
title:
|
|
2739
|
-
description:
|
|
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 =
|
|
2742
|
-
id:
|
|
2743
|
-
description:
|
|
2881
|
+
var PhaseBasicInfoSchema = S3.Struct({
|
|
2882
|
+
id: S3.String,
|
|
2883
|
+
description: S3.String
|
|
2744
2884
|
});
|
|
2745
|
-
var CriterionBasicInfoSchema =
|
|
2746
|
-
id:
|
|
2747
|
-
description:
|
|
2885
|
+
var CriterionBasicInfoSchema = S3.Struct({
|
|
2886
|
+
id: S3.String,
|
|
2887
|
+
description: S3.String
|
|
2748
2888
|
});
|
|
2749
|
-
var TasksDefinedDataSchema =
|
|
2750
|
-
tasks:
|
|
2889
|
+
var TasksDefinedDataSchema = S3.Struct({
|
|
2890
|
+
tasks: S3.Array(TaskBasicInfoSchema)
|
|
2751
2891
|
});
|
|
2752
|
-
var PhasesDefinedDataSchema =
|
|
2753
|
-
taskId:
|
|
2754
|
-
phases:
|
|
2892
|
+
var PhasesDefinedDataSchema = S3.Struct({
|
|
2893
|
+
taskId: S3.String,
|
|
2894
|
+
phases: S3.Array(PhaseBasicInfoSchema)
|
|
2755
2895
|
});
|
|
2756
|
-
var CriteriaDefinedDataSchema =
|
|
2757
|
-
taskId:
|
|
2758
|
-
criteria:
|
|
2896
|
+
var CriteriaDefinedDataSchema = S3.Struct({
|
|
2897
|
+
taskId: S3.String,
|
|
2898
|
+
criteria: S3.Array(CriterionBasicInfoSchema)
|
|
2759
2899
|
});
|
|
2760
|
-
var PhaseIdDataSchema =
|
|
2761
|
-
phaseId:
|
|
2900
|
+
var PhaseIdDataSchema = S3.Struct({
|
|
2901
|
+
phaseId: S3.String
|
|
2762
2902
|
});
|
|
2763
|
-
var PhaseFailedDataSchema =
|
|
2764
|
-
phaseId:
|
|
2765
|
-
reason:
|
|
2903
|
+
var PhaseFailedDataSchema = S3.Struct({
|
|
2904
|
+
phaseId: S3.String,
|
|
2905
|
+
reason: S3.String
|
|
2766
2906
|
});
|
|
2767
|
-
var CriterionIdDataSchema =
|
|
2768
|
-
criterionId:
|
|
2907
|
+
var CriterionIdDataSchema = S3.Struct({
|
|
2908
|
+
criterionId: S3.String
|
|
2769
2909
|
});
|
|
2770
|
-
var CriterionFailedDataSchema =
|
|
2771
|
-
criterionId:
|
|
2772
|
-
reason:
|
|
2910
|
+
var CriterionFailedDataSchema = S3.Struct({
|
|
2911
|
+
criterionId: S3.String,
|
|
2912
|
+
reason: S3.String
|
|
2773
2913
|
});
|
|
2774
|
-
var ReviewCompleteDataSchema =
|
|
2775
|
-
changesMade:
|
|
2914
|
+
var ReviewCompleteDataSchema = S3.Struct({
|
|
2915
|
+
changesMade: S3.Boolean
|
|
2776
2916
|
});
|
|
2777
|
-
var TaskCompleteSignalDataSchema =
|
|
2778
|
-
taskId:
|
|
2779
|
-
summary:
|
|
2780
|
-
filesModified:
|
|
2781
|
-
filesCreated:
|
|
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 =
|
|
2784
|
-
taskId:
|
|
2785
|
-
summary:
|
|
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) =>
|
|
2790
|
-
var taggedFromData = (tag, dataSchema, 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:
|
|
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:
|
|
2942
|
+
timestamp: S4.Number
|
|
2803
2943
|
});
|
|
2804
2944
|
var DiscoveryCompletedEventSchema = taggedEvent("DiscoveryCompleted", {
|
|
2805
|
-
taskCount:
|
|
2806
|
-
timestamp:
|
|
2945
|
+
taskCount: S4.Number,
|
|
2946
|
+
timestamp: S4.Number
|
|
2807
2947
|
});
|
|
2808
2948
|
var IterationStartedEventSchema = taggedEvent("IterationStarted", {
|
|
2809
|
-
iteration:
|
|
2949
|
+
iteration: S4.Number
|
|
2810
2950
|
});
|
|
2811
2951
|
var IterationCompletedEventSchema = taggedEvent("IterationCompleted", {
|
|
2812
|
-
iteration:
|
|
2952
|
+
iteration: S4.Number
|
|
2813
2953
|
});
|
|
2814
2954
|
var LLMTextEventSchema = taggedEvent("LLMText", {
|
|
2815
|
-
text:
|
|
2955
|
+
text: S4.String
|
|
2816
2956
|
});
|
|
2817
2957
|
var LLMToolStartEventSchema = taggedEvent("LLMToolStart", {
|
|
2818
|
-
tool:
|
|
2958
|
+
tool: S4.String
|
|
2819
2959
|
});
|
|
2820
2960
|
var LLMToolUseEventSchema = taggedEvent("LLMToolUse", {
|
|
2821
|
-
tool:
|
|
2822
|
-
input:
|
|
2961
|
+
tool: S4.String,
|
|
2962
|
+
input: S4.Unknown
|
|
2823
2963
|
});
|
|
2824
2964
|
var LLMToolEndEventSchema = taggedEvent("LLMToolEnd", {
|
|
2825
|
-
tool:
|
|
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:
|
|
2982
|
+
{ timestamp: S4.Number }
|
|
2843
2983
|
);
|
|
2844
2984
|
var PhaseCompletedEventSchema = taggedFromData(
|
|
2845
2985
|
"PhaseCompleted",
|
|
2846
2986
|
PhaseIdDataSchema,
|
|
2847
|
-
{ timestamp:
|
|
2987
|
+
{ timestamp: S4.Number }
|
|
2848
2988
|
);
|
|
2849
2989
|
var PhaseFailedEventSchema = taggedFromData(
|
|
2850
2990
|
"PhaseFailed",
|
|
2851
2991
|
PhaseFailedDataSchema,
|
|
2852
|
-
{ timestamp:
|
|
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 =
|
|
3002
|
+
var CheckPassedEventSchema = S4.TaggedStruct("CheckPassed", {});
|
|
2863
3003
|
var CheckFailedEventSchema = taggedEvent("CheckFailed", {
|
|
2864
|
-
failedCriteria:
|
|
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:
|
|
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:
|
|
2883
|
-
error:
|
|
2884
|
-
planId:
|
|
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 =
|
|
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/
|
|
3176
|
+
// src/domain/schemas/progress.ts
|
|
2951
3177
|
init_esm_shims();
|
|
2952
3178
|
import { Schema as S9 } from "effect";
|
|
2953
|
-
var
|
|
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 =
|
|
2960
|
-
id:
|
|
2961
|
-
createdAt:
|
|
3211
|
+
var SessionSchema = S10.Struct({
|
|
3212
|
+
id: S10.String,
|
|
3213
|
+
createdAt: S10.String,
|
|
2962
3214
|
status: SessionStatusSchema,
|
|
2963
|
-
originalTask:
|
|
2964
|
-
completedTasks:
|
|
2965
|
-
currentTaskId:
|
|
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 =
|
|
3221
|
+
var decodeSession = S10.decodeUnknown(SessionSchema);
|
|
2968
3222
|
|
|
2969
3223
|
// src/domain/schemas/signals.ts
|
|
2970
3224
|
init_esm_shims();
|
|
2971
|
-
import { Schema as
|
|
2972
|
-
var taggedFromData2 = (tag, dataSchema) =>
|
|
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 =
|
|
3006
|
-
var CheckFailedSignalSchema =
|
|
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 =
|
|
3016
|
-
var
|
|
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 =
|
|
3032
|
-
var decodeSignalSync =
|
|
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
|
|
3037
|
-
var GeneratedTaskStatusSchema =
|
|
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 =
|
|
3044
|
-
id:
|
|
3045
|
-
title:
|
|
3314
|
+
var GeneratedTaskSchema = S12.Struct({
|
|
3315
|
+
id: S12.String,
|
|
3316
|
+
title: S12.String,
|
|
3046
3317
|
status: GeneratedTaskStatusSchema
|
|
3047
3318
|
});
|
|
3048
|
-
var GeneratedTaskListSchema =
|
|
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
|
|
3108
|
-
var ViewModeSchema =
|
|
3109
|
-
var LoopStatusSchema =
|
|
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 =
|
|
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 =
|
|
3396
|
+
var TUIPhaseStatusSchema = S13.Literal(
|
|
3126
3397
|
"pending",
|
|
3127
3398
|
"in_progress",
|
|
3128
3399
|
"done",
|
|
3129
3400
|
"failed"
|
|
3130
3401
|
);
|
|
3131
|
-
var TUICriterionStatusSchema =
|
|
3402
|
+
var TUICriterionStatusSchema = S13.Literal(
|
|
3132
3403
|
"pending",
|
|
3133
3404
|
"passed",
|
|
3134
3405
|
"failed"
|
|
3135
3406
|
);
|
|
3136
|
-
var TUITaskStatusSchema =
|
|
3407
|
+
var TUITaskStatusSchema = S13.Literal(
|
|
3137
3408
|
"pending",
|
|
3138
3409
|
"in_progress",
|
|
3139
3410
|
"done",
|
|
3140
3411
|
"failed"
|
|
3141
3412
|
);
|
|
3142
|
-
var TUIPhaseSchema =
|
|
3143
|
-
id:
|
|
3144
|
-
description:
|
|
3413
|
+
var TUIPhaseSchema = S13.Struct({
|
|
3414
|
+
id: S13.String,
|
|
3415
|
+
description: S13.String,
|
|
3145
3416
|
status: TUIPhaseStatusSchema,
|
|
3146
|
-
startedAt:
|
|
3147
|
-
completedAt:
|
|
3417
|
+
startedAt: S13.optional(S13.Number),
|
|
3418
|
+
completedAt: S13.optional(S13.Number)
|
|
3148
3419
|
});
|
|
3149
|
-
var TUICriterionSchema =
|
|
3150
|
-
id:
|
|
3151
|
-
description:
|
|
3420
|
+
var TUICriterionSchema = S13.Struct({
|
|
3421
|
+
id: S13.String,
|
|
3422
|
+
description: S13.String,
|
|
3152
3423
|
status: TUICriterionStatusSchema,
|
|
3153
|
-
failureReason:
|
|
3424
|
+
failureReason: S13.optional(S13.String)
|
|
3154
3425
|
});
|
|
3155
|
-
var TUITaskSchema =
|
|
3156
|
-
id:
|
|
3157
|
-
title:
|
|
3426
|
+
var TUITaskSchema = S13.Struct({
|
|
3427
|
+
id: S13.String,
|
|
3428
|
+
title: S13.String,
|
|
3158
3429
|
status: TUITaskStatusSchema,
|
|
3159
|
-
phases:
|
|
3160
|
-
criteria:
|
|
3161
|
-
startedAt:
|
|
3162
|
-
completedAt:
|
|
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 =
|
|
3435
|
+
var TUIStateSchema = S13.Struct({
|
|
3165
3436
|
// Loop info
|
|
3166
|
-
task:
|
|
3167
|
-
iteration:
|
|
3168
|
-
maxIterations:
|
|
3437
|
+
task: S13.String,
|
|
3438
|
+
iteration: S13.Number,
|
|
3439
|
+
maxIterations: S13.Number,
|
|
3169
3440
|
status: LoopStatusSchema,
|
|
3170
|
-
startTime:
|
|
3441
|
+
startTime: S13.Number,
|
|
3171
3442
|
// Discovery phase
|
|
3172
|
-
discoveryInProgress:
|
|
3173
|
-
discoveryCompleted:
|
|
3443
|
+
discoveryInProgress: S13.Boolean,
|
|
3444
|
+
discoveryCompleted: S13.Boolean,
|
|
3174
3445
|
// Current activity
|
|
3175
3446
|
executionMode: ExecutionModeSchema,
|
|
3176
|
-
currentTool:
|
|
3177
|
-
currentTaskId:
|
|
3447
|
+
currentTool: S13.optional(S13.String),
|
|
3448
|
+
currentTaskId: S13.optional(S13.String),
|
|
3178
3449
|
// Output
|
|
3179
|
-
outputLines:
|
|
3180
|
-
partialLine:
|
|
3450
|
+
outputLines: S13.Array(S13.String),
|
|
3451
|
+
partialLine: S13.String,
|
|
3181
3452
|
// Tasks
|
|
3182
|
-
tasks:
|
|
3453
|
+
tasks: S13.Array(TUITaskSchema),
|
|
3183
3454
|
// Navigation
|
|
3184
3455
|
viewMode: ViewModeSchema,
|
|
3185
|
-
selectedTaskIndex:
|
|
3186
|
-
scrollOffset:
|
|
3187
|
-
userScrolled:
|
|
3456
|
+
selectedTaskIndex: S13.Number,
|
|
3457
|
+
scrollOffset: S13.Number,
|
|
3458
|
+
userScrolled: S13.Boolean,
|
|
3188
3459
|
// Git
|
|
3189
|
-
gitBranch:
|
|
3190
|
-
gitPushed:
|
|
3191
|
-
prUrl:
|
|
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
|
|
3197
|
-
var PlanStore = class extends
|
|
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
|
|
3202
|
-
function
|
|
3203
|
-
return
|
|
3204
|
-
try: () =>
|
|
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(
|
|
3949
|
+
}).pipe(Effect11.asVoid);
|
|
3211
3950
|
}
|
|
3212
|
-
function
|
|
3213
|
-
return
|
|
3951
|
+
function getSessionDir2(sessionId) {
|
|
3952
|
+
return join3(process.cwd(), PLANS_DIR2, sessionId);
|
|
3214
3953
|
}
|
|
3215
3954
|
function getPlanPath(sessionId, planId) {
|
|
3216
|
-
return
|
|
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
|
|
3226
|
-
const parsed = yield*
|
|
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
|
-
|
|
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
|
|
3250
|
-
create: (sessionId, plan) =>
|
|
3251
|
-
const sessionDir =
|
|
3252
|
-
yield*
|
|
3253
|
-
const existingPlans = yield*
|
|
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*
|
|
3271
|
-
try: () =>
|
|
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) =>
|
|
4019
|
+
load: (planId, sessionId) => Effect11.gen(function* () {
|
|
3281
4020
|
if (sessionId) {
|
|
3282
4021
|
const planPath = getPlanPath(sessionId, planId);
|
|
3283
|
-
const content = yield*
|
|
3284
|
-
try: () =>
|
|
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*
|
|
4032
|
+
const sessionDirs = yield* Effect11.tryPromise({
|
|
3294
4033
|
try: async () => {
|
|
3295
|
-
const plansDir =
|
|
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*
|
|
4046
|
+
const exists = yield* Effect11.tryPromise({
|
|
3308
4047
|
try: async () => {
|
|
3309
|
-
await
|
|
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(
|
|
4055
|
+
}).pipe(Effect11.orElseSucceed(() => false));
|
|
3317
4056
|
if (exists) {
|
|
3318
|
-
const content = yield*
|
|
3319
|
-
try: () =>
|
|
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*
|
|
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) =>
|
|
4075
|
+
update: (planId, plan) => Effect11.gen(function* () {
|
|
3337
4076
|
const planPath = getPlanPath(plan.sessionId, planId);
|
|
3338
|
-
yield*
|
|
3339
|
-
try: () =>
|
|
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) =>
|
|
3348
|
-
const sessionDir =
|
|
3349
|
-
const files = yield*
|
|
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
|
|
4105
|
+
var Live7 = Layer7.succeed(PlanStore, make4);
|
|
3367
4106
|
var FileSystemPlan = {
|
|
3368
|
-
Live:
|
|
4107
|
+
Live: Live7
|
|
3369
4108
|
};
|
|
3370
4109
|
|
|
3371
4110
|
// src/layers/plan/memory.ts
|
|
3372
4111
|
init_esm_shims();
|
|
3373
|
-
import { Effect as
|
|
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) =>
|
|
3377
|
-
const state = yield*
|
|
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*
|
|
4127
|
+
yield* Ref5.set(stateRef, state);
|
|
3389
4128
|
return planId;
|
|
3390
4129
|
}),
|
|
3391
|
-
load: (planId, sessionId) =>
|
|
3392
|
-
const state = yield*
|
|
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*
|
|
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*
|
|
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) =>
|
|
3420
|
-
const state = yield*
|
|
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*
|
|
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*
|
|
4170
|
+
yield* Ref5.set(stateRef, state);
|
|
3432
4171
|
}),
|
|
3433
|
-
list: (sessionId) =>
|
|
3434
|
-
const state = yield*
|
|
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
|
|
3444
|
-
return
|
|
4182
|
+
function layer4() {
|
|
4183
|
+
return Layer8.effect(
|
|
3445
4184
|
PlanStore,
|
|
3446
|
-
|
|
3447
|
-
const stateRef = yield*
|
|
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
|
|
4191
|
+
var Live8 = layer4();
|
|
3453
4192
|
var MemoryPlan = {
|
|
3454
|
-
Live:
|
|
3455
|
-
layer:
|
|
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
|
|
3461
|
-
import { join as
|
|
3462
|
-
import { DateTime, Effect as
|
|
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
|
|
3468
|
-
var SessionStore = class extends
|
|
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
|
|
3478
|
-
return
|
|
3479
|
-
try: () =>
|
|
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(
|
|
4428
|
+
}).pipe(Effect15.asVoid);
|
|
3486
4429
|
}
|
|
3487
4430
|
function getSessionPath(sessionId) {
|
|
3488
|
-
return
|
|
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
|
|
3495
|
-
const parsed = yield*
|
|
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
|
-
|
|
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
|
|
3516
|
-
create: (originalTask) =>
|
|
3517
|
-
const sessionsDir =
|
|
3518
|
-
yield*
|
|
3519
|
-
const now = yield*
|
|
3520
|
-
const timestampMs =
|
|
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:
|
|
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*
|
|
3531
|
-
try: () =>
|
|
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) =>
|
|
4483
|
+
get: (sessionId) => Effect15.gen(function* () {
|
|
3541
4484
|
const sessionPath = getSessionPath(sessionId);
|
|
3542
|
-
const content = yield*
|
|
3543
|
-
try: () =>
|
|
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) =>
|
|
4495
|
+
update: (sessionId, session) => Effect15.gen(function* () {
|
|
3553
4496
|
const sessionPath = getSessionPath(sessionId);
|
|
3554
|
-
yield*
|
|
3555
|
-
try: () =>
|
|
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
|
|
4507
|
+
var Live11 = Layer11.succeed(SessionStore, make6);
|
|
3565
4508
|
var FileSystemSession = {
|
|
3566
|
-
Live:
|
|
4509
|
+
Live: Live11
|
|
3567
4510
|
};
|
|
3568
4511
|
|
|
3569
4512
|
// src/layers/session/memory.ts
|
|
3570
4513
|
init_esm_shims();
|
|
3571
|
-
import { DateTime as
|
|
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) =>
|
|
3575
|
-
const state = yield*
|
|
3576
|
-
const counter = yield*
|
|
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*
|
|
4521
|
+
const now = yield* DateTime6.now;
|
|
3579
4522
|
const session = {
|
|
3580
4523
|
id: sessionId,
|
|
3581
|
-
createdAt:
|
|
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*
|
|
4530
|
+
yield* Ref7.set(stateRef, state);
|
|
3588
4531
|
return session;
|
|
3589
4532
|
}),
|
|
3590
|
-
get: (sessionId) =>
|
|
3591
|
-
const state = yield*
|
|
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*
|
|
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) =>
|
|
3604
|
-
const state = yield*
|
|
4546
|
+
update: (sessionId, session) => Effect16.gen(function* () {
|
|
4547
|
+
const state = yield* Ref7.get(stateRef);
|
|
3605
4548
|
if (!state.has(sessionId)) {
|
|
3606
|
-
return yield*
|
|
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*
|
|
4557
|
+
yield* Ref7.set(stateRef, state);
|
|
3615
4558
|
})
|
|
3616
4559
|
};
|
|
3617
4560
|
}
|
|
3618
|
-
function
|
|
3619
|
-
return
|
|
4561
|
+
function layer6() {
|
|
4562
|
+
return Layer12.effect(
|
|
3620
4563
|
SessionStore,
|
|
3621
|
-
|
|
3622
|
-
const stateRef = yield*
|
|
3623
|
-
const counterRef = yield*
|
|
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
|
|
4571
|
+
var Live12 = layer6();
|
|
3629
4572
|
var MemorySession = {
|
|
3630
|
-
Live:
|
|
3631
|
-
layer:
|
|
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
|
|
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
|
|
3641
|
-
var SignalParser = class extends
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
4068
|
-
const chunksRef = yield*
|
|
4069
|
-
const emittedRef = yield*
|
|
4070
|
-
const feed = (text) =>
|
|
4071
|
-
const chunks = yield*
|
|
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*
|
|
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*
|
|
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*
|
|
5114
|
+
yield* Ref8.set(chunksRef, [buffer.slice(lastEndPos)]);
|
|
4093
5115
|
}
|
|
4094
5116
|
}
|
|
4095
|
-
yield*
|
|
5117
|
+
yield* Ref8.set(emittedRef, emitted);
|
|
4096
5118
|
return newSignals;
|
|
4097
5119
|
});
|
|
4098
|
-
const flush = () =>
|
|
4099
|
-
const chunks = yield*
|
|
5120
|
+
const flush = () => Effect17.gen(function* () {
|
|
5121
|
+
const chunks = yield* Ref8.get(chunksRef);
|
|
4100
5122
|
const buffer = chunks.join("");
|
|
4101
|
-
yield*
|
|
4102
|
-
const emitted = yield*
|
|
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*
|
|
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
|
|
4114
|
-
parse: (text) =>
|
|
5135
|
+
var make7 = {
|
|
5136
|
+
parse: (text) => Effect17.succeed(signalSpecRegistry.parseAll(text)),
|
|
4115
5137
|
createAccumulator: createAccumulatorImpl
|
|
4116
5138
|
};
|
|
4117
|
-
var
|
|
5139
|
+
var Live13 = Layer13.succeed(SignalParser, make7);
|
|
4118
5140
|
var FerixParser = {
|
|
4119
|
-
Live:
|
|
5141
|
+
Live: Live13
|
|
4120
5142
|
};
|
|
4121
5143
|
|
|
4122
5144
|
// src/layers/index.ts
|
|
4123
|
-
var ProductionLayers =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4158
|
-
import { join as
|
|
4159
|
-
import { Effect as
|
|
4160
|
-
var
|
|
4161
|
-
function
|
|
4162
|
-
return
|
|
4163
|
-
try: () =>
|
|
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(
|
|
5200
|
+
}).pipe(Effect18.asVoid);
|
|
4170
5201
|
}
|
|
4171
|
-
function
|
|
4172
|
-
return
|
|
5202
|
+
function getSessionDir4(sessionId) {
|
|
5203
|
+
return join6(process.cwd(), PLANS_DIR4, sessionId);
|
|
4173
5204
|
}
|
|
4174
5205
|
function getTasksMdPath(sessionId) {
|
|
4175
|
-
return
|
|
5206
|
+
return join6(getSessionDir4(sessionId), "tasks.md");
|
|
4176
5207
|
}
|
|
4177
5208
|
function writeTasksMd(sessionId, tasks) {
|
|
4178
|
-
return
|
|
4179
|
-
const sessionDir =
|
|
4180
|
-
yield*
|
|
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*
|
|
4184
|
-
try: () =>
|
|
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
|
|
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
|
-
|
|
4631
|
-
|
|
4632
|
-
(error) =>
|
|
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
|
|
4643
|
-
const currentPlan = yield*
|
|
4644
|
-
const now = yield*
|
|
4645
|
-
const timestamp =
|
|
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*
|
|
4656
|
-
yield*
|
|
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
|
|
4665
|
-
const state = yield*
|
|
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*
|
|
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*
|
|
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
|
|
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
|
-
|
|
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
|
|
6009
|
+
return Effect20.gen(function* () {
|
|
4933
6010
|
const events = [];
|
|
4934
6011
|
const parsedSignals = [];
|
|
4935
6012
|
const signals = yield* signalParser.parse(text).pipe(
|
|
4936
|
-
|
|
4937
|
-
(error) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5001
|
-
const startTimeUtc = yield*
|
|
5002
|
-
const startTime =
|
|
5003
|
-
const persistenceStateRef = yield*
|
|
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
|
-
|
|
5028
|
-
const now = yield*
|
|
6104
|
+
Effect20.gen(function* () {
|
|
6105
|
+
const now = yield* DateTime8.now;
|
|
5029
6106
|
const context = {
|
|
5030
|
-
timestamp:
|
|
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
|
-
|
|
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*
|
|
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
|
-
|
|
5076
|
-
(error) =>
|
|
6152
|
+
Effect20.tapError(
|
|
6153
|
+
(error) => Effect20.logDebug("Failed to write tasks.md, continuing", {
|
|
5077
6154
|
error: String(error)
|
|
5078
6155
|
})
|
|
5079
6156
|
),
|
|
5080
|
-
|
|
6157
|
+
Effect20.orElseSucceed(() => void 0)
|
|
5081
6158
|
);
|
|
5082
6159
|
}
|
|
5083
|
-
const endTimeUtc = yield*
|
|
5084
|
-
const endTime =
|
|
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
|
|
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
|
|
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
|
-
|
|
5113
|
-
(error) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5168
|
-
const currentPlan = yield*
|
|
5169
|
-
const persistenceStateRef = yield*
|
|
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(
|
|
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
|
-
|
|
5188
|
-
const now = yield*
|
|
6267
|
+
Effect21.gen(function* () {
|
|
6268
|
+
const now = yield* DateTime9.now;
|
|
5189
6269
|
const context = {
|
|
5190
|
-
timestamp:
|
|
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*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
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) =>
|
|
5288
|
-
const completed = yield*
|
|
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
|
-
|
|
5324
|
-
(error) =>
|
|
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.
|
|
5339
|
-
|
|
5340
|
-
const endTimeUtc = yield*
|
|
5341
|
-
const durationMs =
|
|
5342
|
-
const completed = yield*
|
|
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
|
-
|
|
5355
|
-
(error) =>
|
|
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
|
-
|
|
6484
|
+
Effect22.orElseSucceed(() => void 0)
|
|
5361
6485
|
);
|
|
5362
|
-
const
|
|
5363
|
-
return
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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(
|
|
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("-
|
|
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,
|