@cephalization/math 0.2.0

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 ADDED
@@ -0,0 +1,190 @@
1
+ # math - Multi-Agent Todo Harness
2
+
3
+ A light meta agent orchestration harness designed to coordinate multiple AI agents working together to accomplish tasks from a TODO list.
4
+
5
+ ## Core Concept
6
+
7
+ The primary responsibility of this harness is to **reduce context bloat** by digesting a project plan into three documents:
8
+
9
+ | Document | Purpose |
10
+ | ---------- | --------- |
11
+ | `TASKS.md` | Task list with status tracking and dependencies |
12
+ | `LEARNINGS.md` | Accumulated insights from completed tasks |
13
+ | `PROMPT.md` | System prompt with guardrails ("signs") |
14
+
15
+ The harness consists of a simple for-loop, executing a new coding agent with a mandate from `PROMPT.md` to complete a *single* task from `TASKS.md`, while reading and recording any insight gained during the work into `LEARNINGS.md`.
16
+
17
+ ## Requirements
18
+
19
+ **[Bun](https://bun.sh) is required** to run this tool. Node.js is not supported.
20
+
21
+ ```bash
22
+ # Install Bun (macOS, Linux, WSL)
23
+ curl -fsSL https://bun.sh/install | bash
24
+ ```
25
+
26
+ Why Bun?
27
+ - This tool is written in TypeScript and uses Bun's native TypeScript execution (no compilation step)
28
+ - The CLI uses a `#!/usr/bin/env bun` shebang for direct execution
29
+ - Bun's speed makes the agent loop faster and more responsive
30
+
31
+ ## Installation
32
+
33
+ ### From npm (recommended)
34
+
35
+ ```bash
36
+ # One-off usage (recommended)
37
+ bunx @cephalization/math <command>
38
+
39
+ # Global install
40
+ bun install -g @cephalization/math
41
+ ```
42
+
43
+ ### From source (for development)
44
+
45
+ ```bash
46
+ git clone https://github.com/cephalization/math.git
47
+ cd math
48
+ bun install
49
+ bun link
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ### Initialize a project
55
+
56
+ ```bash
57
+ math init
58
+ ```
59
+
60
+ Creates a `todo/` directory with template files and offers to run **planning mode** to help you break down your goal into tasks.
61
+
62
+ Options:
63
+
64
+ - `--no-plan` - Skip the planning prompt
65
+ - `--model <model>` - Model to use (default: `anthropic/claude-opus-4-5`)
66
+
67
+ ### Plan your tasks
68
+
69
+ ```bash
70
+ math plan
71
+ ```
72
+
73
+ Options:
74
+
75
+ - `--model <model>` - Model to use (default: `anthropic/claude-opus-4-5`)
76
+ - `--quick` - Skip clarifying questions and generate plan immediately
77
+
78
+ Interactively plan your tasks with AI assistance. The planner uses a two-phase approach:
79
+
80
+ 1. **Clarification phase**: The AI analyzes your goal and asks 3-5 clarifying questions
81
+ 2. **Planning phase**: Using your answers, it generates a well-structured task list
82
+
83
+ Use `--quick` to skip the clarification phase if you want a faster, assumption-based plan.
84
+
85
+ ### Run the agent loop
86
+
87
+ ```bash
88
+ math run
89
+ ```
90
+
91
+ Options:
92
+
93
+ - `--model <model>` - Model to use (default: `anthropic/claude-opus-4-5`)
94
+ - `--max-iterations <n>` - Safety limit (default: 100)
95
+ - `--pause <seconds>` - Pause between iterations (default: 3)
96
+
97
+ ### Check status
98
+
99
+ ```bash
100
+ math status
101
+ ```
102
+
103
+ Shows task progress with a visual progress bar and next task info.
104
+
105
+ ### Start a new sprint
106
+
107
+ ```bash
108
+ math iterate
109
+ ```
110
+
111
+ Backs up `todo/` to `todo-{M}-{D}-{Y}/` and resets for a new goal:
112
+
113
+ - TASKS.md and LEARNINGS.md are reset to templates
114
+ - PROMPT.md is preserved (keeping your accumulated "signs")
115
+ - Offers to run planning mode for your new goal
116
+
117
+ Options:
118
+
119
+ - `--no-plan` - Skip the planning prompt
120
+
121
+ ## Task Format
122
+
123
+ Tasks in `TASKS.md` follow this format:
124
+
125
+ ```markdown
126
+ ### task-id
127
+
128
+ - content: Description of what to implement
129
+ - status: pending | in_progress | complete
130
+ - dependencies: task-1, task-2
131
+ ```
132
+
133
+ ## The Loop
134
+
135
+ ```
136
+ ┌─────────────────────────────────────────────────────────────┐
137
+ │ math run (loop) │
138
+ │ ┌───────────────────────────────────────────────────────┐ │
139
+ │ │ 1. Check TASKS.md for pending tasks │ │
140
+ │ │ 2. If all complete → EXIT SUCCESS │ │
141
+ │ │ 3. Invoke agent with PROMPT.md + TASKS.md │ │
142
+ │ │ 4. Agent: pick task → implement → test → commit │ │
143
+ │ │ 5. Agent: update TASKS.md → log learnings → EXIT │ │
144
+ │ │ 6. Loop back to step 1 │ │
145
+ │ └───────────────────────────────────────────────────────┘ │
146
+ └─────────────────────────────────────────────────────────────┘
147
+ ```
148
+
149
+ ## Planning Mode
150
+
151
+ When you run `math init`, `math iterate`, or `math plan`, the harness can invoke [OpenCode](https://opencode.ai/docs/cli/) to help you plan:
152
+
153
+ 1. You describe your high-level goal
154
+ 2. OpenCode asks clarifying questions to understand your requirements
155
+ 3. You answer the questions interactively
156
+ 4. OpenCode breaks your goal into discrete, implementable tasks
157
+ 5. Tasks are written to `TASKS.md` with proper dependencies
158
+ 6. You're ready to run `math run`
159
+
160
+ This bridges the gap between "I want to build X" and a structured task list. The clarifying questions phase uses OpenCode's session continuation feature to maintain context across the conversation.
161
+
162
+ ## Signs (Guardrails)
163
+
164
+ "Signs" are explicit guardrails in `PROMPT.md` that prevent common agent mistakes. Add new signs whenever you discover a failure mode:
165
+
166
+ ```markdown
167
+ ### SIGN: Descriptive Name
168
+
169
+ Clear explanation of what to do or avoid.
170
+
171
+ ❌ WRONG: Example of the mistake
172
+ ✅ RIGHT: Example of correct behavior
173
+ ```
174
+
175
+ Signs accumulate over time, making the agent increasingly reliable.
176
+
177
+ ## Environment Variables
178
+
179
+ | Variable | Default | Description |
180
+ |----------|---------|-------------|
181
+ | `MODEL` | `anthropic/claude-opus-4-20250514` | Model to use |
182
+
183
+ ## Credits
184
+
185
+ - **Ralph Methodology**: [Geoffrey Huntley](https://ghuntley.com/ralph/)
186
+ - **Agent Runtime**: [OpenCode](https://opencode.ai)
187
+
188
+ ## License
189
+
190
+ MIT
package/index.ts ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { init } from "./src/commands/init";
4
+ import { run } from "./src/commands/run";
5
+ import { status } from "./src/commands/status";
6
+ import { iterate } from "./src/commands/iterate";
7
+ import { plan } from "./src/commands/plan";
8
+ import { prune } from "./src/commands/prune";
9
+ import { DEFAULT_MODEL } from "./src/constants";
10
+
11
+ // ANSI colors
12
+ const colors = {
13
+ reset: "\x1b[0m",
14
+ bold: "\x1b[1m",
15
+ dim: "\x1b[2m",
16
+ red: "\x1b[31m",
17
+ green: "\x1b[32m",
18
+ yellow: "\x1b[33m",
19
+ blue: "\x1b[34m",
20
+ cyan: "\x1b[36m",
21
+ };
22
+
23
+ function printHelp() {
24
+ console.log(`
25
+ ${colors.bold}math${colors.reset} - Multi-Agent Todo Harness
26
+
27
+ A light meta agent orchestration harness designed to coordinate multiple AI
28
+ agents working together to accomplish tasks from a TODO list.
29
+
30
+ ${colors.bold}USAGE${colors.reset}
31
+ math <command> [options]
32
+
33
+ ${colors.bold}COMMANDS${colors.reset}
34
+ ${colors.cyan}init${colors.reset} Create todo/ directory with template files
35
+ ${colors.cyan}plan${colors.reset} Run planning mode to flesh out tasks
36
+ ${colors.cyan}run${colors.reset} Start the agent loop until all tasks complete
37
+ ${colors.cyan}status${colors.reset} Show current task counts
38
+ ${colors.cyan}iterate${colors.reset} Backup todo/ and reset for a new sprint
39
+ ${colors.cyan}prune${colors.reset} Delete backup artifacts (todo-M-D-Y directories)
40
+ ${colors.cyan}help${colors.reset} Show this help message
41
+
42
+ ${colors.bold}OPTIONS${colors.reset}
43
+ ${colors.dim}--model <model>${colors.reset} Model to use (default: ${DEFAULT_MODEL})
44
+ ${colors.dim}--max-iterations <n>${colors.reset} Safety limit (default: 100)
45
+ ${colors.dim}--pause <seconds>${colors.reset} Pause between iterations (default: 3)
46
+ ${colors.dim}--no-plan${colors.reset} Skip planning mode after init/iterate
47
+ ${colors.dim}--ui${colors.reset} Enable web UI server (run command only)
48
+ ${colors.dim}--quick${colors.reset} Skip clarifying questions in plan mode
49
+ ${colors.dim}--force${colors.reset} Skip confirmation prompts (prune)
50
+
51
+ ${colors.bold}EXAMPLES${colors.reset}
52
+ ${colors.dim}# Initialize and plan a new project${colors.reset}
53
+ math init
54
+
55
+ ${colors.dim}# Initialize without planning${colors.reset}
56
+ math init --no-plan
57
+
58
+ ${colors.dim}# Run planning mode on existing todo/${colors.reset}
59
+ math plan
60
+
61
+ ${colors.dim}# Quick planning without clarifying questions${colors.reset}
62
+ math plan --quick
63
+
64
+ ${colors.dim}# Run the agent loop${colors.reset}
65
+ math run
66
+
67
+ ${colors.dim}# Run with web UI${colors.reset}
68
+ math run --ui
69
+
70
+ ${colors.dim}# Check task status${colors.reset}
71
+ math status
72
+
73
+ ${colors.dim}# Start a new sprint (backup current, reset, plan)${colors.reset}
74
+ math iterate
75
+ `);
76
+ }
77
+
78
+ function parseArgs(args: string[]): Record<string, string | boolean> {
79
+ const parsed: Record<string, string | boolean> = {};
80
+ for (let i = 0; i < args.length; i++) {
81
+ const arg = args[i];
82
+ if (!arg) continue;
83
+ if (arg.startsWith("--")) {
84
+ const key = arg.slice(2);
85
+ const next = args[i + 1];
86
+ if (next && !next.startsWith("--")) {
87
+ parsed[key] = next;
88
+ i++;
89
+ } else {
90
+ parsed[key] = true;
91
+ }
92
+ }
93
+ }
94
+ return parsed;
95
+ }
96
+
97
+ async function main() {
98
+ const [command, ...rest] = Bun.argv.slice(2);
99
+ const options = parseArgs(rest);
100
+
101
+ try {
102
+ switch (command) {
103
+ case "init":
104
+ await init({
105
+ skipPlan: !!options["no-plan"],
106
+ model: options.model as string,
107
+ });
108
+ break;
109
+ case "plan":
110
+ await plan({
111
+ model: options.model as string | undefined,
112
+ quick: !!options.quick,
113
+ });
114
+ break;
115
+ case "run":
116
+ await run(options);
117
+ break;
118
+ case "status":
119
+ await status();
120
+ break;
121
+ case "iterate":
122
+ await iterate({
123
+ skipPlan: !!options["no-plan"],
124
+ model: options.model as string,
125
+ });
126
+ break;
127
+ case "prune":
128
+ await prune({
129
+ force: !!options.force,
130
+ });
131
+ break;
132
+ case "help":
133
+ case "--help":
134
+ case "-h":
135
+ case undefined:
136
+ printHelp();
137
+ break;
138
+ default:
139
+ console.error(
140
+ `${colors.red}Unknown command: ${command}${colors.reset}`
141
+ );
142
+ printHelp();
143
+ process.exit(1);
144
+ }
145
+ } catch (error) {
146
+ console.error(
147
+ `${colors.red}Error: ${error instanceof Error ? error.message : error}${
148
+ colors.reset
149
+ }`
150
+ );
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ main();
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@cephalization/math",
3
+ "version": "0.2.0",
4
+ "author": "Tony Powell <powell.anthonyd@proton.me>",
5
+ "access": "public",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/cephalization/math"
9
+ },
10
+ "homepage": "https://github.com/cephalization/math",
11
+ "bugs": {
12
+ "url": "https://github.com/cephalization/math/issues"
13
+ },
14
+ "description": "Multi-Agent Todo Harness - A light meta agent orchestration harness",
15
+ "type": "module",
16
+ "bin": {
17
+ "math": "./index.ts"
18
+ },
19
+ "files": [
20
+ "index.ts",
21
+ "src/**/*.ts",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "start": "bun index.ts",
26
+ "test": "bun test",
27
+ "typecheck": "tsc --noEmit",
28
+ "changeset": "bunx changeset"
29
+ },
30
+ "devDependencies": {
31
+ "@changesets/cli": "^2.29.8",
32
+ "@types/bun": "latest",
33
+ "typescript": "^5"
34
+ },
35
+ "keywords": [
36
+ "ai",
37
+ "agent",
38
+ "multi-agent",
39
+ "todo",
40
+ "orchestration",
41
+ "bun"
42
+ ],
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@types/react": "^19.2.8",
46
+ "@types/react-dom": "^19.2.3",
47
+ "react": "^19.2.3",
48
+ "react-dom": "^19.2.3"
49
+ }
50
+ }
@@ -0,0 +1,179 @@
1
+ import { test, expect, describe } from "bun:test";
2
+ import {
3
+ MockAgent,
4
+ createMockAgent,
5
+ createLogEntry,
6
+ createAgentOutput,
7
+ type LogCategory,
8
+ type AgentRunOptions,
9
+ } from "./agent";
10
+
11
+ describe("MockAgent", () => {
12
+ test("isAvailable returns true by default", async () => {
13
+ const agent = createMockAgent();
14
+ expect(await agent.isAvailable()).toBe(true);
15
+ });
16
+
17
+ test("isAvailable returns false when configured", async () => {
18
+ const agent = createMockAgent({ available: false });
19
+ expect(await agent.isAvailable()).toBe(false);
20
+ });
21
+
22
+ test("run returns default logs and output", async () => {
23
+ const agent = createMockAgent();
24
+ const options: AgentRunOptions = {
25
+ model: "test-model",
26
+ prompt: "test prompt",
27
+ files: ["file1.md", "file2.md"],
28
+ };
29
+
30
+ const result = await agent.run(options);
31
+
32
+ expect(result.exitCode).toBe(0);
33
+ expect(result.logs).toHaveLength(2);
34
+ expect(result.logs[0]!.category).toBe("info");
35
+ expect(result.logs[0]!.message).toBe("Mock agent starting...");
36
+ expect(result.logs[1]!.category).toBe("success");
37
+ expect(result.logs[1]!.message).toBe("Mock agent completed");
38
+ expect(result.output).toHaveLength(1);
39
+ expect(result.output[0]!.text).toBe("Mock agent output\n");
40
+ });
41
+
42
+ test("run uses configured logs", async () => {
43
+ const customLogs = [
44
+ { category: "info" as LogCategory, message: "Custom log 1" },
45
+ { category: "warning" as LogCategory, message: "Custom warning" },
46
+ { category: "error" as LogCategory, message: "Custom error" },
47
+ ];
48
+ const agent = createMockAgent({ logs: customLogs });
49
+
50
+ const result = await agent.run({
51
+ model: "test",
52
+ prompt: "test",
53
+ files: [],
54
+ });
55
+
56
+ expect(result.logs).toHaveLength(3);
57
+ expect(result.logs[0]!.message).toBe("Custom log 1");
58
+ expect(result.logs[1]!.message).toBe("Custom warning");
59
+ expect(result.logs[2]!.message).toBe("Custom error");
60
+ });
61
+
62
+ test("run uses configured output", async () => {
63
+ const customOutput = ["Line 1\n", "Line 2\n", "Line 3\n"];
64
+ const agent = createMockAgent({ output: customOutput });
65
+
66
+ const result = await agent.run({
67
+ model: "test",
68
+ prompt: "test",
69
+ files: [],
70
+ });
71
+
72
+ expect(result.output).toHaveLength(3);
73
+ expect(result.output[0]!.text).toBe("Line 1\n");
74
+ expect(result.output[1]!.text).toBe("Line 2\n");
75
+ expect(result.output[2]!.text).toBe("Line 3\n");
76
+ });
77
+
78
+ test("run uses configured exit code", async () => {
79
+ const agent = createMockAgent({ exitCode: 1 });
80
+
81
+ const result = await agent.run({
82
+ model: "test",
83
+ prompt: "test",
84
+ files: [],
85
+ });
86
+
87
+ expect(result.exitCode).toBe(1);
88
+ });
89
+
90
+ test("run calls event callbacks", async () => {
91
+ const agent = createMockAgent({
92
+ logs: [{ category: "info", message: "Test log" }],
93
+ output: ["Test output"],
94
+ });
95
+
96
+ const receivedLogs: string[] = [];
97
+ const receivedOutput: string[] = [];
98
+
99
+ await agent.run({
100
+ model: "test",
101
+ prompt: "test",
102
+ files: [],
103
+ events: {
104
+ onLog: (entry) => receivedLogs.push(entry.message),
105
+ onOutput: (out) => receivedOutput.push(out.text),
106
+ },
107
+ });
108
+
109
+ expect(receivedLogs).toEqual(["Test log"]);
110
+ expect(receivedOutput).toEqual(["Test output"]);
111
+ });
112
+
113
+ test("configure updates agent behavior", async () => {
114
+ const agent = createMockAgent();
115
+
116
+ // Initial run
117
+ let result = await agent.run({
118
+ model: "test",
119
+ prompt: "test",
120
+ files: [],
121
+ });
122
+ expect(result.exitCode).toBe(0);
123
+
124
+ // Reconfigure
125
+ agent.configure({
126
+ exitCode: 2,
127
+ logs: [{ category: "error", message: "Reconfigured error" }],
128
+ output: ["Reconfigured output"],
129
+ });
130
+
131
+ // Run again
132
+ result = await agent.run({
133
+ model: "test",
134
+ prompt: "test",
135
+ files: [],
136
+ });
137
+
138
+ expect(result.exitCode).toBe(2);
139
+ expect(result.logs[0]!.message).toBe("Reconfigured error");
140
+ expect(result.output[0]!.text).toBe("Reconfigured output");
141
+ });
142
+
143
+ test("run respects delay configuration", async () => {
144
+ const agent = createMockAgent({ delay: 50 });
145
+
146
+ const start = Date.now();
147
+ await agent.run({
148
+ model: "test",
149
+ prompt: "test",
150
+ files: [],
151
+ });
152
+ const elapsed = Date.now() - start;
153
+
154
+ expect(elapsed).toBeGreaterThanOrEqual(49); // 50ms delay, but allow for some jitter
155
+ });
156
+ });
157
+
158
+ describe("helper functions", () => {
159
+ test("createLogEntry creates entry with timestamp", () => {
160
+ const before = new Date();
161
+ const entry = createLogEntry("success", "Test message");
162
+ const after = new Date();
163
+
164
+ expect(entry.category).toBe("success");
165
+ expect(entry.message).toBe("Test message");
166
+ expect(entry.timestamp.getTime()).toBeGreaterThanOrEqual(before.getTime());
167
+ expect(entry.timestamp.getTime()).toBeLessThanOrEqual(after.getTime());
168
+ });
169
+
170
+ test("createAgentOutput creates output with timestamp", () => {
171
+ const before = new Date();
172
+ const output = createAgentOutput("Test output");
173
+ const after = new Date();
174
+
175
+ expect(output.text).toBe("Test output");
176
+ expect(output.timestamp.getTime()).toBeGreaterThanOrEqual(before.getTime());
177
+ expect(output.timestamp.getTime()).toBeLessThanOrEqual(after.getTime());
178
+ });
179
+ });