ferix-code 0.0.1
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/README.md +216 -0
- package/dist/index.d.ts +2012 -0
- package/dist/index.js +5509 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# @ferix/cli-v2
|
|
2
|
+
|
|
3
|
+
Composable RALPH loops for AI coding agents, built with Effect.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
This CLI implements the RALPH (Review, Analyze, Learn, Plan, Help) loop pattern using Effect for type-safe, composable, and testable code.
|
|
8
|
+
|
|
9
|
+
### Directory Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/
|
|
13
|
+
├── consumers/ # Event consumers (output handlers)
|
|
14
|
+
│ ├── headless/ # Simple console output
|
|
15
|
+
│ └── tui/ # Full-screen terminal UI
|
|
16
|
+
│ ├── input/ # Keyboard/mouse input handling
|
|
17
|
+
│ ├── output/ # ANSI terminal output
|
|
18
|
+
│ ├── reducers/ # State reducers for events
|
|
19
|
+
│ ├── render/ # UI rendering components
|
|
20
|
+
│ └── tools/ # Tool formatting utilities
|
|
21
|
+
│
|
|
22
|
+
├── domain/ # Core domain types and schemas
|
|
23
|
+
│ └── schemas/ # Effect.Schema definitions
|
|
24
|
+
│ ├── config.ts # Loop configuration
|
|
25
|
+
│ ├── events.ts # Domain events
|
|
26
|
+
│ ├── llm.ts # LLM event types
|
|
27
|
+
│ ├── logger.ts # Logger types
|
|
28
|
+
│ ├── plan.ts # Plan/task schemas
|
|
29
|
+
│ ├── program.ts # Program options
|
|
30
|
+
│ ├── session.ts # Session state
|
|
31
|
+
│ ├── signals.ts # Signal types
|
|
32
|
+
│ ├── shared.ts # Shared utilities
|
|
33
|
+
│ └── tui.ts # TUI state types
|
|
34
|
+
│
|
|
35
|
+
├── layers/ # Effect Layer implementations
|
|
36
|
+
│ ├── llm/ # LLM providers (Claude CLI, Mock)
|
|
37
|
+
│ ├── logger/ # Logger implementations
|
|
38
|
+
│ ├── plan/ # Plan storage
|
|
39
|
+
│ ├── session/ # Session storage
|
|
40
|
+
│ └── signal/ # Signal parsers
|
|
41
|
+
│
|
|
42
|
+
├── orchestrator/ # Loop orchestration
|
|
43
|
+
│ └── mapping/ # Event mapping (LLM → Domain)
|
|
44
|
+
│
|
|
45
|
+
└── services/ # Effect Service definitions
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Effect Best Practices
|
|
49
|
+
|
|
50
|
+
### Schema-First Design
|
|
51
|
+
|
|
52
|
+
All data types are defined using `Effect.Schema` for runtime validation and type inference:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Schema as S } from "effect";
|
|
56
|
+
|
|
57
|
+
export const TaskStatusSchema = S.Literal("pending", "in_progress", "done", "failed");
|
|
58
|
+
export type TaskStatus = typeof TaskStatusSchema.Type;
|
|
59
|
+
|
|
60
|
+
export const TaskSchema = S.Struct({
|
|
61
|
+
id: S.String,
|
|
62
|
+
title: S.String,
|
|
63
|
+
status: TaskStatusSchema,
|
|
64
|
+
});
|
|
65
|
+
export type Task = typeof TaskSchema.Type;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Service Pattern
|
|
69
|
+
|
|
70
|
+
Services are defined as interfaces with Effect Context tags:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Context, Effect } from "effect";
|
|
74
|
+
|
|
75
|
+
export interface LoggerService {
|
|
76
|
+
readonly info: (message: string) => Effect.Effect<void>;
|
|
77
|
+
readonly error: (message: string) => Effect.Effect<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class Logger extends Context.Tag("@ferix/Logger")<Logger, LoggerService>() {}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Layer Composition
|
|
84
|
+
|
|
85
|
+
Implementations are provided via Layers for dependency injection:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { Effect, Layer } from "effect";
|
|
89
|
+
|
|
90
|
+
export const ConsoleLoggerLive = Layer.succeed(Logger, {
|
|
91
|
+
info: (msg) => Effect.sync(() => console.log(msg)),
|
|
92
|
+
error: (msg) => Effect.sync(() => console.error(msg)),
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Tagged Unions for Events
|
|
97
|
+
|
|
98
|
+
Domain events use discriminated unions with `_tag`:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
export const TextEventSchema = S.TaggedStruct("Text", {
|
|
102
|
+
text: S.String,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const DoneEventSchema = S.TaggedStruct("Done", {
|
|
106
|
+
output: S.String,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const LLMEventSchema = S.Union(TextEventSchema, DoneEventSchema);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Stream-Based Processing
|
|
113
|
+
|
|
114
|
+
LLM output is processed as Effect Streams for backpressure and composability:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const events: Stream.Stream<LLMEvent, LLMError> = llm.execute(prompt);
|
|
118
|
+
|
|
119
|
+
const domainEvents = events.pipe(
|
|
120
|
+
Stream.mapConcat(mapLLMEventToDomain),
|
|
121
|
+
Stream.tap((event) => logger.info(`Event: ${event._tag}`))
|
|
122
|
+
);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Error Handling
|
|
126
|
+
|
|
127
|
+
Errors are typed and handled explicitly:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { Data } from "effect";
|
|
131
|
+
|
|
132
|
+
export class LLMError extends Data.TaggedError("LLMError")<{
|
|
133
|
+
readonly message: string;
|
|
134
|
+
readonly cause?: unknown;
|
|
135
|
+
}> {}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Key Patterns
|
|
139
|
+
|
|
140
|
+
### Registry Pattern
|
|
141
|
+
|
|
142
|
+
Extensible handlers use a registry pattern:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Define registry
|
|
146
|
+
const registry = new Map<string, Handler>();
|
|
147
|
+
|
|
148
|
+
// Register handlers
|
|
149
|
+
registry.set("Text", handleText);
|
|
150
|
+
registry.set("Done", handleDone);
|
|
151
|
+
|
|
152
|
+
// Use registry
|
|
153
|
+
const handler = registry.get(event._tag);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Reducer Pattern (TUI)
|
|
157
|
+
|
|
158
|
+
TUI state updates use pure reducer functions:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
function reduce(state: TUIState, event: DomainEvent): TUIState {
|
|
162
|
+
return stateReducerRegistry.reduce(state, event);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Direct Schema Imports
|
|
167
|
+
|
|
168
|
+
Types are imported directly from schema files (no re-exports):
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Good - direct import
|
|
172
|
+
import type { TUIState } from "../domain/schemas/tui.js";
|
|
173
|
+
|
|
174
|
+
// Avoid - re-exports
|
|
175
|
+
import type { TUIState } from "../state.js"; // re-exporting from schema
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Usage
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { Effect } from "effect";
|
|
182
|
+
import { run } from "@ferix/cli-v2";
|
|
183
|
+
|
|
184
|
+
await run({
|
|
185
|
+
config: {
|
|
186
|
+
task: "Implement the feature",
|
|
187
|
+
maxIterations: 3,
|
|
188
|
+
verifyCommands: ["bun test"],
|
|
189
|
+
},
|
|
190
|
+
consumer: "tui",
|
|
191
|
+
}).pipe(Effect.runPromise);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Testing
|
|
195
|
+
|
|
196
|
+
Mock layers enable isolated testing:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { Mock } from "./layers/llm/mock.js";
|
|
200
|
+
|
|
201
|
+
const mockEvents: LLMEvent[] = [
|
|
202
|
+
{ _tag: "Text", text: "Working..." },
|
|
203
|
+
{ _tag: "Done", output: "Complete" },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
const testLayer = Mock.layer({ events: mockEvents });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Commands
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
bun run build # Build the CLI
|
|
213
|
+
bun run dev # Watch mode
|
|
214
|
+
bun run test # Run tests
|
|
215
|
+
bun run check-types # Type check
|
|
216
|
+
```
|