sisyphi 1.1.8 → 1.1.10
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/cli.js +840 -165
- package/dist/cli.js.map +1 -1
- package/dist/templates/nvim-tutorial.txt +68 -0
- package/dist/templates/orchestrator-base.md +4 -2
- package/dist/templates/tutorial-demo/CLAUDE.md +14 -0
- package/dist/templates/tutorial-demo/package.json +10 -0
- package/dist/templates/tutorial-demo/server.js +76 -0
- package/dist/templates/tutorial-demo/test.js +48 -0
- package/dist/templates/tutorial-demo/todo.js +35 -0
- package/dist/tui.js +479 -214
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/nvim-tutorial.txt +68 -0
- package/templates/orchestrator-base.md +4 -2
- package/templates/tutorial-demo/CLAUDE.md +14 -0
- package/templates/tutorial-demo/package.json +10 -0
- package/templates/tutorial-demo/server.js +76 -0
- package/templates/tutorial-demo/test.js +48 -0
- package/templates/tutorial-demo/todo.js +35 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Welcome to Neovim!
|
|
2
|
+
===================
|
|
3
|
+
|
|
4
|
+
You're looking at this file in nvim. Right now you're in NORMAL MODE.
|
|
5
|
+
That means keys are commands, not text. Let's learn the basics.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
--- MOVING AROUND (Normal Mode) ---
|
|
9
|
+
|
|
10
|
+
Try these now:
|
|
11
|
+
gg Jump to the top of this file
|
|
12
|
+
G Jump to the bottom of this file
|
|
13
|
+
Ctrl+u Scroll up half a page
|
|
14
|
+
Ctrl+d Scroll down half a page
|
|
15
|
+
/Welcome Search for "Welcome" (press Enter, then n for next match)
|
|
16
|
+
|
|
17
|
+
Arrow keys work too, but h/j/k/l are the vim way:
|
|
18
|
+
h = left j = down k = up l = right
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
--- ENTERING INSERT MODE ---
|
|
22
|
+
|
|
23
|
+
Press i right now. You should see -- INSERT -- at the bottom.
|
|
24
|
+
Now you can type normally! Try typing something on the line below:
|
|
25
|
+
|
|
26
|
+
> [type here]
|
|
27
|
+
|
|
28
|
+
Press Esc when you're done to go back to normal mode.
|
|
29
|
+
|
|
30
|
+
Other ways to enter insert mode:
|
|
31
|
+
i Insert at cursor
|
|
32
|
+
a Insert after cursor
|
|
33
|
+
o Open new line below and insert
|
|
34
|
+
O Open new line above and insert
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
--- PRACTICE: EDIT THIS SECTION ---
|
|
38
|
+
|
|
39
|
+
Fix the typos below (hint: navigate to the typo, press i, fix it, press Esc):
|
|
40
|
+
|
|
41
|
+
1. The quikc brown fox jumps over the lazy dog.
|
|
42
|
+
2. Sisyphus is a multi-agnet orchestrator.
|
|
43
|
+
3. Tmux lets you spilt your terminal into panes.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
--- SAVING AND QUITTING ---
|
|
47
|
+
|
|
48
|
+
You're almost done! Here's how to get out:
|
|
49
|
+
|
|
50
|
+
:w Enter Save the file (write)
|
|
51
|
+
:q Enter Quit (fails if unsaved changes)
|
|
52
|
+
:wq Enter Save and quit
|
|
53
|
+
:q! Enter Quit WITHOUT saving
|
|
54
|
+
ZZ Shortcut for save and quit (Shift+z twice)
|
|
55
|
+
|
|
56
|
+
Try it now: type :wq and press Enter to save your changes and close nvim.
|
|
57
|
+
The pane will close automatically and you'll be back in Claude.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
--- BONUS (optional) ---
|
|
61
|
+
|
|
62
|
+
u Undo last change
|
|
63
|
+
Ctrl+r Redo
|
|
64
|
+
dd Delete entire line
|
|
65
|
+
yy Copy (yank) entire line
|
|
66
|
+
p Paste below cursor
|
|
67
|
+
:123 Jump to line 123
|
|
68
|
+
* Search for the word under cursor
|
|
@@ -95,7 +95,7 @@ Every cycle, read strategy.md first. It tells you:
|
|
|
95
95
|
**Strategy is a living document.** Update it when:
|
|
96
96
|
- **The goal crystallizes** — you now see further ahead than when the strategy was written. Detail the next stage, flesh out "Ahead."
|
|
97
97
|
- **The goal shifts** — new information changes what "done" looks like. Revise the affected stages.
|
|
98
|
-
- **A stage completes** —
|
|
98
|
+
- **A stage completes** — delete it entirely and promote the next stage. Completed work belongs in cycle logs, not the strategy.
|
|
99
99
|
- **The approach is wrong** — backtracking reveals a fundamental issue. Revise the strategy.
|
|
100
100
|
|
|
101
101
|
Strategy updates happen every few cycles, not every cycle. The roadmap tracks cycle-to-cycle progress within a stage; the strategy tracks the shape of the work across stages.
|
|
@@ -113,6 +113,8 @@ You are respawned fresh each cycle — without roadmap.md, you'd have no idea wh
|
|
|
113
113
|
3. **Active Context** — list of context files currently relevant to the work
|
|
114
114
|
4. **Next Steps** — immediate actions for this and the next cycle
|
|
115
115
|
|
|
116
|
+
**Delete completed items entirely.** Do not mark them done, check them off, or summarize them. Completed work belongs in cycle logs, not the roadmap. The roadmap should get shorter as work completes, not longer. No `[done]` markers, no phase summaries, no completion history.
|
|
117
|
+
|
|
116
118
|
**Decisions do not go in the roadmap.** When exploration, review, or user feedback resolves a question or changes the approach, fold the result into the relevant context document (spec, plan, design) or create a new context file. The roadmap references these artifacts but never contains decision content, rationale, or design detail.
|
|
117
119
|
|
|
118
120
|
**The roadmap is not an implementation plan.** Stage breakdowns, design decisions, and file-level detail live in `context/` files.
|
|
@@ -142,7 +144,7 @@ Status: iterating on design after review feedback
|
|
|
142
144
|
- If clean, transition to plan stage
|
|
143
145
|
```
|
|
144
146
|
|
|
145
|
-
**Remove completed
|
|
147
|
+
**Remove completed items as stages finish** — exit criteria that are met, context files that are no longer relevant, next steps that are done. The roadmap reflects only outstanding work.
|
|
146
148
|
|
|
147
149
|
### Cycle Logs — Audit trail (write-only)
|
|
148
150
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Todo App
|
|
2
|
+
|
|
3
|
+
A minimal Node.js todo API. No dependencies — just `node:http` and `node:test`.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
```bash
|
|
7
|
+
node server.js # Start the API on port 3456
|
|
8
|
+
node --test test.js # Run tests
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Structure
|
|
12
|
+
- `todo.js` — In-memory todo store (add, list, get, toggle, delete)
|
|
13
|
+
- `server.js` — HTTP API server
|
|
14
|
+
- `test.js` — Unit tests for the store
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { addTodo, listTodos, getTodo, toggleTodo, deleteTodo } from './todo.js';
|
|
3
|
+
|
|
4
|
+
const PORT = 3456;
|
|
5
|
+
|
|
6
|
+
function parseBody(req) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
let body = '';
|
|
9
|
+
req.on('data', (chunk) => (body += chunk));
|
|
10
|
+
req.on('end', () => {
|
|
11
|
+
try { resolve(JSON.parse(body)); }
|
|
12
|
+
catch { resolve({}); }
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function json(res, status, data) {
|
|
18
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
19
|
+
res.end(JSON.stringify(data, null, 2));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const server = createServer(async (req, res) => {
|
|
23
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
24
|
+
const path = url.pathname;
|
|
25
|
+
|
|
26
|
+
// GET /todos
|
|
27
|
+
if (req.method === 'GET' && path === '/todos') {
|
|
28
|
+
return json(res, 200, listTodos());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// POST /todos
|
|
32
|
+
if (req.method === 'POST' && path === '/todos') {
|
|
33
|
+
const { title } = await parseBody(req);
|
|
34
|
+
if (!title) return json(res, 400, { error: 'title is required' });
|
|
35
|
+
return json(res, 201, addTodo(title));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// GET /todos/:id
|
|
39
|
+
const match = path.match(/^\/todos\/(\d+)$/);
|
|
40
|
+
if (match) {
|
|
41
|
+
const id = parseInt(match[1]);
|
|
42
|
+
if (req.method === 'GET') {
|
|
43
|
+
const todo = getTodo(id);
|
|
44
|
+
return todo ? json(res, 200, todo) : json(res, 404, { error: 'not found' });
|
|
45
|
+
}
|
|
46
|
+
// PATCH /todos/:id/toggle
|
|
47
|
+
if (req.method === 'PATCH' && path.endsWith('/toggle')) {
|
|
48
|
+
// BUG: this never matches because the regex above doesn't include /toggle
|
|
49
|
+
}
|
|
50
|
+
// DELETE /todos/:id
|
|
51
|
+
if (req.method === 'DELETE') {
|
|
52
|
+
return deleteTodo(id) ? json(res, 200, { deleted: true }) : json(res, 404, { error: 'not found' });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// PATCH /todos/:id/toggle
|
|
57
|
+
const toggleMatch = path.match(/^\/todos\/(\d+)\/toggle$/);
|
|
58
|
+
if (req.method === 'PATCH' && toggleMatch) {
|
|
59
|
+
const id = parseInt(toggleMatch[1]);
|
|
60
|
+
const todo = toggleTodo(id);
|
|
61
|
+
return todo ? json(res, 200, todo) : json(res, 404, { error: 'not found' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
json(res, 404, { error: 'not found' });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
server.listen(PORT, () => {
|
|
68
|
+
console.log(`Todo API running at http://localhost:${PORT}`);
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log('Endpoints:');
|
|
71
|
+
console.log(' GET /todos List all todos');
|
|
72
|
+
console.log(' POST /todos Create a todo (body: {"title": "..."})');
|
|
73
|
+
console.log(' GET /todos/:id Get a todo');
|
|
74
|
+
console.log(' PATCH /todos/:id/toggle Toggle done');
|
|
75
|
+
console.log(' DELETE /todos/:id Delete a todo');
|
|
76
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { addTodo, listTodos, getTodo, toggleTodo, deleteTodo, clearAll } from './todo.js';
|
|
4
|
+
|
|
5
|
+
describe('todo store', () => {
|
|
6
|
+
beforeEach(() => clearAll());
|
|
7
|
+
|
|
8
|
+
it('adds a todo', () => {
|
|
9
|
+
const todo = addTodo('Buy milk');
|
|
10
|
+
assert.strictEqual(todo.title, 'Buy milk');
|
|
11
|
+
assert.strictEqual(todo.done, false);
|
|
12
|
+
assert.ok(todo.id);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('lists todos', () => {
|
|
16
|
+
addTodo('First');
|
|
17
|
+
addTodo('Second');
|
|
18
|
+
assert.strictEqual(listTodos().length, 2);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('gets a todo by id', () => {
|
|
22
|
+
const todo = addTodo('Find me');
|
|
23
|
+
assert.deepStrictEqual(getTodo(todo.id), todo);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns null for missing todo', () => {
|
|
27
|
+
assert.strictEqual(getTodo(999), null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('toggles done status', () => {
|
|
31
|
+
const todo = addTodo('Toggle me');
|
|
32
|
+
assert.strictEqual(todo.done, false);
|
|
33
|
+
toggleTodo(todo.id);
|
|
34
|
+
assert.strictEqual(todo.done, true);
|
|
35
|
+
toggleTodo(todo.id);
|
|
36
|
+
assert.strictEqual(todo.done, false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('deletes a todo', () => {
|
|
40
|
+
const todo = addTodo('Delete me');
|
|
41
|
+
assert.strictEqual(deleteTodo(todo.id), true);
|
|
42
|
+
assert.strictEqual(listTodos().length, 0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('returns false when deleting nonexistent', () => {
|
|
46
|
+
assert.strictEqual(deleteTodo(999), false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// In-memory todo store
|
|
2
|
+
const todos = [];
|
|
3
|
+
let nextId = 1;
|
|
4
|
+
|
|
5
|
+
export function addTodo(title) {
|
|
6
|
+
const todo = { id: nextId++, title, done: false, createdAt: new Date().toISOString() };
|
|
7
|
+
todos.push(todo);
|
|
8
|
+
return todo;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function listTodos() {
|
|
12
|
+
return [...todos];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getTodo(id) {
|
|
16
|
+
return todos.find((t) => t.id === id) || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function toggleTodo(id) {
|
|
20
|
+
const todo = getTodo(id);
|
|
21
|
+
if (todo) todo.done = !todo.done;
|
|
22
|
+
return todo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function deleteTodo(id) {
|
|
26
|
+
const idx = todos.findIndex((t) => t.id === id);
|
|
27
|
+
if (idx === -1) return false;
|
|
28
|
+
todos.splice(idx, 1);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function clearAll() {
|
|
33
|
+
todos.length = 0;
|
|
34
|
+
nextId = 1;
|
|
35
|
+
}
|