@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.
@@ -0,0 +1,174 @@
1
+ import { test, expect, beforeEach, afterEach } from "bun:test";
2
+ import { findArtifacts, deleteArtifacts, confirmPrune } from "./prune";
3
+ import { mkdirSync, rmSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ const TEST_DIR = join(import.meta.dir, ".test-prune");
7
+
8
+ beforeEach(() => {
9
+ mkdirSync(TEST_DIR, { recursive: true });
10
+ });
11
+
12
+ afterEach(() => {
13
+ rmSync(TEST_DIR, { recursive: true, force: true });
14
+ });
15
+
16
+ test("findArtifacts returns empty array for empty directory", () => {
17
+ const result = findArtifacts(TEST_DIR);
18
+ expect(result).toEqual([]);
19
+ });
20
+
21
+ test("findArtifacts finds backup directories with basic pattern", () => {
22
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025"));
23
+ mkdirSync(join(TEST_DIR, "todo-12-31-2024"));
24
+
25
+ const result = findArtifacts(TEST_DIR);
26
+
27
+ expect(result).toHaveLength(2);
28
+ expect(result).toContain(join(TEST_DIR, "todo-1-15-2025"));
29
+ expect(result).toContain(join(TEST_DIR, "todo-12-31-2024"));
30
+ });
31
+
32
+ test("findArtifacts finds backup directories with counter suffix", () => {
33
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025"));
34
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025-1"));
35
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025-42"));
36
+
37
+ const result = findArtifacts(TEST_DIR);
38
+
39
+ expect(result).toHaveLength(3);
40
+ expect(result).toContain(join(TEST_DIR, "todo-1-15-2025"));
41
+ expect(result).toContain(join(TEST_DIR, "todo-1-15-2025-1"));
42
+ expect(result).toContain(join(TEST_DIR, "todo-1-15-2025-42"));
43
+ });
44
+
45
+ test("findArtifacts ignores non-matching directories", () => {
46
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025"));
47
+ mkdirSync(join(TEST_DIR, "todo")); // Not a backup
48
+ mkdirSync(join(TEST_DIR, "node_modules")); // Not a backup
49
+ mkdirSync(join(TEST_DIR, "todo-invalid")); // Invalid pattern
50
+
51
+ const result = findArtifacts(TEST_DIR);
52
+
53
+ expect(result).toHaveLength(1);
54
+ expect(result).toContain(join(TEST_DIR, "todo-1-15-2025"));
55
+ });
56
+
57
+ test("findArtifacts ignores files matching pattern", () => {
58
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025"));
59
+ // Create a file that matches the pattern (should be ignored)
60
+ Bun.write(join(TEST_DIR, "todo-2-20-2025"), "not a directory");
61
+
62
+ const result = findArtifacts(TEST_DIR);
63
+
64
+ expect(result).toHaveLength(1);
65
+ expect(result).toContain(join(TEST_DIR, "todo-1-15-2025"));
66
+ });
67
+
68
+ test("findArtifacts returns empty array for non-existent directory", () => {
69
+ const result = findArtifacts(join(TEST_DIR, "does-not-exist"));
70
+ expect(result).toEqual([]);
71
+ });
72
+
73
+ test("findArtifacts returns absolute paths", () => {
74
+ mkdirSync(join(TEST_DIR, "todo-1-15-2025"));
75
+
76
+ const result = findArtifacts(TEST_DIR);
77
+
78
+ expect(result).toHaveLength(1);
79
+ expect(result[0]).toMatch(/^\//); // Starts with / (absolute path)
80
+ });
81
+
82
+ // deleteArtifacts tests
83
+
84
+ test("deleteArtifacts deletes directories successfully", () => {
85
+ const dir1 = join(TEST_DIR, "todo-1-15-2025");
86
+ const dir2 = join(TEST_DIR, "todo-2-20-2025");
87
+ mkdirSync(dir1);
88
+ mkdirSync(dir2);
89
+
90
+ const result = deleteArtifacts([dir1, dir2]);
91
+
92
+ expect(result.deleted).toHaveLength(2);
93
+ expect(result.deleted).toContain(dir1);
94
+ expect(result.deleted).toContain(dir2);
95
+ expect(result.failed).toHaveLength(0);
96
+ expect(existsSync(dir1)).toBe(false);
97
+ expect(existsSync(dir2)).toBe(false);
98
+ });
99
+
100
+ test("deleteArtifacts deletes directories with contents", () => {
101
+ const dir = join(TEST_DIR, "todo-1-15-2025");
102
+ mkdirSync(dir);
103
+ Bun.write(join(dir, "file.txt"), "content");
104
+ mkdirSync(join(dir, "subdir"));
105
+ Bun.write(join(dir, "subdir", "nested.txt"), "nested content");
106
+
107
+ const result = deleteArtifacts([dir]);
108
+
109
+ expect(result.deleted).toHaveLength(1);
110
+ expect(result.failed).toHaveLength(0);
111
+ expect(existsSync(dir)).toBe(false);
112
+ });
113
+
114
+ test("deleteArtifacts returns empty arrays for empty input", () => {
115
+ const result = deleteArtifacts([]);
116
+
117
+ expect(result.deleted).toHaveLength(0);
118
+ expect(result.failed).toHaveLength(0);
119
+ });
120
+
121
+ test("deleteArtifacts handles non-existent paths gracefully", () => {
122
+ const nonExistent = join(TEST_DIR, "does-not-exist");
123
+
124
+ const result = deleteArtifacts([nonExistent]);
125
+
126
+ // rmSync with force: true doesn't throw for non-existent paths
127
+ expect(result.deleted).toHaveLength(1);
128
+ expect(result.failed).toHaveLength(0);
129
+ });
130
+
131
+ test("deleteArtifacts continues after a failure", () => {
132
+ const dir1 = join(TEST_DIR, "todo-1-15-2025");
133
+ const dir2 = join(TEST_DIR, "todo-2-20-2025");
134
+ mkdirSync(dir1);
135
+ mkdirSync(dir2);
136
+
137
+ // Delete first one manually to prove second still gets processed
138
+ rmSync(dir1, { recursive: true });
139
+
140
+ const result = deleteArtifacts([dir1, dir2]);
141
+
142
+ // Both should succeed since rmSync with force:true handles non-existent
143
+ expect(result.deleted).toHaveLength(2);
144
+ expect(result.failed).toHaveLength(0);
145
+ expect(existsSync(dir2)).toBe(false);
146
+ });
147
+
148
+ // confirmPrune tests
149
+
150
+ test("confirmPrune returns confirmed: true with force flag", async () => {
151
+ const paths = ["/some/path/todo-1-15-2025", "/some/path/todo-2-20-2025"];
152
+
153
+ const result = await confirmPrune(paths, { force: true });
154
+
155
+ expect(result.confirmed).toBe(true);
156
+ expect(result.paths).toEqual(paths);
157
+ });
158
+
159
+ test("confirmPrune returns confirmed: true with empty paths", async () => {
160
+ const result = await confirmPrune([]);
161
+
162
+ expect(result.confirmed).toBe(true);
163
+ expect(result.paths).toEqual([]);
164
+ });
165
+
166
+ test("confirmPrune returns paths even with force flag", async () => {
167
+ const paths = ["/a/todo-1-1-2025", "/b/todo-2-2-2025"];
168
+
169
+ const result = await confirmPrune(paths, { force: true });
170
+
171
+ expect(result.paths).toHaveLength(2);
172
+ expect(result.paths).toContain("/a/todo-1-1-2025");
173
+ expect(result.paths).toContain("/b/todo-2-2-2025");
174
+ });
package/src/prune.ts ADDED
@@ -0,0 +1,146 @@
1
+ import { readdirSync, statSync, rmSync } from "node:fs";
2
+ import { join, basename } from "node:path";
3
+ import { createInterface } from "node:readline/promises";
4
+
5
+ /**
6
+ * Pattern for backup directories created by `math iterate`
7
+ * Matches: todo-{M}-{D}-{Y} or todo-{M}-{D}-{Y}-{N}
8
+ * Examples: todo-1-15-2025, todo-12-31-2024-1, todo-1-1-2026-42
9
+ */
10
+ const BACKUP_DIR_PATTERN = /^todo-\d{1,2}-\d{1,2}-\d{4}(-\d+)?$/;
11
+
12
+ /**
13
+ * Finds all math artifacts in a directory.
14
+ *
15
+ * Artifacts include:
16
+ * - Backup directories matching pattern todo-{M}-{D}-{Y} or todo-{M}-{D}-{Y}-{N}
17
+ *
18
+ * @param directory - The directory to search in (defaults to cwd)
19
+ * @returns Array of absolute paths to artifacts
20
+ */
21
+ export function findArtifacts(directory: string = process.cwd()): string[] {
22
+ const artifacts: string[] = [];
23
+
24
+ try {
25
+ const entries = readdirSync(directory);
26
+
27
+ for (const entry of entries) {
28
+ const fullPath = join(directory, entry);
29
+
30
+ // Check if it's a backup directory
31
+ if (BACKUP_DIR_PATTERN.test(entry)) {
32
+ try {
33
+ const stat = statSync(fullPath);
34
+ if (stat.isDirectory()) {
35
+ artifacts.push(fullPath);
36
+ }
37
+ } catch {
38
+ // Skip entries we can't stat (permission issues, etc.)
39
+ }
40
+ }
41
+ }
42
+ } catch {
43
+ // If we can't read the directory, return empty array
44
+ }
45
+
46
+ return artifacts;
47
+ }
48
+
49
+ /**
50
+ * Result of a delete operation
51
+ */
52
+ export interface DeleteResult {
53
+ /** Paths that were successfully deleted */
54
+ deleted: string[];
55
+ /** Paths that failed to delete with their error messages */
56
+ failed: { path: string; error: string }[];
57
+ }
58
+
59
+ /**
60
+ * Deletes the provided artifact paths.
61
+ *
62
+ * Handles errors gracefully - if a path fails to delete (e.g., permission denied),
63
+ * it continues with the remaining paths and reports the failure.
64
+ *
65
+ * @param paths - Array of absolute paths to delete
66
+ * @returns Summary of deleted paths and any failures
67
+ */
68
+ export function deleteArtifacts(paths: string[]): DeleteResult {
69
+ const result: DeleteResult = {
70
+ deleted: [],
71
+ failed: [],
72
+ };
73
+
74
+ for (const path of paths) {
75
+ try {
76
+ rmSync(path, { recursive: true, force: true });
77
+ result.deleted.push(path);
78
+ } catch (err) {
79
+ const errorMessage =
80
+ err instanceof Error ? err.message : "Unknown error";
81
+ result.failed.push({ path, error: errorMessage });
82
+ }
83
+ }
84
+
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * Result of a confirmation prompt
90
+ */
91
+ export interface ConfirmationResult {
92
+ /** Whether the user confirmed the action */
93
+ confirmed: boolean;
94
+ /** The paths that were shown to the user */
95
+ paths: string[];
96
+ }
97
+
98
+ /**
99
+ * Shows an interactive confirmation prompt for pruning artifacts.
100
+ *
101
+ * Lists all artifacts that will be deleted and asks for user confirmation.
102
+ * If `force` is true, skips the prompt and returns confirmed: true.
103
+ *
104
+ * @param paths - Array of absolute paths to be deleted
105
+ * @param options - Configuration options
106
+ * @param options.force - If true, skip confirmation and return confirmed: true
107
+ * @returns Result indicating whether user confirmed and what paths were shown
108
+ */
109
+ export async function confirmPrune(
110
+ paths: string[],
111
+ options: { force?: boolean } = {}
112
+ ): Promise<ConfirmationResult> {
113
+ // If force flag is set, skip confirmation
114
+ if (options.force) {
115
+ return { confirmed: true, paths };
116
+ }
117
+
118
+ // If no paths, nothing to confirm
119
+ if (paths.length === 0) {
120
+ return { confirmed: true, paths };
121
+ }
122
+
123
+ // Show what will be deleted
124
+ console.log("\nThe following artifacts will be deleted:\n");
125
+ for (const path of paths) {
126
+ console.log(` - ${basename(path)}/`);
127
+ }
128
+ console.log();
129
+
130
+ // Ask for confirmation
131
+ const rl = createInterface({
132
+ input: process.stdin,
133
+ output: process.stdout,
134
+ });
135
+
136
+ try {
137
+ const answer = await rl.question("Delete these artifacts? (y/N) ");
138
+ rl.close();
139
+
140
+ const confirmed = answer.toLowerCase() === "y";
141
+ return { confirmed, paths };
142
+ } catch {
143
+ rl.close();
144
+ return { confirmed: false, paths };
145
+ }
146
+ }
package/src/tasks.ts ADDED
@@ -0,0 +1,204 @@
1
+ import { join } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+
4
+ export interface Task {
5
+ id: string;
6
+ content: string;
7
+ status: "pending" | "in_progress" | "complete";
8
+ dependencies: string[];
9
+ }
10
+
11
+ export interface TaskCounts {
12
+ pending: number;
13
+ in_progress: number;
14
+ complete: number;
15
+ total: number;
16
+ }
17
+
18
+ /**
19
+ * Parse TASKS.md file and extract all tasks
20
+ *
21
+ * Expected format:
22
+ * ### task-id
23
+ * - content: Description of the task
24
+ * - status: pending | in_progress | complete
25
+ * - dependencies: task-1, task-2
26
+ */
27
+ export function parseTasks(content: string): Task[] {
28
+ const tasks: Task[] = [];
29
+ const lines = content.split("\n");
30
+
31
+ let currentTask: Partial<Task> | null = null;
32
+
33
+ for (const line of lines) {
34
+ // New task starts with ### task-id
35
+ const taskMatch = line.match(/^###\s+(.+)$/);
36
+ if (taskMatch && taskMatch[1]) {
37
+ // Save previous task if exists
38
+ if (currentTask?.id) {
39
+ tasks.push({
40
+ id: currentTask.id,
41
+ content: currentTask.content || "",
42
+ status: currentTask.status || "pending",
43
+ dependencies: currentTask.dependencies || [],
44
+ });
45
+ }
46
+ currentTask = { id: taskMatch[1].trim() };
47
+ continue;
48
+ }
49
+
50
+ if (!currentTask) continue;
51
+
52
+ // Parse content line
53
+ const contentMatch = line.match(/^-\s+content:\s*(.+)$/);
54
+ if (contentMatch && contentMatch[1]) {
55
+ currentTask.content = contentMatch[1].trim();
56
+ continue;
57
+ }
58
+
59
+ // Parse status line
60
+ const statusMatch = line.match(
61
+ /^-\s+status:\s*(pending|in_progress|complete)$/
62
+ );
63
+ if (statusMatch && statusMatch[1]) {
64
+ currentTask.status = statusMatch[1] as Task["status"];
65
+ continue;
66
+ }
67
+
68
+ // Parse dependencies line
69
+ const depsMatch = line.match(/^-\s+dependencies:\s*(.*)$/);
70
+ if (depsMatch && depsMatch[1]) {
71
+ const deps = depsMatch[1].trim();
72
+ if (deps && deps.toLowerCase() !== "none") {
73
+ currentTask.dependencies = deps
74
+ .split(",")
75
+ .map((d) => d.trim())
76
+ .filter(Boolean);
77
+ } else {
78
+ currentTask.dependencies = [];
79
+ }
80
+ continue;
81
+ }
82
+ }
83
+
84
+ // Don't forget the last task
85
+ if (currentTask?.id) {
86
+ tasks.push({
87
+ id: currentTask.id,
88
+ content: currentTask.content || "",
89
+ status: currentTask.status || "pending",
90
+ dependencies: currentTask.dependencies || [],
91
+ });
92
+ }
93
+
94
+ return tasks;
95
+ }
96
+
97
+ /**
98
+ * Count tasks by status
99
+ */
100
+ export function countTasks(tasks: Task[]): TaskCounts {
101
+ const counts: TaskCounts = {
102
+ pending: 0,
103
+ in_progress: 0,
104
+ complete: 0,
105
+ total: tasks.length,
106
+ };
107
+
108
+ for (const task of tasks) {
109
+ counts[task.status]++;
110
+ }
111
+
112
+ return counts;
113
+ }
114
+
115
+ /**
116
+ * Find the next task to work on:
117
+ * - Status must be "pending"
118
+ * - All dependencies must be "complete"
119
+ */
120
+ export function findNextTask(tasks: Task[]): Task | null {
121
+ const completedIds = new Set(
122
+ tasks.filter((t) => t.status === "complete").map((t) => t.id)
123
+ );
124
+
125
+ for (const task of tasks) {
126
+ if (task.status !== "pending") continue;
127
+
128
+ // Check if all dependencies are complete
129
+ const depsComplete = task.dependencies.every((dep) =>
130
+ completedIds.has(dep)
131
+ );
132
+ if (depsComplete) {
133
+ return task;
134
+ }
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ /**
141
+ * Update a task's status in the TASKS.md content
142
+ */
143
+ export function updateTaskStatus(
144
+ content: string,
145
+ taskId: string,
146
+ newStatus: Task["status"]
147
+ ): string {
148
+ const lines = content.split("\n");
149
+ const result: string[] = [];
150
+ let inTargetTask = false;
151
+
152
+ for (let i = 0; i < lines.length; i++) {
153
+ const line = lines[i] ?? "";
154
+
155
+ // Check if we're entering a task section
156
+ const taskMatch = line.match(/^###\s+(.+)$/);
157
+ if (taskMatch && taskMatch[1]) {
158
+ inTargetTask = taskMatch[1].trim() === taskId;
159
+ }
160
+
161
+ // If we're in the target task and this is a status line, replace it
162
+ if (
163
+ inTargetTask &&
164
+ line.match(/^-\s+status:\s*(pending|in_progress|complete)$/)
165
+ ) {
166
+ result.push(`- status: ${newStatus}`);
167
+ } else {
168
+ result.push(line);
169
+ }
170
+ }
171
+
172
+ return result.join("\n");
173
+ }
174
+
175
+ /**
176
+ * Read and parse tasks from the todo directory
177
+ */
178
+ export async function readTasks(
179
+ todoDir?: string
180
+ ): Promise<{ tasks: Task[]; content: string }> {
181
+ const dir = todoDir || join(process.cwd(), "todo");
182
+ const tasksPath = join(dir, "TASKS.md");
183
+
184
+ if (!existsSync(tasksPath)) {
185
+ throw new Error(`TASKS.md not found at ${tasksPath}`);
186
+ }
187
+
188
+ const content = await Bun.file(tasksPath).text();
189
+ const tasks = parseTasks(content);
190
+
191
+ return { tasks, content };
192
+ }
193
+
194
+ /**
195
+ * Write updated content to TASKS.md
196
+ */
197
+ export async function writeTasks(
198
+ content: string,
199
+ todoDir?: string
200
+ ): Promise<void> {
201
+ const dir = todoDir || join(process.cwd(), "todo");
202
+ const tasksPath = join(dir, "TASKS.md");
203
+ await Bun.write(tasksPath, content);
204
+ }
@@ -0,0 +1,172 @@
1
+ export const PROMPT_TEMPLATE = `# Agent Task Prompt
2
+
3
+ You are a coding agent implementing tasks one at a time.
4
+
5
+ ## Your Mission
6
+
7
+ Implement ONE task from TASKS.md, test it, commit it, log your learnings, then EXIT.
8
+
9
+ ## The Loop
10
+
11
+ 1. **Read TASKS.md** - Find the first task with \`status: pending\` where ALL dependencies have \`status: complete\`
12
+ 2. **Mark in_progress** - Update the task's status to \`in_progress\` in TASKS.md
13
+ 3. **Implement** - Write the code following the project's patterns
14
+ 4. **Write tests** - For behavioral code changes, create unit tests in the appropriate directory. Skip for documentation-only tasks.
15
+ 5. **Run tests** - Execute tests from the package directory (ensures existing tests still pass)
16
+ 6. **Fix failures** - If tests fail, debug and fix. DO NOT PROCEED WITH FAILING TESTS.
17
+ 7. **Mark complete** - Update the task's status to \`complete\` in TASKS.md
18
+ 8. **Log learnings** - Append insights to LEARNINGS.md
19
+ 9. **Commit** - Stage and commit: \`git add -A && git commit -m "feat: <task-id> - <description>"\`
20
+ 10. **EXIT** - Stop. The loop will reinvoke you for the next task.
21
+
22
+ ---
23
+
24
+ ## Signs
25
+
26
+ READ THESE CAREFULLY. They are guardrails that prevent common mistakes.
27
+
28
+ ---
29
+
30
+ ### SIGN: One Task Only
31
+
32
+ - You implement **EXACTLY ONE** task per invocation
33
+ - After your commit, you **STOP**
34
+ - Do NOT continue to the next task
35
+ - Do NOT "while you're here" other improvements
36
+ - The loop will reinvoke you for the next task
37
+
38
+ ---
39
+
40
+ ### SIGN: Dependencies Matter
41
+
42
+ Before starting a task, verify ALL its dependencies have \`status: complete\`.
43
+
44
+ \`\`\`
45
+ ❌ WRONG: Start task with pending dependencies
46
+ ✅ RIGHT: Check deps, proceed only if all complete
47
+ ✅ RIGHT: If deps not complete, EXIT with clear error message
48
+ \`\`\`
49
+
50
+ Do NOT skip ahead. Do NOT work on tasks out of order.
51
+
52
+ ---
53
+
54
+ ### SIGN: Learnings are Required
55
+
56
+ Before exiting, append to \`LEARNINGS.md\`:
57
+
58
+ \`\`\`markdown
59
+ ## <task-id>
60
+
61
+ - Key insight or decision made
62
+ - Gotcha or pitfall discovered
63
+ - Pattern that worked well
64
+ - Anything the next agent should know
65
+ \`\`\`
66
+
67
+ Be specific. Be helpful. Future agents will thank you.
68
+
69
+ ---
70
+
71
+ ### SIGN: Commit Format
72
+
73
+ One commit per task. Format:
74
+
75
+ \`\`\`
76
+ feat: <task-id> - <short description>
77
+ \`\`\`
78
+
79
+ Only commit AFTER tests pass.
80
+
81
+ ---
82
+
83
+ ### SIGN: Don't Over-Engineer
84
+
85
+ - Implement what the task specifies, nothing more
86
+ - Don't add features "while you're here"
87
+ - Don't refactor unrelated code
88
+ - Don't add abstractions for "future flexibility"
89
+ - Don't make perfect mocks in tests - use simple stubs instead
90
+ - Don't use complex test setups - keep tests simple and focused
91
+ - YAGNI: You Ain't Gonna Need It
92
+
93
+ ---
94
+
95
+ ## Quick Reference
96
+
97
+ <!-- This table should be customized for your project's tooling -->
98
+ <!-- Run 'math plan' to auto-detect and populate these commands -->
99
+
100
+ | Action | Command |
101
+ |--------|---------|
102
+ | Run tests | \`<your-test-command>\` |
103
+ | Build | \`<your-build-command>\` |
104
+ | Lint | \`<your-lint-command>\` |
105
+ | Stage all | \`git add -A\` |
106
+ | Commit | \`git commit -m "feat: ..."\` |
107
+
108
+ ---
109
+
110
+ ## Remember
111
+
112
+ You do one thing. You do it well. You learn. You exit.
113
+ `;
114
+
115
+ export const TASKS_TEMPLATE = `# Project Tasks
116
+
117
+ Task tracker for multi-agent development.
118
+ Each agent picks the next pending task, implements it, and marks it complete.
119
+
120
+ ## How to Use
121
+
122
+ 1. Find the first task with \`status: pending\` where ALL dependencies have \`status: complete\`
123
+ 2. Change that task's status to \`in_progress\`
124
+ 3. Implement the task
125
+ 4. Write and run tests
126
+ 5. Change the task's status to \`complete\`
127
+ 6. Append learnings to LEARNINGS.md
128
+ 7. Commit with message: \`feat: <task-id> - <description>\`
129
+ 8. EXIT
130
+
131
+ ## Task Statuses
132
+
133
+ - \`pending\` - Not started
134
+ - \`in_progress\` - Currently being worked on
135
+ - \`complete\` - Done and committed
136
+
137
+ ---
138
+
139
+ ## Phase 1: Setup
140
+
141
+ ### example-task
142
+
143
+ - content: Replace this with your first task description
144
+ - status: pending
145
+ - dependencies: none
146
+
147
+ ### another-task
148
+
149
+ - content: This task depends on example-task
150
+ - status: pending
151
+ - dependencies: example-task
152
+ `;
153
+
154
+ export const LEARNINGS_TEMPLATE = `# Project Learnings Log
155
+
156
+ This file is appended by each agent after completing a task.
157
+ Key insights, gotchas, and patterns discovered during implementation.
158
+
159
+ Use this knowledge to avoid repeating mistakes and build on what works.
160
+
161
+ ---
162
+
163
+ <!-- Agents: Append your learnings below this line -->
164
+ <!-- Format:
165
+ ## <task-id>
166
+
167
+ - Key insight or decision made
168
+ - Gotcha or pitfall discovered
169
+ - Pattern that worked well
170
+ - Anything the next agent should know
171
+ -->
172
+ `;