ferix-code 0.0.2-beta.12 → 0.0.2-beta.14
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 +92 -3
- package/dist/index.js +443 -243
- 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.14",
|
|
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 Effect24, Stream as Stream10 } from "effect";
|
|
122
122
|
|
|
123
123
|
// src/consumers/index.ts
|
|
124
124
|
init_esm_shims();
|
|
@@ -2770,6 +2770,7 @@ init_esm_shims();
|
|
|
2770
2770
|
// src/domain/schemas/config.ts
|
|
2771
2771
|
init_esm_shims();
|
|
2772
2772
|
import { Schema as S } from "effect";
|
|
2773
|
+
var ProviderNameSchema = S.Literal("claude", "cursor", "opencode");
|
|
2773
2774
|
var PhasePromptOverridesSchema = S.Struct({
|
|
2774
2775
|
breakdown: S.optional(S.String),
|
|
2775
2776
|
planning: S.optional(S.String),
|
|
@@ -2793,7 +2794,9 @@ var LoopConfigSchema = S.Struct({
|
|
|
2793
2794
|
push: S.optional(S.Boolean),
|
|
2794
2795
|
pr: S.optional(S.Boolean),
|
|
2795
2796
|
verbose: S.optional(S.Boolean),
|
|
2796
|
-
prompts: S.optional(PromptConfigSchema)
|
|
2797
|
+
prompts: S.optional(PromptConfigSchema),
|
|
2798
|
+
/** LLM provider to use. Defaults to "claude". */
|
|
2799
|
+
provider: S.optional(ProviderNameSchema)
|
|
2797
2800
|
});
|
|
2798
2801
|
var LoopSummarySchema = S.Struct({
|
|
2799
2802
|
iterations: S.Number,
|
|
@@ -3665,10 +3668,9 @@ var MemoryGuardrails = {
|
|
|
3665
3668
|
layer: layer2
|
|
3666
3669
|
};
|
|
3667
3670
|
|
|
3668
|
-
// src/layers/llm/
|
|
3671
|
+
// src/layers/llm/mock.ts
|
|
3669
3672
|
init_esm_shims();
|
|
3670
|
-
import {
|
|
3671
|
-
import { Effect as Effect9, Layer as Layer5, Stream as Stream5 } from "effect";
|
|
3673
|
+
import { Effect as Effect8, Layer as Layer5, Schema as S14, Stream as Stream4 } from "effect";
|
|
3672
3674
|
|
|
3673
3675
|
// src/services/llm.ts
|
|
3674
3676
|
init_esm_shims();
|
|
@@ -3676,12 +3678,123 @@ import { Context as Context3 } from "effect";
|
|
|
3676
3678
|
var LLM = class extends Context3.Tag("@ferix/LLM")() {
|
|
3677
3679
|
};
|
|
3678
3680
|
|
|
3681
|
+
// src/layers/llm/mock.ts
|
|
3682
|
+
var MockLLMConfigSchema = S14.Struct({
|
|
3683
|
+
events: S14.Array(LLMEventSchema),
|
|
3684
|
+
delayMs: S14.optional(S14.Number)
|
|
3685
|
+
});
|
|
3686
|
+
function createMockLLM(config) {
|
|
3687
|
+
return {
|
|
3688
|
+
execute: (_prompt, _options) => {
|
|
3689
|
+
const baseStream = Stream4.fromIterable(config.events);
|
|
3690
|
+
if (config.delayMs !== void 0 && config.delayMs > 0) {
|
|
3691
|
+
const delay = config.delayMs;
|
|
3692
|
+
return baseStream.pipe(Stream4.tap(() => Effect8.sleep(delay)));
|
|
3693
|
+
}
|
|
3694
|
+
return baseStream;
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
var defaultMockEvents = [
|
|
3699
|
+
{ _tag: "Text", text: "Processing task...\n" },
|
|
3700
|
+
{ _tag: "ToolStart", tool: "Read" },
|
|
3701
|
+
{ _tag: "ToolEnd", tool: "Read" },
|
|
3702
|
+
{ _tag: "Text", text: "Task completed successfully.\n" },
|
|
3703
|
+
{
|
|
3704
|
+
_tag: "Done",
|
|
3705
|
+
output: "Processing task...\nTask completed successfully.\n"
|
|
3706
|
+
}
|
|
3707
|
+
];
|
|
3708
|
+
var defaultMock = createMockLLM({ events: defaultMockEvents });
|
|
3709
|
+
var Live5 = Layer5.succeed(LLM, defaultMock);
|
|
3710
|
+
function layer3(config) {
|
|
3711
|
+
return Layer5.succeed(LLM, createMockLLM(config));
|
|
3712
|
+
}
|
|
3713
|
+
var Mock = {
|
|
3714
|
+
Live: Live5,
|
|
3715
|
+
layer: layer3,
|
|
3716
|
+
createMockLLM
|
|
3717
|
+
};
|
|
3718
|
+
|
|
3719
|
+
// src/layers/llm/providers/index.ts
|
|
3720
|
+
init_esm_shims();
|
|
3721
|
+
import { Effect as Effect11 } from "effect";
|
|
3722
|
+
|
|
3723
|
+
// src/layers/llm/types.ts
|
|
3724
|
+
init_esm_shims();
|
|
3725
|
+
var PROVIDER_CONFIGS = {
|
|
3726
|
+
claude: {
|
|
3727
|
+
name: "claude",
|
|
3728
|
+
cliCommand: "claude",
|
|
3729
|
+
args: [
|
|
3730
|
+
"--permission-mode",
|
|
3731
|
+
"acceptEdits",
|
|
3732
|
+
"--output-format",
|
|
3733
|
+
"stream-json",
|
|
3734
|
+
"--verbose",
|
|
3735
|
+
"--include-partial-messages"
|
|
3736
|
+
],
|
|
3737
|
+
permissions: "acceptEdits",
|
|
3738
|
+
installUrl: "https://docs.anthropic.com/claude-code"
|
|
3739
|
+
},
|
|
3740
|
+
cursor: {
|
|
3741
|
+
name: "cursor",
|
|
3742
|
+
cliCommand: "cursor-agent",
|
|
3743
|
+
args: ["--print", "--force", "--output-format", "stream-json"],
|
|
3744
|
+
permissions: "acceptEdits",
|
|
3745
|
+
installUrl: "https://cursor.sh/agent"
|
|
3746
|
+
},
|
|
3747
|
+
opencode: {
|
|
3748
|
+
name: "opencode",
|
|
3749
|
+
cliCommand: "opencode",
|
|
3750
|
+
args: ["run", "--format", "json"],
|
|
3751
|
+
installUrl: "https://opencode.ai/docs/"
|
|
3752
|
+
}
|
|
3753
|
+
};
|
|
3754
|
+
|
|
3755
|
+
// src/layers/llm/providers/claude.ts
|
|
3756
|
+
init_esm_shims();
|
|
3757
|
+
|
|
3758
|
+
// src/layers/llm/provider-factory.ts
|
|
3759
|
+
init_esm_shims();
|
|
3760
|
+
import { spawn } from "child_process";
|
|
3761
|
+
import { Effect as Effect9, Layer as Layer6, Stream as Stream5 } from "effect";
|
|
3762
|
+
function createProvider(name, createStream, checkAvailable) {
|
|
3763
|
+
const config = PROVIDER_CONFIGS[name];
|
|
3764
|
+
return {
|
|
3765
|
+
name,
|
|
3766
|
+
execute: (prompt, options) => {
|
|
3767
|
+
return Stream5.unwrap(
|
|
3768
|
+
checkAvailable(name).pipe(
|
|
3769
|
+
Effect9.map(() => {
|
|
3770
|
+
const args = name === "opencode" ? [...config.args, prompt] : [...config.args, "-p", prompt];
|
|
3771
|
+
const child = spawn(config.cliCommand, args, {
|
|
3772
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
3773
|
+
cwd: options?.cwd,
|
|
3774
|
+
env: {
|
|
3775
|
+
...process.env,
|
|
3776
|
+
FORCE_COLOR: "1",
|
|
3777
|
+
...config.env
|
|
3778
|
+
}
|
|
3779
|
+
});
|
|
3780
|
+
return createStream(child);
|
|
3781
|
+
})
|
|
3782
|
+
)
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
function createProviderLayer(provider) {
|
|
3788
|
+
return Layer6.succeed(LLM, provider);
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3679
3791
|
// src/layers/llm/stream.ts
|
|
3680
3792
|
init_esm_shims();
|
|
3681
|
-
import { createInterface } from "readline";
|
|
3682
|
-
import { Effect as Effect8, Stream as Stream4 } from "effect";
|
|
3683
3793
|
|
|
3684
|
-
// src/layers/llm/parsers.ts
|
|
3794
|
+
// src/layers/llm/providers/parsers/claude.ts
|
|
3795
|
+
init_esm_shims();
|
|
3796
|
+
|
|
3797
|
+
// src/layers/llm/providers/parsers/stream-json.ts
|
|
3685
3798
|
init_esm_shims();
|
|
3686
3799
|
function parseJsonLine(line) {
|
|
3687
3800
|
if (!line.startsWith("{")) {
|
|
@@ -3760,7 +3873,10 @@ function unwrapStreamEvent(json) {
|
|
|
3760
3873
|
return json;
|
|
3761
3874
|
}
|
|
3762
3875
|
|
|
3763
|
-
// src/layers/llm/stream.ts
|
|
3876
|
+
// src/layers/llm/stream-factory.ts
|
|
3877
|
+
init_esm_shims();
|
|
3878
|
+
import { createInterface } from "readline";
|
|
3879
|
+
import { Effect as Effect10, Stream as Stream6 } from "effect";
|
|
3764
3880
|
function handleToolEvent(toolInfo, toolState, emit) {
|
|
3765
3881
|
if (toolInfo.type === "start") {
|
|
3766
3882
|
toolState.currentTool = toolInfo.name;
|
|
@@ -3783,21 +3899,8 @@ function handleToolEvent(toolInfo, toolState, emit) {
|
|
|
3783
3899
|
toolState.inputChunks.length = 0;
|
|
3784
3900
|
}
|
|
3785
3901
|
}
|
|
3786
|
-
function
|
|
3787
|
-
|
|
3788
|
-
const text = extractText(event);
|
|
3789
|
-
if (text) {
|
|
3790
|
-
outputChunks.push(text);
|
|
3791
|
-
emit.single({ _tag: "Text", text });
|
|
3792
|
-
return;
|
|
3793
|
-
}
|
|
3794
|
-
const toolInfo = extractToolInfo(event);
|
|
3795
|
-
if (toolInfo) {
|
|
3796
|
-
handleToolEvent(toolInfo, toolState, emit);
|
|
3797
|
-
}
|
|
3798
|
-
}
|
|
3799
|
-
function createEventStream(child) {
|
|
3800
|
-
return Stream4.async((emit) => {
|
|
3902
|
+
function createLLMEventStream(child, parser, providerName) {
|
|
3903
|
+
return Stream6.async((emit) => {
|
|
3801
3904
|
const outputChunks = [];
|
|
3802
3905
|
const toolState = { currentTool: "", inputChunks: [] };
|
|
3803
3906
|
const stdout = child.stdout;
|
|
@@ -3805,16 +3908,16 @@ function createEventStream(child) {
|
|
|
3805
3908
|
emit.fail(
|
|
3806
3909
|
new LLMError({ message: "Failed to get stdout from child process" })
|
|
3807
3910
|
);
|
|
3808
|
-
return
|
|
3911
|
+
return Effect10.void;
|
|
3809
3912
|
}
|
|
3810
3913
|
const rl = createInterface({
|
|
3811
3914
|
input: stdout,
|
|
3812
3915
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
3813
3916
|
});
|
|
3814
3917
|
rl.on("line", (line) => {
|
|
3815
|
-
const json = parseJsonLine(line);
|
|
3918
|
+
const json = parser.parseJsonLine(line);
|
|
3816
3919
|
if (json) {
|
|
3817
|
-
|
|
3920
|
+
parser.processEvent(json, outputChunks, toolState, emit);
|
|
3818
3921
|
}
|
|
3819
3922
|
});
|
|
3820
3923
|
child.stderr?.on("data", (data) => {
|
|
@@ -3827,7 +3930,7 @@ function createEventStream(child) {
|
|
|
3827
3930
|
if (exitCode !== 0) {
|
|
3828
3931
|
emit.fail(
|
|
3829
3932
|
new LLMError({
|
|
3830
|
-
message:
|
|
3933
|
+
message: `${providerName} CLI exited with code ${exitCode}`
|
|
3831
3934
|
})
|
|
3832
3935
|
);
|
|
3833
3936
|
} else {
|
|
@@ -3844,93 +3947,173 @@ function createEventStream(child) {
|
|
|
3844
3947
|
})
|
|
3845
3948
|
);
|
|
3846
3949
|
});
|
|
3847
|
-
return
|
|
3950
|
+
return Effect10.sync(() => {
|
|
3848
3951
|
child.kill("SIGTERM");
|
|
3849
3952
|
});
|
|
3850
3953
|
});
|
|
3851
3954
|
}
|
|
3852
3955
|
|
|
3853
|
-
// src/layers/llm/
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
"--permission-mode",
|
|
3862
|
-
"acceptEdits",
|
|
3863
|
-
"--output-format",
|
|
3864
|
-
"stream-json",
|
|
3865
|
-
"--verbose",
|
|
3866
|
-
"--include-partial-messages",
|
|
3867
|
-
"-p",
|
|
3868
|
-
prompt
|
|
3869
|
-
],
|
|
3870
|
-
{
|
|
3871
|
-
stdio: ["inherit", "pipe", "pipe"],
|
|
3872
|
-
cwd: options?.cwd,
|
|
3873
|
-
env: {
|
|
3874
|
-
...process.env,
|
|
3875
|
-
FORCE_COLOR: "1"
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
);
|
|
3879
|
-
return createEventStream(child);
|
|
3880
|
-
})
|
|
3881
|
-
);
|
|
3956
|
+
// src/layers/llm/stream.ts
|
|
3957
|
+
function processJsonLine(json, outputChunks, toolState, emit) {
|
|
3958
|
+
const event = unwrapStreamEvent(json);
|
|
3959
|
+
const text = extractText(event);
|
|
3960
|
+
if (text) {
|
|
3961
|
+
outputChunks.push(text);
|
|
3962
|
+
emit.single({ _tag: "Text", text });
|
|
3963
|
+
return;
|
|
3882
3964
|
}
|
|
3965
|
+
const toolInfo = extractToolInfo(event);
|
|
3966
|
+
if (toolInfo) {
|
|
3967
|
+
handleToolEvent(toolInfo, toolState, emit);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
var streamJsonParser = {
|
|
3971
|
+
parseJsonLine,
|
|
3972
|
+
processEvent: processJsonLine
|
|
3883
3973
|
};
|
|
3884
|
-
|
|
3974
|
+
function createEventStream(child, providerName = "LLM") {
|
|
3975
|
+
return createLLMEventStream(child, streamJsonParser, providerName);
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
// src/layers/llm/providers/claude.ts
|
|
3979
|
+
var ClaudeProvider = createProvider(
|
|
3980
|
+
"claude",
|
|
3981
|
+
(child) => createEventStream(child, "Claude"),
|
|
3982
|
+
checkProviderAvailable
|
|
3983
|
+
);
|
|
3984
|
+
var Live6 = createProviderLayer(ClaudeProvider);
|
|
3885
3985
|
var ClaudeCLI = {
|
|
3886
|
-
Live:
|
|
3986
|
+
Live: Live6,
|
|
3987
|
+
Provider: ClaudeProvider
|
|
3887
3988
|
};
|
|
3888
3989
|
|
|
3889
|
-
// src/layers/llm/
|
|
3990
|
+
// src/layers/llm/providers/cursor.ts
|
|
3890
3991
|
init_esm_shims();
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3992
|
+
var CursorProvider = createProvider(
|
|
3993
|
+
"cursor",
|
|
3994
|
+
(child) => createEventStream(child, "Cursor"),
|
|
3995
|
+
checkProviderAvailable
|
|
3996
|
+
);
|
|
3997
|
+
var Live7 = createProviderLayer(CursorProvider);
|
|
3998
|
+
var CursorCLI = {
|
|
3999
|
+
Live: Live7,
|
|
4000
|
+
Provider: CursorProvider
|
|
4001
|
+
};
|
|
4002
|
+
|
|
4003
|
+
// src/layers/llm/providers/opencode.ts
|
|
4004
|
+
init_esm_shims();
|
|
4005
|
+
|
|
4006
|
+
// src/layers/llm/stream-opencode.ts
|
|
4007
|
+
init_esm_shims();
|
|
4008
|
+
|
|
4009
|
+
// src/layers/llm/providers/parsers/opencode.ts
|
|
4010
|
+
init_esm_shims();
|
|
4011
|
+
function parseJsonLine2(line) {
|
|
4012
|
+
if (!line.startsWith("{")) {
|
|
4013
|
+
return null;
|
|
4014
|
+
}
|
|
4015
|
+
try {
|
|
4016
|
+
return JSON.parse(line);
|
|
4017
|
+
} catch {
|
|
4018
|
+
return null;
|
|
4019
|
+
}
|
|
3907
4020
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4021
|
+
function isTextContent2(json) {
|
|
4022
|
+
return typeof json === "object" && json !== null && "type" in json && json.type === "text" && "text" in json && typeof json.text === "string";
|
|
4023
|
+
}
|
|
4024
|
+
function isStepFinish(json) {
|
|
4025
|
+
return typeof json === "object" && json !== null && "type" in json && json.type === "step_finish";
|
|
4026
|
+
}
|
|
4027
|
+
function extractText2(json) {
|
|
4028
|
+
if (isTextContent2(json)) {
|
|
4029
|
+
return json.text;
|
|
3916
4030
|
}
|
|
3917
|
-
|
|
3918
|
-
var defaultMock = createMockLLM({ events: defaultMockEvents });
|
|
3919
|
-
var Live6 = Layer6.succeed(LLM, defaultMock);
|
|
3920
|
-
function layer3(config) {
|
|
3921
|
-
return Layer6.succeed(LLM, createMockLLM(config));
|
|
4031
|
+
return null;
|
|
3922
4032
|
}
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
4033
|
+
|
|
4034
|
+
// src/layers/llm/stream-opencode.ts
|
|
4035
|
+
function processOpenCodeEvent(json, outputChunks, _toolState, emit) {
|
|
4036
|
+
const text = extractText2(json);
|
|
4037
|
+
if (text) {
|
|
4038
|
+
outputChunks.push(text);
|
|
4039
|
+
emit.single({ _tag: "Text", text });
|
|
4040
|
+
return;
|
|
4041
|
+
}
|
|
4042
|
+
if (isStepFinish(json)) {
|
|
4043
|
+
return;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
var openCodeParser = {
|
|
4047
|
+
parseJsonLine: parseJsonLine2,
|
|
4048
|
+
processEvent: processOpenCodeEvent
|
|
4049
|
+
};
|
|
4050
|
+
function createOpenCodeEventStream(child, providerName = "OpenCode") {
|
|
4051
|
+
return createLLMEventStream(child, openCodeParser, providerName);
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
// src/layers/llm/providers/opencode.ts
|
|
4055
|
+
var OpenCodeProvider = createProvider(
|
|
4056
|
+
"opencode",
|
|
4057
|
+
(child) => createOpenCodeEventStream(child, "OpenCode"),
|
|
4058
|
+
checkProviderAvailable
|
|
4059
|
+
);
|
|
4060
|
+
var Live8 = createProviderLayer(OpenCodeProvider);
|
|
4061
|
+
var OpenCodeCLI = {
|
|
4062
|
+
Live: Live8,
|
|
4063
|
+
Provider: OpenCodeProvider
|
|
3927
4064
|
};
|
|
3928
4065
|
|
|
4066
|
+
// src/layers/llm/providers/index.ts
|
|
4067
|
+
var OpenCodeCLI2 = OpenCodeCLI;
|
|
4068
|
+
function isCommandAvailable(command) {
|
|
4069
|
+
return Effect11.tryPromise({
|
|
4070
|
+
try: async () => {
|
|
4071
|
+
const { execSync } = await import("child_process");
|
|
4072
|
+
try {
|
|
4073
|
+
execSync(`which ${command}`, { stdio: "ignore" });
|
|
4074
|
+
return true;
|
|
4075
|
+
} catch {
|
|
4076
|
+
return false;
|
|
4077
|
+
}
|
|
4078
|
+
},
|
|
4079
|
+
catch: () => false
|
|
4080
|
+
}).pipe(Effect11.orElseSucceed(() => false));
|
|
4081
|
+
}
|
|
4082
|
+
function checkProviderAvailable(name) {
|
|
4083
|
+
const config = PROVIDER_CONFIGS[name];
|
|
4084
|
+
return isCommandAvailable(config.cliCommand).pipe(
|
|
4085
|
+
Effect11.flatMap(
|
|
4086
|
+
(available) => available ? Effect11.void : Effect11.fail(
|
|
4087
|
+
new LLMError({
|
|
4088
|
+
message: `Provider "${name}" is not available. The CLI command "${config.cliCommand}" was not found.
|
|
4089
|
+
|
|
4090
|
+
To use this provider, install it first:
|
|
4091
|
+
${config.installUrl}`
|
|
4092
|
+
})
|
|
4093
|
+
)
|
|
4094
|
+
)
|
|
4095
|
+
);
|
|
4096
|
+
}
|
|
4097
|
+
function createProviderLayer2(name) {
|
|
4098
|
+
switch (name) {
|
|
4099
|
+
case "claude":
|
|
4100
|
+
return ClaudeCLI.Live;
|
|
4101
|
+
case "cursor":
|
|
4102
|
+
return CursorCLI.Live;
|
|
4103
|
+
case "opencode":
|
|
4104
|
+
return OpenCodeCLI2.Live;
|
|
4105
|
+
default: {
|
|
4106
|
+
const _exhaustive = name;
|
|
4107
|
+
return _exhaustive;
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
|
|
3929
4112
|
// src/layers/plan/file-system.ts
|
|
3930
4113
|
init_esm_shims();
|
|
3931
4114
|
import { access as access2, mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3932
4115
|
import { join as join3 } from "path";
|
|
3933
|
-
import { Effect as
|
|
4116
|
+
import { Effect as Effect12, Layer as Layer7 } from "effect";
|
|
3934
4117
|
|
|
3935
4118
|
// src/services/plan-store.ts
|
|
3936
4119
|
init_esm_shims();
|
|
@@ -3941,14 +4124,14 @@ var PlanStore = class extends Context4.Tag("@ferix/PlanStore")() {
|
|
|
3941
4124
|
// src/layers/plan/file-system.ts
|
|
3942
4125
|
var PLANS_DIR2 = ".ferix/plans";
|
|
3943
4126
|
function ensureDir2(dirPath) {
|
|
3944
|
-
return
|
|
4127
|
+
return Effect12.tryPromise({
|
|
3945
4128
|
try: () => mkdir3(dirPath, { recursive: true }),
|
|
3946
4129
|
catch: (error) => new PlanStoreError({
|
|
3947
4130
|
message: `Failed to create directory: ${dirPath}`,
|
|
3948
4131
|
operation: "create",
|
|
3949
4132
|
cause: error
|
|
3950
4133
|
})
|
|
3951
|
-
}).pipe(
|
|
4134
|
+
}).pipe(Effect12.asVoid);
|
|
3952
4135
|
}
|
|
3953
4136
|
function getSessionDir2(sessionId) {
|
|
3954
4137
|
return join3(process.cwd(), PLANS_DIR2, sessionId);
|
|
@@ -3963,8 +4146,8 @@ function serializePlan(plan) {
|
|
|
3963
4146
|
return JSON.stringify(plan, null, 2);
|
|
3964
4147
|
}
|
|
3965
4148
|
function deserializePlan(json, planId) {
|
|
3966
|
-
return
|
|
3967
|
-
const parsed = yield*
|
|
4149
|
+
return Effect12.gen(function* () {
|
|
4150
|
+
const parsed = yield* Effect12.try({
|
|
3968
4151
|
try: () => JSON.parse(json),
|
|
3969
4152
|
catch: (error) => new PlanStoreError({
|
|
3970
4153
|
message: `Invalid JSON in plan file: ${String(error)}`,
|
|
@@ -3973,7 +4156,7 @@ function deserializePlan(json, planId) {
|
|
|
3973
4156
|
})
|
|
3974
4157
|
});
|
|
3975
4158
|
const validated = yield* decodePlanData(parsed).pipe(
|
|
3976
|
-
|
|
4159
|
+
Effect12.mapError(
|
|
3977
4160
|
(error) => new PlanStoreError({
|
|
3978
4161
|
message: `Plan validation failed: ${String(error)}`,
|
|
3979
4162
|
operation: "load",
|
|
@@ -3987,11 +4170,11 @@ function deserializePlan(json, planId) {
|
|
|
3987
4170
|
};
|
|
3988
4171
|
});
|
|
3989
4172
|
}
|
|
3990
|
-
var
|
|
3991
|
-
create: (sessionId, plan) =>
|
|
4173
|
+
var make3 = {
|
|
4174
|
+
create: (sessionId, plan) => Effect12.gen(function* () {
|
|
3992
4175
|
const sessionDir = getSessionDir2(sessionId);
|
|
3993
4176
|
yield* ensureDir2(sessionDir);
|
|
3994
|
-
const existingPlans = yield*
|
|
4177
|
+
const existingPlans = yield* Effect12.tryPromise({
|
|
3995
4178
|
try: async () => {
|
|
3996
4179
|
try {
|
|
3997
4180
|
const files = await readdir(sessionDir);
|
|
@@ -4008,7 +4191,7 @@ var make4 = {
|
|
|
4008
4191
|
const planId = generatePlanId(existingPlans + 1);
|
|
4009
4192
|
const planPath = getPlanPath(sessionId, planId);
|
|
4010
4193
|
const fullPlan = { ...plan, id: planId };
|
|
4011
|
-
yield*
|
|
4194
|
+
yield* Effect12.tryPromise({
|
|
4012
4195
|
try: () => writeFile2(planPath, serializePlan(fullPlan), "utf-8"),
|
|
4013
4196
|
catch: (error) => new PlanStoreError({
|
|
4014
4197
|
message: `Failed to write plan file: ${planPath}`,
|
|
@@ -4018,10 +4201,10 @@ var make4 = {
|
|
|
4018
4201
|
});
|
|
4019
4202
|
return planId;
|
|
4020
4203
|
}),
|
|
4021
|
-
load: (planId, sessionId) =>
|
|
4204
|
+
load: (planId, sessionId) => Effect12.gen(function* () {
|
|
4022
4205
|
if (sessionId) {
|
|
4023
4206
|
const planPath = getPlanPath(sessionId, planId);
|
|
4024
|
-
const content = yield*
|
|
4207
|
+
const content = yield* Effect12.tryPromise({
|
|
4025
4208
|
try: () => readFile2(planPath, "utf-8"),
|
|
4026
4209
|
catch: (error) => new PlanStoreError({
|
|
4027
4210
|
message: `Failed to read plan file: ${planPath}`,
|
|
@@ -4031,7 +4214,7 @@ var make4 = {
|
|
|
4031
4214
|
});
|
|
4032
4215
|
return yield* deserializePlan(content, planId);
|
|
4033
4216
|
}
|
|
4034
|
-
const sessionDirs = yield*
|
|
4217
|
+
const sessionDirs = yield* Effect12.tryPromise({
|
|
4035
4218
|
try: async () => {
|
|
4036
4219
|
const plansDir = join3(process.cwd(), PLANS_DIR2);
|
|
4037
4220
|
const dirs = await readdir(plansDir);
|
|
@@ -4045,7 +4228,7 @@ var make4 = {
|
|
|
4045
4228
|
});
|
|
4046
4229
|
for (const sid of sessionDirs) {
|
|
4047
4230
|
const planPath = getPlanPath(sid, planId);
|
|
4048
|
-
const exists = yield*
|
|
4231
|
+
const exists = yield* Effect12.tryPromise({
|
|
4049
4232
|
try: async () => {
|
|
4050
4233
|
await access2(planPath);
|
|
4051
4234
|
return true;
|
|
@@ -4054,9 +4237,9 @@ var make4 = {
|
|
|
4054
4237
|
message: "File not found",
|
|
4055
4238
|
operation: "load"
|
|
4056
4239
|
})
|
|
4057
|
-
}).pipe(
|
|
4240
|
+
}).pipe(Effect12.orElseSucceed(() => false));
|
|
4058
4241
|
if (exists) {
|
|
4059
|
-
const content = yield*
|
|
4242
|
+
const content = yield* Effect12.tryPromise({
|
|
4060
4243
|
try: () => readFile2(planPath, "utf-8"),
|
|
4061
4244
|
catch: (error) => new PlanStoreError({
|
|
4062
4245
|
message: `Failed to read plan file: ${planPath}`,
|
|
@@ -4067,16 +4250,16 @@ var make4 = {
|
|
|
4067
4250
|
return yield* deserializePlan(content, planId);
|
|
4068
4251
|
}
|
|
4069
4252
|
}
|
|
4070
|
-
return yield*
|
|
4253
|
+
return yield* Effect12.fail(
|
|
4071
4254
|
new PlanStoreError({
|
|
4072
4255
|
message: `Plan not found: ${planId}`,
|
|
4073
4256
|
operation: "load"
|
|
4074
4257
|
})
|
|
4075
4258
|
);
|
|
4076
4259
|
}),
|
|
4077
|
-
update: (planId, plan) =>
|
|
4260
|
+
update: (planId, plan) => Effect12.gen(function* () {
|
|
4078
4261
|
const planPath = getPlanPath(plan.sessionId, planId);
|
|
4079
|
-
yield*
|
|
4262
|
+
yield* Effect12.tryPromise({
|
|
4080
4263
|
try: () => writeFile2(planPath, serializePlan(plan), "utf-8"),
|
|
4081
4264
|
catch: (error) => new PlanStoreError({
|
|
4082
4265
|
message: `Failed to update plan file: ${planPath}`,
|
|
@@ -4085,9 +4268,9 @@ var make4 = {
|
|
|
4085
4268
|
})
|
|
4086
4269
|
});
|
|
4087
4270
|
}),
|
|
4088
|
-
list: (sessionId) =>
|
|
4271
|
+
list: (sessionId) => Effect12.gen(function* () {
|
|
4089
4272
|
const sessionDir = getSessionDir2(sessionId);
|
|
4090
|
-
const files = yield*
|
|
4273
|
+
const files = yield* Effect12.tryPromise({
|
|
4091
4274
|
try: async () => {
|
|
4092
4275
|
try {
|
|
4093
4276
|
return await readdir(sessionDir);
|
|
@@ -4104,17 +4287,17 @@ var make4 = {
|
|
|
4104
4287
|
return files.filter((f) => f.endsWith(".json")).map((f) => PlanId(f.replace(".json", "")));
|
|
4105
4288
|
})
|
|
4106
4289
|
};
|
|
4107
|
-
var
|
|
4290
|
+
var Live9 = Layer7.succeed(PlanStore, make3);
|
|
4108
4291
|
var FileSystemPlan = {
|
|
4109
|
-
Live:
|
|
4292
|
+
Live: Live9
|
|
4110
4293
|
};
|
|
4111
4294
|
|
|
4112
4295
|
// src/layers/plan/memory.ts
|
|
4113
4296
|
init_esm_shims();
|
|
4114
|
-
import { Effect as
|
|
4297
|
+
import { Effect as Effect13, Layer as Layer8, Ref as Ref5 } from "effect";
|
|
4115
4298
|
function createMemoryPlanStore(stateRef) {
|
|
4116
4299
|
return {
|
|
4117
|
-
create: (sessionId, plan) =>
|
|
4300
|
+
create: (sessionId, plan) => Effect13.gen(function* () {
|
|
4118
4301
|
const state = yield* Ref5.get(stateRef);
|
|
4119
4302
|
if (!state.has(sessionId)) {
|
|
4120
4303
|
state.set(sessionId, /* @__PURE__ */ new Map());
|
|
@@ -4129,7 +4312,7 @@ function createMemoryPlanStore(stateRef) {
|
|
|
4129
4312
|
yield* Ref5.set(stateRef, state);
|
|
4130
4313
|
return planId;
|
|
4131
4314
|
}),
|
|
4132
|
-
load: (planId, sessionId) =>
|
|
4315
|
+
load: (planId, sessionId) => Effect13.gen(function* () {
|
|
4133
4316
|
const state = yield* Ref5.get(stateRef);
|
|
4134
4317
|
if (sessionId) {
|
|
4135
4318
|
const sessionPlans = state.get(sessionId);
|
|
@@ -4137,7 +4320,7 @@ function createMemoryPlanStore(stateRef) {
|
|
|
4137
4320
|
if (plan) {
|
|
4138
4321
|
return plan;
|
|
4139
4322
|
}
|
|
4140
|
-
return yield*
|
|
4323
|
+
return yield* Effect13.fail(
|
|
4141
4324
|
new PlanStoreError({
|
|
4142
4325
|
message: `Plan not found: ${planId}`,
|
|
4143
4326
|
operation: "load"
|
|
@@ -4150,18 +4333,18 @@ function createMemoryPlanStore(stateRef) {
|
|
|
4150
4333
|
return plan;
|
|
4151
4334
|
}
|
|
4152
4335
|
}
|
|
4153
|
-
return yield*
|
|
4336
|
+
return yield* Effect13.fail(
|
|
4154
4337
|
new PlanStoreError({
|
|
4155
4338
|
message: `Plan not found: ${planId}`,
|
|
4156
4339
|
operation: "load"
|
|
4157
4340
|
})
|
|
4158
4341
|
);
|
|
4159
4342
|
}),
|
|
4160
|
-
update: (planId, plan) =>
|
|
4343
|
+
update: (planId, plan) => Effect13.gen(function* () {
|
|
4161
4344
|
const state = yield* Ref5.get(stateRef);
|
|
4162
4345
|
const sessionPlans = state.get(plan.sessionId);
|
|
4163
4346
|
if (!sessionPlans) {
|
|
4164
|
-
return yield*
|
|
4347
|
+
return yield* Effect13.fail(
|
|
4165
4348
|
new PlanStoreError({
|
|
4166
4349
|
message: `Session not found: ${plan.sessionId}`,
|
|
4167
4350
|
operation: "update"
|
|
@@ -4171,7 +4354,7 @@ function createMemoryPlanStore(stateRef) {
|
|
|
4171
4354
|
sessionPlans.set(planId, plan);
|
|
4172
4355
|
yield* Ref5.set(stateRef, state);
|
|
4173
4356
|
}),
|
|
4174
|
-
list: (sessionId) =>
|
|
4357
|
+
list: (sessionId) => Effect13.gen(function* () {
|
|
4175
4358
|
const state = yield* Ref5.get(stateRef);
|
|
4176
4359
|
const sessionPlans = state.get(sessionId);
|
|
4177
4360
|
if (!sessionPlans) {
|
|
@@ -4184,15 +4367,15 @@ function createMemoryPlanStore(stateRef) {
|
|
|
4184
4367
|
function layer4() {
|
|
4185
4368
|
return Layer8.effect(
|
|
4186
4369
|
PlanStore,
|
|
4187
|
-
|
|
4370
|
+
Effect13.gen(function* () {
|
|
4188
4371
|
const stateRef = yield* Ref5.make(/* @__PURE__ */ new Map());
|
|
4189
4372
|
return createMemoryPlanStore(stateRef);
|
|
4190
4373
|
})
|
|
4191
4374
|
);
|
|
4192
4375
|
}
|
|
4193
|
-
var
|
|
4376
|
+
var Live10 = layer4();
|
|
4194
4377
|
var MemoryPlan = {
|
|
4195
|
-
Live:
|
|
4378
|
+
Live: Live10,
|
|
4196
4379
|
layer: layer4
|
|
4197
4380
|
};
|
|
4198
4381
|
|
|
@@ -4200,7 +4383,7 @@ var MemoryPlan = {
|
|
|
4200
4383
|
init_esm_shims();
|
|
4201
4384
|
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
4202
4385
|
import { join as join4 } from "path";
|
|
4203
|
-
import { DateTime as DateTime3, Effect as
|
|
4386
|
+
import { DateTime as DateTime3, Effect as Effect14, Layer as Layer9 } from "effect";
|
|
4204
4387
|
|
|
4205
4388
|
// src/services/progress-store.ts
|
|
4206
4389
|
init_esm_shims();
|
|
@@ -4211,14 +4394,14 @@ var ProgressStore = class extends Context5.Tag("@ferix/ProgressStore")() {
|
|
|
4211
4394
|
// src/layers/progress/file-system.ts
|
|
4212
4395
|
var PLANS_DIR3 = ".ferix/plans";
|
|
4213
4396
|
function ensureDir3(dirPath) {
|
|
4214
|
-
return
|
|
4397
|
+
return Effect14.tryPromise({
|
|
4215
4398
|
try: () => mkdir4(dirPath, { recursive: true }),
|
|
4216
4399
|
catch: (error) => new ProgressStoreError({
|
|
4217
4400
|
message: `Failed to create directory: ${dirPath}`,
|
|
4218
4401
|
operation: "append",
|
|
4219
4402
|
cause: error
|
|
4220
4403
|
})
|
|
4221
|
-
}).pipe(
|
|
4404
|
+
}).pipe(Effect14.asVoid);
|
|
4222
4405
|
}
|
|
4223
4406
|
function getSessionDir3(sessionId) {
|
|
4224
4407
|
return join4(process.cwd(), PLANS_DIR3, sessionId);
|
|
@@ -4230,8 +4413,8 @@ function serializeProgress(progress) {
|
|
|
4230
4413
|
return JSON.stringify(progress, null, 2);
|
|
4231
4414
|
}
|
|
4232
4415
|
function deserializeProgress(json) {
|
|
4233
|
-
return
|
|
4234
|
-
const parsed = yield*
|
|
4416
|
+
return Effect14.gen(function* () {
|
|
4417
|
+
const parsed = yield* Effect14.try({
|
|
4235
4418
|
try: () => JSON.parse(json),
|
|
4236
4419
|
catch: (error) => new ProgressStoreError({
|
|
4237
4420
|
message: `Invalid JSON in progress file: ${String(error)}`,
|
|
@@ -4240,7 +4423,7 @@ function deserializeProgress(json) {
|
|
|
4240
4423
|
})
|
|
4241
4424
|
});
|
|
4242
4425
|
const validated = yield* decodeProgressFile(parsed).pipe(
|
|
4243
|
-
|
|
4426
|
+
Effect14.mapError(
|
|
4244
4427
|
(error) => new ProgressStoreError({
|
|
4245
4428
|
message: `Progress validation failed: ${String(error)}`,
|
|
4246
4429
|
operation: "load",
|
|
@@ -4258,12 +4441,12 @@ function createEmptyProgress(sessionId, createdAt) {
|
|
|
4258
4441
|
entries: []
|
|
4259
4442
|
};
|
|
4260
4443
|
}
|
|
4261
|
-
var
|
|
4262
|
-
append: (sessionId, entry) =>
|
|
4444
|
+
var make4 = {
|
|
4445
|
+
append: (sessionId, entry) => Effect14.gen(function* () {
|
|
4263
4446
|
const sessionDir = getSessionDir3(sessionId);
|
|
4264
4447
|
yield* ensureDir3(sessionDir);
|
|
4265
4448
|
const progressPath = getProgressPath(sessionId);
|
|
4266
|
-
const existing = yield*
|
|
4449
|
+
const existing = yield* Effect14.tryPromise({
|
|
4267
4450
|
try: async () => {
|
|
4268
4451
|
try {
|
|
4269
4452
|
const content = await readFile3(progressPath, "utf-8");
|
|
@@ -4281,7 +4464,7 @@ var make5 = {
|
|
|
4281
4464
|
let progress;
|
|
4282
4465
|
if (existing) {
|
|
4283
4466
|
progress = yield* deserializeProgress(existing).pipe(
|
|
4284
|
-
|
|
4467
|
+
Effect14.mapError(
|
|
4285
4468
|
(err) => new ProgressStoreError({
|
|
4286
4469
|
message: err.message,
|
|
4287
4470
|
operation: "append",
|
|
@@ -4297,7 +4480,7 @@ var make5 = {
|
|
|
4297
4480
|
...progress,
|
|
4298
4481
|
entries: [...progress.entries, entry]
|
|
4299
4482
|
};
|
|
4300
|
-
yield*
|
|
4483
|
+
yield* Effect14.tryPromise({
|
|
4301
4484
|
try: () => writeFile3(progressPath, serializeProgress(updatedProgress), "utf-8"),
|
|
4302
4485
|
catch: (error) => new ProgressStoreError({
|
|
4303
4486
|
message: `Failed to write progress file: ${progressPath}`,
|
|
@@ -4306,9 +4489,9 @@ var make5 = {
|
|
|
4306
4489
|
})
|
|
4307
4490
|
});
|
|
4308
4491
|
}),
|
|
4309
|
-
load: (sessionId) =>
|
|
4492
|
+
load: (sessionId) => Effect14.gen(function* () {
|
|
4310
4493
|
const progressPath = getProgressPath(sessionId);
|
|
4311
|
-
const content = yield*
|
|
4494
|
+
const content = yield* Effect14.tryPromise({
|
|
4312
4495
|
try: async () => {
|
|
4313
4496
|
try {
|
|
4314
4497
|
return await readFile3(progressPath, "utf-8");
|
|
@@ -4328,23 +4511,23 @@ var make5 = {
|
|
|
4328
4511
|
}
|
|
4329
4512
|
return yield* deserializeProgress(content);
|
|
4330
4513
|
}),
|
|
4331
|
-
getRecent: (sessionId, count) =>
|
|
4332
|
-
const progress = yield*
|
|
4514
|
+
getRecent: (sessionId, count) => Effect14.gen(function* () {
|
|
4515
|
+
const progress = yield* make4.load(sessionId);
|
|
4333
4516
|
const entries = progress.entries;
|
|
4334
4517
|
return entries.slice(-count);
|
|
4335
4518
|
})
|
|
4336
4519
|
};
|
|
4337
|
-
var
|
|
4520
|
+
var Live11 = Layer9.succeed(ProgressStore, make4);
|
|
4338
4521
|
var FileSystemProgress = {
|
|
4339
|
-
Live:
|
|
4522
|
+
Live: Live11
|
|
4340
4523
|
};
|
|
4341
4524
|
|
|
4342
4525
|
// src/layers/progress/memory.ts
|
|
4343
4526
|
init_esm_shims();
|
|
4344
|
-
import { DateTime as DateTime4, Effect as
|
|
4527
|
+
import { DateTime as DateTime4, Effect as Effect15, Layer as Layer10, Ref as Ref6 } from "effect";
|
|
4345
4528
|
function createMemoryProgressStore(stateRef) {
|
|
4346
4529
|
return {
|
|
4347
|
-
append: (sessionId, entry) =>
|
|
4530
|
+
append: (sessionId, entry) => Effect15.gen(function* () {
|
|
4348
4531
|
const state = yield* Ref6.get(stateRef);
|
|
4349
4532
|
let progress = state.get(sessionId);
|
|
4350
4533
|
if (!progress) {
|
|
@@ -4362,7 +4545,7 @@ function createMemoryProgressStore(stateRef) {
|
|
|
4362
4545
|
state.set(sessionId, updatedProgress);
|
|
4363
4546
|
yield* Ref6.set(stateRef, state);
|
|
4364
4547
|
}),
|
|
4365
|
-
load: (sessionId) =>
|
|
4548
|
+
load: (sessionId) => Effect15.gen(function* () {
|
|
4366
4549
|
const state = yield* Ref6.get(stateRef);
|
|
4367
4550
|
const progress = state.get(sessionId);
|
|
4368
4551
|
if (!progress) {
|
|
@@ -4375,7 +4558,7 @@ function createMemoryProgressStore(stateRef) {
|
|
|
4375
4558
|
}
|
|
4376
4559
|
return progress;
|
|
4377
4560
|
}),
|
|
4378
|
-
getRecent: (sessionId, count) =>
|
|
4561
|
+
getRecent: (sessionId, count) => Effect15.gen(function* () {
|
|
4379
4562
|
const state = yield* Ref6.get(stateRef);
|
|
4380
4563
|
const progress = state.get(sessionId);
|
|
4381
4564
|
if (!progress) {
|
|
@@ -4388,15 +4571,15 @@ function createMemoryProgressStore(stateRef) {
|
|
|
4388
4571
|
function layer5() {
|
|
4389
4572
|
return Layer10.effect(
|
|
4390
4573
|
ProgressStore,
|
|
4391
|
-
|
|
4574
|
+
Effect15.gen(function* () {
|
|
4392
4575
|
const stateRef = yield* Ref6.make(/* @__PURE__ */ new Map());
|
|
4393
4576
|
return createMemoryProgressStore(stateRef);
|
|
4394
4577
|
})
|
|
4395
4578
|
);
|
|
4396
4579
|
}
|
|
4397
|
-
var
|
|
4580
|
+
var Live12 = layer5();
|
|
4398
4581
|
var MemoryProgress = {
|
|
4399
|
-
Live:
|
|
4582
|
+
Live: Live12,
|
|
4400
4583
|
layer: layer5
|
|
4401
4584
|
};
|
|
4402
4585
|
|
|
@@ -4404,7 +4587,7 @@ var MemoryProgress = {
|
|
|
4404
4587
|
init_esm_shims();
|
|
4405
4588
|
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
4406
4589
|
import { join as join5 } from "path";
|
|
4407
|
-
import { DateTime as DateTime5, Effect as
|
|
4590
|
+
import { DateTime as DateTime5, Effect as Effect16, Layer as Layer11 } from "effect";
|
|
4408
4591
|
import { humanId } from "human-id";
|
|
4409
4592
|
|
|
4410
4593
|
// src/services/session-store.ts
|
|
@@ -4420,14 +4603,14 @@ function generateSessionId(timestampMs) {
|
|
|
4420
4603
|
return `${id}-${timestampMs}`;
|
|
4421
4604
|
}
|
|
4422
4605
|
function ensureDir4(dirPath) {
|
|
4423
|
-
return
|
|
4606
|
+
return Effect16.tryPromise({
|
|
4424
4607
|
try: () => mkdir5(dirPath, { recursive: true }),
|
|
4425
4608
|
catch: (error) => new SessionStoreError({
|
|
4426
4609
|
message: `Failed to create directory: ${dirPath}`,
|
|
4427
4610
|
operation: "create",
|
|
4428
4611
|
cause: error
|
|
4429
4612
|
})
|
|
4430
|
-
}).pipe(
|
|
4613
|
+
}).pipe(Effect16.asVoid);
|
|
4431
4614
|
}
|
|
4432
4615
|
function getSessionPath(sessionId) {
|
|
4433
4616
|
return join5(process.cwd(), SESSIONS_DIR, `${sessionId}.json`);
|
|
@@ -4436,8 +4619,8 @@ function serializeSession(session) {
|
|
|
4436
4619
|
return JSON.stringify(session, null, 2);
|
|
4437
4620
|
}
|
|
4438
4621
|
function deserializeSession(json) {
|
|
4439
|
-
return
|
|
4440
|
-
const parsed = yield*
|
|
4622
|
+
return Effect16.gen(function* () {
|
|
4623
|
+
const parsed = yield* Effect16.try({
|
|
4441
4624
|
try: () => JSON.parse(json),
|
|
4442
4625
|
catch: (error) => new SessionStoreError({
|
|
4443
4626
|
message: `Invalid JSON in session file: ${String(error)}`,
|
|
@@ -4446,7 +4629,7 @@ function deserializeSession(json) {
|
|
|
4446
4629
|
})
|
|
4447
4630
|
});
|
|
4448
4631
|
const validated = yield* decodeSession(parsed).pipe(
|
|
4449
|
-
|
|
4632
|
+
Effect16.mapError(
|
|
4450
4633
|
(error) => new SessionStoreError({
|
|
4451
4634
|
message: `Session validation failed: ${String(error)}`,
|
|
4452
4635
|
operation: "get",
|
|
@@ -4457,8 +4640,8 @@ function deserializeSession(json) {
|
|
|
4457
4640
|
return validated;
|
|
4458
4641
|
});
|
|
4459
4642
|
}
|
|
4460
|
-
var
|
|
4461
|
-
create: (originalTask) =>
|
|
4643
|
+
var make5 = {
|
|
4644
|
+
create: (originalTask) => Effect16.gen(function* () {
|
|
4462
4645
|
const sessionsDir = join5(process.cwd(), SESSIONS_DIR);
|
|
4463
4646
|
yield* ensureDir4(sessionsDir);
|
|
4464
4647
|
const now = yield* DateTime5.now;
|
|
@@ -4472,7 +4655,7 @@ var make6 = {
|
|
|
4472
4655
|
completedTasks: []
|
|
4473
4656
|
};
|
|
4474
4657
|
const sessionPath = getSessionPath(sessionId);
|
|
4475
|
-
yield*
|
|
4658
|
+
yield* Effect16.tryPromise({
|
|
4476
4659
|
try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
|
|
4477
4660
|
catch: (error) => new SessionStoreError({
|
|
4478
4661
|
message: `Failed to write session file: ${sessionPath}`,
|
|
@@ -4482,9 +4665,9 @@ var make6 = {
|
|
|
4482
4665
|
});
|
|
4483
4666
|
return session;
|
|
4484
4667
|
}),
|
|
4485
|
-
get: (sessionId) =>
|
|
4668
|
+
get: (sessionId) => Effect16.gen(function* () {
|
|
4486
4669
|
const sessionPath = getSessionPath(sessionId);
|
|
4487
|
-
const content = yield*
|
|
4670
|
+
const content = yield* Effect16.tryPromise({
|
|
4488
4671
|
try: () => readFile4(sessionPath, "utf-8"),
|
|
4489
4672
|
catch: (error) => new SessionStoreError({
|
|
4490
4673
|
message: `Failed to read session file: ${sessionPath}`,
|
|
@@ -4494,9 +4677,9 @@ var make6 = {
|
|
|
4494
4677
|
});
|
|
4495
4678
|
return yield* deserializeSession(content);
|
|
4496
4679
|
}),
|
|
4497
|
-
update: (sessionId, session) =>
|
|
4680
|
+
update: (sessionId, session) => Effect16.gen(function* () {
|
|
4498
4681
|
const sessionPath = getSessionPath(sessionId);
|
|
4499
|
-
yield*
|
|
4682
|
+
yield* Effect16.tryPromise({
|
|
4500
4683
|
try: () => writeFile4(sessionPath, serializeSession(session), "utf-8"),
|
|
4501
4684
|
catch: (error) => new SessionStoreError({
|
|
4502
4685
|
message: `Failed to update session file: ${sessionPath}`,
|
|
@@ -4506,17 +4689,17 @@ var make6 = {
|
|
|
4506
4689
|
});
|
|
4507
4690
|
})
|
|
4508
4691
|
};
|
|
4509
|
-
var
|
|
4692
|
+
var Live13 = Layer11.succeed(SessionStore, make5);
|
|
4510
4693
|
var FileSystemSession = {
|
|
4511
|
-
Live:
|
|
4694
|
+
Live: Live13
|
|
4512
4695
|
};
|
|
4513
4696
|
|
|
4514
4697
|
// src/layers/session/memory.ts
|
|
4515
4698
|
init_esm_shims();
|
|
4516
|
-
import { DateTime as DateTime6, Effect as
|
|
4699
|
+
import { DateTime as DateTime6, Effect as Effect17, Layer as Layer12, Ref as Ref7 } from "effect";
|
|
4517
4700
|
function createMemorySessionStore(stateRef, counterRef) {
|
|
4518
4701
|
return {
|
|
4519
|
-
create: (originalTask) =>
|
|
4702
|
+
create: (originalTask) => Effect17.gen(function* () {
|
|
4520
4703
|
const state = yield* Ref7.get(stateRef);
|
|
4521
4704
|
const counter = yield* Ref7.updateAndGet(counterRef, (n) => n + 1);
|
|
4522
4705
|
const sessionId = `test-session-${counter}`;
|
|
@@ -4532,11 +4715,11 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4532
4715
|
yield* Ref7.set(stateRef, state);
|
|
4533
4716
|
return session;
|
|
4534
4717
|
}),
|
|
4535
|
-
get: (sessionId) =>
|
|
4718
|
+
get: (sessionId) => Effect17.gen(function* () {
|
|
4536
4719
|
const state = yield* Ref7.get(stateRef);
|
|
4537
4720
|
const session = state.get(sessionId);
|
|
4538
4721
|
if (!session) {
|
|
4539
|
-
return yield*
|
|
4722
|
+
return yield* Effect17.fail(
|
|
4540
4723
|
new SessionStoreError({
|
|
4541
4724
|
message: `Session not found: ${sessionId}`,
|
|
4542
4725
|
operation: "get"
|
|
@@ -4545,10 +4728,10 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4545
4728
|
}
|
|
4546
4729
|
return session;
|
|
4547
4730
|
}),
|
|
4548
|
-
update: (sessionId, session) =>
|
|
4731
|
+
update: (sessionId, session) => Effect17.gen(function* () {
|
|
4549
4732
|
const state = yield* Ref7.get(stateRef);
|
|
4550
4733
|
if (!state.has(sessionId)) {
|
|
4551
|
-
return yield*
|
|
4734
|
+
return yield* Effect17.fail(
|
|
4552
4735
|
new SessionStoreError({
|
|
4553
4736
|
message: `Session not found: ${sessionId}`,
|
|
4554
4737
|
operation: "update"
|
|
@@ -4563,22 +4746,22 @@ function createMemorySessionStore(stateRef, counterRef) {
|
|
|
4563
4746
|
function layer6() {
|
|
4564
4747
|
return Layer12.effect(
|
|
4565
4748
|
SessionStore,
|
|
4566
|
-
|
|
4749
|
+
Effect17.gen(function* () {
|
|
4567
4750
|
const stateRef = yield* Ref7.make(/* @__PURE__ */ new Map());
|
|
4568
4751
|
const counterRef = yield* Ref7.make(0);
|
|
4569
4752
|
return createMemorySessionStore(stateRef, counterRef);
|
|
4570
4753
|
})
|
|
4571
4754
|
);
|
|
4572
4755
|
}
|
|
4573
|
-
var
|
|
4756
|
+
var Live14 = layer6();
|
|
4574
4757
|
var MemorySession = {
|
|
4575
|
-
Live:
|
|
4758
|
+
Live: Live14,
|
|
4576
4759
|
layer: layer6
|
|
4577
4760
|
};
|
|
4578
4761
|
|
|
4579
4762
|
// src/layers/signal/ferix-parser.ts
|
|
4580
4763
|
init_esm_shims();
|
|
4581
|
-
import { Effect as
|
|
4764
|
+
import { Effect as Effect18, Layer as Layer13, Ref as Ref8 } from "effect";
|
|
4582
4765
|
|
|
4583
4766
|
// src/services/signal-parser.ts
|
|
4584
4767
|
init_esm_shims();
|
|
@@ -5088,10 +5271,10 @@ signalSpecRegistry.register(tasksDefinedSpec);
|
|
|
5088
5271
|
// src/layers/signal/ferix-parser.ts
|
|
5089
5272
|
var MAX_BUFFER_SIZE = 1024 * 1024;
|
|
5090
5273
|
function createAccumulatorImpl() {
|
|
5091
|
-
return
|
|
5274
|
+
return Effect18.gen(function* () {
|
|
5092
5275
|
const chunksRef = yield* Ref8.make([]);
|
|
5093
5276
|
const emittedRef = yield* Ref8.make(/* @__PURE__ */ new Set());
|
|
5094
|
-
const feed = (text) =>
|
|
5277
|
+
const feed = (text) => Effect18.gen(function* () {
|
|
5095
5278
|
const chunks = yield* Ref8.get(chunksRef);
|
|
5096
5279
|
chunks.push(text);
|
|
5097
5280
|
const buffer = chunks.join("");
|
|
@@ -5119,7 +5302,7 @@ function createAccumulatorImpl() {
|
|
|
5119
5302
|
yield* Ref8.set(emittedRef, emitted);
|
|
5120
5303
|
return newSignals;
|
|
5121
5304
|
});
|
|
5122
|
-
const flush = () =>
|
|
5305
|
+
const flush = () => Effect18.gen(function* () {
|
|
5123
5306
|
const chunks = yield* Ref8.get(chunksRef);
|
|
5124
5307
|
const buffer = chunks.join("");
|
|
5125
5308
|
yield* Ref8.set(chunksRef, []);
|
|
@@ -5134,13 +5317,13 @@ function createAccumulatorImpl() {
|
|
|
5134
5317
|
return { feed, flush };
|
|
5135
5318
|
});
|
|
5136
5319
|
}
|
|
5137
|
-
var
|
|
5138
|
-
parse: (text) =>
|
|
5320
|
+
var make6 = {
|
|
5321
|
+
parse: (text) => Effect18.succeed(signalSpecRegistry.parseAll(text)),
|
|
5139
5322
|
createAccumulator: createAccumulatorImpl
|
|
5140
5323
|
};
|
|
5141
|
-
var
|
|
5324
|
+
var Live15 = Layer13.succeed(SignalParser, make6);
|
|
5142
5325
|
var FerixParser = {
|
|
5143
|
-
Live:
|
|
5326
|
+
Live: Live15
|
|
5144
5327
|
};
|
|
5145
5328
|
|
|
5146
5329
|
// src/layers/index.ts
|
|
@@ -5153,6 +5336,18 @@ var ProductionLayers = Layer14.mergeAll(
|
|
|
5153
5336
|
FileSystemGuardrails.Live,
|
|
5154
5337
|
FileSystemGit.Live
|
|
5155
5338
|
);
|
|
5339
|
+
function createProductionLayers(provider = "claude") {
|
|
5340
|
+
const llmLayer = createProviderLayer2(provider);
|
|
5341
|
+
return Layer14.mergeAll(
|
|
5342
|
+
llmLayer,
|
|
5343
|
+
FerixParser.Live,
|
|
5344
|
+
FileSystemPlan.Live,
|
|
5345
|
+
FileSystemSession.Live,
|
|
5346
|
+
FileSystemProgress.Live,
|
|
5347
|
+
FileSystemGuardrails.Live,
|
|
5348
|
+
FileSystemGit.Live
|
|
5349
|
+
);
|
|
5350
|
+
}
|
|
5156
5351
|
var TestLayers = Layer14.mergeAll(
|
|
5157
5352
|
Mock.Live,
|
|
5158
5353
|
FerixParser.Live,
|
|
@@ -5179,27 +5374,27 @@ init_esm_shims();
|
|
|
5179
5374
|
|
|
5180
5375
|
// src/orchestrator/loop.ts
|
|
5181
5376
|
init_esm_shims();
|
|
5182
|
-
import { DateTime as DateTime10, Effect as
|
|
5377
|
+
import { DateTime as DateTime10, Effect as Effect23, Option, pipe as pipe3, Ref as Ref12, Stream as Stream9 } from "effect";
|
|
5183
5378
|
|
|
5184
5379
|
// src/orchestrator/discovery.ts
|
|
5185
5380
|
init_esm_shims();
|
|
5186
|
-
import { DateTime as DateTime8, Effect as
|
|
5381
|
+
import { DateTime as DateTime8, Effect as Effect21, pipe, Ref as Ref10, Stream as Stream7 } from "effect";
|
|
5187
5382
|
|
|
5188
5383
|
// src/layers/plan/task-generation.ts
|
|
5189
5384
|
init_esm_shims();
|
|
5190
5385
|
import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
5191
5386
|
import { join as join6 } from "path";
|
|
5192
|
-
import { Effect as
|
|
5387
|
+
import { Effect as Effect19 } from "effect";
|
|
5193
5388
|
var PLANS_DIR4 = ".ferix/plans";
|
|
5194
5389
|
function ensureDir5(dirPath) {
|
|
5195
|
-
return
|
|
5390
|
+
return Effect19.tryPromise({
|
|
5196
5391
|
try: () => mkdir6(dirPath, { recursive: true }),
|
|
5197
5392
|
catch: (error) => new PlanStoreError({
|
|
5198
5393
|
message: `Failed to create directory: ${dirPath}`,
|
|
5199
5394
|
operation: "create",
|
|
5200
5395
|
cause: error
|
|
5201
5396
|
})
|
|
5202
|
-
}).pipe(
|
|
5397
|
+
}).pipe(Effect19.asVoid);
|
|
5203
5398
|
}
|
|
5204
5399
|
function getSessionDir4(sessionId) {
|
|
5205
5400
|
return join6(process.cwd(), PLANS_DIR4, sessionId);
|
|
@@ -5208,12 +5403,12 @@ function getTasksMdPath(sessionId) {
|
|
|
5208
5403
|
return join6(getSessionDir4(sessionId), "tasks.md");
|
|
5209
5404
|
}
|
|
5210
5405
|
function writeTasksMd(sessionId, tasks) {
|
|
5211
|
-
return
|
|
5406
|
+
return Effect19.gen(function* () {
|
|
5212
5407
|
const sessionDir = getSessionDir4(sessionId);
|
|
5213
5408
|
yield* ensureDir5(sessionDir);
|
|
5214
5409
|
const tasksMdPath = getTasksMdPath(sessionId);
|
|
5215
5410
|
const content = formatTasksMd(tasks);
|
|
5216
|
-
yield*
|
|
5411
|
+
yield* Effect19.tryPromise({
|
|
5217
5412
|
try: () => writeFile5(tasksMdPath, content, "utf-8"),
|
|
5218
5413
|
catch: (error) => new PlanStoreError({
|
|
5219
5414
|
message: `Failed to write tasks.md: ${tasksMdPath}`,
|
|
@@ -5395,7 +5590,7 @@ function mapSignalToDomain(signal, context) {
|
|
|
5395
5590
|
|
|
5396
5591
|
// src/orchestrator/plan-updates.ts
|
|
5397
5592
|
init_esm_shims();
|
|
5398
|
-
import { DateTime as DateTime7, Effect as
|
|
5593
|
+
import { DateTime as DateTime7, Effect as Effect20, Ref as Ref9 } from "effect";
|
|
5399
5594
|
|
|
5400
5595
|
// src/orchestrator/plan-updates/index.ts
|
|
5401
5596
|
init_esm_shims();
|
|
@@ -5700,9 +5895,9 @@ function persistPlanUpdate(planStore, plan, operation) {
|
|
|
5700
5895
|
tasks: plan.tasks
|
|
5701
5896
|
}) : planStore.update(plan.id, plan);
|
|
5702
5897
|
return storeOp.pipe(
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
(error) =>
|
|
5898
|
+
Effect20.map(() => null),
|
|
5899
|
+
Effect20.catchAll(
|
|
5900
|
+
(error) => Effect20.succeed({
|
|
5706
5901
|
_tag: "PlanUpdateFailed",
|
|
5707
5902
|
operation,
|
|
5708
5903
|
error: error.message,
|
|
@@ -5712,7 +5907,7 @@ function persistPlanUpdate(planStore, plan, operation) {
|
|
|
5712
5907
|
);
|
|
5713
5908
|
}
|
|
5714
5909
|
function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessionId, originalTask) {
|
|
5715
|
-
return
|
|
5910
|
+
return Effect20.gen(function* () {
|
|
5716
5911
|
const currentPlan = yield* Ref9.get(currentPlanRef);
|
|
5717
5912
|
const now = yield* DateTime7.now;
|
|
5718
5913
|
const timestamp = DateTime7.formatIso(now);
|
|
@@ -5734,7 +5929,7 @@ function updatePlanFromSignal(currentPlanRef, persistenceStateRef, signal, sessi
|
|
|
5734
5929
|
});
|
|
5735
5930
|
}
|
|
5736
5931
|
function flushPlanPersistence(planStore, currentPlanRef, persistenceStateRef) {
|
|
5737
|
-
return
|
|
5932
|
+
return Effect20.gen(function* () {
|
|
5738
5933
|
const state = yield* Ref9.get(persistenceStateRef);
|
|
5739
5934
|
if (!(state.dirty && state.pendingOperation)) {
|
|
5740
5935
|
return [];
|
|
@@ -6008,12 +6203,12 @@ Begin.`);
|
|
|
6008
6203
|
|
|
6009
6204
|
// src/orchestrator/discovery.ts
|
|
6010
6205
|
function processTextSignals(signalParser, text, context) {
|
|
6011
|
-
return
|
|
6206
|
+
return Effect21.gen(function* () {
|
|
6012
6207
|
const events = [];
|
|
6013
6208
|
const parsedSignals = [];
|
|
6014
6209
|
const signals = yield* signalParser.parse(text).pipe(
|
|
6015
|
-
|
|
6016
|
-
(error) =>
|
|
6210
|
+
Effect21.tapError(
|
|
6211
|
+
(error) => Effect21.logDebug(
|
|
6017
6212
|
"Signal parsing failed, continuing with empty signals",
|
|
6018
6213
|
{
|
|
6019
6214
|
error: String(error),
|
|
@@ -6021,7 +6216,7 @@ function processTextSignals(signalParser, text, context) {
|
|
|
6021
6216
|
}
|
|
6022
6217
|
)
|
|
6023
6218
|
),
|
|
6024
|
-
|
|
6219
|
+
Effect21.orElseSucceed(() => [])
|
|
6025
6220
|
);
|
|
6026
6221
|
for (const signal of signals) {
|
|
6027
6222
|
events.push(mapSignalToDomain(signal, context));
|
|
@@ -6031,7 +6226,7 @@ function processTextSignals(signalParser, text, context) {
|
|
|
6031
6226
|
});
|
|
6032
6227
|
}
|
|
6033
6228
|
function processLLMEvent(signalParser, llmEvent, context) {
|
|
6034
|
-
return
|
|
6229
|
+
return Effect21.gen(function* () {
|
|
6035
6230
|
const domainEvent = mapLLMEventToDomain(llmEvent, context);
|
|
6036
6231
|
const events = [domainEvent];
|
|
6037
6232
|
const allSignals = [];
|
|
@@ -6076,7 +6271,7 @@ function planTasksToGeneratedTasks(plan) {
|
|
|
6076
6271
|
}
|
|
6077
6272
|
function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, config, sessionId, worktreePath) {
|
|
6078
6273
|
return Stream7.unwrap(
|
|
6079
|
-
|
|
6274
|
+
Effect21.gen(function* () {
|
|
6080
6275
|
const startTimeUtc = yield* DateTime8.now;
|
|
6081
6276
|
const startTime = DateTime8.toEpochMillis(startTimeUtc);
|
|
6082
6277
|
const persistenceStateRef = yield* Ref10.make({
|
|
@@ -6103,7 +6298,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
6103
6298
|
),
|
|
6104
6299
|
Stream7.flatMap(
|
|
6105
6300
|
(llmEvent) => Stream7.unwrap(
|
|
6106
|
-
|
|
6301
|
+
Effect21.gen(function* () {
|
|
6107
6302
|
const now = yield* DateTime8.now;
|
|
6108
6303
|
const context = {
|
|
6109
6304
|
timestamp: DateTime8.toEpochMillis(now)
|
|
@@ -6140,7 +6335,7 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
6140
6335
|
)
|
|
6141
6336
|
);
|
|
6142
6337
|
const completionStream = Stream7.fromEffect(
|
|
6143
|
-
|
|
6338
|
+
Effect21.gen(function* () {
|
|
6144
6339
|
const persistEvents = yield* flushPlanPersistence(
|
|
6145
6340
|
planStore,
|
|
6146
6341
|
currentPlanRef,
|
|
@@ -6151,12 +6346,12 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
6151
6346
|
if (plan && plan.tasks.length > 0) {
|
|
6152
6347
|
const generatedTasks = planTasksToGeneratedTasks(plan);
|
|
6153
6348
|
yield* writeTasksMd(sessionId, generatedTasks).pipe(
|
|
6154
|
-
|
|
6155
|
-
(error) =>
|
|
6349
|
+
Effect21.tapError(
|
|
6350
|
+
(error) => Effect21.logDebug("Failed to write tasks.md, continuing", {
|
|
6156
6351
|
error: String(error)
|
|
6157
6352
|
})
|
|
6158
6353
|
),
|
|
6159
|
-
|
|
6354
|
+
Effect21.orElseSucceed(() => void 0)
|
|
6160
6355
|
);
|
|
6161
6356
|
}
|
|
6162
6357
|
const endTimeUtc = yield* DateTime8.now;
|
|
@@ -6181,15 +6376,15 @@ function createDiscoveryStream(llm, signalParser, planStore, currentPlanRef, con
|
|
|
6181
6376
|
|
|
6182
6377
|
// src/orchestrator/iteration.ts
|
|
6183
6378
|
init_esm_shims();
|
|
6184
|
-
import { DateTime as DateTime9, Effect as
|
|
6379
|
+
import { DateTime as DateTime9, Effect as Effect22, pipe as pipe2, Ref as Ref11, Stream as Stream8 } from "effect";
|
|
6185
6380
|
function processTextSignals2(signalParser, text, context) {
|
|
6186
|
-
return
|
|
6381
|
+
return Effect22.gen(function* () {
|
|
6187
6382
|
const events = [];
|
|
6188
6383
|
let completed = false;
|
|
6189
6384
|
const parsedSignals = [];
|
|
6190
6385
|
const signals = yield* signalParser.parse(text).pipe(
|
|
6191
|
-
|
|
6192
|
-
(error) =>
|
|
6386
|
+
Effect22.tapError(
|
|
6387
|
+
(error) => Effect22.logDebug(
|
|
6193
6388
|
"Signal parsing failed, continuing with empty signals",
|
|
6194
6389
|
{
|
|
6195
6390
|
error: String(error),
|
|
@@ -6197,7 +6392,7 @@ function processTextSignals2(signalParser, text, context) {
|
|
|
6197
6392
|
}
|
|
6198
6393
|
)
|
|
6199
6394
|
),
|
|
6200
|
-
|
|
6395
|
+
Effect22.orElseSucceed(() => [])
|
|
6201
6396
|
);
|
|
6202
6397
|
for (const signal of signals) {
|
|
6203
6398
|
events.push(mapSignalToDomain(signal, context));
|
|
@@ -6210,7 +6405,7 @@ function processTextSignals2(signalParser, text, context) {
|
|
|
6210
6405
|
});
|
|
6211
6406
|
}
|
|
6212
6407
|
function processLLMEvent2(signalParser, llmEvent, context) {
|
|
6213
|
-
return
|
|
6408
|
+
return Effect22.gen(function* () {
|
|
6214
6409
|
const domainEvent = mapLLMEventToDomain(llmEvent, context);
|
|
6215
6410
|
const events = [domainEvent];
|
|
6216
6411
|
let completed = false;
|
|
@@ -6243,7 +6438,7 @@ function processLLMEvent2(signalParser, llmEvent, context) {
|
|
|
6243
6438
|
}
|
|
6244
6439
|
function createIterationStream(llm, signalParser, planStore, currentPlanRef, loopCompletedRef, config, iteration, sessionId, worktreePath) {
|
|
6245
6440
|
return Stream8.unwrap(
|
|
6246
|
-
|
|
6441
|
+
Effect22.gen(function* () {
|
|
6247
6442
|
const currentPlan = yield* Ref11.get(currentPlanRef);
|
|
6248
6443
|
const persistenceStateRef = yield* Ref11.make({
|
|
6249
6444
|
dirty: false,
|
|
@@ -6266,7 +6461,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
6266
6461
|
),
|
|
6267
6462
|
Stream8.flatMap(
|
|
6268
6463
|
(llmEvent) => Stream8.unwrap(
|
|
6269
|
-
|
|
6464
|
+
Effect22.gen(function* () {
|
|
6270
6465
|
const now = yield* DateTime9.now;
|
|
6271
6466
|
const context = {
|
|
6272
6467
|
timestamp: DateTime9.toEpochMillis(now)
|
|
@@ -6307,7 +6502,7 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
6307
6502
|
)
|
|
6308
6503
|
);
|
|
6309
6504
|
const completionStream = Stream8.fromEffect(
|
|
6310
|
-
|
|
6505
|
+
Effect22.gen(function* () {
|
|
6311
6506
|
const persistEvents = yield* flushPlanPersistence(
|
|
6312
6507
|
planStore,
|
|
6313
6508
|
currentPlanRef,
|
|
@@ -6332,14 +6527,14 @@ function createIterationStream(llm, signalParser, planStore, currentPlanRef, loo
|
|
|
6332
6527
|
// src/orchestrator/loop.ts
|
|
6333
6528
|
function runLoop(config) {
|
|
6334
6529
|
return Stream9.unwrap(
|
|
6335
|
-
|
|
6530
|
+
Effect23.gen(function* () {
|
|
6336
6531
|
const llm = yield* LLM;
|
|
6337
6532
|
const signalParser = yield* SignalParser;
|
|
6338
6533
|
const sessionStore = yield* SessionStore;
|
|
6339
6534
|
const planStore = yield* PlanStore;
|
|
6340
6535
|
const git = yield* Git;
|
|
6341
6536
|
const session = yield* sessionStore.create(config.task).pipe(
|
|
6342
|
-
|
|
6537
|
+
Effect23.mapError(
|
|
6343
6538
|
(e) => new OrchestratorError({
|
|
6344
6539
|
message: `Failed to create session: ${e.message}`,
|
|
6345
6540
|
phase: "setup",
|
|
@@ -6348,7 +6543,7 @@ function runLoop(config) {
|
|
|
6348
6543
|
)
|
|
6349
6544
|
);
|
|
6350
6545
|
const worktreePath = yield* git.createWorktree(session.id).pipe(
|
|
6351
|
-
|
|
6546
|
+
Effect23.mapError(
|
|
6352
6547
|
(e) => new OrchestratorError({
|
|
6353
6548
|
message: `Failed to create worktree: ${e.message}`,
|
|
6354
6549
|
phase: "setup",
|
|
@@ -6362,12 +6557,12 @@ function runLoop(config) {
|
|
|
6362
6557
|
worktreePath,
|
|
6363
6558
|
branchName
|
|
6364
6559
|
}).pipe(
|
|
6365
|
-
|
|
6366
|
-
(error) =>
|
|
6560
|
+
Effect23.tapError(
|
|
6561
|
+
(error) => Effect23.logDebug("Failed to update session with worktree info", {
|
|
6367
6562
|
error: String(error)
|
|
6368
6563
|
})
|
|
6369
6564
|
),
|
|
6370
|
-
|
|
6565
|
+
Effect23.orElseSucceed(() => void 0)
|
|
6371
6566
|
);
|
|
6372
6567
|
const startTimeUtc = yield* DateTime10.now;
|
|
6373
6568
|
const startTime = DateTime10.toEpochMillis(startTimeUtc);
|
|
@@ -6397,7 +6592,7 @@ function runLoop(config) {
|
|
|
6397
6592
|
);
|
|
6398
6593
|
const iterationsStream = Stream9.unfoldEffect(
|
|
6399
6594
|
1,
|
|
6400
|
-
(iteration) =>
|
|
6595
|
+
(iteration) => Effect23.gen(function* () {
|
|
6401
6596
|
const completed = yield* Ref12.get(loopCompletedRef);
|
|
6402
6597
|
if (completed || iteration > maxIterations) {
|
|
6403
6598
|
return Option.none();
|
|
@@ -6437,8 +6632,8 @@ function runLoop(config) {
|
|
|
6437
6632
|
);
|
|
6438
6633
|
}).pipe(
|
|
6439
6634
|
// Also catch setup errors (e.g., session creation failure)
|
|
6440
|
-
|
|
6441
|
-
(error) =>
|
|
6635
|
+
Effect23.catchAll(
|
|
6636
|
+
(error) => Effect23.succeed(
|
|
6442
6637
|
Stream9.succeed({
|
|
6443
6638
|
_tag: "LoopFailed",
|
|
6444
6639
|
error: {
|
|
@@ -6453,18 +6648,18 @@ function runLoop(config) {
|
|
|
6453
6648
|
}
|
|
6454
6649
|
function createCompletionStream(sessionStore, git, session, config, startTime, loopCompletedRef, _worktreePath) {
|
|
6455
6650
|
return Stream9.unwrap(
|
|
6456
|
-
|
|
6651
|
+
Effect23.gen(function* () {
|
|
6457
6652
|
const endTimeUtc = yield* DateTime10.now;
|
|
6458
6653
|
const durationMs = DateTime10.toEpochMillis(endTimeUtc) - startTime;
|
|
6459
6654
|
const completed = yield* Ref12.get(loopCompletedRef);
|
|
6460
6655
|
yield* git.commitChanges(session.id, `feat: complete session ${session.id}`).pipe(
|
|
6461
|
-
|
|
6462
|
-
(error) =>
|
|
6656
|
+
Effect23.tapError(
|
|
6657
|
+
(error) => Effect23.logDebug("Final commit failed, continuing", {
|
|
6463
6658
|
sessionId: session.id,
|
|
6464
6659
|
error: String(error)
|
|
6465
6660
|
})
|
|
6466
6661
|
),
|
|
6467
|
-
|
|
6662
|
+
Effect23.orElseSucceed(() => void 0)
|
|
6468
6663
|
);
|
|
6469
6664
|
const summary = {
|
|
6470
6665
|
iterations: config.maxIterations,
|
|
@@ -6477,13 +6672,13 @@ function createCompletionStream(sessionStore, git, session, config, startTime, l
|
|
|
6477
6672
|
...session,
|
|
6478
6673
|
status: completed ? "completed" : "paused"
|
|
6479
6674
|
}).pipe(
|
|
6480
|
-
|
|
6481
|
-
(error) =>
|
|
6675
|
+
Effect23.tapError(
|
|
6676
|
+
(error) => Effect23.logDebug("Session update failed, continuing", {
|
|
6482
6677
|
sessionId: session.id,
|
|
6483
6678
|
error: String(error)
|
|
6484
6679
|
})
|
|
6485
6680
|
),
|
|
6486
|
-
|
|
6681
|
+
Effect23.orElseSucceed(() => void 0)
|
|
6487
6682
|
);
|
|
6488
6683
|
const loopCompleted = { _tag: "LoopCompleted", summary };
|
|
6489
6684
|
return Stream9.succeed(loopCompleted);
|
|
@@ -6495,10 +6690,9 @@ function createCompletionStream(sessionStore, git, session, config, startTime, l
|
|
|
6495
6690
|
function run(options) {
|
|
6496
6691
|
const { config, consumer: consumerType = "headless", onEvent } = options;
|
|
6497
6692
|
const events = runLoop(config);
|
|
6498
|
-
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) =>
|
|
6499
|
-
const
|
|
6500
|
-
|
|
6501
|
-
);
|
|
6693
|
+
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect24.sync(() => onEvent(event)))) : events;
|
|
6694
|
+
const layers = config.provider ? createProductionLayers(config.provider) : ProductionLayers;
|
|
6695
|
+
const eventsWithLayers = eventsWithCallback.pipe(Stream10.provideLayer(layers));
|
|
6502
6696
|
if (consumerType === "none") {
|
|
6503
6697
|
return eventsWithLayers.pipe(Stream10.runDrain);
|
|
6504
6698
|
}
|
|
@@ -6508,7 +6702,7 @@ function run(options) {
|
|
|
6508
6702
|
function runTest(options, mockEvents) {
|
|
6509
6703
|
const { config, onEvent } = options;
|
|
6510
6704
|
const events = runLoop(config);
|
|
6511
|
-
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) =>
|
|
6705
|
+
const eventsWithCallback = onEvent ? events.pipe(Stream10.tap((event) => Effect24.sync(() => onEvent(event)))) : events;
|
|
6512
6706
|
const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
|
|
6513
6707
|
const eventsWithLayers = eventsWithCallback.pipe(Stream10.provideLayer(layers));
|
|
6514
6708
|
return eventsWithLayers.pipe(Stream10.runDrain);
|
|
@@ -6516,11 +6710,11 @@ function runTest(options, mockEvents) {
|
|
|
6516
6710
|
function collectEvents(config, mockEvents) {
|
|
6517
6711
|
const events = runLoop(config);
|
|
6518
6712
|
const layers = mockEvents ? createTestLayers(mockEvents) : TestLayers;
|
|
6519
|
-
return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(
|
|
6713
|
+
return events.pipe(Stream10.provideLayer(layers), Stream10.runCollect).pipe(Effect24.map((chunk) => Array.from(chunk)));
|
|
6520
6714
|
}
|
|
6521
6715
|
function main(config) {
|
|
6522
6716
|
const consumerType = process.stdout.isTTY ? "tui" : "headless";
|
|
6523
|
-
return run({ config, consumer: consumerType }).pipe(
|
|
6717
|
+
return run({ config, consumer: consumerType }).pipe(Effect24.runPromise);
|
|
6524
6718
|
}
|
|
6525
6719
|
|
|
6526
6720
|
// src/services/index.ts
|
|
@@ -6528,14 +6722,15 @@ init_esm_shims();
|
|
|
6528
6722
|
|
|
6529
6723
|
// src/index.ts
|
|
6530
6724
|
var program = new Command();
|
|
6531
|
-
program.name("ferix-code").description("Composable RALPH loops for AI coding agents").version(package_default.version, "-v, --version", "Output the version number").argument("<task>", "Task description or path to PRD file").option("-i, --iterations <n>", "Maximum iterations", "1").option("-c, --verify <commands...>", "Verification commands to run").option("--branch <name>", "Git branch to create").option("--push", "Push branch after completion").option("--pr", "Create PR after pushing").action(async (task, options) => {
|
|
6725
|
+
program.name("ferix-code").description("Composable RALPH loops for AI coding agents").version(package_default.version, "-v, --version", "Output the version number").argument("<task>", "Task description or path to PRD file").option("-i, --iterations <n>", "Maximum iterations", "1").option("-c, --verify <commands...>", "Verification commands to run").option("--branch <name>", "Git branch to create").option("--push", "Push branch after completion").option("--pr", "Create PR after pushing").option("--provider <name>", "LLM provider to use (claude, cursor)", "claude").action(async (task, options) => {
|
|
6532
6726
|
const config = {
|
|
6533
6727
|
task,
|
|
6534
6728
|
maxIterations: Number.parseInt(options.iterations, 10),
|
|
6535
6729
|
verifyCommands: options.verify || [],
|
|
6536
6730
|
branch: options.branch,
|
|
6537
6731
|
push: options.push,
|
|
6538
|
-
pr: options.pr
|
|
6732
|
+
pr: options.pr,
|
|
6733
|
+
provider: options.provider
|
|
6539
6734
|
};
|
|
6540
6735
|
try {
|
|
6541
6736
|
await main(config);
|
|
@@ -6565,6 +6760,7 @@ export {
|
|
|
6565
6760
|
CriterionPassedSignalSchema,
|
|
6566
6761
|
CriterionSchema,
|
|
6567
6762
|
CriterionStatusSchema,
|
|
6763
|
+
CursorCLI,
|
|
6568
6764
|
DiscoveryCompletedEventSchema,
|
|
6569
6765
|
DiscoveryStartedEventSchema,
|
|
6570
6766
|
DomainEventSchema,
|
|
@@ -6620,6 +6816,7 @@ export {
|
|
|
6620
6816
|
Mock,
|
|
6621
6817
|
Mock as MockLLM,
|
|
6622
6818
|
OrchestratorError,
|
|
6819
|
+
PROVIDER_CONFIGS,
|
|
6623
6820
|
ParseError,
|
|
6624
6821
|
PhaseBasicInfoSchema,
|
|
6625
6822
|
PhaseCompletedEventSchema,
|
|
@@ -6652,6 +6849,7 @@ export {
|
|
|
6652
6849
|
ProgressStoreError,
|
|
6653
6850
|
ProgressUpdatedEventSchema,
|
|
6654
6851
|
PromptConfigSchema,
|
|
6852
|
+
ProviderNameSchema,
|
|
6655
6853
|
ReviewCompleteDataSchema,
|
|
6656
6854
|
ReviewCompleteEventSchema,
|
|
6657
6855
|
ReviewCompleteSignalSchema,
|
|
@@ -6690,6 +6888,8 @@ export {
|
|
|
6690
6888
|
buildPrompt,
|
|
6691
6889
|
collectEvents,
|
|
6692
6890
|
createHeadlessConsumer,
|
|
6891
|
+
createProductionLayers,
|
|
6892
|
+
createProviderLayer2 as createProviderLayer,
|
|
6693
6893
|
createTUIConsumer,
|
|
6694
6894
|
createTestLayers,
|
|
6695
6895
|
decodeGuardrail,
|