ferix-code 0.0.2-beta.5 → 0.0.2-beta.7
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 +226 -20
- package/dist/index.js +747 -305
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -71,7 +71,7 @@ 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.7",
|
|
75
75
|
description: "Composable RALPH loops for AI coding agents - v2 with Effect",
|
|
76
76
|
type: "module",
|
|
77
77
|
bin: {
|
|
@@ -86,7 +86,8 @@ var package_default = {
|
|
|
86
86
|
dev: "tsup --watch",
|
|
87
87
|
"check-types": "tsc --noEmit",
|
|
88
88
|
test: "bun test",
|
|
89
|
-
"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"
|
|
90
91
|
},
|
|
91
92
|
dependencies: {
|
|
92
93
|
commander: "^14.0.0",
|
|
@@ -117,7 +118,7 @@ var package_default = {
|
|
|
117
118
|
|
|
118
119
|
// src/program.ts
|
|
119
120
|
init_esm_shims();
|
|
120
|
-
import { Effect as
|
|
121
|
+
import { Effect as Effect23, Stream as Stream10 } from "effect";
|
|
121
122
|
|
|
122
123
|
// src/consumers/index.ts
|
|
123
124
|
init_esm_shims();
|
|
@@ -2216,6 +2217,7 @@ var ANSIOutput = class {
|
|
|
2216
2217
|
init_esm_shims();
|
|
2217
2218
|
|
|
2218
2219
|
// src/consumers/tui/consumer.ts
|
|
2220
|
+
var CLOCK_REFRESH_INTERVAL_MS = 1e3;
|
|
2219
2221
|
function formatErrorToLines2(err, lines) {
|
|
2220
2222
|
if (err instanceof Error) {
|
|
2221
2223
|
lines.push(` - ${err.name}: ${err.message}`);
|
|
@@ -2275,6 +2277,17 @@ function createTUIConsumer() {
|
|
|
2275
2277
|
const inputFiber = yield* Effect3.forkDaemon(
|
|
2276
2278
|
runInputLoop(stateRef, output)
|
|
2277
2279
|
);
|
|
2280
|
+
const clockFiber = yield* Effect3.forkDaemon(
|
|
2281
|
+
Effect3.forever(
|
|
2282
|
+
Effect3.gen(function* () {
|
|
2283
|
+
yield* Effect3.sleep(CLOCK_REFRESH_INTERVAL_MS);
|
|
2284
|
+
const state = yield* Ref2.get(stateRef);
|
|
2285
|
+
if (state.status === "running") {
|
|
2286
|
+
yield* Effect3.sync(() => safeRender(state, output));
|
|
2287
|
+
}
|
|
2288
|
+
})
|
|
2289
|
+
)
|
|
2290
|
+
);
|
|
2278
2291
|
const processEvents = events.pipe(
|
|
2279
2292
|
Stream3.runForEach(
|
|
2280
2293
|
(event) => Effect3.gen(function* () {
|
|
@@ -2326,6 +2339,7 @@ ${errorText}
|
|
|
2326
2339
|
})
|
|
2327
2340
|
);
|
|
2328
2341
|
}
|
|
2342
|
+
yield* Fiber.interrupt(clockFiber);
|
|
2329
2343
|
unsubscribeResize();
|
|
2330
2344
|
cleanup();
|
|
2331
2345
|
})
|
|
@@ -2334,13 +2348,15 @@ ${errorText}
|
|
|
2334
2348
|
|
|
2335
2349
|
// src/layers/index.ts
|
|
2336
2350
|
init_esm_shims();
|
|
2337
|
-
import { Layer as
|
|
2351
|
+
import { Layer as Layer14 } from "effect";
|
|
2338
2352
|
|
|
2339
|
-
// src/layers/
|
|
2353
|
+
// src/layers/git/file-system.ts
|
|
2340
2354
|
init_esm_shims();
|
|
2341
|
-
import {
|
|
2355
|
+
import { exec } from "child_process";
|
|
2356
|
+
import { access, mkdir, rm } from "fs/promises";
|
|
2342
2357
|
import { join } from "path";
|
|
2343
|
-
import {
|
|
2358
|
+
import { promisify } from "util";
|
|
2359
|
+
import { Effect as Effect4, Layer } from "effect";
|
|
2344
2360
|
|
|
2345
2361
|
// src/domain/errors.ts
|
|
2346
2362
|
init_esm_shims();
|
|
@@ -2361,6 +2377,338 @@ var GuardrailsStoreError = class extends Data.TaggedError(
|
|
|
2361
2377
|
};
|
|
2362
2378
|
var OrchestratorError = class extends Data.TaggedError("OrchestratorError") {
|
|
2363
2379
|
};
|
|
2380
|
+
var GitError = class extends Data.TaggedError("GitError") {
|
|
2381
|
+
};
|
|
2382
|
+
|
|
2383
|
+
// src/services/git.ts
|
|
2384
|
+
init_esm_shims();
|
|
2385
|
+
import { Context } from "effect";
|
|
2386
|
+
var Git = class extends Context.Tag("@ferix/Git")() {
|
|
2387
|
+
};
|
|
2388
|
+
|
|
2389
|
+
// src/layers/git/file-system.ts
|
|
2390
|
+
var execAsync = promisify(exec);
|
|
2391
|
+
var WORKTREES_DIR = ".ferix/worktrees";
|
|
2392
|
+
var BRANCH_PREFIX = "ferix";
|
|
2393
|
+
function getWorktreeDir(sessionId) {
|
|
2394
|
+
return join(process.cwd(), WORKTREES_DIR, sessionId);
|
|
2395
|
+
}
|
|
2396
|
+
function getBranchName(sessionId) {
|
|
2397
|
+
return `${BRANCH_PREFIX}/${sessionId}`;
|
|
2398
|
+
}
|
|
2399
|
+
function gitExec(command, cwd) {
|
|
2400
|
+
return Effect4.tryPromise({
|
|
2401
|
+
try: async () => {
|
|
2402
|
+
const { stdout } = await execAsync(command, { cwd });
|
|
2403
|
+
return stdout.trim();
|
|
2404
|
+
},
|
|
2405
|
+
catch: (error) => {
|
|
2406
|
+
const execError = error;
|
|
2407
|
+
return new GitError({
|
|
2408
|
+
message: execError.stderr || execError.message || String(error),
|
|
2409
|
+
operation: "status",
|
|
2410
|
+
cause: error
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
function directoryExists(dirPath) {
|
|
2416
|
+
return Effect4.tryPromise({
|
|
2417
|
+
try: async () => {
|
|
2418
|
+
await access(dirPath);
|
|
2419
|
+
return true;
|
|
2420
|
+
},
|
|
2421
|
+
catch: () => new Error("Directory does not exist")
|
|
2422
|
+
}).pipe(Effect4.orElseSucceed(() => false));
|
|
2423
|
+
}
|
|
2424
|
+
var make = {
|
|
2425
|
+
createWorktree: (sessionId, baseBranch) => Effect4.gen(function* () {
|
|
2426
|
+
const worktreeDir = getWorktreeDir(sessionId);
|
|
2427
|
+
const branchName = getBranchName(sessionId);
|
|
2428
|
+
const worktreesBase = join(process.cwd(), WORKTREES_DIR);
|
|
2429
|
+
yield* Effect4.tryPromise({
|
|
2430
|
+
try: () => mkdir(worktreesBase, { recursive: true }),
|
|
2431
|
+
catch: (error) => new GitError({
|
|
2432
|
+
message: `Failed to create worktrees directory: ${String(error)}`,
|
|
2433
|
+
operation: "createWorktree",
|
|
2434
|
+
cause: error
|
|
2435
|
+
})
|
|
2436
|
+
});
|
|
2437
|
+
const exists = yield* directoryExists(worktreeDir);
|
|
2438
|
+
if (exists) {
|
|
2439
|
+
return worktreeDir;
|
|
2440
|
+
}
|
|
2441
|
+
const baseRef = baseBranch || "HEAD";
|
|
2442
|
+
const command = `git worktree add "${worktreeDir}" -b "${branchName}" ${baseRef}`;
|
|
2443
|
+
yield* gitExec(command).pipe(
|
|
2444
|
+
Effect4.mapError(
|
|
2445
|
+
(error) => new GitError({
|
|
2446
|
+
message: `Failed to create worktree: ${error.message}`,
|
|
2447
|
+
operation: "createWorktree",
|
|
2448
|
+
cause: error
|
|
2449
|
+
})
|
|
2450
|
+
)
|
|
2451
|
+
);
|
|
2452
|
+
return worktreeDir;
|
|
2453
|
+
}),
|
|
2454
|
+
removeWorktree: (sessionId) => Effect4.gen(function* () {
|
|
2455
|
+
const worktreeDir = getWorktreeDir(sessionId);
|
|
2456
|
+
const branchName = getBranchName(sessionId);
|
|
2457
|
+
const exists = yield* directoryExists(worktreeDir);
|
|
2458
|
+
if (!exists) {
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
yield* gitExec(`git worktree remove "${worktreeDir}" --force`).pipe(
|
|
2462
|
+
Effect4.mapError(
|
|
2463
|
+
(error) => new GitError({
|
|
2464
|
+
message: `Failed to remove worktree: ${error.message}`,
|
|
2465
|
+
operation: "removeWorktree",
|
|
2466
|
+
cause: error
|
|
2467
|
+
})
|
|
2468
|
+
),
|
|
2469
|
+
// If git worktree remove fails, try manual cleanup
|
|
2470
|
+
Effect4.catchAll(
|
|
2471
|
+
() => Effect4.tryPromise({
|
|
2472
|
+
try: () => rm(worktreeDir, { recursive: true, force: true }),
|
|
2473
|
+
catch: (error) => new GitError({
|
|
2474
|
+
message: `Failed to remove worktree directory: ${String(error)}`,
|
|
2475
|
+
operation: "removeWorktree",
|
|
2476
|
+
cause: error
|
|
2477
|
+
})
|
|
2478
|
+
})
|
|
2479
|
+
)
|
|
2480
|
+
);
|
|
2481
|
+
yield* gitExec(`git branch -D "${branchName}"`).pipe(
|
|
2482
|
+
Effect4.catchAll(() => Effect4.succeed(void 0))
|
|
2483
|
+
);
|
|
2484
|
+
yield* gitExec("git worktree prune").pipe(
|
|
2485
|
+
Effect4.catchAll(() => Effect4.succeed(void 0))
|
|
2486
|
+
);
|
|
2487
|
+
}),
|
|
2488
|
+
getWorktreePath: (sessionId) => Effect4.gen(function* () {
|
|
2489
|
+
const worktreeDir = getWorktreeDir(sessionId);
|
|
2490
|
+
const exists = yield* directoryExists(worktreeDir);
|
|
2491
|
+
return exists ? worktreeDir : void 0;
|
|
2492
|
+
}),
|
|
2493
|
+
commitChanges: (sessionId, message) => Effect4.gen(function* () {
|
|
2494
|
+
const worktreeDir = getWorktreeDir(sessionId);
|
|
2495
|
+
const exists = yield* directoryExists(worktreeDir);
|
|
2496
|
+
if (!exists) {
|
|
2497
|
+
return yield* Effect4.fail(
|
|
2498
|
+
new GitError({
|
|
2499
|
+
message: `Worktree not found for session: ${sessionId}`,
|
|
2500
|
+
operation: "commit"
|
|
2501
|
+
})
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
yield* gitExec("git add -A", worktreeDir).pipe(
|
|
2505
|
+
Effect4.mapError(
|
|
2506
|
+
(error) => new GitError({
|
|
2507
|
+
message: `Failed to stage changes: ${error.message}`,
|
|
2508
|
+
operation: "commit",
|
|
2509
|
+
cause: error
|
|
2510
|
+
})
|
|
2511
|
+
)
|
|
2512
|
+
);
|
|
2513
|
+
const status = yield* gitExec("git status --porcelain", worktreeDir).pipe(
|
|
2514
|
+
Effect4.catchAll(() => Effect4.succeed(""))
|
|
2515
|
+
);
|
|
2516
|
+
if (!status) {
|
|
2517
|
+
const head = yield* gitExec("git rev-parse HEAD", worktreeDir).pipe(
|
|
2518
|
+
Effect4.mapError(
|
|
2519
|
+
(error) => new GitError({
|
|
2520
|
+
message: `Failed to get HEAD: ${error.message}`,
|
|
2521
|
+
operation: "commit",
|
|
2522
|
+
cause: error
|
|
2523
|
+
})
|
|
2524
|
+
)
|
|
2525
|
+
);
|
|
2526
|
+
return head;
|
|
2527
|
+
}
|
|
2528
|
+
const escapedMessage = message.replace(/"/g, '\\"');
|
|
2529
|
+
yield* gitExec(`git commit -m "${escapedMessage}"`, worktreeDir).pipe(
|
|
2530
|
+
Effect4.mapError(
|
|
2531
|
+
(error) => new GitError({
|
|
2532
|
+
message: `Failed to commit: ${error.message}`,
|
|
2533
|
+
operation: "commit",
|
|
2534
|
+
cause: error
|
|
2535
|
+
})
|
|
2536
|
+
)
|
|
2537
|
+
);
|
|
2538
|
+
const hash = yield* gitExec("git rev-parse HEAD", worktreeDir).pipe(
|
|
2539
|
+
Effect4.mapError(
|
|
2540
|
+
(error) => new GitError({
|
|
2541
|
+
message: `Failed to get commit hash: ${error.message}`,
|
|
2542
|
+
operation: "commit",
|
|
2543
|
+
cause: error
|
|
2544
|
+
})
|
|
2545
|
+
)
|
|
2546
|
+
);
|
|
2547
|
+
return hash;
|
|
2548
|
+
}),
|
|
2549
|
+
pushBranch: (sessionId) => Effect4.gen(function* () {
|
|
2550
|
+
const worktreeDir = getWorktreeDir(sessionId);
|
|
2551
|
+
const branchName = getBranchName(sessionId);
|
|
2552
|
+
const exists = yield* directoryExists(worktreeDir);
|
|
2553
|
+
if (!exists) {
|
|
2554
|
+
return yield* Effect4.fail(
|
|
2555
|
+
new GitError({
|
|
2556
|
+
message: `Worktree not found for session: ${sessionId}`,
|
|
2557
|
+
operation: "push"
|
|
2558
|
+
})
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
yield* gitExec(`git push -u origin "${branchName}"`, worktreeDir).pipe(
|
|
2562
|
+
Effect4.mapError(
|
|
2563
|
+
(error) => new GitError({
|
|
2564
|
+
message: `Failed to push branch: ${error.message}`,
|
|
2565
|
+
operation: "push",
|
|
2566
|
+
cause: error
|
|
2567
|
+
})
|
|
2568
|
+
)
|
|
2569
|
+
);
|
|
2570
|
+
}),
|
|
2571
|
+
createPR: (sessionId, title, body) => Effect4.gen(function* () {
|
|
2572
|
+
const worktreeDir = getWorktreeDir(sessionId);
|
|
2573
|
+
const exists = yield* directoryExists(worktreeDir);
|
|
2574
|
+
if (!exists) {
|
|
2575
|
+
return yield* Effect4.fail(
|
|
2576
|
+
new GitError({
|
|
2577
|
+
message: `Worktree not found for session: ${sessionId}`,
|
|
2578
|
+
operation: "createPR"
|
|
2579
|
+
})
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
const escapedTitle = title.replace(/"/g, '\\"');
|
|
2583
|
+
const escapedBody = body.replace(/"/g, '\\"');
|
|
2584
|
+
const prUrl = yield* gitExec(
|
|
2585
|
+
`gh pr create --title "${escapedTitle}" --body "${escapedBody}"`,
|
|
2586
|
+
worktreeDir
|
|
2587
|
+
).pipe(
|
|
2588
|
+
Effect4.mapError(
|
|
2589
|
+
(error) => new GitError({
|
|
2590
|
+
message: `Failed to create PR: ${error.message}`,
|
|
2591
|
+
operation: "createPR",
|
|
2592
|
+
cause: error
|
|
2593
|
+
})
|
|
2594
|
+
)
|
|
2595
|
+
);
|
|
2596
|
+
return prUrl;
|
|
2597
|
+
}),
|
|
2598
|
+
getBranchName
|
|
2599
|
+
};
|
|
2600
|
+
var Live = Layer.succeed(Git, make);
|
|
2601
|
+
var FileSystemGit = {
|
|
2602
|
+
Live
|
|
2603
|
+
};
|
|
2604
|
+
|
|
2605
|
+
// src/layers/git/memory.ts
|
|
2606
|
+
init_esm_shims();
|
|
2607
|
+
import { Effect as Effect5, Layer as Layer2, Ref as Ref3 } from "effect";
|
|
2608
|
+
var BRANCH_PREFIX2 = "ferix";
|
|
2609
|
+
function getBranchName2(sessionId) {
|
|
2610
|
+
return `${BRANCH_PREFIX2}/${sessionId}`;
|
|
2611
|
+
}
|
|
2612
|
+
function createMemoryGitService(stateRef, commitCounterRef) {
|
|
2613
|
+
return {
|
|
2614
|
+
createWorktree: (sessionId, _baseBranch) => Effect5.gen(function* () {
|
|
2615
|
+
const state = yield* Ref3.get(stateRef);
|
|
2616
|
+
const existing = state.get(sessionId);
|
|
2617
|
+
if (existing) {
|
|
2618
|
+
return existing.path;
|
|
2619
|
+
}
|
|
2620
|
+
const path2 = `.ferix/worktrees/${sessionId}`;
|
|
2621
|
+
const branch = getBranchName2(sessionId);
|
|
2622
|
+
const worktree = {
|
|
2623
|
+
path: path2,
|
|
2624
|
+
branch,
|
|
2625
|
+
commits: []
|
|
2626
|
+
};
|
|
2627
|
+
state.set(sessionId, worktree);
|
|
2628
|
+
yield* Ref3.set(stateRef, state);
|
|
2629
|
+
return path2;
|
|
2630
|
+
}),
|
|
2631
|
+
removeWorktree: (sessionId) => Effect5.gen(function* () {
|
|
2632
|
+
const state = yield* Ref3.get(stateRef);
|
|
2633
|
+
state.delete(sessionId);
|
|
2634
|
+
yield* Ref3.set(stateRef, state);
|
|
2635
|
+
}),
|
|
2636
|
+
getWorktreePath: (sessionId) => Effect5.gen(function* () {
|
|
2637
|
+
const state = yield* Ref3.get(stateRef);
|
|
2638
|
+
const worktree = state.get(sessionId);
|
|
2639
|
+
return worktree?.path;
|
|
2640
|
+
}),
|
|
2641
|
+
commitChanges: (sessionId, message) => Effect5.gen(function* () {
|
|
2642
|
+
const state = yield* Ref3.get(stateRef);
|
|
2643
|
+
const worktree = state.get(sessionId);
|
|
2644
|
+
if (!worktree) {
|
|
2645
|
+
return yield* Effect5.fail(
|
|
2646
|
+
new GitError({
|
|
2647
|
+
message: `Worktree not found for session: ${sessionId}`,
|
|
2648
|
+
operation: "commit"
|
|
2649
|
+
})
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
const counter = yield* Ref3.updateAndGet(commitCounterRef, (n) => n + 1);
|
|
2653
|
+
const hash = `test-commit-${counter}`;
|
|
2654
|
+
const updatedWorktree = {
|
|
2655
|
+
...worktree,
|
|
2656
|
+
commits: [...worktree.commits, `${hash}: ${message}`]
|
|
2657
|
+
};
|
|
2658
|
+
state.set(sessionId, updatedWorktree);
|
|
2659
|
+
yield* Ref3.set(stateRef, state);
|
|
2660
|
+
return hash;
|
|
2661
|
+
}),
|
|
2662
|
+
pushBranch: (sessionId) => Effect5.gen(function* () {
|
|
2663
|
+
const state = yield* Ref3.get(stateRef);
|
|
2664
|
+
const worktree = state.get(sessionId);
|
|
2665
|
+
if (!worktree) {
|
|
2666
|
+
return yield* Effect5.fail(
|
|
2667
|
+
new GitError({
|
|
2668
|
+
message: `Worktree not found for session: ${sessionId}`,
|
|
2669
|
+
operation: "push"
|
|
2670
|
+
})
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
}),
|
|
2674
|
+
createPR: (sessionId, title, _body) => Effect5.gen(function* () {
|
|
2675
|
+
const state = yield* Ref3.get(stateRef);
|
|
2676
|
+
const worktree = state.get(sessionId);
|
|
2677
|
+
if (!worktree) {
|
|
2678
|
+
return yield* Effect5.fail(
|
|
2679
|
+
new GitError({
|
|
2680
|
+
message: `Worktree not found for session: ${sessionId}`,
|
|
2681
|
+
operation: "createPR"
|
|
2682
|
+
})
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
const slug = title.toLowerCase().replace(/\s+/g, "-").slice(0, 30);
|
|
2686
|
+
return `https://github.com/test/repo/pull/${slug}`;
|
|
2687
|
+
}),
|
|
2688
|
+
getBranchName: getBranchName2
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
function layer() {
|
|
2692
|
+
return Layer2.effect(
|
|
2693
|
+
Git,
|
|
2694
|
+
Effect5.gen(function* () {
|
|
2695
|
+
const stateRef = yield* Ref3.make(/* @__PURE__ */ new Map());
|
|
2696
|
+
const commitCounterRef = yield* Ref3.make(0);
|
|
2697
|
+
return createMemoryGitService(stateRef, commitCounterRef);
|
|
2698
|
+
})
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
var Live2 = layer();
|
|
2702
|
+
var MemoryGit = {
|
|
2703
|
+
Live: Live2,
|
|
2704
|
+
layer
|
|
2705
|
+
};
|
|
2706
|
+
|
|
2707
|
+
// src/layers/guardrails/file-system.ts
|
|
2708
|
+
init_esm_shims();
|
|
2709
|
+
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
2710
|
+
import { join as join2 } from "path";
|
|
2711
|
+
import { DateTime, Effect as Effect6, Layer as Layer3 } from "effect";
|
|
2364
2712
|
|
|
2365
2713
|
// src/domain/index.ts
|
|
2366
2714
|
init_esm_shims();
|
|
@@ -2648,6 +2996,16 @@ var ProgressUpdatedEventSchema = taggedEvent("ProgressUpdated", {
|
|
|
2648
2996
|
action: S4.Literal("started", "completed", "failed", "learning"),
|
|
2649
2997
|
timestamp: S4.Number
|
|
2650
2998
|
});
|
|
2999
|
+
var WorktreeCreatedEventSchema = taggedEvent("WorktreeCreated", {
|
|
3000
|
+
sessionId: S4.String,
|
|
3001
|
+
worktreePath: S4.String,
|
|
3002
|
+
branchName: S4.String,
|
|
3003
|
+
timestamp: S4.Number
|
|
3004
|
+
});
|
|
3005
|
+
var WorktreeRemovedEventSchema = taggedEvent("WorktreeRemoved", {
|
|
3006
|
+
sessionId: S4.String,
|
|
3007
|
+
timestamp: S4.Number
|
|
3008
|
+
});
|
|
2651
3009
|
var DomainEventSchema = S4.Union(
|
|
2652
3010
|
LoopStartedEventSchema,
|
|
2653
3011
|
LoopCompletedEventSchema,
|
|
@@ -2677,7 +3035,9 @@ var DomainEventSchema = S4.Union(
|
|
|
2677
3035
|
PlanUpdateFailedEventSchema,
|
|
2678
3036
|
LearningRecordedEventSchema,
|
|
2679
3037
|
GuardrailAddedEventSchema,
|
|
2680
|
-
ProgressUpdatedEventSchema
|
|
3038
|
+
ProgressUpdatedEventSchema,
|
|
3039
|
+
WorktreeCreatedEventSchema,
|
|
3040
|
+
WorktreeRemovedEventSchema
|
|
2681
3041
|
);
|
|
2682
3042
|
var DomainEventUtils = {
|
|
2683
3043
|
isLLMEvent: (e) => e._tag.startsWith("LLM"),
|
|
@@ -2805,7 +3165,9 @@ var SessionSchema = S10.Struct({
|
|
|
2805
3165
|
status: SessionStatusSchema,
|
|
2806
3166
|
originalTask: S10.String,
|
|
2807
3167
|
completedTasks: S10.Array(S10.String),
|
|
2808
|
-
currentTaskId: S10.optional(S10.String)
|
|
3168
|
+
currentTaskId: S10.optional(S10.String),
|
|
3169
|
+
worktreePath: S10.optional(S10.String),
|
|
3170
|
+
branchName: S10.optional(S10.String)
|
|
2809
3171
|
});
|
|
2810
3172
|
var decodeSession = S10.decodeUnknown(SessionSchema);
|
|
2811
3173
|
|
|
@@ -3053,34 +3415,34 @@ var TUIStateSchema = S13.Struct({
|
|
|
3053
3415
|
|
|
3054
3416
|
// src/services/guardrails-store.ts
|
|
3055
3417
|
init_esm_shims();
|
|
3056
|
-
import { Context } from "effect";
|
|
3057
|
-
var GuardrailsStore = class extends
|
|
3418
|
+
import { Context as Context2 } from "effect";
|
|
3419
|
+
var GuardrailsStore = class extends Context2.Tag("@ferix/GuardrailsStore")() {
|
|
3058
3420
|
};
|
|
3059
3421
|
|
|
3060
3422
|
// src/layers/guardrails/file-system.ts
|
|
3061
3423
|
var PLANS_DIR = ".ferix/plans";
|
|
3062
3424
|
function ensureDir(dirPath) {
|
|
3063
|
-
return
|
|
3064
|
-
try: () =>
|
|
3425
|
+
return Effect6.tryPromise({
|
|
3426
|
+
try: () => mkdir2(dirPath, { recursive: true }),
|
|
3065
3427
|
catch: (error) => new GuardrailsStoreError({
|
|
3066
3428
|
message: `Failed to create directory: ${dirPath}`,
|
|
3067
3429
|
operation: "add",
|
|
3068
3430
|
cause: error
|
|
3069
3431
|
})
|
|
3070
|
-
}).pipe(
|
|
3432
|
+
}).pipe(Effect6.asVoid);
|
|
3071
3433
|
}
|
|
3072
3434
|
function getSessionDir(sessionId) {
|
|
3073
|
-
return
|
|
3435
|
+
return join2(process.cwd(), PLANS_DIR, sessionId);
|
|
3074
3436
|
}
|
|
3075
3437
|
function getGuardrailsPath(sessionId) {
|
|
3076
|
-
return
|
|
3438
|
+
return join2(getSessionDir(sessionId), "guardrails.json");
|
|
3077
3439
|
}
|
|
3078
3440
|
function serializeGuardrails(guardrails) {
|
|
3079
3441
|
return JSON.stringify(guardrails, null, 2);
|
|
3080
3442
|
}
|
|
3081
3443
|
function deserializeGuardrails(json) {
|
|
3082
|
-
return
|
|
3083
|
-
const parsed = yield*
|
|
3444
|
+
return Effect6.gen(function* () {
|
|
3445
|
+
const parsed = yield* Effect6.try({
|
|
3084
3446
|
try: () => JSON.parse(json),
|
|
3085
3447
|
catch: (error) => new GuardrailsStoreError({
|
|
3086
3448
|
message: `Invalid JSON in guardrails file: ${String(error)}`,
|
|
@@ -3089,7 +3451,7 @@ function deserializeGuardrails(json) {
|
|
|
3089
3451
|
})
|
|
3090
3452
|
});
|
|
3091
3453
|
const validated = yield* decodeGuardrailsFile(parsed).pipe(
|
|
3092
|
-
|
|
3454
|
+
Effect6.mapError(
|
|
3093
3455
|
(error) => new GuardrailsStoreError({
|
|
3094
3456
|
message: `Guardrails validation failed: ${String(error)}`,
|
|
3095
3457
|
operation: "load",
|
|
@@ -3107,12 +3469,12 @@ function createEmptyGuardrails(sessionId, createdAt) {
|
|
|
3107
3469
|
guardrails: []
|
|
3108
3470
|
};
|
|
3109
3471
|
}
|
|
3110
|
-
var
|
|
3111
|
-
add: (sessionId, guardrail) =>
|
|
3472
|
+
var make2 = {
|
|
3473
|
+
add: (sessionId, guardrail) => Effect6.gen(function* () {
|
|
3112
3474
|
const sessionDir = getSessionDir(sessionId);
|
|
3113
3475
|
yield* ensureDir(sessionDir);
|
|
3114
3476
|
const guardrailsPath = getGuardrailsPath(sessionId);
|
|
3115
|
-
const existing = yield*
|
|
3477
|
+
const existing = yield* Effect6.tryPromise({
|
|
3116
3478
|
try: async () => {
|
|
3117
3479
|
try {
|
|
3118
3480
|
const content = await readFile(guardrailsPath, "utf-8");
|
|
@@ -3130,7 +3492,7 @@ var make = {
|
|
|
3130
3492
|
let guardrails;
|
|
3131
3493
|
if (existing) {
|
|
3132
3494
|
guardrails = yield* deserializeGuardrails(existing).pipe(
|
|
3133
|
-
|
|
3495
|
+
Effect6.mapError(
|
|
3134
3496
|
(err) => new GuardrailsStoreError({
|
|
3135
3497
|
message: err.message,
|
|
3136
3498
|
operation: "add",
|
|
@@ -3146,7 +3508,7 @@ var make = {
|
|
|
3146
3508
|
...guardrails,
|
|
3147
3509
|
guardrails: [...guardrails.guardrails, guardrail]
|
|
3148
3510
|
};
|
|
3149
|
-
yield*
|
|
3511
|
+
yield* Effect6.tryPromise({
|
|
3150
3512
|
try: () => writeFile(
|
|
3151
3513
|
guardrailsPath,
|
|
3152
3514
|
serializeGuardrails(updatedGuardrails),
|
|
@@ -3159,9 +3521,9 @@ var make = {
|
|
|
3159
3521
|
})
|
|
3160
3522
|
});
|
|
3161
3523
|
}),
|
|
3162
|
-
load: (sessionId) =>
|
|
3524
|
+
load: (sessionId) => Effect6.gen(function* () {
|
|
3163
3525
|
const guardrailsPath = getGuardrailsPath(sessionId);
|
|
3164
|
-
const content = yield*
|
|
3526
|
+
const content = yield* Effect6.tryPromise({
|
|
3165
3527
|
try: async () => {
|
|
3166
3528
|
try {
|
|
3167
3529
|
return await readFile(guardrailsPath, "utf-8");
|
|
@@ -3181,23 +3543,23 @@ var make = {
|
|
|
3181
3543
|
}
|
|
3182
3544
|
return yield* deserializeGuardrails(content);
|
|
3183
3545
|
}),
|
|
3184
|
-
getActive: (sessionId) =>
|
|
3185
|
-
const guardrails = yield*
|
|
3546
|
+
getActive: (sessionId) => Effect6.gen(function* () {
|
|
3547
|
+
const guardrails = yield* make2.load(sessionId);
|
|
3186
3548
|
return guardrails.guardrails;
|
|
3187
3549
|
})
|
|
3188
3550
|
};
|
|
3189
|
-
var
|
|
3551
|
+
var Live3 = Layer3.succeed(GuardrailsStore, make2);
|
|
3190
3552
|
var FileSystemGuardrails = {
|
|
3191
|
-
Live
|
|
3553
|
+
Live: Live3
|
|
3192
3554
|
};
|
|
3193
3555
|
|
|
3194
3556
|
// src/layers/guardrails/memory.ts
|
|
3195
3557
|
init_esm_shims();
|
|
3196
|
-
import { DateTime as DateTime2, Effect as
|
|
3558
|
+
import { DateTime as DateTime2, Effect as Effect7, Layer as Layer4, Ref as Ref4 } from "effect";
|
|
3197
3559
|
function createMemoryGuardrailsStore(stateRef) {
|
|
3198
3560
|
return {
|
|
3199
|
-
add: (sessionId, guardrail) =>
|
|
3200
|
-
const state = yield*
|
|
3561
|
+
add: (sessionId, guardrail) => Effect7.gen(function* () {
|
|
3562
|
+
const state = yield* Ref4.get(stateRef);
|
|
3201
3563
|
let guardrails = state.get(sessionId);
|
|
3202
3564
|
if (!guardrails) {
|
|
3203
3565
|
const now = yield* DateTime2.now;
|
|
@@ -3212,10 +3574,10 @@ function createMemoryGuardrailsStore(stateRef) {
|
|
|
3212
3574
|
guardrails: [...guardrails.guardrails, guardrail]
|
|
3213
3575
|
};
|
|
3214
3576
|
state.set(sessionId, updatedGuardrails);
|
|
3215
|
-
yield*
|
|
3577
|
+
yield* Ref4.set(stateRef, state);
|
|
3216
3578
|
}),
|
|
3217
|
-
load: (sessionId) =>
|
|
3218
|
-
const state = yield*
|
|
3579
|
+
load: (sessionId) => Effect7.gen(function* () {
|
|
3580
|
+
const state = yield* Ref4.get(stateRef);
|
|
3219
3581
|
const guardrails = state.get(sessionId);
|
|
3220
3582
|
if (!guardrails) {
|
|
3221
3583
|
const now = yield* DateTime2.now;
|
|
@@ -3227,8 +3589,8 @@ function createMemoryGuardrailsStore(stateRef) {
|
|
|
3227
3589
|
}
|
|
3228
3590
|
return guardrails;
|
|
3229
3591
|
}),
|
|
3230
|
-
getActive: (sessionId) =>
|
|
3231
|
-
const state = yield*
|
|
3592
|
+
getActive: (sessionId) => Effect7.gen(function* () {
|
|
3593
|
+
const state = yield* Ref4.get(stateRef);
|
|
3232
3594
|
const guardrails = state.get(sessionId);
|
|
3233
3595
|
if (!guardrails) {
|
|
3234
3596
|
return [];
|
|
@@ -3237,36 +3599,36 @@ function createMemoryGuardrailsStore(stateRef) {
|
|
|
3237
3599
|
})
|
|
3238
3600
|
};
|
|
3239
3601
|
}
|
|
3240
|
-
function
|
|
3241
|
-
return
|
|
3602
|
+
function layer2() {
|
|
3603
|
+
return Layer4.effect(
|
|
3242
3604
|
GuardrailsStore,
|
|
3243
|
-
|
|
3244
|
-
const stateRef = yield*
|
|
3605
|
+
Effect7.gen(function* () {
|
|
3606
|
+
const stateRef = yield* Ref4.make(/* @__PURE__ */ new Map());
|
|
3245
3607
|
return createMemoryGuardrailsStore(stateRef);
|
|
3246
3608
|
})
|
|
3247
3609
|
);
|
|
3248
3610
|
}
|
|
3249
|
-
var
|
|
3611
|
+
var Live4 = layer2();
|
|
3250
3612
|
var MemoryGuardrails = {
|
|
3251
|
-
Live:
|
|
3252
|
-
layer
|
|
3613
|
+
Live: Live4,
|
|
3614
|
+
layer: layer2
|
|
3253
3615
|
};
|
|
3254
3616
|
|
|
3255
3617
|
// src/layers/llm/claude-cli.ts
|
|
3256
3618
|
init_esm_shims();
|
|
3257
3619
|
import { spawn } from "child_process";
|
|
3258
|
-
import { Effect as
|
|
3620
|
+
import { Effect as Effect9, Layer as Layer5, Stream as Stream5 } from "effect";
|
|
3259
3621
|
|
|
3260
3622
|
// src/services/llm.ts
|
|
3261
3623
|
init_esm_shims();
|
|
3262
|
-
import { Context as
|
|
3263
|
-
var LLM = class extends
|
|
3624
|
+
import { Context as Context3 } from "effect";
|
|
3625
|
+
var LLM = class extends Context3.Tag("@ferix/LLM")() {
|
|
3264
3626
|
};
|
|
3265
3627
|
|
|
3266
3628
|
// src/layers/llm/stream.ts
|
|
3267
3629
|
init_esm_shims();
|
|
3268
3630
|
import { createInterface } from "readline";
|
|
3269
|
-
import { Effect as
|
|
3631
|
+
import { Effect as Effect8, Stream as Stream4 } from "effect";
|
|
3270
3632
|
|
|
3271
3633
|
// src/layers/llm/parsers.ts
|
|
3272
3634
|
init_esm_shims();
|
|
@@ -3392,7 +3754,7 @@ function createEventStream(child) {
|
|
|
3392
3754
|
emit.fail(
|
|
3393
3755
|
new LLMError({ message: "Failed to get stdout from child process" })
|
|
3394
3756
|
);
|
|
3395
|
-
return
|
|
3757
|
+
return Effect8.void;
|
|
3396
3758
|
}
|
|
3397
3759
|
const rl = createInterface({
|
|
3398
3760
|
input: stdout,
|
|
@@ -3431,17 +3793,17 @@ function createEventStream(child) {
|
|
|
3431
3793
|
})
|
|
3432
3794
|
);
|
|
3433
3795
|
});
|
|
3434
|
-
return
|
|
3796
|
+
return Effect8.sync(() => {
|
|
3435
3797
|
child.kill("SIGTERM");
|
|
3436
3798
|
});
|
|
3437
3799
|
});
|
|
3438
3800
|
}
|
|
3439
3801
|
|
|
3440
3802
|
// src/layers/llm/claude-cli.ts
|
|
3441
|
-
var
|
|
3442
|
-
execute: (prompt) => {
|
|
3803
|
+
var make3 = {
|
|
3804
|
+
execute: (prompt, options) => {
|
|
3443
3805
|
return Stream5.unwrap(
|
|
3444
|
-
|
|
3806
|
+
Effect9.sync(() => {
|
|
3445
3807
|
const child = spawn(
|
|
3446
3808
|
"claude",
|
|
3447
3809
|
[
|
|
@@ -3456,6 +3818,7 @@ var make2 = {
|
|
|
3456
3818
|
],
|
|
3457
3819
|
{
|
|
3458
3820
|
stdio: ["inherit", "pipe", "pipe"],
|
|
3821
|
+
cwd: options?.cwd,
|
|
3459
3822
|
env: {
|
|
3460
3823
|
...process.env,
|
|
3461
3824
|
FORCE_COLOR: "1"
|
|
@@ -3467,25 +3830,25 @@ var make2 = {
|
|
|
3467
3830
|
);
|
|
3468
3831
|
}
|
|
3469
3832
|
};
|
|
3470
|
-
var
|
|
3833
|
+
var Live5 = Layer5.succeed(LLM, make3);
|
|
3471
3834
|
var ClaudeCLI = {
|
|
3472
|
-
Live:
|
|
3835
|
+
Live: Live5
|
|
3473
3836
|
};
|
|
3474
3837
|
|
|
3475
3838
|
// src/layers/llm/mock.ts
|
|
3476
3839
|
init_esm_shims();
|
|
3477
|
-
import { Effect as
|
|
3840
|
+
import { Effect as Effect10, Layer as Layer6, Schema as S14, Stream as Stream6 } from "effect";
|
|
3478
3841
|
var MockLLMConfigSchema = S14.Struct({
|
|
3479
3842
|
events: S14.Array(LLMEventSchema),
|
|
3480
3843
|
delayMs: S14.optional(S14.Number)
|
|
3481
3844
|
});
|
|
3482
3845
|
function createMockLLM(config) {
|
|
3483
3846
|
return {
|
|
3484
|
-
execute: (_prompt) => {
|
|
3847
|
+
execute: (_prompt, _options) => {
|
|
3485
3848
|
const baseStream = Stream6.fromIterable(config.events);
|
|
3486
3849
|
if (config.delayMs !== void 0 && config.delayMs > 0) {
|
|
3487
3850
|
const delay = config.delayMs;
|
|
3488
|
-
return baseStream.pipe(Stream6.tap(() =>
|
|
3851
|
+
return baseStream.pipe(Stream6.tap(() => Effect10.sleep(delay)));
|
|
3489
3852
|
}
|
|
3490
3853
|
return baseStream;
|
|
3491
3854
|
}
|
|
@@ -3502,45 +3865,45 @@ var defaultMockEvents = [
|
|
|
3502
3865
|
}
|
|
3503
3866
|
];
|
|
3504
3867
|
var defaultMock = createMockLLM({ events: defaultMockEvents });
|
|
3505
|
-
var
|
|
3506
|
-
function
|
|
3507
|
-
return
|
|
3868
|
+
var Live6 = Layer6.succeed(LLM, defaultMock);
|
|
3869
|
+
function layer3(config) {
|
|
3870
|
+
return Layer6.succeed(LLM, createMockLLM(config));
|
|
3508
3871
|
}
|
|
3509
3872
|
var Mock = {
|
|
3510
|
-
Live:
|
|
3511
|
-
layer:
|
|
3873
|
+
Live: Live6,
|
|
3874
|
+
layer: layer3,
|
|
3512
3875
|
createMockLLM
|
|
3513
3876
|
};
|
|
3514
3877
|
|
|
3515
3878
|
// src/layers/plan/file-system.ts
|
|
3516
3879
|
init_esm_shims();
|
|
3517
|
-
import { access, mkdir as
|
|
3518
|
-
import { join as
|
|
3519
|
-
import { Effect as
|
|
3880
|
+
import { access as access2, mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3881
|
+
import { join as join3 } from "path";
|
|
3882
|
+
import { Effect as Effect11, Layer as Layer7 } from "effect";
|
|
3520
3883
|
|
|
3521
3884
|
// src/services/plan-store.ts
|
|
3522
3885
|
init_esm_shims();
|
|
3523
|
-
import { Context as
|
|
3524
|
-
var PlanStore = class extends
|
|
3886
|
+
import { Context as Context4 } from "effect";
|
|
3887
|
+
var PlanStore = class extends Context4.Tag("@ferix/PlanStore")() {
|
|
3525
3888
|
};
|
|
3526
3889
|
|
|
3527
3890
|
// src/layers/plan/file-system.ts
|
|
3528
3891
|
var PLANS_DIR2 = ".ferix/plans";
|
|
3529
3892
|
function ensureDir2(dirPath) {
|
|
3530
|
-
return
|
|
3531
|
-
try: () =>
|
|
3893
|
+
return Effect11.tryPromise({
|
|
3894
|
+
try: () => mkdir3(dirPath, { recursive: true }),
|
|
3532
3895
|
catch: (error) => new PlanStoreError({
|
|
3533
3896
|
message: `Failed to create directory: ${dirPath}`,
|
|
3534
3897
|
operation: "create",
|
|
3535
3898
|
cause: error
|
|
3536
3899
|
})
|
|
3537
|
-
}).pipe(
|
|
3900
|
+
}).pipe(Effect11.asVoid);
|
|
3538
3901
|
}
|
|
3539
3902
|
function getSessionDir2(sessionId) {
|
|
3540
|
-
return
|
|
3903
|
+
return join3(process.cwd(), PLANS_DIR2, sessionId);
|
|
3541
3904
|
}
|
|
3542
3905
|
function getPlanPath(sessionId, planId) {
|
|
3543
|
-
return
|
|
3906
|
+
return join3(getSessionDir2(sessionId), `${planId}.json`);
|
|
3544
3907
|
}
|
|
3545
3908
|
function generatePlanId(taskNumber) {
|
|
3546
3909
|
return PlanId(`task-${taskNumber}`);
|
|
@@ -3549,8 +3912,8 @@ function serializePlan(plan) {
|
|
|
3549
3912
|
return JSON.stringify(plan, null, 2);
|
|
3550
3913
|
}
|
|
3551
3914
|
function deserializePlan(json, planId) {
|
|
3552
|
-
return
|
|
3553
|
-
const parsed = yield*
|
|
3915
|
+
return Effect11.gen(function* () {
|
|
3916
|
+
const parsed = yield* Effect11.try({
|
|
3554
3917
|
try: () => JSON.parse(json),
|
|
3555
3918
|
catch: (error) => new PlanStoreError({
|
|
3556
3919
|
message: `Invalid JSON in plan file: ${String(error)}`,
|
|
@@ -3559,7 +3922,7 @@ function deserializePlan(json, planId) {
|
|
|
3559
3922
|
})
|
|
3560
3923
|
});
|
|
3561
3924
|
const validated = yield* decodePlanData(parsed).pipe(
|
|
3562
|
-
|
|
3925
|
+
Effect11.mapError(
|
|
3563
3926
|
(error) => new PlanStoreError({
|
|
3564
3927
|
message: `Plan validation failed: ${String(error)}`,
|
|
3565
3928
|
operation: "load",
|
|
@@ -3573,11 +3936,11 @@ function deserializePlan(json, planId) {
|
|
|
3573
3936
|
};
|
|
3574
3937
|
});
|
|
3575
3938
|
}
|
|
3576
|
-
var
|
|
3577
|
-
create: (sessionId, plan) =>
|
|
3939
|
+
var make4 = {
|
|
3940
|
+
create: (sessionId, plan) => Effect11.gen(function* () {
|
|
3578
3941
|
const sessionDir = getSessionDir2(sessionId);
|
|
3579
3942
|
yield* ensureDir2(sessionDir);
|
|
3580
|
-
const existingPlans = yield*
|
|
3943
|
+
const existingPlans = yield* Effect11.tryPromise({
|
|
3581
3944
|
try: async () => {
|
|
3582
3945
|
try {
|
|
3583
3946
|
const files = await readdir(sessionDir);
|
|
@@ -3594,7 +3957,7 @@ var make3 = {
|
|
|
3594
3957
|
const planId = generatePlanId(existingPlans + 1);
|
|
3595
3958
|
const planPath = getPlanPath(sessionId, planId);
|
|
3596
3959
|
const fullPlan = { ...plan, id: planId };
|
|
3597
|
-
yield*
|
|
3960
|
+
yield* Effect11.tryPromise({
|
|
3598
3961
|
try: () => writeFile2(planPath, serializePlan(fullPlan), "utf-8"),
|
|
3599
3962
|
catch: (error) => new PlanStoreError({
|
|
3600
3963
|
message: `Failed to write plan file: ${planPath}`,
|
|
@@ -3604,10 +3967,10 @@ var make3 = {
|
|
|
3604
3967
|
});
|
|
3605
3968
|
return planId;
|
|
3606
3969
|
}),
|
|
3607
|
-
load: (planId, sessionId) =>
|
|
3970
|
+
load: (planId, sessionId) => Effect11.gen(function* () {
|
|
3608
3971
|
if (sessionId) {
|
|
3609
3972
|
const planPath = getPlanPath(sessionId, planId);
|
|
3610
|
-
const content = yield*
|
|
3973
|
+
const content = yield* Effect11.tryPromise({
|
|
3611
3974
|
try: () => readFile2(planPath, "utf-8"),
|
|
3612
3975
|
catch: (error) => new PlanStoreError({
|
|
3613
3976
|
message: `Failed to read plan file: ${planPath}`,
|
|
@@ -3617,9 +3980,9 @@ var make3 = {
|
|
|
3617
3980
|
});
|
|
3618
3981
|
return yield* deserializePlan(content, planId);
|
|
3619
3982
|
}
|
|
3620
|
-
const sessionDirs = yield*
|
|
3983
|
+
const sessionDirs = yield* Effect11.tryPromise({
|
|
3621
3984
|
try: async () => {
|
|
3622
|
-
const plansDir =
|
|
3985
|
+
const plansDir = join3(process.cwd(), PLANS_DIR2);
|
|
3623
3986
|
const dirs = await readdir(plansDir);
|
|
3624
3987
|
return dirs;
|
|
3625
3988
|
},
|
|
@@ -3631,18 +3994,18 @@ var make3 = {
|
|
|
3631
3994
|
});
|
|
3632
3995
|
for (const sid of sessionDirs) {
|
|
3633
3996
|
const planPath = getPlanPath(sid, planId);
|
|
3634
|
-
const exists = yield*
|
|
3997
|
+
const exists = yield* Effect11.tryPromise({
|
|
3635
3998
|
try: async () => {
|
|
3636
|
-
await
|
|
3999
|
+
await access2(planPath);
|
|
3637
4000
|
return true;
|
|
3638
4001
|
},
|
|
3639
4002
|
catch: () => new PlanStoreError({
|
|
3640
4003
|
message: "File not found",
|
|
3641
4004
|
operation: "load"
|
|
3642
4005
|
})
|
|
3643
|
-
}).pipe(
|
|
4006
|
+
}).pipe(Effect11.orElseSucceed(() => false));
|
|
3644
4007
|
if (exists) {
|
|
3645
|
-
const content = yield*
|
|
4008
|
+
const content = yield* Effect11.tryPromise({
|
|
3646
4009
|
try: () => readFile2(planPath, "utf-8"),
|
|
3647
4010
|
catch: (error) => new PlanStoreError({
|
|
3648
4011
|
message: `Failed to read plan file: ${planPath}`,
|
|
@@ -3653,16 +4016,16 @@ var make3 = {
|
|
|
3653
4016
|
return yield* deserializePlan(content, planId);
|
|
3654
4017
|
}
|
|
3655
4018
|
}
|
|
3656
|
-
return yield*
|
|
4019
|
+
return yield* Effect11.fail(
|
|
3657
4020
|
new PlanStoreError({
|
|
3658
4021
|
message: `Plan not found: ${planId}`,
|
|
3659
4022
|
operation: "load"
|
|
3660
4023
|
})
|
|
3661
4024
|
);
|
|
3662
4025
|
}),
|
|
3663
|
-
update: (planId, plan) =>
|
|
4026
|
+
update: (planId, plan) => Effect11.gen(function* () {
|
|
3664
4027
|
const planPath = getPlanPath(plan.sessionId, planId);
|
|
3665
|
-
yield*
|
|
4028
|
+
yield* Effect11.tryPromise({
|
|
3666
4029
|
try: () => writeFile2(planPath, serializePlan(plan), "utf-8"),
|
|
3667
4030
|
catch: (error) => new PlanStoreError({
|
|
3668
4031
|
message: `Failed to update plan file: ${planPath}`,
|
|
@@ -3671,9 +4034,9 @@ var make3 = {
|
|
|
3671
4034
|
})
|
|
3672
4035
|
});
|
|
3673
4036
|
}),
|
|
3674
|
-
list: (sessionId) =>
|
|
4037
|
+
list: (sessionId) => Effect11.gen(function* () {
|
|
3675
4038
|
const sessionDir = getSessionDir2(sessionId);
|
|
3676
|
-
const files = yield*
|
|
4039
|
+
const files = yield* Effect11.tryPromise({
|
|
3677
4040
|
try: async () => {
|
|
3678
4041
|
try {
|
|
3679
4042
|
return await readdir(sessionDir);
|
|
@@ -3690,18 +4053,18 @@ var make3 = {
|
|
|
3690
4053
|
return files.filter((f) => f.endsWith(".json")).map((f) => PlanId(f.replace(".json", "")));
|
|
3691
4054
|
})
|
|
3692
4055
|
};
|
|
3693
|
-
var
|
|
4056
|
+
var Live7 = Layer7.succeed(PlanStore, make4);
|
|
3694
4057
|
var FileSystemPlan = {
|
|
3695
|
-
Live:
|
|
4058
|
+
Live: Live7
|
|
3696
4059
|
};
|
|
3697
4060
|
|
|
3698
4061
|
// src/layers/plan/memory.ts
|
|
3699
4062
|
init_esm_shims();
|
|
3700
|
-
import { Effect as
|
|
4063
|
+
import { Effect as Effect12, Layer as Layer8, Ref as Ref5 } from "effect";
|
|
3701
4064
|
function createMemoryPlanStore(stateRef) {
|
|
3702
4065
|
return {
|
|
3703
|
-
create: (sessionId, plan) =>
|
|
3704
|
-
const state = yield*
|
|
4066
|
+
create: (sessionId, plan) => Effect12.gen(function* () {
|
|
4067
|
+
const state = yield* Ref5.get(stateRef);
|
|
3705
4068
|
if (!state.has(sessionId)) {
|
|
3706
4069
|
state.set(sessionId, /* @__PURE__ */ new Map());
|
|
3707
4070
|
}
|
|
@@ -3712,18 +4075,18 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3712
4075
|
const planId = PlanId(`task-${sessionPlans.size + 1}`);
|
|
3713
4076
|
const fullPlan = { ...plan, id: planId };
|
|
3714
4077
|
sessionPlans.set(planId, fullPlan);
|
|
3715
|
-
yield*
|
|
4078
|
+
yield* Ref5.set(stateRef, state);
|
|
3716
4079
|
return planId;
|
|
3717
4080
|
}),
|
|
3718
|
-
load: (planId, sessionId) =>
|
|
3719
|
-
const state = yield*
|
|
4081
|
+
load: (planId, sessionId) => Effect12.gen(function* () {
|
|
4082
|
+
const state = yield* Ref5.get(stateRef);
|
|
3720
4083
|
if (sessionId) {
|
|
3721
4084
|
const sessionPlans = state.get(sessionId);
|
|
3722
4085
|
const plan = sessionPlans?.get(planId);
|
|
3723
4086
|
if (plan) {
|
|
3724
4087
|
return plan;
|
|
3725
4088
|
}
|
|
3726
|
-
return yield*
|
|
4089
|
+
return yield* Effect12.fail(
|
|
3727
4090
|
new PlanStoreError({
|
|
3728
4091
|
message: `Plan not found: ${planId}`,
|
|
3729
4092
|
operation: "load"
|
|
@@ -3736,18 +4099,18 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3736
4099
|
return plan;
|
|
3737
4100
|
}
|
|
3738
4101
|
}
|
|
3739
|
-
return yield*
|
|
4102
|
+
return yield* Effect12.fail(
|
|
3740
4103
|
new PlanStoreError({
|
|
3741
4104
|
message: `Plan not found: ${planId}`,
|
|
3742
4105
|
operation: "load"
|
|
3743
4106
|
})
|
|
3744
4107
|
);
|
|
3745
4108
|
}),
|
|
3746
|
-
update: (planId, plan) =>
|
|
3747
|
-
const state = yield*
|
|
4109
|
+
update: (planId, plan) => Effect12.gen(function* () {
|
|
4110
|
+
const state = yield* Ref5.get(stateRef);
|
|
3748
4111
|
const sessionPlans = state.get(plan.sessionId);
|
|
3749
4112
|
if (!sessionPlans) {
|
|
3750
|
-
return yield*
|
|
4113
|
+
return yield* Effect12.fail(
|
|
3751
4114
|
new PlanStoreError({
|
|
3752
4115
|
message: `Session not found: ${plan.sessionId}`,
|
|
3753
4116
|
operation: "update"
|
|
@@ -3755,10 +4118,10 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3755
4118
|
);
|
|
3756
4119
|
}
|
|
3757
4120
|
sessionPlans.set(planId, plan);
|
|
3758
|
-
yield*
|
|
4121
|
+
yield* Ref5.set(stateRef, state);
|
|
3759
4122
|
}),
|
|
3760
|
-
list: (sessionId) =>
|
|
3761
|
-
const state = yield*
|
|
4123
|
+
list: (sessionId) => Effect12.gen(function* () {
|
|
4124
|
+
const state = yield* Ref5.get(stateRef);
|
|
3762
4125
|
const sessionPlans = state.get(sessionId);
|
|
3763
4126
|
if (!sessionPlans) {
|
|
3764
4127
|
return [];
|
|
@@ -3767,57 +4130,57 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3767
4130
|
})
|
|
3768
4131
|
};
|
|
3769
4132
|
}
|
|
3770
|
-
function
|
|
3771
|
-
return
|
|
4133
|
+
function layer4() {
|
|
4134
|
+
return Layer8.effect(
|
|
3772
4135
|
PlanStore,
|
|
3773
|
-
|
|
3774
|
-
const stateRef = yield*
|
|
4136
|
+
Effect12.gen(function* () {
|
|
4137
|
+
const stateRef = yield* Ref5.make(/* @__PURE__ */ new Map());
|
|
3775
4138
|
return createMemoryPlanStore(stateRef);
|
|
3776
4139
|
})
|
|
3777
4140
|
);
|
|
3778
4141
|
}
|
|
3779
|
-
var
|
|
4142
|
+
var Live8 = layer4();
|
|
3780
4143
|
var MemoryPlan = {
|
|
3781
|
-
Live:
|
|
3782
|
-
layer:
|
|
4144
|
+
Live: Live8,
|
|
4145
|
+
layer: layer4
|
|
3783
4146
|
};
|
|
3784
4147
|
|
|
3785
4148
|
// src/layers/progress/file-system.ts
|
|
3786
4149
|
init_esm_shims();
|
|
3787
|
-
import { mkdir as
|
|
3788
|
-
import { join as
|
|
3789
|
-
import { DateTime as DateTime3, Effect as
|
|
4150
|
+
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
4151
|
+
import { join as join4 } from "path";
|
|
4152
|
+
import { DateTime as DateTime3, Effect as Effect13, Layer as Layer9 } from "effect";
|
|
3790
4153
|
|
|
3791
4154
|
// src/services/progress-store.ts
|
|
3792
4155
|
init_esm_shims();
|
|
3793
|
-
import { Context as
|
|
3794
|
-
var ProgressStore = class extends
|
|
4156
|
+
import { Context as Context5 } from "effect";
|
|
4157
|
+
var ProgressStore = class extends Context5.Tag("@ferix/ProgressStore")() {
|
|
3795
4158
|
};
|
|
3796
4159
|
|
|
3797
4160
|
// src/layers/progress/file-system.ts
|
|
3798
4161
|
var PLANS_DIR3 = ".ferix/plans";
|
|
3799
4162
|
function ensureDir3(dirPath) {
|
|
3800
|
-
return
|
|
3801
|
-
try: () =>
|
|
4163
|
+
return Effect13.tryPromise({
|
|
4164
|
+
try: () => mkdir4(dirPath, { recursive: true }),
|
|
3802
4165
|
catch: (error) => new ProgressStoreError({
|
|
3803
4166
|
message: `Failed to create directory: ${dirPath}`,
|
|
3804
4167
|
operation: "append",
|
|
3805
4168
|
cause: error
|
|
3806
4169
|
})
|
|
3807
|
-
}).pipe(
|
|
4170
|
+
}).pipe(Effect13.asVoid);
|
|
3808
4171
|
}
|
|
3809
4172
|
function getSessionDir3(sessionId) {
|
|
3810
|
-
return
|
|
4173
|
+
return join4(process.cwd(), PLANS_DIR3, sessionId);
|
|
3811
4174
|
}
|
|
3812
4175
|
function getProgressPath(sessionId) {
|
|
3813
|
-
return
|
|
4176
|
+
return join4(getSessionDir3(sessionId), "progress.json");
|
|
3814
4177
|
}
|
|
3815
4178
|
function serializeProgress(progress) {
|
|
3816
4179
|
return JSON.stringify(progress, null, 2);
|
|
3817
4180
|
}
|
|
3818
4181
|
function deserializeProgress(json) {
|
|
3819
|
-
return
|
|
3820
|
-
const parsed = yield*
|
|
4182
|
+
return Effect13.gen(function* () {
|
|
4183
|
+
const parsed = yield* Effect13.try({
|
|
3821
4184
|
try: () => JSON.parse(json),
|
|
3822
4185
|
catch: (error) => new ProgressStoreError({
|
|
3823
4186
|
message: `Invalid JSON in progress file: ${String(error)}`,
|
|
@@ -3826,7 +4189,7 @@ function deserializeProgress(json) {
|
|
|
3826
4189
|
})
|
|
3827
4190
|
});
|
|
3828
4191
|
const validated = yield* decodeProgressFile(parsed).pipe(
|
|
3829
|
-
|
|
4192
|
+
Effect13.mapError(
|
|
3830
4193
|
(error) => new ProgressStoreError({
|
|
3831
4194
|
message: `Progress validation failed: ${String(error)}`,
|
|
3832
4195
|
operation: "load",
|
|
@@ -3844,12 +4207,12 @@ function createEmptyProgress(sessionId, createdAt) {
|
|
|
3844
4207
|
entries: []
|
|
3845
4208
|
};
|
|
3846
4209
|
}
|
|
3847
|
-
var
|
|
3848
|
-
append: (sessionId, entry) =>
|
|
4210
|
+
var make5 = {
|
|
4211
|
+
append: (sessionId, entry) => Effect13.gen(function* () {
|
|
3849
4212
|
const sessionDir = getSessionDir3(sessionId);
|
|
3850
4213
|
yield* ensureDir3(sessionDir);
|
|
3851
4214
|
const progressPath = getProgressPath(sessionId);
|
|
3852
|
-
const existing = yield*
|
|
4215
|
+
const existing = yield* Effect13.tryPromise({
|
|
3853
4216
|
try: async () => {
|
|
3854
4217
|
try {
|
|
3855
4218
|
const content = await readFile3(progressPath, "utf-8");
|
|
@@ -3867,7 +4230,7 @@ var make4 = {
|
|
|
3867
4230
|
let progress;
|
|
3868
4231
|
if (existing) {
|
|
3869
4232
|
progress = yield* deserializeProgress(existing).pipe(
|
|
3870
|
-
|
|
4233
|
+
Effect13.mapError(
|
|
3871
4234
|
(err) => new ProgressStoreError({
|
|
3872
4235
|
message: err.message,
|
|
3873
4236
|
operation: "append",
|
|
@@ -3883,7 +4246,7 @@ var make4 = {
|
|
|
3883
4246
|
...progress,
|
|
3884
4247
|
entries: [...progress.entries, entry]
|
|
3885
4248
|
};
|
|
3886
|
-
yield*
|
|
4249
|
+
yield* Effect13.tryPromise({
|
|
3887
4250
|
try: () => writeFile3(progressPath, serializeProgress(updatedProgress), "utf-8"),
|
|
3888
4251
|
catch: (error) => new ProgressStoreError({
|
|
3889
4252
|
message: `Failed to write progress file: ${progressPath}`,
|
|
@@ -3892,9 +4255,9 @@ var make4 = {
|
|
|
3892
4255
|
})
|
|
3893
4256
|
});
|
|
3894
4257
|
}),
|
|
3895
|
-
load: (sessionId) =>
|
|
4258
|
+
load: (sessionId) => Effect13.gen(function* () {
|
|
3896
4259
|
const progressPath = getProgressPath(sessionId);
|
|
3897
|
-
const content = yield*
|
|
4260
|
+
const content = yield* Effect13.tryPromise({
|
|
3898
4261
|
try: async () => {
|
|
3899
4262
|
try {
|
|
3900
4263
|
return await readFile3(progressPath, "utf-8");
|
|
@@ -3914,24 +4277,24 @@ var make4 = {
|
|
|
3914
4277
|
}
|
|
3915
4278
|
return yield* deserializeProgress(content);
|
|
3916
4279
|
}),
|
|
3917
|
-
getRecent: (sessionId, count) =>
|
|
3918
|
-
const progress = yield*
|
|
4280
|
+
getRecent: (sessionId, count) => Effect13.gen(function* () {
|
|
4281
|
+
const progress = yield* make5.load(sessionId);
|
|
3919
4282
|
const entries = progress.entries;
|
|
3920
4283
|
return entries.slice(-count);
|
|
3921
4284
|
})
|
|
3922
4285
|
};
|
|
3923
|
-
var
|
|
4286
|
+
var Live9 = Layer9.succeed(ProgressStore, make5);
|
|
3924
4287
|
var FileSystemProgress = {
|
|
3925
|
-
Live:
|
|
4288
|
+
Live: Live9
|
|
3926
4289
|
};
|
|
3927
4290
|
|
|
3928
4291
|
// src/layers/progress/memory.ts
|
|
3929
4292
|
init_esm_shims();
|
|
3930
|
-
import { DateTime as DateTime4, Effect as
|
|
4293
|
+
import { DateTime as DateTime4, Effect as Effect14, Layer as Layer10, Ref as Ref6 } from "effect";
|
|
3931
4294
|
function createMemoryProgressStore(stateRef) {
|
|
3932
4295
|
return {
|
|
3933
|
-
append: (sessionId, entry) =>
|
|
3934
|
-
const state = yield*
|
|
4296
|
+
append: (sessionId, entry) => Effect14.gen(function* () {
|
|
4297
|
+
const state = yield* Ref6.get(stateRef);
|
|
3935
4298
|
let progress = state.get(sessionId);
|
|
3936
4299
|
if (!progress) {
|
|
3937
4300
|
const now = yield* DateTime4.now;
|
|
@@ -3946,10 +4309,10 @@ function createMemoryProgressStore(stateRef) {
|
|
|
3946
4309
|
entries: [...progress.entries, entry]
|
|
3947
4310
|
};
|
|
3948
4311
|
state.set(sessionId, updatedProgress);
|
|
3949
|
-
yield*
|
|
4312
|
+
yield* Ref6.set(stateRef, state);
|
|
3950
4313
|
}),
|
|
3951
|
-
load: (sessionId) =>
|
|
3952
|
-
const state = yield*
|
|
4314
|
+
load: (sessionId) => Effect14.gen(function* () {
|
|
4315
|
+
const state = yield* Ref6.get(stateRef);
|
|
3953
4316
|
const progress = state.get(sessionId);
|
|
3954
4317
|
if (!progress) {
|
|
3955
4318
|
const now = yield* DateTime4.now;
|
|
@@ -3961,8 +4324,8 @@ function createMemoryProgressStore(stateRef) {
|
|
|
3961
4324
|
}
|
|
3962
4325
|
return progress;
|
|
3963
4326
|
}),
|
|
3964
|
-
getRecent: (sessionId, count) =>
|
|
3965
|
-
const state = yield*
|
|
4327
|
+
getRecent: (sessionId, count) => Effect14.gen(function* () {
|
|
4328
|
+
const state = yield* Ref6.get(stateRef);
|
|
3966
4329
|
const progress = state.get(sessionId);
|
|
3967
4330
|
if (!progress) {
|
|
3968
4331
|
return [];
|
|
@@ -3971,32 +4334,32 @@ function createMemoryProgressStore(stateRef) {
|
|
|
3971
4334
|
})
|
|
3972
4335
|
};
|
|
3973
4336
|
}
|
|
3974
|
-
function
|
|
3975
|
-
return
|
|
4337
|
+
function layer5() {
|
|
4338
|
+
return Layer10.effect(
|
|
3976
4339
|
ProgressStore,
|
|
3977
|
-
|
|
3978
|
-
const stateRef = yield*
|
|
4340
|
+
Effect14.gen(function* () {
|
|
4341
|
+
const stateRef = yield* Ref6.make(/* @__PURE__ */ new Map());
|
|
3979
4342
|
return createMemoryProgressStore(stateRef);
|
|
3980
4343
|
})
|
|
3981
4344
|
);
|
|
3982
4345
|
}
|
|
3983
|
-
var
|
|
4346
|
+
var Live10 = layer5();
|
|
3984
4347
|
var MemoryProgress = {
|
|
3985
|
-
Live:
|
|
3986
|
-
layer:
|
|
4348
|
+
Live: Live10,
|
|
4349
|
+
layer: layer5
|
|
3987
4350
|
};
|
|
3988
4351
|
|
|
3989
4352
|
// src/layers/session/file-system.ts
|
|
3990
4353
|
init_esm_shims();
|
|
3991
|
-
import { mkdir as
|
|
3992
|
-
import { join as
|
|
3993
|
-
import { DateTime as DateTime5, Effect as
|
|
4354
|
+
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
4355
|
+
import { join as join5 } from "path";
|
|
4356
|
+
import { DateTime as DateTime5, Effect as Effect15, Layer as Layer11 } from "effect";
|
|
3994
4357
|
import { humanId } from "human-id";
|
|
3995
4358
|
|
|
3996
4359
|
// src/services/session-store.ts
|
|
3997
4360
|
init_esm_shims();
|
|
3998
|
-
import { Context as
|
|
3999
|
-
var SessionStore = class extends
|
|
4361
|
+
import { Context as Context6 } from "effect";
|
|
4362
|
+
var SessionStore = class extends Context6.Tag("@ferix/SessionStore")() {
|
|
4000
4363
|
};
|
|
4001
4364
|
|
|
4002
4365
|
// src/layers/session/file-system.ts
|
|
@@ -4006,24 +4369,24 @@ function generateSessionId(timestampMs) {
|
|
|
4006
4369
|
return `${id}-${timestampMs}`;
|
|
4007
4370
|
}
|
|
4008
4371
|
function ensureDir4(dirPath) {
|
|
4009
|
-
return
|
|
4010
|
-
try: () =>
|
|
4372
|
+
return Effect15.tryPromise({
|
|
4373
|
+
try: () => mkdir5(dirPath, { recursive: true }),
|
|
4011
4374
|
catch: (error) => new SessionStoreError({
|
|
4012
4375
|
message: `Failed to create directory: ${dirPath}`,
|
|
4013
4376
|
operation: "create",
|
|
4014
4377
|
cause: error
|
|
4015
4378
|
})
|
|
4016
|
-
}).pipe(
|
|
4379
|
+
}).pipe(Effect15.asVoid);
|
|
4017
4380
|
}
|
|
4018
4381
|
function getSessionPath(sessionId) {
|
|
4019
|
-
return
|
|
4382
|
+
return join5(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
|
|
4020
4383
|
}
|
|
4021
4384
|
function serializeSession(session) {
|
|
4022
4385
|
return JSON.stringify(session, null, 2);
|
|
4023
4386
|
}
|
|
4024
4387
|
function deserializeSession(json) {
|
|
4025
|
-
return
|
|
4026
|
-
const parsed = yield*
|
|
4388
|
+
return Effect15.gen(function* () {
|
|
4389
|
+
const parsed = yield* Effect15.try({
|
|
4027
4390
|
try: () => JSON.parse(json),
|
|
4028
4391
|
catch: (error) => new SessionStoreError({
|
|
4029
4392
|
message: `Invalid JSON in session file: ${String(error)}`,
|
|
@@ -4032,7 +4395,7 @@ function deserializeSession(json) {
|
|
|
4032
4395
|
})
|
|
4033
4396
|
});
|
|
4034
4397
|
const validated = yield* decodeSession(parsed).pipe(
|
|
4035
|
-
|
|
4398
|
+
Effect15.mapError(
|
|
4036
4399
|
(error) => new SessionStoreError({
|
|
4037
4400
|
message: `Session validation failed: ${String(error)}`,
|
|
4038
4401
|
operation: "get",
|
|
@@ -4043,9 +4406,9 @@ function deserializeSession(json) {
|
|
|
4043
4406
|
return validated;
|
|
4044
4407
|
});
|
|
4045
4408
|
}
|
|
4046
|
-
var
|
|
4047
|
-
create: (originalTask) =>
|
|
4048
|
-
const sessionsDir =
|
|
4409
|
+
var make6 = {
|
|
4410
|
+
create: (originalTask) => Effect15.gen(function* () {
|
|
4411
|
+
const sessionsDir = join5(process.cwd(), SESSIONS_DIR);
|
|
4049
4412
|
yield* ensureDir4(sessionsDir);
|
|
4050
4413
|
const now = yield* DateTime5.now;
|
|
4051
4414
|
const timestampMs = DateTime5.toEpochMillis(now);
|
|
@@ -4058,7 +4421,7 @@ var make5 = {
|
|
|
4058
4421
|
completedTasks: []
|
|
4059
4422
|
};
|
|
4060
4423
|
const sessionPath = getSessionPath(sessionId);
|
|
4061
|
-
yield*
|
|
4424
|
+
yield* Effect15.tryPromise({
|
|
4062
4425
|
try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
|
|
4063
4426
|
catch: (error) => new SessionStoreError({
|
|
4064
4427
|
message: `Failed to write session file: ${sessionPath}`,
|
|
@@ -4068,9 +4431,9 @@ var make5 = {
|
|
|
4068
4431
|
});
|
|
4069
4432
|
return session;
|
|
4070
4433
|
}),
|
|
4071
|
-
get: (sessionId) =>
|
|
4434
|
+
get: (sessionId) => Effect15.gen(function* () {
|
|
4072
4435
|
const sessionPath = getSessionPath(sessionId);
|
|
4073
|
-
const content = yield*
|
|
4436
|
+
const content = yield* Effect15.tryPromise({
|
|
4074
4437
|
try: () => readFile4(sessionPath, "utf-8"),
|
|
4075
4438
|
catch: (error) => new SessionStoreError({
|
|
4076
4439
|
message: `Failed to read session file: ${sessionPath}`,
|
|
@@ -4080,9 +4443,9 @@ var make5 = {
|
|
|
4080
4443
|
});
|
|
4081
4444
|
return yield* deserializeSession(content);
|
|
4082
4445
|
}),
|
|
4083
|
-
update: (sessionId, session) =>
|
|
4446
|
+
update: (sessionId, session) => Effect15.gen(function* () {
|
|
4084
4447
|
const sessionPath = getSessionPath(sessionId);
|
|
4085
|
-
yield*
|
|
4448
|
+
yield* Effect15.tryPromise({
|
|
4086
4449
|
try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
|
|
4087
4450
|
catch: (error) => new SessionStoreError({
|
|
4088
4451
|
message: `Failed to update session file: ${sessionPath}`,
|
|
@@ -4092,19 +4455,19 @@ var make5 = {
|
|
|
4092
4455
|
});
|
|
4093
4456
|
})
|
|
4094
4457
|
};
|
|
4095
|
-
var
|
|
4458
|
+
var Live11 = Layer11.succeed(SessionStore, make6);
|
|
4096
4459
|
var FileSystemSession = {
|
|
4097
|
-
Live:
|
|
4460
|
+
Live: Live11
|
|
4098
4461
|
};
|
|
4099
4462
|
|
|
4100
4463
|
// src/layers/session/memory.ts
|
|
4101
4464
|
init_esm_shims();
|
|
4102
|
-
import { DateTime as DateTime6, Effect as
|
|
4465
|
+
import { DateTime as DateTime6, Effect as Effect16, Layer as Layer12, Ref as Ref7 } from "effect";
|
|
4103
4466
|
function createMemorySessionStore(stateRef, counterRef) {
|
|
4104
4467
|
return {
|
|
4105
|
-
create: (originalTask) =>
|
|
4106
|
-
const state = yield*
|
|
4107
|
-
const counter = yield*
|
|
4468
|
+
create: (originalTask) => Effect16.gen(function* () {
|
|
4469
|
+
const state = yield* Ref7.get(stateRef);
|
|
4470
|
+
const counter = yield* Ref7.updateAndGet(counterRef, (n) => n + 1);
|
|
4108
4471
|
const sessionId = `test-session-${counter}`;
|
|
4109
4472
|
const now = yield* DateTime6.now;
|
|
4110
4473
|
const session = {
|
|
@@ -4115,14 +4478,14 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4115
4478
|
completedTasks: []
|
|
4116
4479
|
};
|
|
4117
4480
|
state.set(sessionId, session);
|
|
4118
|
-
yield*
|
|
4481
|
+
yield* Ref7.set(stateRef, state);
|
|
4119
4482
|
return session;
|
|
4120
4483
|
}),
|
|
4121
|
-
get: (sessionId) =>
|
|
4122
|
-
const state = yield*
|
|
4484
|
+
get: (sessionId) => Effect16.gen(function* () {
|
|
4485
|
+
const state = yield* Ref7.get(stateRef);
|
|
4123
4486
|
const session = state.get(sessionId);
|
|
4124
4487
|
if (!session) {
|
|
4125
|
-
return yield*
|
|
4488
|
+
return yield* Effect16.fail(
|
|
4126
4489
|
new SessionStoreError({
|
|
4127
4490
|
message: `Session not found: ${sessionId}`,
|
|
4128
4491
|
operation: "get"
|
|
@@ -4131,10 +4494,10 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4131
4494
|
}
|
|
4132
4495
|
return session;
|
|
4133
4496
|
}),
|
|
4134
|
-
update: (sessionId, session) =>
|
|
4135
|
-
const state = yield*
|
|
4497
|
+
update: (sessionId, session) => Effect16.gen(function* () {
|
|
4498
|
+
const state = yield* Ref7.get(stateRef);
|
|
4136
4499
|
if (!state.has(sessionId)) {
|
|
4137
|
-
return yield*
|
|
4500
|
+
return yield* Effect16.fail(
|
|
4138
4501
|
new SessionStoreError({
|
|
4139
4502
|
message: `Session not found: ${sessionId}`,
|
|
4140
4503
|
operation: "update"
|
|
@@ -4142,34 +4505,34 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4142
4505
|
);
|
|
4143
4506
|
}
|
|
4144
4507
|
state.set(sessionId, session);
|
|
4145
|
-
yield*
|
|
4508
|
+
yield* Ref7.set(stateRef, state);
|
|
4146
4509
|
})
|
|
4147
4510
|
};
|
|
4148
4511
|
}
|
|
4149
|
-
function
|
|
4150
|
-
return
|
|
4512
|
+
function layer6() {
|
|
4513
|
+
return Layer12.effect(
|
|
4151
4514
|
SessionStore,
|
|
4152
|
-
|
|
4153
|
-
const stateRef = yield*
|
|
4154
|
-
const counterRef = yield*
|
|
4515
|
+
Effect16.gen(function* () {
|
|
4516
|
+
const stateRef = yield* Ref7.make(/* @__PURE__ */ new Map());
|
|
4517
|
+
const counterRef = yield* Ref7.make(0);
|
|
4155
4518
|
return createMemorySessionStore(stateRef, counterRef);
|
|
4156
4519
|
})
|
|
4157
4520
|
);
|
|
4158
4521
|
}
|
|
4159
|
-
var
|
|
4522
|
+
var Live12 = layer6();
|
|
4160
4523
|
var MemorySession = {
|
|
4161
|
-
Live:
|
|
4162
|
-
layer:
|
|
4524
|
+
Live: Live12,
|
|
4525
|
+
layer: layer6
|
|
4163
4526
|
};
|
|
4164
4527
|
|
|
4165
4528
|
// src/layers/signal/ferix-parser.ts
|
|
4166
4529
|
init_esm_shims();
|
|
4167
|
-
import { Effect as
|
|
4530
|
+
import { Effect as Effect17, Layer as Layer13, Ref as Ref8 } from "effect";
|
|
4168
4531
|
|
|
4169
4532
|
// src/services/signal-parser.ts
|
|
4170
4533
|
init_esm_shims();
|
|
4171
|
-
import { Context as
|
|
4172
|
-
var SignalParser = class extends
|
|
4534
|
+
import { Context as Context7 } from "effect";
|
|
4535
|
+
var SignalParser = class extends Context7.Tag("@ferix/SignalParser")() {
|
|
4173
4536
|
};
|
|
4174
4537
|
|
|
4175
4538
|
// src/layers/signal/specs/index.ts
|
|
@@ -4674,20 +5037,20 @@ signalSpecRegistry.register(tasksDefinedSpec);
|
|
|
4674
5037
|
// src/layers/signal/ferix-parser.ts
|
|
4675
5038
|
var MAX_BUFFER_SIZE = 1024 * 1024;
|
|
4676
5039
|
function createAccumulatorImpl() {
|
|
4677
|
-
return
|
|
4678
|
-
const chunksRef = yield*
|
|
4679
|
-
const emittedRef = yield*
|
|
4680
|
-
const feed = (text) =>
|
|
4681
|
-
const chunks = yield*
|
|
5040
|
+
return Effect17.gen(function* () {
|
|
5041
|
+
const chunksRef = yield* Ref8.make([]);
|
|
5042
|
+
const emittedRef = yield* Ref8.make(/* @__PURE__ */ new Set());
|
|
5043
|
+
const feed = (text) => Effect17.gen(function* () {
|
|
5044
|
+
const chunks = yield* Ref8.get(chunksRef);
|
|
4682
5045
|
chunks.push(text);
|
|
4683
5046
|
const buffer = chunks.join("");
|
|
4684
5047
|
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
4685
|
-
yield*
|
|
5048
|
+
yield* Ref8.set(chunksRef, [
|
|
4686
5049
|
buffer.slice(buffer.length - MAX_BUFFER_SIZE)
|
|
4687
5050
|
]);
|
|
4688
5051
|
}
|
|
4689
5052
|
const signals = signalSpecRegistry.parseAll(buffer);
|
|
4690
|
-
const emitted = yield*
|
|
5053
|
+
const emitted = yield* Ref8.get(emittedRef);
|
|
4691
5054
|
const newSignals = signals.filter((signal) => {
|
|
4692
5055
|
const key = signalSpecRegistry.getSignalKey(signal);
|
|
4693
5056
|
if (emitted.has(key)) {
|
|
@@ -4699,61 +5062,64 @@ function createAccumulatorImpl() {
|
|
|
4699
5062
|
if (newSignals.length > 0) {
|
|
4700
5063
|
const lastEndPos = signalSpecRegistry.findLastCompleteSignalEnd(buffer);
|
|
4701
5064
|
if (lastEndPos > 0 && lastEndPos < buffer.length) {
|
|
4702
|
-
yield*
|
|
5065
|
+
yield* Ref8.set(chunksRef, [buffer.slice(lastEndPos)]);
|
|
4703
5066
|
}
|
|
4704
5067
|
}
|
|
4705
|
-
yield*
|
|
5068
|
+
yield* Ref8.set(emittedRef, emitted);
|
|
4706
5069
|
return newSignals;
|
|
4707
5070
|
});
|
|
4708
|
-
const flush = () =>
|
|
4709
|
-
const chunks = yield*
|
|
5071
|
+
const flush = () => Effect17.gen(function* () {
|
|
5072
|
+
const chunks = yield* Ref8.get(chunksRef);
|
|
4710
5073
|
const buffer = chunks.join("");
|
|
4711
|
-
yield*
|
|
4712
|
-
const emitted = yield*
|
|
5074
|
+
yield* Ref8.set(chunksRef, []);
|
|
5075
|
+
const emitted = yield* Ref8.get(emittedRef);
|
|
4713
5076
|
const signals = signalSpecRegistry.parseAll(buffer);
|
|
4714
5077
|
const result = signals.filter(
|
|
4715
5078
|
(signal) => !emitted.has(signalSpecRegistry.getSignalKey(signal))
|
|
4716
5079
|
);
|
|
4717
|
-
yield*
|
|
5080
|
+
yield* Ref8.set(emittedRef, /* @__PURE__ */ new Set());
|
|
4718
5081
|
return result;
|
|
4719
5082
|
});
|
|
4720
5083
|
return { feed, flush };
|
|
4721
5084
|
});
|
|
4722
5085
|
}
|
|
4723
|
-
var
|
|
4724
|
-
parse: (text) =>
|
|
5086
|
+
var make7 = {
|
|
5087
|
+
parse: (text) => Effect17.succeed(signalSpecRegistry.parseAll(text)),
|
|
4725
5088
|
createAccumulator: createAccumulatorImpl
|
|
4726
5089
|
};
|
|
4727
|
-
var
|
|
5090
|
+
var Live13 = Layer13.succeed(SignalParser, make7);
|
|
4728
5091
|
var FerixParser = {
|
|
4729
|
-
Live:
|
|
5092
|
+
Live: Live13
|
|
4730
5093
|
};
|
|
4731
5094
|
|
|
4732
5095
|
// src/layers/index.ts
|
|
4733
|
-
var ProductionLayers =
|
|
5096
|
+
var ProductionLayers = Layer14.mergeAll(
|
|
4734
5097
|
ClaudeCLI.Live,
|
|
4735
5098
|
FerixParser.Live,
|
|
4736
5099
|
FileSystemPlan.Live,
|
|
4737
5100
|
FileSystemSession.Live,
|
|
4738
5101
|
FileSystemProgress.Live,
|
|
4739
|
-
FileSystemGuardrails.Live
|
|
5102
|
+
FileSystemGuardrails.Live,
|
|
5103
|
+
FileSystemGit.Live
|
|
4740
5104
|
);
|
|
4741
|
-
var TestLayers =
|
|
5105
|
+
var TestLayers = Layer14.mergeAll(
|
|
4742
5106
|
Mock.Live,
|
|
4743
5107
|
FerixParser.Live,
|
|
4744
5108
|
MemoryPlan.Live,
|
|
4745
5109
|
MemorySession.Live,
|
|
4746
5110
|
MemoryProgress.Live,
|
|
4747
|
-
MemoryGuardrails.Live
|
|
5111
|
+
MemoryGuardrails.Live,
|
|
5112
|
+
MemoryGit.Live
|
|
4748
5113
|
);
|
|
4749
5114
|
function createTestLayers(events) {
|
|
4750
|
-
return
|
|
5115
|
+
return Layer14.mergeAll(
|
|
4751
5116
|
Mock.layer({ events }),
|
|
4752
5117
|
FerixParser.Live,
|
|
4753
5118
|
MemoryPlan.layer(),
|
|
4754
5119
|
MemorySession.layer(),
|
|
4755
5120
|
MemoryProgress.layer(),
|
|
4756
|
-
MemoryGuardrails.layer()
|
|
5121
|
+
MemoryGuardrails.layer(),
|
|
5122
|
+
MemoryGit.layer()
|
|
4757
5123
|
);
|
|
4758
5124
|
}
|
|
4759
5125
|
|
|
@@ -4762,41 +5128,41 @@ init_esm_shims();
|
|
|
4762
5128
|
|
|
4763
5129
|
// src/orchestrator/loop.ts
|
|
4764
5130
|
init_esm_shims();
|
|
4765
|
-
import { DateTime as DateTime10, Effect as
|
|
5131
|
+
import { DateTime as DateTime10, Effect as Effect22, Option, pipe as pipe3, Ref as Ref12, Stream as Stream9 } from "effect";
|
|
4766
5132
|
|
|
4767
5133
|
// src/orchestrator/discovery.ts
|
|
4768
5134
|
init_esm_shims();
|
|
4769
|
-
import { DateTime as DateTime8, Effect as
|
|
5135
|
+
import { DateTime as DateTime8, Effect as Effect20, pipe, Ref as Ref10, Stream as Stream7 } from "effect";
|
|
4770
5136
|
|
|
4771
5137
|
// src/layers/plan/task-generation.ts
|
|
4772
5138
|
init_esm_shims();
|
|
4773
|
-
import { mkdir as
|
|
4774
|
-
import { join as
|
|
4775
|
-
import { Effect as
|
|
5139
|
+
import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
5140
|
+
import { join as join6 } from "path";
|
|
5141
|
+
import { Effect as Effect18 } from "effect";
|
|
4776
5142
|
var PLANS_DIR4 = ".ferix/plans";
|
|
4777
5143
|
function ensureDir5(dirPath) {
|
|
4778
|
-
return
|
|
4779
|
-
try: () =>
|
|
5144
|
+
return Effect18.tryPromise({
|
|
5145
|
+
try: () => mkdir6(dirPath, { recursive: true }),
|
|
4780
5146
|
catch: (error) => new PlanStoreError({
|
|
4781
5147
|
message: `Failed to create directory: ${dirPath}`,
|
|
4782
5148
|
operation: "create",
|
|
4783
5149
|
cause: error
|
|
4784
5150
|
})
|
|
4785
|
-
}).pipe(
|
|
5151
|
+
}).pipe(Effect18.asVoid);
|
|
4786
5152
|
}
|
|
4787
5153
|
function getSessionDir4(sessionId) {
|
|
4788
|
-
return
|
|
5154
|
+
return join6(process.cwd(), PLANS_DIR4, sessionId);
|
|
4789
5155
|
}
|
|
4790
5156
|
function getTasksMdPath(sessionId) {
|
|
4791
|
-
return
|
|
5157
|
+
return join6(getSessionDir4(sessionId), "tasks.md");
|
|
4792
5158
|
}
|
|
4793
5159
|
function writeTasksMd(sessionId, tasks) {
|
|
4794
|
-
return
|
|
5160
|
+
return Effect18.gen(function* () {
|
|
4795
5161
|
const sessionDir = getSessionDir4(sessionId);
|
|
4796
5162
|
yield* ensureDir5(sessionDir);
|
|
4797
5163
|
const tasksMdPath = getTasksMdPath(sessionId);
|
|
4798
5164
|
const content = formatTasksMd(tasks);
|
|
4799
|
-
yield*
|
|
5165
|
+
yield* Effect18.tryPromise({
|
|
4800
5166
|
try: () => writeFile5(tasksMdPath, content, "utf-8"),
|
|
4801
5167
|
catch: (error) => new PlanStoreError({
|
|
4802
5168
|
message: `Failed to write tasks.md: ${tasksMdPath}`,
|
|
@@ -4978,7 +5344,7 @@ function mapSignalToDomain(signal, context) {
|
|
|
4978
5344
|
|
|
4979
5345
|
// src/orchestrator/plan-updates.ts
|
|
4980
5346
|
init_esm_shims();
|
|
4981
|
-
import { DateTime as DateTime7, Effect as
|
|
5347
|
+
import { DateTime as DateTime7, Effect as Effect19, Ref as Ref9 } from "effect";
|
|
4982
5348
|
|
|
4983
5349
|
// src/orchestrator/plan-updates/index.ts
|
|
4984
5350
|
init_esm_shims();
|
|
@@ -5283,9 +5649,9 @@ function persistPlanUpdate(planStore, plan, operation) {
|
|
|
5283
5649
|
tasks: plan.tasks
|
|
5284
5650
|
}) : planStore.update(plan.id, plan);
|
|
5285
5651
|
return storeOp.pipe(
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
(error) =>
|
|
5652
|
+
Effect19.map(() => null),
|
|
5653
|
+
Effect19.catchAll(
|
|
5654
|
+
(error) => Effect19.succeed({
|
|
5289
5655
|
_tag: "PlanUpdateFailed",
|
|
5290
5656
|
operation,
|
|
5291
5657
|
error: error.message,
|
|
@@ -5295,8 +5661,8 @@ function persistPlanUpdate(planStore, plan, operation) {
|
|
|
5295
5661
|
);
|
|
5296
5662
|
}
|
|
5297
5663
|
function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessionId, originalTask) {
|
|
5298
|
-
return
|
|
5299
|
-
const currentPlan = yield*
|
|
5664
|
+
return Effect19.gen(function* () {
|
|
5665
|
+
const currentPlan = yield* Ref9.get(currentPlanRef);
|
|
5300
5666
|
const now = yield* DateTime7.now;
|
|
5301
5667
|
const timestamp = DateTime7.formatIso(now);
|
|
5302
5668
|
const updateResult = computePlanUpdate(signal, currentPlan, {
|
|
@@ -5308,8 +5674,8 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
|
|
|
5308
5674
|
return [];
|
|
5309
5675
|
}
|
|
5310
5676
|
const { plan, operation, eventTag } = updateResult;
|
|
5311
|
-
yield*
|
|
5312
|
-
yield*
|
|
5677
|
+
yield* Ref9.set(currentPlanRef, plan);
|
|
5678
|
+
yield* Ref9.update(persistenceStateRef, (state) => ({
|
|
5313
5679
|
dirty: true,
|
|
5314
5680
|
pendingOperation: state.pendingOperation === "create" ? "create" : operation
|
|
5315
5681
|
}));
|
|
@@ -5317,12 +5683,12 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
|
|
|
5317
5683
|
});
|
|
5318
5684
|
}
|
|
5319
5685
|
function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
|
|
5320
|
-
return
|
|
5321
|
-
const state = yield*
|
|
5686
|
+
return Effect19.gen(function* () {
|
|
5687
|
+
const state = yield* Ref9.get(persistenceStateRef);
|
|
5322
5688
|
if (!(state.dirty && state.pendingOperation)) {
|
|
5323
5689
|
return [];
|
|
5324
5690
|
}
|
|
5325
|
-
const plan = yield*
|
|
5691
|
+
const plan = yield* Ref9.get(currentPlanRef);
|
|
5326
5692
|
if (!plan) {
|
|
5327
5693
|
return [];
|
|
5328
5694
|
}
|
|
@@ -5335,7 +5701,7 @@ function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
|
|
|
5335
5701
|
if (failureEvent) {
|
|
5336
5702
|
events.push(failureEvent);
|
|
5337
5703
|
}
|
|
5338
|
-
yield*
|
|
5704
|
+
yield* Ref9.set(persistenceStateRef, {
|
|
5339
5705
|
dirty: false,
|
|
5340
5706
|
pendingOperation: null
|
|
5341
5707
|
});
|
|
@@ -5394,9 +5760,12 @@ Use these XML-like tags to communicate structured information:
|
|
|
5394
5760
|
<files-created>new-file.ts</files-created>
|
|
5395
5761
|
</ferix:task-complete>
|
|
5396
5762
|
|
|
5397
|
-
### Loop Completion
|
|
5763
|
+
### Loop Completion (use ONLY when ALL tasks are done)
|
|
5398
5764
|
<ferix:complete>
|
|
5399
5765
|
|
|
5766
|
+
\u26A0\uFE0F IMPORTANT: Only emit <ferix:complete> after ALL tasks in the plan are complete.
|
|
5767
|
+
After completing a single task, emit <ferix:task-complete> and continue to the next task.
|
|
5768
|
+
|
|
5400
5769
|
IMPORTANT: Always emit signals on their own lines, never inside markdown code blocks.`;
|
|
5401
5770
|
var DISCOVERY_SYSTEM_PROMPT = `You are in the DISCOVERY phase of a ralph loop - an iterative AI coding workflow.
|
|
5402
5771
|
|
|
@@ -5437,14 +5806,17 @@ Review the code for quality:
|
|
|
5437
5806
|
- When done, emit <ferix:review-complete/>`;
|
|
5438
5807
|
var DEFAULT_COMPLETION_PROMPT = `## Completion
|
|
5439
5808
|
|
|
5440
|
-
When the task
|
|
5809
|
+
When you finish the CURRENT task, emit:
|
|
5441
5810
|
<ferix:task-complete id="N">
|
|
5442
5811
|
<summary>What was accomplished</summary>
|
|
5443
5812
|
<files-modified>list of modified files</files-modified>
|
|
5444
5813
|
<files-created>list of new files</files-created>
|
|
5445
5814
|
</ferix:task-complete>
|
|
5446
5815
|
|
|
5447
|
-
|
|
5816
|
+
After emitting task-complete, CONTINUE working on the next pending task.
|
|
5817
|
+
|
|
5818
|
+
\u26A0\uFE0F ONLY emit <ferix:complete> when ALL tasks in the plan are done.
|
|
5819
|
+
Do NOT emit <ferix:complete> after completing just one task - continue to the next task instead.`;
|
|
5448
5820
|
function getPhasePrompt(phase, prompts, defaultPrompt) {
|
|
5449
5821
|
return prompts?.phases?.[phase] ?? defaultPrompt;
|
|
5450
5822
|
}
|
|
@@ -5585,12 +5957,12 @@ Begin.`);
|
|
|
5585
5957
|
|
|
5586
5958
|
// src/orchestrator/discovery.ts
|
|
5587
5959
|
function processTextSignals(signalParser, text, context) {
|
|
5588
|
-
return
|
|
5960
|
+
return Effect20.gen(function* () {
|
|
5589
5961
|
const events = [];
|
|
5590
5962
|
const parsedSignals = [];
|
|
5591
5963
|
const signals = yield* signalParser.parse(text).pipe(
|
|
5592
|
-
|
|
5593
|
-
(error) =>
|
|
5964
|
+
Effect20.tapError(
|
|
5965
|
+
(error) => Effect20.logDebug(
|
|
5594
5966
|
"Signal parsing failed, continuing with empty signals",
|
|
5595
5967
|
{
|
|
5596
5968
|
error: String(error),
|
|
@@ -5598,7 +5970,7 @@ function processTextSignals(signalParser, text, context) {
|
|
|
5598
5970
|
}
|
|
5599
5971
|
)
|
|
5600
5972
|
),
|
|
5601
|
-
|
|
5973
|
+
Effect20.orElseSucceed(() => [])
|
|
5602
5974
|
);
|
|
5603
5975
|
for (const signal of signals) {
|
|
5604
5976
|
events.push(mapSignalToDomain(signal, context));
|
|
@@ -5608,7 +5980,7 @@ function processTextSignals(signalParser, text, context) {
|
|
|
5608
5980
|
});
|
|
5609
5981
|
}
|
|
5610
5982
|
function processLLMEvent(signalParser, llmEvent, context) {
|
|
5611
|
-
return
|
|
5983
|
+
return Effect20.gen(function* () {
|
|
5612
5984
|
const domainEvent = mapLLMEventToDomain(llmEvent, context);
|
|
5613
5985
|
const events = [domainEvent];
|
|
5614
5986
|
const allSignals = [];
|
|
@@ -5651,12 +6023,12 @@ function planTasksToGeneratedTasks(plan) {
|
|
|
5651
6023
|
status: mapTaskStatus(task.status)
|
|
5652
6024
|
}));
|
|
5653
6025
|
}
|
|
5654
|
-
function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId) {
|
|
6026
|
+
function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId, worktreePath) {
|
|
5655
6027
|
return Stream7.unwrap(
|
|
5656
|
-
|
|
6028
|
+
Effect20.gen(function* () {
|
|
5657
6029
|
const startTimeUtc = yield* DateTime8.now;
|
|
5658
6030
|
const startTime = DateTime8.toEpochMillis(startTimeUtc);
|
|
5659
|
-
const persistenceStateRef = yield*
|
|
6031
|
+
const persistenceStateRef = yield* Ref10.make({
|
|
5660
6032
|
dirty: false,
|
|
5661
6033
|
pendingOperation: null
|
|
5662
6034
|
});
|
|
@@ -5670,7 +6042,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5670
6042
|
input: {}
|
|
5671
6043
|
};
|
|
5672
6044
|
const prompt = buildDiscoveryPrompt(config);
|
|
5673
|
-
const llmStream = llm.execute(prompt).pipe(
|
|
6045
|
+
const llmStream = llm.execute(prompt, worktreePath ? { cwd: worktreePath } : void 0).pipe(
|
|
5674
6046
|
Stream7.mapError(
|
|
5675
6047
|
(e) => new OrchestratorError({
|
|
5676
6048
|
message: `LLM execution failed during discovery: ${String(e)}`,
|
|
@@ -5680,7 +6052,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5680
6052
|
),
|
|
5681
6053
|
Stream7.flatMap(
|
|
5682
6054
|
(llmEvent) => Stream7.unwrap(
|
|
5683
|
-
|
|
6055
|
+
Effect20.gen(function* () {
|
|
5684
6056
|
const now = yield* DateTime8.now;
|
|
5685
6057
|
const context = {
|
|
5686
6058
|
timestamp: DateTime8.toEpochMillis(now)
|
|
@@ -5717,23 +6089,23 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5717
6089
|
)
|
|
5718
6090
|
);
|
|
5719
6091
|
const completionStream = Stream7.fromEffect(
|
|
5720
|
-
|
|
6092
|
+
Effect20.gen(function* () {
|
|
5721
6093
|
const persistEvents = yield* flushPlanPersistence(
|
|
5722
6094
|
planStore,
|
|
5723
6095
|
currentPlanRef,
|
|
5724
6096
|
persistenceStateRef
|
|
5725
6097
|
);
|
|
5726
|
-
const plan = yield*
|
|
6098
|
+
const plan = yield* Ref10.get(currentPlanRef);
|
|
5727
6099
|
const taskCount = plan?.tasks.length ?? 0;
|
|
5728
6100
|
if (plan && plan.tasks.length > 0) {
|
|
5729
6101
|
const generatedTasks = planTasksToGeneratedTasks(plan);
|
|
5730
6102
|
yield* writeTasksMd(sessionId, generatedTasks).pipe(
|
|
5731
|
-
|
|
5732
|
-
(error) =>
|
|
6103
|
+
Effect20.tapError(
|
|
6104
|
+
(error) => Effect20.logDebug("Failed to write tasks.md, continuing", {
|
|
5733
6105
|
error: String(error)
|
|
5734
6106
|
})
|
|
5735
6107
|
),
|
|
5736
|
-
|
|
6108
|
+
Effect20.orElseSucceed(() => void 0)
|
|
5737
6109
|
);
|
|
5738
6110
|
}
|
|
5739
6111
|
const endTimeUtc = yield* DateTime8.now;
|
|
@@ -5758,15 +6130,15 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5758
6130
|
|
|
5759
6131
|
// src/orchestrator/iteration.ts
|
|
5760
6132
|
init_esm_shims();
|
|
5761
|
-
import { DateTime as DateTime9, Effect as
|
|
6133
|
+
import { DateTime as DateTime9, Effect as Effect21, pipe as pipe2, Ref as Ref11, Stream as Stream8 } from "effect";
|
|
5762
6134
|
function processTextSignals2(signalParser, text, context) {
|
|
5763
|
-
return
|
|
6135
|
+
return Effect21.gen(function* () {
|
|
5764
6136
|
const events = [];
|
|
5765
6137
|
let completed = false;
|
|
5766
6138
|
const parsedSignals = [];
|
|
5767
6139
|
const signals = yield* signalParser.parse(text).pipe(
|
|
5768
|
-
|
|
5769
|
-
(error) =>
|
|
6140
|
+
Effect21.tapError(
|
|
6141
|
+
(error) => Effect21.logDebug(
|
|
5770
6142
|
"Signal parsing failed, continuing with empty signals",
|
|
5771
6143
|
{
|
|
5772
6144
|
error: String(error),
|
|
@@ -5774,7 +6146,7 @@ function processTextSignals2(signalParser, text, context) {
|
|
|
5774
6146
|
}
|
|
5775
6147
|
)
|
|
5776
6148
|
),
|
|
5777
|
-
|
|
6149
|
+
Effect21.orElseSucceed(() => [])
|
|
5778
6150
|
);
|
|
5779
6151
|
for (const signal of signals) {
|
|
5780
6152
|
events.push(mapSignalToDomain(signal, context));
|
|
@@ -5787,7 +6159,7 @@ function processTextSignals2(signalParser, text, context) {
|
|
|
5787
6159
|
});
|
|
5788
6160
|
}
|
|
5789
6161
|
function processLLMEvent2(signalParser, llmEvent, context) {
|
|
5790
|
-
return
|
|
6162
|
+
return Effect21.gen(function* () {
|
|
5791
6163
|
const domainEvent = mapLLMEventToDomain(llmEvent, context);
|
|
5792
6164
|
const events = [domainEvent];
|
|
5793
6165
|
let completed = false;
|
|
@@ -5818,11 +6190,11 @@ function processLLMEvent2(signalParser, llmEvent, context) {
|
|
|
5818
6190
|
return { events, completed, signals: allSignals };
|
|
5819
6191
|
});
|
|
5820
6192
|
}
|
|
5821
|
-
function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId) {
|
|
6193
|
+
function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId, worktreePath) {
|
|
5822
6194
|
return Stream8.unwrap(
|
|
5823
|
-
|
|
5824
|
-
const currentPlan = yield*
|
|
5825
|
-
const persistenceStateRef = yield*
|
|
6195
|
+
Effect21.gen(function* () {
|
|
6196
|
+
const currentPlan = yield* Ref11.get(currentPlanRef);
|
|
6197
|
+
const persistenceStateRef = yield* Ref11.make({
|
|
5826
6198
|
dirty: false,
|
|
5827
6199
|
pendingOperation: null
|
|
5828
6200
|
});
|
|
@@ -5830,7 +6202,10 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5830
6202
|
_tag: "IterationStarted",
|
|
5831
6203
|
iteration
|
|
5832
6204
|
};
|
|
5833
|
-
const llmStream = llm.execute(
|
|
6205
|
+
const llmStream = llm.execute(
|
|
6206
|
+
buildPrompt(config, iteration, currentPlan),
|
|
6207
|
+
worktreePath ? { cwd: worktreePath } : void 0
|
|
6208
|
+
).pipe(
|
|
5834
6209
|
Stream8.mapError(
|
|
5835
6210
|
(e) => new OrchestratorError({
|
|
5836
6211
|
message: `LLM execution failed: ${String(e)}`,
|
|
@@ -5840,7 +6215,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5840
6215
|
),
|
|
5841
6216
|
Stream8.flatMap(
|
|
5842
6217
|
(llmEvent) => Stream8.unwrap(
|
|
5843
|
-
|
|
6218
|
+
Effect21.gen(function* () {
|
|
5844
6219
|
const now = yield* DateTime9.now;
|
|
5845
6220
|
const context = {
|
|
5846
6221
|
timestamp: DateTime9.toEpochMillis(now)
|
|
@@ -5862,7 +6237,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5862
6237
|
events.push(...planEvents);
|
|
5863
6238
|
}
|
|
5864
6239
|
if (result.completed) {
|
|
5865
|
-
yield*
|
|
6240
|
+
yield* Ref11.set(loopCompletedRef, true);
|
|
5866
6241
|
}
|
|
5867
6242
|
return Stream8.fromIterable(events);
|
|
5868
6243
|
})
|
|
@@ -5881,7 +6256,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5881
6256
|
)
|
|
5882
6257
|
);
|
|
5883
6258
|
const completionStream = Stream8.fromEffect(
|
|
5884
|
-
|
|
6259
|
+
Effect21.gen(function* () {
|
|
5885
6260
|
const persistEvents = yield* flushPlanPersistence(
|
|
5886
6261
|
planStore,
|
|
5887
6262
|
currentPlanRef,
|
|
@@ -5906,13 +6281,14 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5906
6281
|
// src/orchestrator/loop.ts
|
|
5907
6282
|
function runLoop(config) {
|
|
5908
6283
|
return Stream9.unwrap(
|
|
5909
|
-
|
|
6284
|
+
Effect22.gen(function* () {
|
|
5910
6285
|
const llm = yield* LLM;
|
|
5911
6286
|
const signalParser = yield* SignalParser;
|
|
5912
6287
|
const sessionStore = yield* SessionStore;
|
|
5913
6288
|
const planStore = yield* PlanStore;
|
|
6289
|
+
const git = yield* Git;
|
|
5914
6290
|
const session = yield* sessionStore.create(config.task).pipe(
|
|
5915
|
-
|
|
6291
|
+
Effect22.mapError(
|
|
5916
6292
|
(e) => new OrchestratorError({
|
|
5917
6293
|
message: `Failed to create session: ${e.message}`,
|
|
5918
6294
|
phase: "setup",
|
|
@@ -5920,10 +6296,39 @@ function runLoop(config) {
|
|
|
5920
6296
|
})
|
|
5921
6297
|
)
|
|
5922
6298
|
);
|
|
6299
|
+
const worktreePath = yield* git.createWorktree(session.id).pipe(
|
|
6300
|
+
Effect22.mapError(
|
|
6301
|
+
(e) => new OrchestratorError({
|
|
6302
|
+
message: `Failed to create worktree: ${e.message}`,
|
|
6303
|
+
phase: "setup",
|
|
6304
|
+
cause: e
|
|
6305
|
+
})
|
|
6306
|
+
)
|
|
6307
|
+
);
|
|
6308
|
+
const branchName = git.getBranchName(session.id);
|
|
6309
|
+
yield* sessionStore.update(session.id, {
|
|
6310
|
+
...session,
|
|
6311
|
+
worktreePath,
|
|
6312
|
+
branchName
|
|
6313
|
+
}).pipe(
|
|
6314
|
+
Effect22.tapError(
|
|
6315
|
+
(error) => Effect22.logDebug("Failed to update session with worktree info", {
|
|
6316
|
+
error: String(error)
|
|
6317
|
+
})
|
|
6318
|
+
),
|
|
6319
|
+
Effect22.orElseSucceed(() => void 0)
|
|
6320
|
+
);
|
|
5923
6321
|
const startTimeUtc = yield* DateTime10.now;
|
|
5924
6322
|
const startTime = DateTime10.toEpochMillis(startTimeUtc);
|
|
5925
|
-
const
|
|
5926
|
-
|
|
6323
|
+
const worktreeCreated = {
|
|
6324
|
+
_tag: "WorktreeCreated",
|
|
6325
|
+
sessionId: session.id,
|
|
6326
|
+
worktreePath,
|
|
6327
|
+
branchName,
|
|
6328
|
+
timestamp: startTime
|
|
6329
|
+
};
|
|
6330
|
+
const loopCompletedRef = yield* Ref12.make(false);
|
|
6331
|
+
const currentPlanRef = yield* Ref12.make(void 0);
|
|
5927
6332
|
const maxIterations = config.maxIterations === 0 ? Number.POSITIVE_INFINITY : config.maxIterations;
|
|
5928
6333
|
const loopStarted = {
|
|
5929
6334
|
_tag: "LoopStarted",
|
|
@@ -5936,12 +6341,13 @@ function runLoop(config) {
|
|
|
5936
6341
|
planStore,
|
|
5937
6342
|
currentPlanRef,
|
|
5938
6343
|
config,
|
|
5939
|
-
session.id
|
|
6344
|
+
session.id,
|
|
6345
|
+
worktreePath
|
|
5940
6346
|
);
|
|
5941
6347
|
const iterationsStream = Stream9.unfoldEffect(
|
|
5942
6348
|
1,
|
|
5943
|
-
(iteration) =>
|
|
5944
|
-
const completed = yield*
|
|
6349
|
+
(iteration) => Effect22.gen(function* () {
|
|
6350
|
+
const completed = yield* Ref12.get(loopCompletedRef);
|
|
5945
6351
|
if (completed || iteration > maxIterations) {
|
|
5946
6352
|
return Option.none();
|
|
5947
6353
|
}
|
|
@@ -5957,27 +6363,31 @@ function runLoop(config) {
|
|
|
5957
6363
|
loopCompletedRef,
|
|
5958
6364
|
config,
|
|
5959
6365
|
iteration,
|
|
5960
|
-
session.id
|
|
6366
|
+
session.id,
|
|
6367
|
+
worktreePath
|
|
5961
6368
|
)
|
|
5962
6369
|
)
|
|
5963
6370
|
);
|
|
5964
6371
|
const completionStream = createCompletionStream(
|
|
5965
6372
|
sessionStore,
|
|
6373
|
+
git,
|
|
5966
6374
|
session,
|
|
5967
6375
|
config,
|
|
5968
6376
|
startTime,
|
|
5969
|
-
loopCompletedRef
|
|
6377
|
+
loopCompletedRef,
|
|
6378
|
+
worktreePath
|
|
5970
6379
|
);
|
|
5971
6380
|
return pipe3(
|
|
5972
6381
|
Stream9.succeed(loopStarted),
|
|
6382
|
+
Stream9.concat(Stream9.succeed(worktreeCreated)),
|
|
5973
6383
|
Stream9.concat(discoveryStream),
|
|
5974
6384
|
Stream9.concat(iterationsStream),
|
|
5975
6385
|
Stream9.concat(completionStream)
|
|
5976
6386
|
);
|
|
5977
6387
|
}).pipe(
|
|
5978
6388
|
// Also catch setup errors (e.g., session creation failure)
|
|
5979
|
-
|
|
5980
|
-
(error) =>
|
|
6389
|
+
Effect22.catchAll(
|
|
6390
|
+
(error) => Effect22.succeed(
|
|
5981
6391
|
Stream9.succeed({
|
|
5982
6392
|
_tag: "LoopFailed",
|
|
5983
6393
|
error: {
|
|
@@ -5990,12 +6400,38 @@ function runLoop(config) {
|
|
|
5990
6400
|
)
|
|
5991
6401
|
);
|
|
5992
6402
|
}
|
|
5993
|
-
function createCompletionStream(sessionStore, session, config, startTime, loopCompletedRef) {
|
|
5994
|
-
return Stream9.
|
|
5995
|
-
|
|
6403
|
+
function createCompletionStream(sessionStore, git, session, config, startTime, loopCompletedRef, worktreePath) {
|
|
6404
|
+
return Stream9.unwrap(
|
|
6405
|
+
Effect22.gen(function* () {
|
|
5996
6406
|
const endTimeUtc = yield* DateTime10.now;
|
|
5997
6407
|
const durationMs = DateTime10.toEpochMillis(endTimeUtc) - startTime;
|
|
5998
|
-
const completed = yield*
|
|
6408
|
+
const completed = yield* Ref12.get(loopCompletedRef);
|
|
6409
|
+
yield* git.commitChanges(session.id, `feat: complete session ${session.id}`).pipe(
|
|
6410
|
+
Effect22.tapError(
|
|
6411
|
+
(error) => Effect22.logDebug("Final commit failed, continuing cleanup", {
|
|
6412
|
+
sessionId: session.id,
|
|
6413
|
+
error: String(error)
|
|
6414
|
+
})
|
|
6415
|
+
),
|
|
6416
|
+
Effect22.orElseSucceed(() => void 0)
|
|
6417
|
+
);
|
|
6418
|
+
yield* git.removeWorktree(session.id).pipe(
|
|
6419
|
+
Effect22.tapError(
|
|
6420
|
+
(error) => Effect22.logDebug("Worktree cleanup failed, continuing", {
|
|
6421
|
+
sessionId: session.id,
|
|
6422
|
+
worktreePath,
|
|
6423
|
+
error: String(error)
|
|
6424
|
+
})
|
|
6425
|
+
),
|
|
6426
|
+
Effect22.orElseSucceed(() => void 0)
|
|
6427
|
+
);
|
|
6428
|
+
const cleanupTimeUtc = yield* DateTime10.now;
|
|
6429
|
+
const cleanupTime = DateTime10.toEpochMillis(cleanupTimeUtc);
|
|
6430
|
+
const worktreeRemoved = {
|
|
6431
|
+
_tag: "WorktreeRemoved",
|
|
6432
|
+
sessionId: session.id,
|
|
6433
|
+
timestamp: cleanupTime
|
|
6434
|
+
};
|
|
5999
6435
|
const summary = {
|
|
6000
6436
|
iterations: config.maxIterations,
|
|
6001
6437
|
success: completed,
|
|
@@ -6007,16 +6443,16 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
|
|
|
6007
6443
|
...session,
|
|
6008
6444
|
status: completed ? "completed" : "paused"
|
|
6009
6445
|
}).pipe(
|
|
6010
|
-
|
|
6011
|
-
(error) =>
|
|
6446
|
+
Effect22.tapError(
|
|
6447
|
+
(error) => Effect22.logDebug("Session update failed, continuing", {
|
|
6012
6448
|
sessionId: session.id,
|
|
6013
6449
|
error: String(error)
|
|
6014
6450
|
})
|
|
6015
6451
|
),
|
|
6016
|
-
|
|
6452
|
+
Effect22.orElseSucceed(() => void 0)
|
|
6017
6453
|
);
|
|
6018
|
-
const
|
|
6019
|
-
return
|
|
6454
|
+
const loopCompleted = { _tag: "LoopCompleted", summary };
|
|
6455
|
+
return Stream9.fromIterable([worktreeRemoved, loopCompleted]);
|
|
6020
6456
|
})
|
|
6021
6457
|
);
|
|
6022
6458
|
}
|
|
@@ -6025,7 +6461,7 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
|
|
|
6025
6461
|
function run(options) {
|
|
6026
6462
|
const { config, consumer: consumerType = "headless", onEvent } = options;
|
|
6027
6463
|
const events = runLoop(config);
|
|
6028
|
-
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) =>
|
|
6464
|
+
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
|
|
6029
6465
|
const eventsWithLayers = eventsWithCallback.pipe(
|
|
6030
6466
|
Stream10.provideLayer(ProductionLayers)
|
|
6031
6467
|
);
|
|
@@ -6038,7 +6474,7 @@ function run(options) {
|
|
|
6038
6474
|
function runTest(options, mockEvents) {
|
|
6039
6475
|
const { config, onEvent } = options;
|
|
6040
6476
|
const events = runLoop(config);
|
|
6041
|
-
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) =>
|
|
6477
|
+
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
|
|
6042
6478
|
const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
|
|
6043
6479
|
const eventsWithLayers = eventsWithCallback.pipe(Stream10.provideLayer(layers));
|
|
6044
6480
|
return eventsWithLayers.pipe(Stream10.runDrain);
|
|
@@ -6046,11 +6482,11 @@ function runTest(options, mockEvents) {
|
|
|
6046
6482
|
function collectEvents(config, mockEvents) {
|
|
6047
6483
|
const events = runLoop(config);
|
|
6048
6484
|
const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
|
|
6049
|
-
return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(
|
|
6485
|
+
return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(Effect23.map((chunk) => Array.from(chunk)));
|
|
6050
6486
|
}
|
|
6051
6487
|
function main(config) {
|
|
6052
6488
|
const consumerType = process.stdout.isTTY ? "tui" : "headless";
|
|
6053
|
-
return run({ config, consumer: consumerType }).pipe(
|
|
6489
|
+
return run({ config, consumer: consumerType }).pipe(Effect23.runPromise);
|
|
6054
6490
|
}
|
|
6055
6491
|
|
|
6056
6492
|
// src/services/index.ts
|
|
@@ -6103,6 +6539,7 @@ export {
|
|
|
6103
6539
|
ExecutionModeSchema,
|
|
6104
6540
|
FerixParser,
|
|
6105
6541
|
FileLoggerConfigSchema,
|
|
6542
|
+
FileSystemGit,
|
|
6106
6543
|
FileSystemGuardrails,
|
|
6107
6544
|
FileSystemPlan,
|
|
6108
6545
|
FileSystemProgress,
|
|
@@ -6110,6 +6547,8 @@ export {
|
|
|
6110
6547
|
GeneratedTaskListSchema,
|
|
6111
6548
|
GeneratedTaskSchema,
|
|
6112
6549
|
GeneratedTaskStatusSchema,
|
|
6550
|
+
Git,
|
|
6551
|
+
GitError,
|
|
6113
6552
|
GuardrailAddedEventSchema,
|
|
6114
6553
|
GuardrailSchema,
|
|
6115
6554
|
GuardrailSeveritySchema,
|
|
@@ -6139,6 +6578,7 @@ export {
|
|
|
6139
6578
|
LoopStartedEventSchema,
|
|
6140
6579
|
LoopStatusSchema,
|
|
6141
6580
|
LoopSummarySchema,
|
|
6581
|
+
MemoryGit,
|
|
6142
6582
|
MemoryGuardrails,
|
|
6143
6583
|
MemoryPlan,
|
|
6144
6584
|
MemoryProgress,
|
|
@@ -6211,6 +6651,8 @@ export {
|
|
|
6211
6651
|
ToolStartEventSchema,
|
|
6212
6652
|
ToolUseEventSchema,
|
|
6213
6653
|
ViewModeSchema,
|
|
6654
|
+
WorktreeCreatedEventSchema,
|
|
6655
|
+
WorktreeRemovedEventSchema,
|
|
6214
6656
|
buildPrompt,
|
|
6215
6657
|
collectEvents,
|
|
6216
6658
|
createHeadlessConsumer,
|