ferix-code 0.0.2-beta.6 → 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 +736 -301
- package/package.json +1 -1
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: {
|
|
@@ -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();
|
|
@@ -2217,6 +2217,7 @@ var ANSIOutput = class {
|
|
|
2217
2217
|
init_esm_shims();
|
|
2218
2218
|
|
|
2219
2219
|
// src/consumers/tui/consumer.ts
|
|
2220
|
+
var CLOCK_REFRESH_INTERVAL_MS = 1e3;
|
|
2220
2221
|
function formatErrorToLines2(err, lines) {
|
|
2221
2222
|
if (err instanceof Error) {
|
|
2222
2223
|
lines.push(` - ${err.name}: ${err.message}`);
|
|
@@ -2276,6 +2277,17 @@ function createTUIConsumer() {
|
|
|
2276
2277
|
const inputFiber = yield* Effect3.forkDaemon(
|
|
2277
2278
|
runInputLoop(stateRef, output)
|
|
2278
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
|
+
);
|
|
2279
2291
|
const processEvents = events.pipe(
|
|
2280
2292
|
Stream3.runForEach(
|
|
2281
2293
|
(event) => Effect3.gen(function* () {
|
|
@@ -2327,6 +2339,7 @@ ${errorText}
|
|
|
2327
2339
|
})
|
|
2328
2340
|
);
|
|
2329
2341
|
}
|
|
2342
|
+
yield* Fiber.interrupt(clockFiber);
|
|
2330
2343
|
unsubscribeResize();
|
|
2331
2344
|
cleanup();
|
|
2332
2345
|
})
|
|
@@ -2335,13 +2348,15 @@ ${errorText}
|
|
|
2335
2348
|
|
|
2336
2349
|
// src/layers/index.ts
|
|
2337
2350
|
init_esm_shims();
|
|
2338
|
-
import { Layer as
|
|
2351
|
+
import { Layer as Layer14 } from "effect";
|
|
2339
2352
|
|
|
2340
|
-
// src/layers/
|
|
2353
|
+
// src/layers/git/file-system.ts
|
|
2341
2354
|
init_esm_shims();
|
|
2342
|
-
import {
|
|
2355
|
+
import { exec } from "child_process";
|
|
2356
|
+
import { access, mkdir, rm } from "fs/promises";
|
|
2343
2357
|
import { join } from "path";
|
|
2344
|
-
import {
|
|
2358
|
+
import { promisify } from "util";
|
|
2359
|
+
import { Effect as Effect4, Layer } from "effect";
|
|
2345
2360
|
|
|
2346
2361
|
// src/domain/errors.ts
|
|
2347
2362
|
init_esm_shims();
|
|
@@ -2362,6 +2377,338 @@ var GuardrailsStoreError = class extends Data.TaggedError(
|
|
|
2362
2377
|
};
|
|
2363
2378
|
var OrchestratorError = class extends Data.TaggedError("OrchestratorError") {
|
|
2364
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";
|
|
2365
2712
|
|
|
2366
2713
|
// src/domain/index.ts
|
|
2367
2714
|
init_esm_shims();
|
|
@@ -2649,6 +2996,16 @@ var ProgressUpdatedEventSchema = taggedEvent("ProgressUpdated", {
|
|
|
2649
2996
|
action: S4.Literal("started", "completed", "failed", "learning"),
|
|
2650
2997
|
timestamp: S4.Number
|
|
2651
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
|
+
});
|
|
2652
3009
|
var DomainEventSchema = S4.Union(
|
|
2653
3010
|
LoopStartedEventSchema,
|
|
2654
3011
|
LoopCompletedEventSchema,
|
|
@@ -2678,7 +3035,9 @@ var DomainEventSchema = S4.Union(
|
|
|
2678
3035
|
PlanUpdateFailedEventSchema,
|
|
2679
3036
|
LearningRecordedEventSchema,
|
|
2680
3037
|
GuardrailAddedEventSchema,
|
|
2681
|
-
ProgressUpdatedEventSchema
|
|
3038
|
+
ProgressUpdatedEventSchema,
|
|
3039
|
+
WorktreeCreatedEventSchema,
|
|
3040
|
+
WorktreeRemovedEventSchema
|
|
2682
3041
|
);
|
|
2683
3042
|
var DomainEventUtils = {
|
|
2684
3043
|
isLLMEvent: (e) => e._tag.startsWith("LLM"),
|
|
@@ -2806,7 +3165,9 @@ var SessionSchema = S10.Struct({
|
|
|
2806
3165
|
status: SessionStatusSchema,
|
|
2807
3166
|
originalTask: S10.String,
|
|
2808
3167
|
completedTasks: S10.Array(S10.String),
|
|
2809
|
-
currentTaskId: S10.optional(S10.String)
|
|
3168
|
+
currentTaskId: S10.optional(S10.String),
|
|
3169
|
+
worktreePath: S10.optional(S10.String),
|
|
3170
|
+
branchName: S10.optional(S10.String)
|
|
2810
3171
|
});
|
|
2811
3172
|
var decodeSession = S10.decodeUnknown(SessionSchema);
|
|
2812
3173
|
|
|
@@ -3054,34 +3415,34 @@ var TUIStateSchema = S13.Struct({
|
|
|
3054
3415
|
|
|
3055
3416
|
// src/services/guardrails-store.ts
|
|
3056
3417
|
init_esm_shims();
|
|
3057
|
-
import { Context } from "effect";
|
|
3058
|
-
var GuardrailsStore = class extends
|
|
3418
|
+
import { Context as Context2 } from "effect";
|
|
3419
|
+
var GuardrailsStore = class extends Context2.Tag("@ferix/GuardrailsStore")() {
|
|
3059
3420
|
};
|
|
3060
3421
|
|
|
3061
3422
|
// src/layers/guardrails/file-system.ts
|
|
3062
3423
|
var PLANS_DIR = ".ferix/plans";
|
|
3063
3424
|
function ensureDir(dirPath) {
|
|
3064
|
-
return
|
|
3065
|
-
try: () =>
|
|
3425
|
+
return Effect6.tryPromise({
|
|
3426
|
+
try: () => mkdir2(dirPath, { recursive: true }),
|
|
3066
3427
|
catch: (error) => new GuardrailsStoreError({
|
|
3067
3428
|
message: `Failed to create directory: ${dirPath}`,
|
|
3068
3429
|
operation: "add",
|
|
3069
3430
|
cause: error
|
|
3070
3431
|
})
|
|
3071
|
-
}).pipe(
|
|
3432
|
+
}).pipe(Effect6.asVoid);
|
|
3072
3433
|
}
|
|
3073
3434
|
function getSessionDir(sessionId) {
|
|
3074
|
-
return
|
|
3435
|
+
return join2(process.cwd(), PLANS_DIR, sessionId);
|
|
3075
3436
|
}
|
|
3076
3437
|
function getGuardrailsPath(sessionId) {
|
|
3077
|
-
return
|
|
3438
|
+
return join2(getSessionDir(sessionId), "guardrails.json");
|
|
3078
3439
|
}
|
|
3079
3440
|
function serializeGuardrails(guardrails) {
|
|
3080
3441
|
return JSON.stringify(guardrails, null, 2);
|
|
3081
3442
|
}
|
|
3082
3443
|
function deserializeGuardrails(json) {
|
|
3083
|
-
return
|
|
3084
|
-
const parsed = yield*
|
|
3444
|
+
return Effect6.gen(function* () {
|
|
3445
|
+
const parsed = yield* Effect6.try({
|
|
3085
3446
|
try: () => JSON.parse(json),
|
|
3086
3447
|
catch: (error) => new GuardrailsStoreError({
|
|
3087
3448
|
message: `Invalid JSON in guardrails file: ${String(error)}`,
|
|
@@ -3090,7 +3451,7 @@ function deserializeGuardrails(json) {
|
|
|
3090
3451
|
})
|
|
3091
3452
|
});
|
|
3092
3453
|
const validated = yield* decodeGuardrailsFile(parsed).pipe(
|
|
3093
|
-
|
|
3454
|
+
Effect6.mapError(
|
|
3094
3455
|
(error) => new GuardrailsStoreError({
|
|
3095
3456
|
message: `Guardrails validation failed: ${String(error)}`,
|
|
3096
3457
|
operation: "load",
|
|
@@ -3108,12 +3469,12 @@ function createEmptyGuardrails(sessionId, createdAt) {
|
|
|
3108
3469
|
guardrails: []
|
|
3109
3470
|
};
|
|
3110
3471
|
}
|
|
3111
|
-
var
|
|
3112
|
-
add: (sessionId, guardrail) =>
|
|
3472
|
+
var make2 = {
|
|
3473
|
+
add: (sessionId, guardrail) => Effect6.gen(function* () {
|
|
3113
3474
|
const sessionDir = getSessionDir(sessionId);
|
|
3114
3475
|
yield* ensureDir(sessionDir);
|
|
3115
3476
|
const guardrailsPath = getGuardrailsPath(sessionId);
|
|
3116
|
-
const existing = yield*
|
|
3477
|
+
const existing = yield* Effect6.tryPromise({
|
|
3117
3478
|
try: async () => {
|
|
3118
3479
|
try {
|
|
3119
3480
|
const content = await readFile(guardrailsPath, "utf-8");
|
|
@@ -3131,7 +3492,7 @@ var make = {
|
|
|
3131
3492
|
let guardrails;
|
|
3132
3493
|
if (existing) {
|
|
3133
3494
|
guardrails = yield* deserializeGuardrails(existing).pipe(
|
|
3134
|
-
|
|
3495
|
+
Effect6.mapError(
|
|
3135
3496
|
(err) => new GuardrailsStoreError({
|
|
3136
3497
|
message: err.message,
|
|
3137
3498
|
operation: "add",
|
|
@@ -3147,7 +3508,7 @@ var make = {
|
|
|
3147
3508
|
...guardrails,
|
|
3148
3509
|
guardrails: [...guardrails.guardrails, guardrail]
|
|
3149
3510
|
};
|
|
3150
|
-
yield*
|
|
3511
|
+
yield* Effect6.tryPromise({
|
|
3151
3512
|
try: () => writeFile(
|
|
3152
3513
|
guardrailsPath,
|
|
3153
3514
|
serializeGuardrails(updatedGuardrails),
|
|
@@ -3160,9 +3521,9 @@ var make = {
|
|
|
3160
3521
|
})
|
|
3161
3522
|
});
|
|
3162
3523
|
}),
|
|
3163
|
-
load: (sessionId) =>
|
|
3524
|
+
load: (sessionId) => Effect6.gen(function* () {
|
|
3164
3525
|
const guardrailsPath = getGuardrailsPath(sessionId);
|
|
3165
|
-
const content = yield*
|
|
3526
|
+
const content = yield* Effect6.tryPromise({
|
|
3166
3527
|
try: async () => {
|
|
3167
3528
|
try {
|
|
3168
3529
|
return await readFile(guardrailsPath, "utf-8");
|
|
@@ -3182,23 +3543,23 @@ var make = {
|
|
|
3182
3543
|
}
|
|
3183
3544
|
return yield* deserializeGuardrails(content);
|
|
3184
3545
|
}),
|
|
3185
|
-
getActive: (sessionId) =>
|
|
3186
|
-
const guardrails = yield*
|
|
3546
|
+
getActive: (sessionId) => Effect6.gen(function* () {
|
|
3547
|
+
const guardrails = yield* make2.load(sessionId);
|
|
3187
3548
|
return guardrails.guardrails;
|
|
3188
3549
|
})
|
|
3189
3550
|
};
|
|
3190
|
-
var
|
|
3551
|
+
var Live3 = Layer3.succeed(GuardrailsStore, make2);
|
|
3191
3552
|
var FileSystemGuardrails = {
|
|
3192
|
-
Live
|
|
3553
|
+
Live: Live3
|
|
3193
3554
|
};
|
|
3194
3555
|
|
|
3195
3556
|
// src/layers/guardrails/memory.ts
|
|
3196
3557
|
init_esm_shims();
|
|
3197
|
-
import { DateTime as DateTime2, Effect as
|
|
3558
|
+
import { DateTime as DateTime2, Effect as Effect7, Layer as Layer4, Ref as Ref4 } from "effect";
|
|
3198
3559
|
function createMemoryGuardrailsStore(stateRef) {
|
|
3199
3560
|
return {
|
|
3200
|
-
add: (sessionId, guardrail) =>
|
|
3201
|
-
const state = yield*
|
|
3561
|
+
add: (sessionId, guardrail) => Effect7.gen(function* () {
|
|
3562
|
+
const state = yield* Ref4.get(stateRef);
|
|
3202
3563
|
let guardrails = state.get(sessionId);
|
|
3203
3564
|
if (!guardrails) {
|
|
3204
3565
|
const now = yield* DateTime2.now;
|
|
@@ -3213,10 +3574,10 @@ function createMemoryGuardrailsStore(stateRef) {
|
|
|
3213
3574
|
guardrails: [...guardrails.guardrails, guardrail]
|
|
3214
3575
|
};
|
|
3215
3576
|
state.set(sessionId, updatedGuardrails);
|
|
3216
|
-
yield*
|
|
3577
|
+
yield* Ref4.set(stateRef, state);
|
|
3217
3578
|
}),
|
|
3218
|
-
load: (sessionId) =>
|
|
3219
|
-
const state = yield*
|
|
3579
|
+
load: (sessionId) => Effect7.gen(function* () {
|
|
3580
|
+
const state = yield* Ref4.get(stateRef);
|
|
3220
3581
|
const guardrails = state.get(sessionId);
|
|
3221
3582
|
if (!guardrails) {
|
|
3222
3583
|
const now = yield* DateTime2.now;
|
|
@@ -3228,8 +3589,8 @@ function createMemoryGuardrailsStore(stateRef) {
|
|
|
3228
3589
|
}
|
|
3229
3590
|
return guardrails;
|
|
3230
3591
|
}),
|
|
3231
|
-
getActive: (sessionId) =>
|
|
3232
|
-
const state = yield*
|
|
3592
|
+
getActive: (sessionId) => Effect7.gen(function* () {
|
|
3593
|
+
const state = yield* Ref4.get(stateRef);
|
|
3233
3594
|
const guardrails = state.get(sessionId);
|
|
3234
3595
|
if (!guardrails) {
|
|
3235
3596
|
return [];
|
|
@@ -3238,36 +3599,36 @@ function createMemoryGuardrailsStore(stateRef) {
|
|
|
3238
3599
|
})
|
|
3239
3600
|
};
|
|
3240
3601
|
}
|
|
3241
|
-
function
|
|
3242
|
-
return
|
|
3602
|
+
function layer2() {
|
|
3603
|
+
return Layer4.effect(
|
|
3243
3604
|
GuardrailsStore,
|
|
3244
|
-
|
|
3245
|
-
const stateRef = yield*
|
|
3605
|
+
Effect7.gen(function* () {
|
|
3606
|
+
const stateRef = yield* Ref4.make(/* @__PURE__ */ new Map());
|
|
3246
3607
|
return createMemoryGuardrailsStore(stateRef);
|
|
3247
3608
|
})
|
|
3248
3609
|
);
|
|
3249
3610
|
}
|
|
3250
|
-
var
|
|
3611
|
+
var Live4 = layer2();
|
|
3251
3612
|
var MemoryGuardrails = {
|
|
3252
|
-
Live:
|
|
3253
|
-
layer
|
|
3613
|
+
Live: Live4,
|
|
3614
|
+
layer: layer2
|
|
3254
3615
|
};
|
|
3255
3616
|
|
|
3256
3617
|
// src/layers/llm/claude-cli.ts
|
|
3257
3618
|
init_esm_shims();
|
|
3258
3619
|
import { spawn } from "child_process";
|
|
3259
|
-
import { Effect as
|
|
3620
|
+
import { Effect as Effect9, Layer as Layer5, Stream as Stream5 } from "effect";
|
|
3260
3621
|
|
|
3261
3622
|
// src/services/llm.ts
|
|
3262
3623
|
init_esm_shims();
|
|
3263
|
-
import { Context as
|
|
3264
|
-
var LLM = class extends
|
|
3624
|
+
import { Context as Context3 } from "effect";
|
|
3625
|
+
var LLM = class extends Context3.Tag("@ferix/LLM")() {
|
|
3265
3626
|
};
|
|
3266
3627
|
|
|
3267
3628
|
// src/layers/llm/stream.ts
|
|
3268
3629
|
init_esm_shims();
|
|
3269
3630
|
import { createInterface } from "readline";
|
|
3270
|
-
import { Effect as
|
|
3631
|
+
import { Effect as Effect8, Stream as Stream4 } from "effect";
|
|
3271
3632
|
|
|
3272
3633
|
// src/layers/llm/parsers.ts
|
|
3273
3634
|
init_esm_shims();
|
|
@@ -3393,7 +3754,7 @@ function createEventStream(child) {
|
|
|
3393
3754
|
emit.fail(
|
|
3394
3755
|
new LLMError({ message: "Failed to get stdout from child process" })
|
|
3395
3756
|
);
|
|
3396
|
-
return
|
|
3757
|
+
return Effect8.void;
|
|
3397
3758
|
}
|
|
3398
3759
|
const rl = createInterface({
|
|
3399
3760
|
input: stdout,
|
|
@@ -3432,17 +3793,17 @@ function createEventStream(child) {
|
|
|
3432
3793
|
})
|
|
3433
3794
|
);
|
|
3434
3795
|
});
|
|
3435
|
-
return
|
|
3796
|
+
return Effect8.sync(() => {
|
|
3436
3797
|
child.kill("SIGTERM");
|
|
3437
3798
|
});
|
|
3438
3799
|
});
|
|
3439
3800
|
}
|
|
3440
3801
|
|
|
3441
3802
|
// src/layers/llm/claude-cli.ts
|
|
3442
|
-
var
|
|
3443
|
-
execute: (prompt) => {
|
|
3803
|
+
var make3 = {
|
|
3804
|
+
execute: (prompt, options) => {
|
|
3444
3805
|
return Stream5.unwrap(
|
|
3445
|
-
|
|
3806
|
+
Effect9.sync(() => {
|
|
3446
3807
|
const child = spawn(
|
|
3447
3808
|
"claude",
|
|
3448
3809
|
[
|
|
@@ -3457,6 +3818,7 @@ var make2 = {
|
|
|
3457
3818
|
],
|
|
3458
3819
|
{
|
|
3459
3820
|
stdio: ["inherit", "pipe", "pipe"],
|
|
3821
|
+
cwd: options?.cwd,
|
|
3460
3822
|
env: {
|
|
3461
3823
|
...process.env,
|
|
3462
3824
|
FORCE_COLOR: "1"
|
|
@@ -3468,25 +3830,25 @@ var make2 = {
|
|
|
3468
3830
|
);
|
|
3469
3831
|
}
|
|
3470
3832
|
};
|
|
3471
|
-
var
|
|
3833
|
+
var Live5 = Layer5.succeed(LLM, make3);
|
|
3472
3834
|
var ClaudeCLI = {
|
|
3473
|
-
Live:
|
|
3835
|
+
Live: Live5
|
|
3474
3836
|
};
|
|
3475
3837
|
|
|
3476
3838
|
// src/layers/llm/mock.ts
|
|
3477
3839
|
init_esm_shims();
|
|
3478
|
-
import { Effect as
|
|
3840
|
+
import { Effect as Effect10, Layer as Layer6, Schema as S14, Stream as Stream6 } from "effect";
|
|
3479
3841
|
var MockLLMConfigSchema = S14.Struct({
|
|
3480
3842
|
events: S14.Array(LLMEventSchema),
|
|
3481
3843
|
delayMs: S14.optional(S14.Number)
|
|
3482
3844
|
});
|
|
3483
3845
|
function createMockLLM(config) {
|
|
3484
3846
|
return {
|
|
3485
|
-
execute: (_prompt) => {
|
|
3847
|
+
execute: (_prompt, _options) => {
|
|
3486
3848
|
const baseStream = Stream6.fromIterable(config.events);
|
|
3487
3849
|
if (config.delayMs !== void 0 && config.delayMs > 0) {
|
|
3488
3850
|
const delay = config.delayMs;
|
|
3489
|
-
return baseStream.pipe(Stream6.tap(() =>
|
|
3851
|
+
return baseStream.pipe(Stream6.tap(() => Effect10.sleep(delay)));
|
|
3490
3852
|
}
|
|
3491
3853
|
return baseStream;
|
|
3492
3854
|
}
|
|
@@ -3503,45 +3865,45 @@ var defaultMockEvents = [
|
|
|
3503
3865
|
}
|
|
3504
3866
|
];
|
|
3505
3867
|
var defaultMock = createMockLLM({ events: defaultMockEvents });
|
|
3506
|
-
var
|
|
3507
|
-
function
|
|
3508
|
-
return
|
|
3868
|
+
var Live6 = Layer6.succeed(LLM, defaultMock);
|
|
3869
|
+
function layer3(config) {
|
|
3870
|
+
return Layer6.succeed(LLM, createMockLLM(config));
|
|
3509
3871
|
}
|
|
3510
3872
|
var Mock = {
|
|
3511
|
-
Live:
|
|
3512
|
-
layer:
|
|
3873
|
+
Live: Live6,
|
|
3874
|
+
layer: layer3,
|
|
3513
3875
|
createMockLLM
|
|
3514
3876
|
};
|
|
3515
3877
|
|
|
3516
3878
|
// src/layers/plan/file-system.ts
|
|
3517
3879
|
init_esm_shims();
|
|
3518
|
-
import { access, mkdir as
|
|
3519
|
-
import { join as
|
|
3520
|
-
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";
|
|
3521
3883
|
|
|
3522
3884
|
// src/services/plan-store.ts
|
|
3523
3885
|
init_esm_shims();
|
|
3524
|
-
import { Context as
|
|
3525
|
-
var PlanStore = class extends
|
|
3886
|
+
import { Context as Context4 } from "effect";
|
|
3887
|
+
var PlanStore = class extends Context4.Tag("@ferix/PlanStore")() {
|
|
3526
3888
|
};
|
|
3527
3889
|
|
|
3528
3890
|
// src/layers/plan/file-system.ts
|
|
3529
3891
|
var PLANS_DIR2 = ".ferix/plans";
|
|
3530
3892
|
function ensureDir2(dirPath) {
|
|
3531
|
-
return
|
|
3532
|
-
try: () =>
|
|
3893
|
+
return Effect11.tryPromise({
|
|
3894
|
+
try: () => mkdir3(dirPath, { recursive: true }),
|
|
3533
3895
|
catch: (error) => new PlanStoreError({
|
|
3534
3896
|
message: `Failed to create directory: ${dirPath}`,
|
|
3535
3897
|
operation: "create",
|
|
3536
3898
|
cause: error
|
|
3537
3899
|
})
|
|
3538
|
-
}).pipe(
|
|
3900
|
+
}).pipe(Effect11.asVoid);
|
|
3539
3901
|
}
|
|
3540
3902
|
function getSessionDir2(sessionId) {
|
|
3541
|
-
return
|
|
3903
|
+
return join3(process.cwd(), PLANS_DIR2, sessionId);
|
|
3542
3904
|
}
|
|
3543
3905
|
function getPlanPath(sessionId, planId) {
|
|
3544
|
-
return
|
|
3906
|
+
return join3(getSessionDir2(sessionId), `${planId}.json`);
|
|
3545
3907
|
}
|
|
3546
3908
|
function generatePlanId(taskNumber) {
|
|
3547
3909
|
return PlanId(`task-${taskNumber}`);
|
|
@@ -3550,8 +3912,8 @@ function serializePlan(plan) {
|
|
|
3550
3912
|
return JSON.stringify(plan, null, 2);
|
|
3551
3913
|
}
|
|
3552
3914
|
function deserializePlan(json, planId) {
|
|
3553
|
-
return
|
|
3554
|
-
const parsed = yield*
|
|
3915
|
+
return Effect11.gen(function* () {
|
|
3916
|
+
const parsed = yield* Effect11.try({
|
|
3555
3917
|
try: () => JSON.parse(json),
|
|
3556
3918
|
catch: (error) => new PlanStoreError({
|
|
3557
3919
|
message: `Invalid JSON in plan file: ${String(error)}`,
|
|
@@ -3560,7 +3922,7 @@ function deserializePlan(json, planId) {
|
|
|
3560
3922
|
})
|
|
3561
3923
|
});
|
|
3562
3924
|
const validated = yield* decodePlanData(parsed).pipe(
|
|
3563
|
-
|
|
3925
|
+
Effect11.mapError(
|
|
3564
3926
|
(error) => new PlanStoreError({
|
|
3565
3927
|
message: `Plan validation failed: ${String(error)}`,
|
|
3566
3928
|
operation: "load",
|
|
@@ -3574,11 +3936,11 @@ function deserializePlan(json, planId) {
|
|
|
3574
3936
|
};
|
|
3575
3937
|
});
|
|
3576
3938
|
}
|
|
3577
|
-
var
|
|
3578
|
-
create: (sessionId, plan) =>
|
|
3939
|
+
var make4 = {
|
|
3940
|
+
create: (sessionId, plan) => Effect11.gen(function* () {
|
|
3579
3941
|
const sessionDir = getSessionDir2(sessionId);
|
|
3580
3942
|
yield* ensureDir2(sessionDir);
|
|
3581
|
-
const existingPlans = yield*
|
|
3943
|
+
const existingPlans = yield* Effect11.tryPromise({
|
|
3582
3944
|
try: async () => {
|
|
3583
3945
|
try {
|
|
3584
3946
|
const files = await readdir(sessionDir);
|
|
@@ -3595,7 +3957,7 @@ var make3 = {
|
|
|
3595
3957
|
const planId = generatePlanId(existingPlans + 1);
|
|
3596
3958
|
const planPath = getPlanPath(sessionId, planId);
|
|
3597
3959
|
const fullPlan = { ...plan, id: planId };
|
|
3598
|
-
yield*
|
|
3960
|
+
yield* Effect11.tryPromise({
|
|
3599
3961
|
try: () => writeFile2(planPath, serializePlan(fullPlan), "utf-8"),
|
|
3600
3962
|
catch: (error) => new PlanStoreError({
|
|
3601
3963
|
message: `Failed to write plan file: ${planPath}`,
|
|
@@ -3605,10 +3967,10 @@ var make3 = {
|
|
|
3605
3967
|
});
|
|
3606
3968
|
return planId;
|
|
3607
3969
|
}),
|
|
3608
|
-
load: (planId, sessionId) =>
|
|
3970
|
+
load: (planId, sessionId) => Effect11.gen(function* () {
|
|
3609
3971
|
if (sessionId) {
|
|
3610
3972
|
const planPath = getPlanPath(sessionId, planId);
|
|
3611
|
-
const content = yield*
|
|
3973
|
+
const content = yield* Effect11.tryPromise({
|
|
3612
3974
|
try: () => readFile2(planPath, "utf-8"),
|
|
3613
3975
|
catch: (error) => new PlanStoreError({
|
|
3614
3976
|
message: `Failed to read plan file: ${planPath}`,
|
|
@@ -3618,9 +3980,9 @@ var make3 = {
|
|
|
3618
3980
|
});
|
|
3619
3981
|
return yield* deserializePlan(content, planId);
|
|
3620
3982
|
}
|
|
3621
|
-
const sessionDirs = yield*
|
|
3983
|
+
const sessionDirs = yield* Effect11.tryPromise({
|
|
3622
3984
|
try: async () => {
|
|
3623
|
-
const plansDir =
|
|
3985
|
+
const plansDir = join3(process.cwd(), PLANS_DIR2);
|
|
3624
3986
|
const dirs = await readdir(plansDir);
|
|
3625
3987
|
return dirs;
|
|
3626
3988
|
},
|
|
@@ -3632,18 +3994,18 @@ var make3 = {
|
|
|
3632
3994
|
});
|
|
3633
3995
|
for (const sid of sessionDirs) {
|
|
3634
3996
|
const planPath = getPlanPath(sid, planId);
|
|
3635
|
-
const exists = yield*
|
|
3997
|
+
const exists = yield* Effect11.tryPromise({
|
|
3636
3998
|
try: async () => {
|
|
3637
|
-
await
|
|
3999
|
+
await access2(planPath);
|
|
3638
4000
|
return true;
|
|
3639
4001
|
},
|
|
3640
4002
|
catch: () => new PlanStoreError({
|
|
3641
4003
|
message: "File not found",
|
|
3642
4004
|
operation: "load"
|
|
3643
4005
|
})
|
|
3644
|
-
}).pipe(
|
|
4006
|
+
}).pipe(Effect11.orElseSucceed(() => false));
|
|
3645
4007
|
if (exists) {
|
|
3646
|
-
const content = yield*
|
|
4008
|
+
const content = yield* Effect11.tryPromise({
|
|
3647
4009
|
try: () => readFile2(planPath, "utf-8"),
|
|
3648
4010
|
catch: (error) => new PlanStoreError({
|
|
3649
4011
|
message: `Failed to read plan file: ${planPath}`,
|
|
@@ -3654,16 +4016,16 @@ var make3 = {
|
|
|
3654
4016
|
return yield* deserializePlan(content, planId);
|
|
3655
4017
|
}
|
|
3656
4018
|
}
|
|
3657
|
-
return yield*
|
|
4019
|
+
return yield* Effect11.fail(
|
|
3658
4020
|
new PlanStoreError({
|
|
3659
4021
|
message: `Plan not found: ${planId}`,
|
|
3660
4022
|
operation: "load"
|
|
3661
4023
|
})
|
|
3662
4024
|
);
|
|
3663
4025
|
}),
|
|
3664
|
-
update: (planId, plan) =>
|
|
4026
|
+
update: (planId, plan) => Effect11.gen(function* () {
|
|
3665
4027
|
const planPath = getPlanPath(plan.sessionId, planId);
|
|
3666
|
-
yield*
|
|
4028
|
+
yield* Effect11.tryPromise({
|
|
3667
4029
|
try: () => writeFile2(planPath, serializePlan(plan), "utf-8"),
|
|
3668
4030
|
catch: (error) => new PlanStoreError({
|
|
3669
4031
|
message: `Failed to update plan file: ${planPath}`,
|
|
@@ -3672,9 +4034,9 @@ var make3 = {
|
|
|
3672
4034
|
})
|
|
3673
4035
|
});
|
|
3674
4036
|
}),
|
|
3675
|
-
list: (sessionId) =>
|
|
4037
|
+
list: (sessionId) => Effect11.gen(function* () {
|
|
3676
4038
|
const sessionDir = getSessionDir2(sessionId);
|
|
3677
|
-
const files = yield*
|
|
4039
|
+
const files = yield* Effect11.tryPromise({
|
|
3678
4040
|
try: async () => {
|
|
3679
4041
|
try {
|
|
3680
4042
|
return await readdir(sessionDir);
|
|
@@ -3691,18 +4053,18 @@ var make3 = {
|
|
|
3691
4053
|
return files.filter((f) => f.endsWith(".json")).map((f) => PlanId(f.replace(".json", "")));
|
|
3692
4054
|
})
|
|
3693
4055
|
};
|
|
3694
|
-
var
|
|
4056
|
+
var Live7 = Layer7.succeed(PlanStore, make4);
|
|
3695
4057
|
var FileSystemPlan = {
|
|
3696
|
-
Live:
|
|
4058
|
+
Live: Live7
|
|
3697
4059
|
};
|
|
3698
4060
|
|
|
3699
4061
|
// src/layers/plan/memory.ts
|
|
3700
4062
|
init_esm_shims();
|
|
3701
|
-
import { Effect as
|
|
4063
|
+
import { Effect as Effect12, Layer as Layer8, Ref as Ref5 } from "effect";
|
|
3702
4064
|
function createMemoryPlanStore(stateRef) {
|
|
3703
4065
|
return {
|
|
3704
|
-
create: (sessionId, plan) =>
|
|
3705
|
-
const state = yield*
|
|
4066
|
+
create: (sessionId, plan) => Effect12.gen(function* () {
|
|
4067
|
+
const state = yield* Ref5.get(stateRef);
|
|
3706
4068
|
if (!state.has(sessionId)) {
|
|
3707
4069
|
state.set(sessionId, /* @__PURE__ */ new Map());
|
|
3708
4070
|
}
|
|
@@ -3713,18 +4075,18 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3713
4075
|
const planId = PlanId(`task-${sessionPlans.size + 1}`);
|
|
3714
4076
|
const fullPlan = { ...plan, id: planId };
|
|
3715
4077
|
sessionPlans.set(planId, fullPlan);
|
|
3716
|
-
yield*
|
|
4078
|
+
yield* Ref5.set(stateRef, state);
|
|
3717
4079
|
return planId;
|
|
3718
4080
|
}),
|
|
3719
|
-
load: (planId, sessionId) =>
|
|
3720
|
-
const state = yield*
|
|
4081
|
+
load: (planId, sessionId) => Effect12.gen(function* () {
|
|
4082
|
+
const state = yield* Ref5.get(stateRef);
|
|
3721
4083
|
if (sessionId) {
|
|
3722
4084
|
const sessionPlans = state.get(sessionId);
|
|
3723
4085
|
const plan = sessionPlans?.get(planId);
|
|
3724
4086
|
if (plan) {
|
|
3725
4087
|
return plan;
|
|
3726
4088
|
}
|
|
3727
|
-
return yield*
|
|
4089
|
+
return yield* Effect12.fail(
|
|
3728
4090
|
new PlanStoreError({
|
|
3729
4091
|
message: `Plan not found: ${planId}`,
|
|
3730
4092
|
operation: "load"
|
|
@@ -3737,18 +4099,18 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3737
4099
|
return plan;
|
|
3738
4100
|
}
|
|
3739
4101
|
}
|
|
3740
|
-
return yield*
|
|
4102
|
+
return yield* Effect12.fail(
|
|
3741
4103
|
new PlanStoreError({
|
|
3742
4104
|
message: `Plan not found: ${planId}`,
|
|
3743
4105
|
operation: "load"
|
|
3744
4106
|
})
|
|
3745
4107
|
);
|
|
3746
4108
|
}),
|
|
3747
|
-
update: (planId, plan) =>
|
|
3748
|
-
const state = yield*
|
|
4109
|
+
update: (planId, plan) => Effect12.gen(function* () {
|
|
4110
|
+
const state = yield* Ref5.get(stateRef);
|
|
3749
4111
|
const sessionPlans = state.get(plan.sessionId);
|
|
3750
4112
|
if (!sessionPlans) {
|
|
3751
|
-
return yield*
|
|
4113
|
+
return yield* Effect12.fail(
|
|
3752
4114
|
new PlanStoreError({
|
|
3753
4115
|
message: `Session not found: ${plan.sessionId}`,
|
|
3754
4116
|
operation: "update"
|
|
@@ -3756,10 +4118,10 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3756
4118
|
);
|
|
3757
4119
|
}
|
|
3758
4120
|
sessionPlans.set(planId, plan);
|
|
3759
|
-
yield*
|
|
4121
|
+
yield* Ref5.set(stateRef, state);
|
|
3760
4122
|
}),
|
|
3761
|
-
list: (sessionId) =>
|
|
3762
|
-
const state = yield*
|
|
4123
|
+
list: (sessionId) => Effect12.gen(function* () {
|
|
4124
|
+
const state = yield* Ref5.get(stateRef);
|
|
3763
4125
|
const sessionPlans = state.get(sessionId);
|
|
3764
4126
|
if (!sessionPlans) {
|
|
3765
4127
|
return [];
|
|
@@ -3768,57 +4130,57 @@ function createMemoryPlanStore(stateRef) {
|
|
|
3768
4130
|
})
|
|
3769
4131
|
};
|
|
3770
4132
|
}
|
|
3771
|
-
function
|
|
3772
|
-
return
|
|
4133
|
+
function layer4() {
|
|
4134
|
+
return Layer8.effect(
|
|
3773
4135
|
PlanStore,
|
|
3774
|
-
|
|
3775
|
-
const stateRef = yield*
|
|
4136
|
+
Effect12.gen(function* () {
|
|
4137
|
+
const stateRef = yield* Ref5.make(/* @__PURE__ */ new Map());
|
|
3776
4138
|
return createMemoryPlanStore(stateRef);
|
|
3777
4139
|
})
|
|
3778
4140
|
);
|
|
3779
4141
|
}
|
|
3780
|
-
var
|
|
4142
|
+
var Live8 = layer4();
|
|
3781
4143
|
var MemoryPlan = {
|
|
3782
|
-
Live:
|
|
3783
|
-
layer:
|
|
4144
|
+
Live: Live8,
|
|
4145
|
+
layer: layer4
|
|
3784
4146
|
};
|
|
3785
4147
|
|
|
3786
4148
|
// src/layers/progress/file-system.ts
|
|
3787
4149
|
init_esm_shims();
|
|
3788
|
-
import { mkdir as
|
|
3789
|
-
import { join as
|
|
3790
|
-
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";
|
|
3791
4153
|
|
|
3792
4154
|
// src/services/progress-store.ts
|
|
3793
4155
|
init_esm_shims();
|
|
3794
|
-
import { Context as
|
|
3795
|
-
var ProgressStore = class extends
|
|
4156
|
+
import { Context as Context5 } from "effect";
|
|
4157
|
+
var ProgressStore = class extends Context5.Tag("@ferix/ProgressStore")() {
|
|
3796
4158
|
};
|
|
3797
4159
|
|
|
3798
4160
|
// src/layers/progress/file-system.ts
|
|
3799
4161
|
var PLANS_DIR3 = ".ferix/plans";
|
|
3800
4162
|
function ensureDir3(dirPath) {
|
|
3801
|
-
return
|
|
3802
|
-
try: () =>
|
|
4163
|
+
return Effect13.tryPromise({
|
|
4164
|
+
try: () => mkdir4(dirPath, { recursive: true }),
|
|
3803
4165
|
catch: (error) => new ProgressStoreError({
|
|
3804
4166
|
message: `Failed to create directory: ${dirPath}`,
|
|
3805
4167
|
operation: "append",
|
|
3806
4168
|
cause: error
|
|
3807
4169
|
})
|
|
3808
|
-
}).pipe(
|
|
4170
|
+
}).pipe(Effect13.asVoid);
|
|
3809
4171
|
}
|
|
3810
4172
|
function getSessionDir3(sessionId) {
|
|
3811
|
-
return
|
|
4173
|
+
return join4(process.cwd(), PLANS_DIR3, sessionId);
|
|
3812
4174
|
}
|
|
3813
4175
|
function getProgressPath(sessionId) {
|
|
3814
|
-
return
|
|
4176
|
+
return join4(getSessionDir3(sessionId), "progress.json");
|
|
3815
4177
|
}
|
|
3816
4178
|
function serializeProgress(progress) {
|
|
3817
4179
|
return JSON.stringify(progress, null, 2);
|
|
3818
4180
|
}
|
|
3819
4181
|
function deserializeProgress(json) {
|
|
3820
|
-
return
|
|
3821
|
-
const parsed = yield*
|
|
4182
|
+
return Effect13.gen(function* () {
|
|
4183
|
+
const parsed = yield* Effect13.try({
|
|
3822
4184
|
try: () => JSON.parse(json),
|
|
3823
4185
|
catch: (error) => new ProgressStoreError({
|
|
3824
4186
|
message: `Invalid JSON in progress file: ${String(error)}`,
|
|
@@ -3827,7 +4189,7 @@ function deserializeProgress(json) {
|
|
|
3827
4189
|
})
|
|
3828
4190
|
});
|
|
3829
4191
|
const validated = yield* decodeProgressFile(parsed).pipe(
|
|
3830
|
-
|
|
4192
|
+
Effect13.mapError(
|
|
3831
4193
|
(error) => new ProgressStoreError({
|
|
3832
4194
|
message: `Progress validation failed: ${String(error)}`,
|
|
3833
4195
|
operation: "load",
|
|
@@ -3845,12 +4207,12 @@ function createEmptyProgress(sessionId, createdAt) {
|
|
|
3845
4207
|
entries: []
|
|
3846
4208
|
};
|
|
3847
4209
|
}
|
|
3848
|
-
var
|
|
3849
|
-
append: (sessionId, entry) =>
|
|
4210
|
+
var make5 = {
|
|
4211
|
+
append: (sessionId, entry) => Effect13.gen(function* () {
|
|
3850
4212
|
const sessionDir = getSessionDir3(sessionId);
|
|
3851
4213
|
yield* ensureDir3(sessionDir);
|
|
3852
4214
|
const progressPath = getProgressPath(sessionId);
|
|
3853
|
-
const existing = yield*
|
|
4215
|
+
const existing = yield* Effect13.tryPromise({
|
|
3854
4216
|
try: async () => {
|
|
3855
4217
|
try {
|
|
3856
4218
|
const content = await readFile3(progressPath, "utf-8");
|
|
@@ -3868,7 +4230,7 @@ var make4 = {
|
|
|
3868
4230
|
let progress;
|
|
3869
4231
|
if (existing) {
|
|
3870
4232
|
progress = yield* deserializeProgress(existing).pipe(
|
|
3871
|
-
|
|
4233
|
+
Effect13.mapError(
|
|
3872
4234
|
(err) => new ProgressStoreError({
|
|
3873
4235
|
message: err.message,
|
|
3874
4236
|
operation: "append",
|
|
@@ -3884,7 +4246,7 @@ var make4 = {
|
|
|
3884
4246
|
...progress,
|
|
3885
4247
|
entries: [...progress.entries, entry]
|
|
3886
4248
|
};
|
|
3887
|
-
yield*
|
|
4249
|
+
yield* Effect13.tryPromise({
|
|
3888
4250
|
try: () => writeFile3(progressPath, serializeProgress(updatedProgress), "utf-8"),
|
|
3889
4251
|
catch: (error) => new ProgressStoreError({
|
|
3890
4252
|
message: `Failed to write progress file: ${progressPath}`,
|
|
@@ -3893,9 +4255,9 @@ var make4 = {
|
|
|
3893
4255
|
})
|
|
3894
4256
|
});
|
|
3895
4257
|
}),
|
|
3896
|
-
load: (sessionId) =>
|
|
4258
|
+
load: (sessionId) => Effect13.gen(function* () {
|
|
3897
4259
|
const progressPath = getProgressPath(sessionId);
|
|
3898
|
-
const content = yield*
|
|
4260
|
+
const content = yield* Effect13.tryPromise({
|
|
3899
4261
|
try: async () => {
|
|
3900
4262
|
try {
|
|
3901
4263
|
return await readFile3(progressPath, "utf-8");
|
|
@@ -3915,24 +4277,24 @@ var make4 = {
|
|
|
3915
4277
|
}
|
|
3916
4278
|
return yield* deserializeProgress(content);
|
|
3917
4279
|
}),
|
|
3918
|
-
getRecent: (sessionId, count) =>
|
|
3919
|
-
const progress = yield*
|
|
4280
|
+
getRecent: (sessionId, count) => Effect13.gen(function* () {
|
|
4281
|
+
const progress = yield* make5.load(sessionId);
|
|
3920
4282
|
const entries = progress.entries;
|
|
3921
4283
|
return entries.slice(-count);
|
|
3922
4284
|
})
|
|
3923
4285
|
};
|
|
3924
|
-
var
|
|
4286
|
+
var Live9 = Layer9.succeed(ProgressStore, make5);
|
|
3925
4287
|
var FileSystemProgress = {
|
|
3926
|
-
Live:
|
|
4288
|
+
Live: Live9
|
|
3927
4289
|
};
|
|
3928
4290
|
|
|
3929
4291
|
// src/layers/progress/memory.ts
|
|
3930
4292
|
init_esm_shims();
|
|
3931
|
-
import { DateTime as DateTime4, Effect as
|
|
4293
|
+
import { DateTime as DateTime4, Effect as Effect14, Layer as Layer10, Ref as Ref6 } from "effect";
|
|
3932
4294
|
function createMemoryProgressStore(stateRef) {
|
|
3933
4295
|
return {
|
|
3934
|
-
append: (sessionId, entry) =>
|
|
3935
|
-
const state = yield*
|
|
4296
|
+
append: (sessionId, entry) => Effect14.gen(function* () {
|
|
4297
|
+
const state = yield* Ref6.get(stateRef);
|
|
3936
4298
|
let progress = state.get(sessionId);
|
|
3937
4299
|
if (!progress) {
|
|
3938
4300
|
const now = yield* DateTime4.now;
|
|
@@ -3947,10 +4309,10 @@ function createMemoryProgressStore(stateRef) {
|
|
|
3947
4309
|
entries: [...progress.entries, entry]
|
|
3948
4310
|
};
|
|
3949
4311
|
state.set(sessionId, updatedProgress);
|
|
3950
|
-
yield*
|
|
4312
|
+
yield* Ref6.set(stateRef, state);
|
|
3951
4313
|
}),
|
|
3952
|
-
load: (sessionId) =>
|
|
3953
|
-
const state = yield*
|
|
4314
|
+
load: (sessionId) => Effect14.gen(function* () {
|
|
4315
|
+
const state = yield* Ref6.get(stateRef);
|
|
3954
4316
|
const progress = state.get(sessionId);
|
|
3955
4317
|
if (!progress) {
|
|
3956
4318
|
const now = yield* DateTime4.now;
|
|
@@ -3962,8 +4324,8 @@ function createMemoryProgressStore(stateRef) {
|
|
|
3962
4324
|
}
|
|
3963
4325
|
return progress;
|
|
3964
4326
|
}),
|
|
3965
|
-
getRecent: (sessionId, count) =>
|
|
3966
|
-
const state = yield*
|
|
4327
|
+
getRecent: (sessionId, count) => Effect14.gen(function* () {
|
|
4328
|
+
const state = yield* Ref6.get(stateRef);
|
|
3967
4329
|
const progress = state.get(sessionId);
|
|
3968
4330
|
if (!progress) {
|
|
3969
4331
|
return [];
|
|
@@ -3972,32 +4334,32 @@ function createMemoryProgressStore(stateRef) {
|
|
|
3972
4334
|
})
|
|
3973
4335
|
};
|
|
3974
4336
|
}
|
|
3975
|
-
function
|
|
3976
|
-
return
|
|
4337
|
+
function layer5() {
|
|
4338
|
+
return Layer10.effect(
|
|
3977
4339
|
ProgressStore,
|
|
3978
|
-
|
|
3979
|
-
const stateRef = yield*
|
|
4340
|
+
Effect14.gen(function* () {
|
|
4341
|
+
const stateRef = yield* Ref6.make(/* @__PURE__ */ new Map());
|
|
3980
4342
|
return createMemoryProgressStore(stateRef);
|
|
3981
4343
|
})
|
|
3982
4344
|
);
|
|
3983
4345
|
}
|
|
3984
|
-
var
|
|
4346
|
+
var Live10 = layer5();
|
|
3985
4347
|
var MemoryProgress = {
|
|
3986
|
-
Live:
|
|
3987
|
-
layer:
|
|
4348
|
+
Live: Live10,
|
|
4349
|
+
layer: layer5
|
|
3988
4350
|
};
|
|
3989
4351
|
|
|
3990
4352
|
// src/layers/session/file-system.ts
|
|
3991
4353
|
init_esm_shims();
|
|
3992
|
-
import { mkdir as
|
|
3993
|
-
import { join as
|
|
3994
|
-
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";
|
|
3995
4357
|
import { humanId } from "human-id";
|
|
3996
4358
|
|
|
3997
4359
|
// src/services/session-store.ts
|
|
3998
4360
|
init_esm_shims();
|
|
3999
|
-
import { Context as
|
|
4000
|
-
var SessionStore = class extends
|
|
4361
|
+
import { Context as Context6 } from "effect";
|
|
4362
|
+
var SessionStore = class extends Context6.Tag("@ferix/SessionStore")() {
|
|
4001
4363
|
};
|
|
4002
4364
|
|
|
4003
4365
|
// src/layers/session/file-system.ts
|
|
@@ -4007,24 +4369,24 @@ function generateSessionId(timestampMs) {
|
|
|
4007
4369
|
return `${id}-${timestampMs}`;
|
|
4008
4370
|
}
|
|
4009
4371
|
function ensureDir4(dirPath) {
|
|
4010
|
-
return
|
|
4011
|
-
try: () =>
|
|
4372
|
+
return Effect15.tryPromise({
|
|
4373
|
+
try: () => mkdir5(dirPath, { recursive: true }),
|
|
4012
4374
|
catch: (error) => new SessionStoreError({
|
|
4013
4375
|
message: `Failed to create directory: ${dirPath}`,
|
|
4014
4376
|
operation: "create",
|
|
4015
4377
|
cause: error
|
|
4016
4378
|
})
|
|
4017
|
-
}).pipe(
|
|
4379
|
+
}).pipe(Effect15.asVoid);
|
|
4018
4380
|
}
|
|
4019
4381
|
function getSessionPath(sessionId) {
|
|
4020
|
-
return
|
|
4382
|
+
return join5(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
|
|
4021
4383
|
}
|
|
4022
4384
|
function serializeSession(session) {
|
|
4023
4385
|
return JSON.stringify(session, null, 2);
|
|
4024
4386
|
}
|
|
4025
4387
|
function deserializeSession(json) {
|
|
4026
|
-
return
|
|
4027
|
-
const parsed = yield*
|
|
4388
|
+
return Effect15.gen(function* () {
|
|
4389
|
+
const parsed = yield* Effect15.try({
|
|
4028
4390
|
try: () => JSON.parse(json),
|
|
4029
4391
|
catch: (error) => new SessionStoreError({
|
|
4030
4392
|
message: `Invalid JSON in session file: ${String(error)}`,
|
|
@@ -4033,7 +4395,7 @@ function deserializeSession(json) {
|
|
|
4033
4395
|
})
|
|
4034
4396
|
});
|
|
4035
4397
|
const validated = yield* decodeSession(parsed).pipe(
|
|
4036
|
-
|
|
4398
|
+
Effect15.mapError(
|
|
4037
4399
|
(error) => new SessionStoreError({
|
|
4038
4400
|
message: `Session validation failed: ${String(error)}`,
|
|
4039
4401
|
operation: "get",
|
|
@@ -4044,9 +4406,9 @@ function deserializeSession(json) {
|
|
|
4044
4406
|
return validated;
|
|
4045
4407
|
});
|
|
4046
4408
|
}
|
|
4047
|
-
var
|
|
4048
|
-
create: (originalTask) =>
|
|
4049
|
-
const sessionsDir =
|
|
4409
|
+
var make6 = {
|
|
4410
|
+
create: (originalTask) => Effect15.gen(function* () {
|
|
4411
|
+
const sessionsDir = join5(process.cwd(), SESSIONS_DIR);
|
|
4050
4412
|
yield* ensureDir4(sessionsDir);
|
|
4051
4413
|
const now = yield* DateTime5.now;
|
|
4052
4414
|
const timestampMs = DateTime5.toEpochMillis(now);
|
|
@@ -4059,7 +4421,7 @@ var make5 = {
|
|
|
4059
4421
|
completedTasks: []
|
|
4060
4422
|
};
|
|
4061
4423
|
const sessionPath = getSessionPath(sessionId);
|
|
4062
|
-
yield*
|
|
4424
|
+
yield* Effect15.tryPromise({
|
|
4063
4425
|
try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
|
|
4064
4426
|
catch: (error) => new SessionStoreError({
|
|
4065
4427
|
message: `Failed to write session file: ${sessionPath}`,
|
|
@@ -4069,9 +4431,9 @@ var make5 = {
|
|
|
4069
4431
|
});
|
|
4070
4432
|
return session;
|
|
4071
4433
|
}),
|
|
4072
|
-
get: (sessionId) =>
|
|
4434
|
+
get: (sessionId) => Effect15.gen(function* () {
|
|
4073
4435
|
const sessionPath = getSessionPath(sessionId);
|
|
4074
|
-
const content = yield*
|
|
4436
|
+
const content = yield* Effect15.tryPromise({
|
|
4075
4437
|
try: () => readFile4(sessionPath, "utf-8"),
|
|
4076
4438
|
catch: (error) => new SessionStoreError({
|
|
4077
4439
|
message: `Failed to read session file: ${sessionPath}`,
|
|
@@ -4081,9 +4443,9 @@ var make5 = {
|
|
|
4081
4443
|
});
|
|
4082
4444
|
return yield* deserializeSession(content);
|
|
4083
4445
|
}),
|
|
4084
|
-
update: (sessionId, session) =>
|
|
4446
|
+
update: (sessionId, session) => Effect15.gen(function* () {
|
|
4085
4447
|
const sessionPath = getSessionPath(sessionId);
|
|
4086
|
-
yield*
|
|
4448
|
+
yield* Effect15.tryPromise({
|
|
4087
4449
|
try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
|
|
4088
4450
|
catch: (error) => new SessionStoreError({
|
|
4089
4451
|
message: `Failed to update session file: ${sessionPath}`,
|
|
@@ -4093,19 +4455,19 @@ var make5 = {
|
|
|
4093
4455
|
});
|
|
4094
4456
|
})
|
|
4095
4457
|
};
|
|
4096
|
-
var
|
|
4458
|
+
var Live11 = Layer11.succeed(SessionStore, make6);
|
|
4097
4459
|
var FileSystemSession = {
|
|
4098
|
-
Live:
|
|
4460
|
+
Live: Live11
|
|
4099
4461
|
};
|
|
4100
4462
|
|
|
4101
4463
|
// src/layers/session/memory.ts
|
|
4102
4464
|
init_esm_shims();
|
|
4103
|
-
import { DateTime as DateTime6, Effect as
|
|
4465
|
+
import { DateTime as DateTime6, Effect as Effect16, Layer as Layer12, Ref as Ref7 } from "effect";
|
|
4104
4466
|
function createMemorySessionStore(stateRef, counterRef) {
|
|
4105
4467
|
return {
|
|
4106
|
-
create: (originalTask) =>
|
|
4107
|
-
const state = yield*
|
|
4108
|
-
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);
|
|
4109
4471
|
const sessionId = `test-session-${counter}`;
|
|
4110
4472
|
const now = yield* DateTime6.now;
|
|
4111
4473
|
const session = {
|
|
@@ -4116,14 +4478,14 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4116
4478
|
completedTasks: []
|
|
4117
4479
|
};
|
|
4118
4480
|
state.set(sessionId, session);
|
|
4119
|
-
yield*
|
|
4481
|
+
yield* Ref7.set(stateRef, state);
|
|
4120
4482
|
return session;
|
|
4121
4483
|
}),
|
|
4122
|
-
get: (sessionId) =>
|
|
4123
|
-
const state = yield*
|
|
4484
|
+
get: (sessionId) => Effect16.gen(function* () {
|
|
4485
|
+
const state = yield* Ref7.get(stateRef);
|
|
4124
4486
|
const session = state.get(sessionId);
|
|
4125
4487
|
if (!session) {
|
|
4126
|
-
return yield*
|
|
4488
|
+
return yield* Effect16.fail(
|
|
4127
4489
|
new SessionStoreError({
|
|
4128
4490
|
message: `Session not found: ${sessionId}`,
|
|
4129
4491
|
operation: "get"
|
|
@@ -4132,10 +4494,10 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4132
4494
|
}
|
|
4133
4495
|
return session;
|
|
4134
4496
|
}),
|
|
4135
|
-
update: (sessionId, session) =>
|
|
4136
|
-
const state = yield*
|
|
4497
|
+
update: (sessionId, session) => Effect16.gen(function* () {
|
|
4498
|
+
const state = yield* Ref7.get(stateRef);
|
|
4137
4499
|
if (!state.has(sessionId)) {
|
|
4138
|
-
return yield*
|
|
4500
|
+
return yield* Effect16.fail(
|
|
4139
4501
|
new SessionStoreError({
|
|
4140
4502
|
message: `Session not found: ${sessionId}`,
|
|
4141
4503
|
operation: "update"
|
|
@@ -4143,34 +4505,34 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4143
4505
|
);
|
|
4144
4506
|
}
|
|
4145
4507
|
state.set(sessionId, session);
|
|
4146
|
-
yield*
|
|
4508
|
+
yield* Ref7.set(stateRef, state);
|
|
4147
4509
|
})
|
|
4148
4510
|
};
|
|
4149
4511
|
}
|
|
4150
|
-
function
|
|
4151
|
-
return
|
|
4512
|
+
function layer6() {
|
|
4513
|
+
return Layer12.effect(
|
|
4152
4514
|
SessionStore,
|
|
4153
|
-
|
|
4154
|
-
const stateRef = yield*
|
|
4155
|
-
const counterRef = yield*
|
|
4515
|
+
Effect16.gen(function* () {
|
|
4516
|
+
const stateRef = yield* Ref7.make(/* @__PURE__ */ new Map());
|
|
4517
|
+
const counterRef = yield* Ref7.make(0);
|
|
4156
4518
|
return createMemorySessionStore(stateRef, counterRef);
|
|
4157
4519
|
})
|
|
4158
4520
|
);
|
|
4159
4521
|
}
|
|
4160
|
-
var
|
|
4522
|
+
var Live12 = layer6();
|
|
4161
4523
|
var MemorySession = {
|
|
4162
|
-
Live:
|
|
4163
|
-
layer:
|
|
4524
|
+
Live: Live12,
|
|
4525
|
+
layer: layer6
|
|
4164
4526
|
};
|
|
4165
4527
|
|
|
4166
4528
|
// src/layers/signal/ferix-parser.ts
|
|
4167
4529
|
init_esm_shims();
|
|
4168
|
-
import { Effect as
|
|
4530
|
+
import { Effect as Effect17, Layer as Layer13, Ref as Ref8 } from "effect";
|
|
4169
4531
|
|
|
4170
4532
|
// src/services/signal-parser.ts
|
|
4171
4533
|
init_esm_shims();
|
|
4172
|
-
import { Context as
|
|
4173
|
-
var SignalParser = class extends
|
|
4534
|
+
import { Context as Context7 } from "effect";
|
|
4535
|
+
var SignalParser = class extends Context7.Tag("@ferix/SignalParser")() {
|
|
4174
4536
|
};
|
|
4175
4537
|
|
|
4176
4538
|
// src/layers/signal/specs/index.ts
|
|
@@ -4675,20 +5037,20 @@ signalSpecRegistry.register(tasksDefinedSpec);
|
|
|
4675
5037
|
// src/layers/signal/ferix-parser.ts
|
|
4676
5038
|
var MAX_BUFFER_SIZE = 1024 * 1024;
|
|
4677
5039
|
function createAccumulatorImpl() {
|
|
4678
|
-
return
|
|
4679
|
-
const chunksRef = yield*
|
|
4680
|
-
const emittedRef = yield*
|
|
4681
|
-
const feed = (text) =>
|
|
4682
|
-
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);
|
|
4683
5045
|
chunks.push(text);
|
|
4684
5046
|
const buffer = chunks.join("");
|
|
4685
5047
|
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
4686
|
-
yield*
|
|
5048
|
+
yield* Ref8.set(chunksRef, [
|
|
4687
5049
|
buffer.slice(buffer.length - MAX_BUFFER_SIZE)
|
|
4688
5050
|
]);
|
|
4689
5051
|
}
|
|
4690
5052
|
const signals = signalSpecRegistry.parseAll(buffer);
|
|
4691
|
-
const emitted = yield*
|
|
5053
|
+
const emitted = yield* Ref8.get(emittedRef);
|
|
4692
5054
|
const newSignals = signals.filter((signal) => {
|
|
4693
5055
|
const key = signalSpecRegistry.getSignalKey(signal);
|
|
4694
5056
|
if (emitted.has(key)) {
|
|
@@ -4700,61 +5062,64 @@ function createAccumulatorImpl() {
|
|
|
4700
5062
|
if (newSignals.length > 0) {
|
|
4701
5063
|
const lastEndPos = signalSpecRegistry.findLastCompleteSignalEnd(buffer);
|
|
4702
5064
|
if (lastEndPos > 0 && lastEndPos < buffer.length) {
|
|
4703
|
-
yield*
|
|
5065
|
+
yield* Ref8.set(chunksRef, [buffer.slice(lastEndPos)]);
|
|
4704
5066
|
}
|
|
4705
5067
|
}
|
|
4706
|
-
yield*
|
|
5068
|
+
yield* Ref8.set(emittedRef, emitted);
|
|
4707
5069
|
return newSignals;
|
|
4708
5070
|
});
|
|
4709
|
-
const flush = () =>
|
|
4710
|
-
const chunks = yield*
|
|
5071
|
+
const flush = () => Effect17.gen(function* () {
|
|
5072
|
+
const chunks = yield* Ref8.get(chunksRef);
|
|
4711
5073
|
const buffer = chunks.join("");
|
|
4712
|
-
yield*
|
|
4713
|
-
const emitted = yield*
|
|
5074
|
+
yield* Ref8.set(chunksRef, []);
|
|
5075
|
+
const emitted = yield* Ref8.get(emittedRef);
|
|
4714
5076
|
const signals = signalSpecRegistry.parseAll(buffer);
|
|
4715
5077
|
const result = signals.filter(
|
|
4716
5078
|
(signal) => !emitted.has(signalSpecRegistry.getSignalKey(signal))
|
|
4717
5079
|
);
|
|
4718
|
-
yield*
|
|
5080
|
+
yield* Ref8.set(emittedRef, /* @__PURE__ */ new Set());
|
|
4719
5081
|
return result;
|
|
4720
5082
|
});
|
|
4721
5083
|
return { feed, flush };
|
|
4722
5084
|
});
|
|
4723
5085
|
}
|
|
4724
|
-
var
|
|
4725
|
-
parse: (text) =>
|
|
5086
|
+
var make7 = {
|
|
5087
|
+
parse: (text) => Effect17.succeed(signalSpecRegistry.parseAll(text)),
|
|
4726
5088
|
createAccumulator: createAccumulatorImpl
|
|
4727
5089
|
};
|
|
4728
|
-
var
|
|
5090
|
+
var Live13 = Layer13.succeed(SignalParser, make7);
|
|
4729
5091
|
var FerixParser = {
|
|
4730
|
-
Live:
|
|
5092
|
+
Live: Live13
|
|
4731
5093
|
};
|
|
4732
5094
|
|
|
4733
5095
|
// src/layers/index.ts
|
|
4734
|
-
var ProductionLayers =
|
|
5096
|
+
var ProductionLayers = Layer14.mergeAll(
|
|
4735
5097
|
ClaudeCLI.Live,
|
|
4736
5098
|
FerixParser.Live,
|
|
4737
5099
|
FileSystemPlan.Live,
|
|
4738
5100
|
FileSystemSession.Live,
|
|
4739
5101
|
FileSystemProgress.Live,
|
|
4740
|
-
FileSystemGuardrails.Live
|
|
5102
|
+
FileSystemGuardrails.Live,
|
|
5103
|
+
FileSystemGit.Live
|
|
4741
5104
|
);
|
|
4742
|
-
var TestLayers =
|
|
5105
|
+
var TestLayers = Layer14.mergeAll(
|
|
4743
5106
|
Mock.Live,
|
|
4744
5107
|
FerixParser.Live,
|
|
4745
5108
|
MemoryPlan.Live,
|
|
4746
5109
|
MemorySession.Live,
|
|
4747
5110
|
MemoryProgress.Live,
|
|
4748
|
-
MemoryGuardrails.Live
|
|
5111
|
+
MemoryGuardrails.Live,
|
|
5112
|
+
MemoryGit.Live
|
|
4749
5113
|
);
|
|
4750
5114
|
function createTestLayers(events) {
|
|
4751
|
-
return
|
|
5115
|
+
return Layer14.mergeAll(
|
|
4752
5116
|
Mock.layer({ events }),
|
|
4753
5117
|
FerixParser.Live,
|
|
4754
5118
|
MemoryPlan.layer(),
|
|
4755
5119
|
MemorySession.layer(),
|
|
4756
5120
|
MemoryProgress.layer(),
|
|
4757
|
-
MemoryGuardrails.layer()
|
|
5121
|
+
MemoryGuardrails.layer(),
|
|
5122
|
+
MemoryGit.layer()
|
|
4758
5123
|
);
|
|
4759
5124
|
}
|
|
4760
5125
|
|
|
@@ -4763,41 +5128,41 @@ init_esm_shims();
|
|
|
4763
5128
|
|
|
4764
5129
|
// src/orchestrator/loop.ts
|
|
4765
5130
|
init_esm_shims();
|
|
4766
|
-
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";
|
|
4767
5132
|
|
|
4768
5133
|
// src/orchestrator/discovery.ts
|
|
4769
5134
|
init_esm_shims();
|
|
4770
|
-
import { DateTime as DateTime8, Effect as
|
|
5135
|
+
import { DateTime as DateTime8, Effect as Effect20, pipe, Ref as Ref10, Stream as Stream7 } from "effect";
|
|
4771
5136
|
|
|
4772
5137
|
// src/layers/plan/task-generation.ts
|
|
4773
5138
|
init_esm_shims();
|
|
4774
|
-
import { mkdir as
|
|
4775
|
-
import { join as
|
|
4776
|
-
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";
|
|
4777
5142
|
var PLANS_DIR4 = ".ferix/plans";
|
|
4778
5143
|
function ensureDir5(dirPath) {
|
|
4779
|
-
return
|
|
4780
|
-
try: () =>
|
|
5144
|
+
return Effect18.tryPromise({
|
|
5145
|
+
try: () => mkdir6(dirPath, { recursive: true }),
|
|
4781
5146
|
catch: (error) => new PlanStoreError({
|
|
4782
5147
|
message: `Failed to create directory: ${dirPath}`,
|
|
4783
5148
|
operation: "create",
|
|
4784
5149
|
cause: error
|
|
4785
5150
|
})
|
|
4786
|
-
}).pipe(
|
|
5151
|
+
}).pipe(Effect18.asVoid);
|
|
4787
5152
|
}
|
|
4788
5153
|
function getSessionDir4(sessionId) {
|
|
4789
|
-
return
|
|
5154
|
+
return join6(process.cwd(), PLANS_DIR4, sessionId);
|
|
4790
5155
|
}
|
|
4791
5156
|
function getTasksMdPath(sessionId) {
|
|
4792
|
-
return
|
|
5157
|
+
return join6(getSessionDir4(sessionId), "tasks.md");
|
|
4793
5158
|
}
|
|
4794
5159
|
function writeTasksMd(sessionId, tasks) {
|
|
4795
|
-
return
|
|
5160
|
+
return Effect18.gen(function* () {
|
|
4796
5161
|
const sessionDir = getSessionDir4(sessionId);
|
|
4797
5162
|
yield* ensureDir5(sessionDir);
|
|
4798
5163
|
const tasksMdPath = getTasksMdPath(sessionId);
|
|
4799
5164
|
const content = formatTasksMd(tasks);
|
|
4800
|
-
yield*
|
|
5165
|
+
yield* Effect18.tryPromise({
|
|
4801
5166
|
try: () => writeFile5(tasksMdPath, content, "utf-8"),
|
|
4802
5167
|
catch: (error) => new PlanStoreError({
|
|
4803
5168
|
message: `Failed to write tasks.md: ${tasksMdPath}`,
|
|
@@ -4979,7 +5344,7 @@ function mapSignalToDomain(signal, context) {
|
|
|
4979
5344
|
|
|
4980
5345
|
// src/orchestrator/plan-updates.ts
|
|
4981
5346
|
init_esm_shims();
|
|
4982
|
-
import { DateTime as DateTime7, Effect as
|
|
5347
|
+
import { DateTime as DateTime7, Effect as Effect19, Ref as Ref9 } from "effect";
|
|
4983
5348
|
|
|
4984
5349
|
// src/orchestrator/plan-updates/index.ts
|
|
4985
5350
|
init_esm_shims();
|
|
@@ -5284,9 +5649,9 @@ function persistPlanUpdate(planStore, plan, operation) {
|
|
|
5284
5649
|
tasks: plan.tasks
|
|
5285
5650
|
}) : planStore.update(plan.id, plan);
|
|
5286
5651
|
return storeOp.pipe(
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
(error) =>
|
|
5652
|
+
Effect19.map(() => null),
|
|
5653
|
+
Effect19.catchAll(
|
|
5654
|
+
(error) => Effect19.succeed({
|
|
5290
5655
|
_tag: "PlanUpdateFailed",
|
|
5291
5656
|
operation,
|
|
5292
5657
|
error: error.message,
|
|
@@ -5296,8 +5661,8 @@ function persistPlanUpdate(planStore, plan, operation) {
|
|
|
5296
5661
|
);
|
|
5297
5662
|
}
|
|
5298
5663
|
function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessionId, originalTask) {
|
|
5299
|
-
return
|
|
5300
|
-
const currentPlan = yield*
|
|
5664
|
+
return Effect19.gen(function* () {
|
|
5665
|
+
const currentPlan = yield* Ref9.get(currentPlanRef);
|
|
5301
5666
|
const now = yield* DateTime7.now;
|
|
5302
5667
|
const timestamp = DateTime7.formatIso(now);
|
|
5303
5668
|
const updateResult = computePlanUpdate(signal, currentPlan, {
|
|
@@ -5309,8 +5674,8 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
|
|
|
5309
5674
|
return [];
|
|
5310
5675
|
}
|
|
5311
5676
|
const { plan, operation, eventTag } = updateResult;
|
|
5312
|
-
yield*
|
|
5313
|
-
yield*
|
|
5677
|
+
yield* Ref9.set(currentPlanRef, plan);
|
|
5678
|
+
yield* Ref9.update(persistenceStateRef, (state) => ({
|
|
5314
5679
|
dirty: true,
|
|
5315
5680
|
pendingOperation: state.pendingOperation === "create" ? "create" : operation
|
|
5316
5681
|
}));
|
|
@@ -5318,12 +5683,12 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
|
|
|
5318
5683
|
});
|
|
5319
5684
|
}
|
|
5320
5685
|
function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
|
|
5321
|
-
return
|
|
5322
|
-
const state = yield*
|
|
5686
|
+
return Effect19.gen(function* () {
|
|
5687
|
+
const state = yield* Ref9.get(persistenceStateRef);
|
|
5323
5688
|
if (!(state.dirty && state.pendingOperation)) {
|
|
5324
5689
|
return [];
|
|
5325
5690
|
}
|
|
5326
|
-
const plan = yield*
|
|
5691
|
+
const plan = yield* Ref9.get(currentPlanRef);
|
|
5327
5692
|
if (!plan) {
|
|
5328
5693
|
return [];
|
|
5329
5694
|
}
|
|
@@ -5336,7 +5701,7 @@ function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
|
|
|
5336
5701
|
if (failureEvent) {
|
|
5337
5702
|
events.push(failureEvent);
|
|
5338
5703
|
}
|
|
5339
|
-
yield*
|
|
5704
|
+
yield* Ref9.set(persistenceStateRef, {
|
|
5340
5705
|
dirty: false,
|
|
5341
5706
|
pendingOperation: null
|
|
5342
5707
|
});
|
|
@@ -5592,12 +5957,12 @@ Begin.`);
|
|
|
5592
5957
|
|
|
5593
5958
|
// src/orchestrator/discovery.ts
|
|
5594
5959
|
function processTextSignals(signalParser, text, context) {
|
|
5595
|
-
return
|
|
5960
|
+
return Effect20.gen(function* () {
|
|
5596
5961
|
const events = [];
|
|
5597
5962
|
const parsedSignals = [];
|
|
5598
5963
|
const signals = yield* signalParser.parse(text).pipe(
|
|
5599
|
-
|
|
5600
|
-
(error) =>
|
|
5964
|
+
Effect20.tapError(
|
|
5965
|
+
(error) => Effect20.logDebug(
|
|
5601
5966
|
"Signal parsing failed, continuing with empty signals",
|
|
5602
5967
|
{
|
|
5603
5968
|
error: String(error),
|
|
@@ -5605,7 +5970,7 @@ function processTextSignals(signalParser, text, context) {
|
|
|
5605
5970
|
}
|
|
5606
5971
|
)
|
|
5607
5972
|
),
|
|
5608
|
-
|
|
5973
|
+
Effect20.orElseSucceed(() => [])
|
|
5609
5974
|
);
|
|
5610
5975
|
for (const signal of signals) {
|
|
5611
5976
|
events.push(mapSignalToDomain(signal, context));
|
|
@@ -5615,7 +5980,7 @@ function processTextSignals(signalParser, text, context) {
|
|
|
5615
5980
|
});
|
|
5616
5981
|
}
|
|
5617
5982
|
function processLLMEvent(signalParser, llmEvent, context) {
|
|
5618
|
-
return
|
|
5983
|
+
return Effect20.gen(function* () {
|
|
5619
5984
|
const domainEvent = mapLLMEventToDomain(llmEvent, context);
|
|
5620
5985
|
const events = [domainEvent];
|
|
5621
5986
|
const allSignals = [];
|
|
@@ -5658,12 +6023,12 @@ function planTasksToGeneratedTasks(plan) {
|
|
|
5658
6023
|
status: mapTaskStatus(task.status)
|
|
5659
6024
|
}));
|
|
5660
6025
|
}
|
|
5661
|
-
function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId) {
|
|
6026
|
+
function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId, worktreePath) {
|
|
5662
6027
|
return Stream7.unwrap(
|
|
5663
|
-
|
|
6028
|
+
Effect20.gen(function* () {
|
|
5664
6029
|
const startTimeUtc = yield* DateTime8.now;
|
|
5665
6030
|
const startTime = DateTime8.toEpochMillis(startTimeUtc);
|
|
5666
|
-
const persistenceStateRef = yield*
|
|
6031
|
+
const persistenceStateRef = yield* Ref10.make({
|
|
5667
6032
|
dirty: false,
|
|
5668
6033
|
pendingOperation: null
|
|
5669
6034
|
});
|
|
@@ -5677,7 +6042,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5677
6042
|
input: {}
|
|
5678
6043
|
};
|
|
5679
6044
|
const prompt = buildDiscoveryPrompt(config);
|
|
5680
|
-
const llmStream = llm.execute(prompt).pipe(
|
|
6045
|
+
const llmStream = llm.execute(prompt, worktreePath ? { cwd: worktreePath } : void 0).pipe(
|
|
5681
6046
|
Stream7.mapError(
|
|
5682
6047
|
(e) => new OrchestratorError({
|
|
5683
6048
|
message: `LLM execution failed during discovery: ${String(e)}`,
|
|
@@ -5687,7 +6052,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5687
6052
|
),
|
|
5688
6053
|
Stream7.flatMap(
|
|
5689
6054
|
(llmEvent) => Stream7.unwrap(
|
|
5690
|
-
|
|
6055
|
+
Effect20.gen(function* () {
|
|
5691
6056
|
const now = yield* DateTime8.now;
|
|
5692
6057
|
const context = {
|
|
5693
6058
|
timestamp: DateTime8.toEpochMillis(now)
|
|
@@ -5724,23 +6089,23 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5724
6089
|
)
|
|
5725
6090
|
);
|
|
5726
6091
|
const completionStream = Stream7.fromEffect(
|
|
5727
|
-
|
|
6092
|
+
Effect20.gen(function* () {
|
|
5728
6093
|
const persistEvents = yield* flushPlanPersistence(
|
|
5729
6094
|
planStore,
|
|
5730
6095
|
currentPlanRef,
|
|
5731
6096
|
persistenceStateRef
|
|
5732
6097
|
);
|
|
5733
|
-
const plan = yield*
|
|
6098
|
+
const plan = yield* Ref10.get(currentPlanRef);
|
|
5734
6099
|
const taskCount = plan?.tasks.length ?? 0;
|
|
5735
6100
|
if (plan && plan.tasks.length > 0) {
|
|
5736
6101
|
const generatedTasks = planTasksToGeneratedTasks(plan);
|
|
5737
6102
|
yield* writeTasksMd(sessionId, generatedTasks).pipe(
|
|
5738
|
-
|
|
5739
|
-
(error) =>
|
|
6103
|
+
Effect20.tapError(
|
|
6104
|
+
(error) => Effect20.logDebug("Failed to write tasks.md, continuing", {
|
|
5740
6105
|
error: String(error)
|
|
5741
6106
|
})
|
|
5742
6107
|
),
|
|
5743
|
-
|
|
6108
|
+
Effect20.orElseSucceed(() => void 0)
|
|
5744
6109
|
);
|
|
5745
6110
|
}
|
|
5746
6111
|
const endTimeUtc = yield* DateTime8.now;
|
|
@@ -5765,15 +6130,15 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
5765
6130
|
|
|
5766
6131
|
// src/orchestrator/iteration.ts
|
|
5767
6132
|
init_esm_shims();
|
|
5768
|
-
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";
|
|
5769
6134
|
function processTextSignals2(signalParser, text, context) {
|
|
5770
|
-
return
|
|
6135
|
+
return Effect21.gen(function* () {
|
|
5771
6136
|
const events = [];
|
|
5772
6137
|
let completed = false;
|
|
5773
6138
|
const parsedSignals = [];
|
|
5774
6139
|
const signals = yield* signalParser.parse(text).pipe(
|
|
5775
|
-
|
|
5776
|
-
(error) =>
|
|
6140
|
+
Effect21.tapError(
|
|
6141
|
+
(error) => Effect21.logDebug(
|
|
5777
6142
|
"Signal parsing failed, continuing with empty signals",
|
|
5778
6143
|
{
|
|
5779
6144
|
error: String(error),
|
|
@@ -5781,7 +6146,7 @@ function processTextSignals2(signalParser, text, context) {
|
|
|
5781
6146
|
}
|
|
5782
6147
|
)
|
|
5783
6148
|
),
|
|
5784
|
-
|
|
6149
|
+
Effect21.orElseSucceed(() => [])
|
|
5785
6150
|
);
|
|
5786
6151
|
for (const signal of signals) {
|
|
5787
6152
|
events.push(mapSignalToDomain(signal, context));
|
|
@@ -5794,7 +6159,7 @@ function processTextSignals2(signalParser, text, context) {
|
|
|
5794
6159
|
});
|
|
5795
6160
|
}
|
|
5796
6161
|
function processLLMEvent2(signalParser, llmEvent, context) {
|
|
5797
|
-
return
|
|
6162
|
+
return Effect21.gen(function* () {
|
|
5798
6163
|
const domainEvent = mapLLMEventToDomain(llmEvent, context);
|
|
5799
6164
|
const events = [domainEvent];
|
|
5800
6165
|
let completed = false;
|
|
@@ -5825,11 +6190,11 @@ function processLLMEvent2(signalParser, llmEvent, context) {
|
|
|
5825
6190
|
return { events, completed, signals: allSignals };
|
|
5826
6191
|
});
|
|
5827
6192
|
}
|
|
5828
|
-
function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId) {
|
|
6193
|
+
function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId, worktreePath) {
|
|
5829
6194
|
return Stream8.unwrap(
|
|
5830
|
-
|
|
5831
|
-
const currentPlan = yield*
|
|
5832
|
-
const persistenceStateRef = yield*
|
|
6195
|
+
Effect21.gen(function* () {
|
|
6196
|
+
const currentPlan = yield* Ref11.get(currentPlanRef);
|
|
6197
|
+
const persistenceStateRef = yield* Ref11.make({
|
|
5833
6198
|
dirty: false,
|
|
5834
6199
|
pendingOperation: null
|
|
5835
6200
|
});
|
|
@@ -5837,7 +6202,10 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5837
6202
|
_tag: "IterationStarted",
|
|
5838
6203
|
iteration
|
|
5839
6204
|
};
|
|
5840
|
-
const llmStream = llm.execute(
|
|
6205
|
+
const llmStream = llm.execute(
|
|
6206
|
+
buildPrompt(config, iteration, currentPlan),
|
|
6207
|
+
worktreePath ? { cwd: worktreePath } : void 0
|
|
6208
|
+
).pipe(
|
|
5841
6209
|
Stream8.mapError(
|
|
5842
6210
|
(e) => new OrchestratorError({
|
|
5843
6211
|
message: `LLM execution failed: ${String(e)}`,
|
|
@@ -5847,7 +6215,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5847
6215
|
),
|
|
5848
6216
|
Stream8.flatMap(
|
|
5849
6217
|
(llmEvent) => Stream8.unwrap(
|
|
5850
|
-
|
|
6218
|
+
Effect21.gen(function* () {
|
|
5851
6219
|
const now = yield* DateTime9.now;
|
|
5852
6220
|
const context = {
|
|
5853
6221
|
timestamp: DateTime9.toEpochMillis(now)
|
|
@@ -5869,7 +6237,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5869
6237
|
events.push(...planEvents);
|
|
5870
6238
|
}
|
|
5871
6239
|
if (result.completed) {
|
|
5872
|
-
yield*
|
|
6240
|
+
yield* Ref11.set(loopCompletedRef, true);
|
|
5873
6241
|
}
|
|
5874
6242
|
return Stream8.fromIterable(events);
|
|
5875
6243
|
})
|
|
@@ -5888,7 +6256,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5888
6256
|
)
|
|
5889
6257
|
);
|
|
5890
6258
|
const completionStream = Stream8.fromEffect(
|
|
5891
|
-
|
|
6259
|
+
Effect21.gen(function* () {
|
|
5892
6260
|
const persistEvents = yield* flushPlanPersistence(
|
|
5893
6261
|
planStore,
|
|
5894
6262
|
currentPlanRef,
|
|
@@ -5913,13 +6281,14 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
5913
6281
|
// src/orchestrator/loop.ts
|
|
5914
6282
|
function runLoop(config) {
|
|
5915
6283
|
return Stream9.unwrap(
|
|
5916
|
-
|
|
6284
|
+
Effect22.gen(function* () {
|
|
5917
6285
|
const llm = yield* LLM;
|
|
5918
6286
|
const signalParser = yield* SignalParser;
|
|
5919
6287
|
const sessionStore = yield* SessionStore;
|
|
5920
6288
|
const planStore = yield* PlanStore;
|
|
6289
|
+
const git = yield* Git;
|
|
5921
6290
|
const session = yield* sessionStore.create(config.task).pipe(
|
|
5922
|
-
|
|
6291
|
+
Effect22.mapError(
|
|
5923
6292
|
(e) => new OrchestratorError({
|
|
5924
6293
|
message: `Failed to create session: ${e.message}`,
|
|
5925
6294
|
phase: "setup",
|
|
@@ -5927,10 +6296,39 @@ function runLoop(config) {
|
|
|
5927
6296
|
})
|
|
5928
6297
|
)
|
|
5929
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
|
+
);
|
|
5930
6321
|
const startTimeUtc = yield* DateTime10.now;
|
|
5931
6322
|
const startTime = DateTime10.toEpochMillis(startTimeUtc);
|
|
5932
|
-
const
|
|
5933
|
-
|
|
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);
|
|
5934
6332
|
const maxIterations = config.maxIterations === 0 ? Number.POSITIVE_INFINITY : config.maxIterations;
|
|
5935
6333
|
const loopStarted = {
|
|
5936
6334
|
_tag: "LoopStarted",
|
|
@@ -5943,12 +6341,13 @@ function runLoop(config) {
|
|
|
5943
6341
|
planStore,
|
|
5944
6342
|
currentPlanRef,
|
|
5945
6343
|
config,
|
|
5946
|
-
session.id
|
|
6344
|
+
session.id,
|
|
6345
|
+
worktreePath
|
|
5947
6346
|
);
|
|
5948
6347
|
const iterationsStream = Stream9.unfoldEffect(
|
|
5949
6348
|
1,
|
|
5950
|
-
(iteration) =>
|
|
5951
|
-
const completed = yield*
|
|
6349
|
+
(iteration) => Effect22.gen(function* () {
|
|
6350
|
+
const completed = yield* Ref12.get(loopCompletedRef);
|
|
5952
6351
|
if (completed || iteration > maxIterations) {
|
|
5953
6352
|
return Option.none();
|
|
5954
6353
|
}
|
|
@@ -5964,27 +6363,31 @@ function runLoop(config) {
|
|
|
5964
6363
|
loopCompletedRef,
|
|
5965
6364
|
config,
|
|
5966
6365
|
iteration,
|
|
5967
|
-
session.id
|
|
6366
|
+
session.id,
|
|
6367
|
+
worktreePath
|
|
5968
6368
|
)
|
|
5969
6369
|
)
|
|
5970
6370
|
);
|
|
5971
6371
|
const completionStream = createCompletionStream(
|
|
5972
6372
|
sessionStore,
|
|
6373
|
+
git,
|
|
5973
6374
|
session,
|
|
5974
6375
|
config,
|
|
5975
6376
|
startTime,
|
|
5976
|
-
loopCompletedRef
|
|
6377
|
+
loopCompletedRef,
|
|
6378
|
+
worktreePath
|
|
5977
6379
|
);
|
|
5978
6380
|
return pipe3(
|
|
5979
6381
|
Stream9.succeed(loopStarted),
|
|
6382
|
+
Stream9.concat(Stream9.succeed(worktreeCreated)),
|
|
5980
6383
|
Stream9.concat(discoveryStream),
|
|
5981
6384
|
Stream9.concat(iterationsStream),
|
|
5982
6385
|
Stream9.concat(completionStream)
|
|
5983
6386
|
);
|
|
5984
6387
|
}).pipe(
|
|
5985
6388
|
// Also catch setup errors (e.g., session creation failure)
|
|
5986
|
-
|
|
5987
|
-
(error) =>
|
|
6389
|
+
Effect22.catchAll(
|
|
6390
|
+
(error) => Effect22.succeed(
|
|
5988
6391
|
Stream9.succeed({
|
|
5989
6392
|
_tag: "LoopFailed",
|
|
5990
6393
|
error: {
|
|
@@ -5997,12 +6400,38 @@ function runLoop(config) {
|
|
|
5997
6400
|
)
|
|
5998
6401
|
);
|
|
5999
6402
|
}
|
|
6000
|
-
function createCompletionStream(sessionStore, session, config, startTime, loopCompletedRef) {
|
|
6001
|
-
return Stream9.
|
|
6002
|
-
|
|
6403
|
+
function createCompletionStream(sessionStore, git, session, config, startTime, loopCompletedRef, worktreePath) {
|
|
6404
|
+
return Stream9.unwrap(
|
|
6405
|
+
Effect22.gen(function* () {
|
|
6003
6406
|
const endTimeUtc = yield* DateTime10.now;
|
|
6004
6407
|
const durationMs = DateTime10.toEpochMillis(endTimeUtc) - startTime;
|
|
6005
|
-
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
|
+
};
|
|
6006
6435
|
const summary = {
|
|
6007
6436
|
iterations: config.maxIterations,
|
|
6008
6437
|
success: completed,
|
|
@@ -6014,16 +6443,16 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
|
|
|
6014
6443
|
...session,
|
|
6015
6444
|
status: completed ? "completed" : "paused"
|
|
6016
6445
|
}).pipe(
|
|
6017
|
-
|
|
6018
|
-
(error) =>
|
|
6446
|
+
Effect22.tapError(
|
|
6447
|
+
(error) => Effect22.logDebug("Session update failed, continuing", {
|
|
6019
6448
|
sessionId: session.id,
|
|
6020
6449
|
error: String(error)
|
|
6021
6450
|
})
|
|
6022
6451
|
),
|
|
6023
|
-
|
|
6452
|
+
Effect22.orElseSucceed(() => void 0)
|
|
6024
6453
|
);
|
|
6025
|
-
const
|
|
6026
|
-
return
|
|
6454
|
+
const loopCompleted = { _tag: "LoopCompleted", summary };
|
|
6455
|
+
return Stream9.fromIterable([worktreeRemoved, loopCompleted]);
|
|
6027
6456
|
})
|
|
6028
6457
|
);
|
|
6029
6458
|
}
|
|
@@ -6032,7 +6461,7 @@ function createCompletionStream(sessionStore, session, config, startTime, loopCo
|
|
|
6032
6461
|
function run(options) {
|
|
6033
6462
|
const { config, consumer: consumerType = "headless", onEvent } = options;
|
|
6034
6463
|
const events = runLoop(config);
|
|
6035
|
-
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) =>
|
|
6464
|
+
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
|
|
6036
6465
|
const eventsWithLayers = eventsWithCallback.pipe(
|
|
6037
6466
|
Stream10.provideLayer(ProductionLayers)
|
|
6038
6467
|
);
|
|
@@ -6045,7 +6474,7 @@ function run(options) {
|
|
|
6045
6474
|
function runTest(options, mockEvents) {
|
|
6046
6475
|
const { config, onEvent } = options;
|
|
6047
6476
|
const events = runLoop(config);
|
|
6048
|
-
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) =>
|
|
6477
|
+
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect23.sync(() => onEvent(event)))) : events;
|
|
6049
6478
|
const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
|
|
6050
6479
|
const eventsWithLayers = eventsWithCallback.pipe(Stream10.provideLayer(layers));
|
|
6051
6480
|
return eventsWithLayers.pipe(Stream10.runDrain);
|
|
@@ -6053,11 +6482,11 @@ function runTest(options, mockEvents) {
|
|
|
6053
6482
|
function collectEvents(config, mockEvents) {
|
|
6054
6483
|
const events = runLoop(config);
|
|
6055
6484
|
const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
|
|
6056
|
-
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)));
|
|
6057
6486
|
}
|
|
6058
6487
|
function main(config) {
|
|
6059
6488
|
const consumerType = process.stdout.isTTY ? "tui" : "headless";
|
|
6060
|
-
return run({ config, consumer: consumerType }).pipe(
|
|
6489
|
+
return run({ config, consumer: consumerType }).pipe(Effect23.runPromise);
|
|
6061
6490
|
}
|
|
6062
6491
|
|
|
6063
6492
|
// src/services/index.ts
|
|
@@ -6110,6 +6539,7 @@ export {
|
|
|
6110
6539
|
ExecutionModeSchema,
|
|
6111
6540
|
FerixParser,
|
|
6112
6541
|
FileLoggerConfigSchema,
|
|
6542
|
+
FileSystemGit,
|
|
6113
6543
|
FileSystemGuardrails,
|
|
6114
6544
|
FileSystemPlan,
|
|
6115
6545
|
FileSystemProgress,
|
|
@@ -6117,6 +6547,8 @@ export {
|
|
|
6117
6547
|
GeneratedTaskListSchema,
|
|
6118
6548
|
GeneratedTaskSchema,
|
|
6119
6549
|
GeneratedTaskStatusSchema,
|
|
6550
|
+
Git,
|
|
6551
|
+
GitError,
|
|
6120
6552
|
GuardrailAddedEventSchema,
|
|
6121
6553
|
GuardrailSchema,
|
|
6122
6554
|
GuardrailSeveritySchema,
|
|
@@ -6146,6 +6578,7 @@ export {
|
|
|
6146
6578
|
LoopStartedEventSchema,
|
|
6147
6579
|
LoopStatusSchema,
|
|
6148
6580
|
LoopSummarySchema,
|
|
6581
|
+
MemoryGit,
|
|
6149
6582
|
MemoryGuardrails,
|
|
6150
6583
|
MemoryPlan,
|
|
6151
6584
|
MemoryProgress,
|
|
@@ -6218,6 +6651,8 @@ export {
|
|
|
6218
6651
|
ToolStartEventSchema,
|
|
6219
6652
|
ToolUseEventSchema,
|
|
6220
6653
|
ViewModeSchema,
|
|
6654
|
+
WorktreeCreatedEventSchema,
|
|
6655
|
+
WorktreeRemovedEventSchema,
|
|
6221
6656
|
buildPrompt,
|
|
6222
6657
|
collectEvents,
|
|
6223
6658
|
createHeadlessConsumer,
|