@4-r-c-4-n-4/todo 0.1.2 → 0.1.3
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/BIBLE.md +60 -8
- package/README.md +2 -0
- package/dist/cli.js +2 -0
- package/dist/commands/next.d.ts +2 -0
- package/dist/commands/next.js +81 -0
- package/dist/commands/work.js +26 -1
- package/dist/git.js +5 -1
- package/package.json +2 -2
package/BIBLE.md
CHANGED
|
@@ -21,11 +21,24 @@ todo close <id>
|
|
|
21
21
|
### Feature build (parent + children)
|
|
22
22
|
A parent ticket tracks the feature. Children track subtasks. All children share the parent's branch.
|
|
23
23
|
|
|
24
|
-
```
|
|
24
|
+
```bash
|
|
25
25
|
todo new "Add OAuth2 login" --type feature
|
|
26
26
|
todo new "Add /auth/callback route" --type chore --parent <feature-id>
|
|
27
27
|
todo new "Store session tokens" --type chore --parent <feature-id>
|
|
28
|
-
todo
|
|
28
|
+
todo new "Write integration tests" --type chore --parent <feature-id>
|
|
29
|
+
|
|
30
|
+
# First child creates the shared branch
|
|
31
|
+
todo work <child-1-id>
|
|
32
|
+
|
|
33
|
+
# Subsequent children — use todo next (preferred)
|
|
34
|
+
while next=$(todo next <feature-id> 2>/dev/null); do
|
|
35
|
+
# implement $next ...
|
|
36
|
+
git add -A && git commit -m "todo:$next — ..."
|
|
37
|
+
todo close $next --note "..."
|
|
38
|
+
git add .todo/ && git commit -m "todo:$next — close"
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
todo close <feature-id> --note "All subtasks done."
|
|
29
42
|
```
|
|
30
43
|
|
|
31
44
|
Children resolve on the parent branch. Close all children before closing the parent.
|
|
@@ -71,7 +84,7 @@ Children resolve on the parent branch. Close all children before closing the par
|
|
|
71
84
|
|
|
72
85
|
## The Branch Workflow
|
|
73
86
|
|
|
74
|
-
|
|
87
|
+
### Standalone ticket
|
|
75
88
|
|
|
76
89
|
```bash
|
|
77
90
|
# 1. Start work — creates branch todo/<id>, transitions ticket to active
|
|
@@ -97,7 +110,35 @@ git merge --no-ff todo/<id>
|
|
|
97
110
|
git branch -d todo/<id>
|
|
98
111
|
```
|
|
99
112
|
|
|
100
|
-
|
|
113
|
+
### Feature build (shared branch)
|
|
114
|
+
|
|
115
|
+
All children share `todo/<parent-id>`. The first `todo work` creates the branch; subsequent children must not trigger a redundant checkout.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# First child — creates todo/<parent-id>
|
|
119
|
+
todo work <child-1-id>
|
|
120
|
+
git add -A && git commit -m "todo:<child-1-id> — ..."
|
|
121
|
+
todo close <child-1-id> --note "..."
|
|
122
|
+
git add .todo/ && git commit -m "todo:<child-1-id> — close"
|
|
123
|
+
|
|
124
|
+
# Remaining children — todo next handles activation cleanly
|
|
125
|
+
while next=$(todo next <parent-id> 2>/dev/null); do
|
|
126
|
+
# implement ...
|
|
127
|
+
git add -A && git commit -m "todo:$next — ..."
|
|
128
|
+
todo close $next --note "..."
|
|
129
|
+
git add .todo/ && git commit -m "todo:$next — close"
|
|
130
|
+
done
|
|
131
|
+
|
|
132
|
+
# Close parent
|
|
133
|
+
todo close <parent-id> --note "All children done."
|
|
134
|
+
git add .todo/ && git commit -m "todo:<parent-id> — close"
|
|
135
|
+
git checkout main && git merge --no-ff todo/<parent-id>
|
|
136
|
+
git branch -d todo/<parent-id>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`todo next <parent-id>` finds the first open child (in creation order), activates it on the current branch without any git checkout, prints its ID to stdout and a summary to stderr. Exits 1 when all children are done — that's what stops the `while` loop.
|
|
140
|
+
|
|
141
|
+
If you need manual control instead of a loop, use `todo work --skip-branch <child-id>` to activate a child without a redundant checkout. Do NOT use plain `todo work` for subsequent children on a shared branch — it performs a no-op checkout and prints confusing resume output.
|
|
101
142
|
|
|
102
143
|
---
|
|
103
144
|
|
|
@@ -138,6 +179,7 @@ Examples:
|
|
|
138
179
|
todo:a1b2c3d4 — fix null pointer in auth handler
|
|
139
180
|
todo:a1b2c3d4 — close
|
|
140
181
|
todo:e5f6a7b8 — add /auth/callback route
|
|
182
|
+
todo:e8e874e9 — plan: 4 subtasks
|
|
141
183
|
```
|
|
142
184
|
|
|
143
185
|
The `<id>` is the full 8-char ticket ID. Always include it. This ties commits to tickets and enables commit-based dedup and linking.
|
|
@@ -156,6 +198,10 @@ The `<id>` is the full 8-char ticket ID. Always include it. This ties commits to
|
|
|
156
198
|
|
|
157
199
|
5. **Not committing parent ticket after adding children** — Parent `relationships.children` is updated when you create a child. Commit `.todo/` after creating children.
|
|
158
200
|
|
|
201
|
+
6. **Using plain `todo work` for subsequent children on a shared branch** — After the first child creates `todo/<parent-id>`, calling `todo work <child-N>` again does a redundant checkout and prints misleading "Resumed branch" output. Use `todo next <parent-id>` (preferred) or `todo work --skip-branch <child-N>` instead.
|
|
202
|
+
|
|
203
|
+
7. **Using `--no-branch` instead of `--skip-branch`** — Commander.js treats `--no-X` as negating the `--X <value>` option. `--no-branch` silently overrides `--branch <name>` rather than setting a new flag. The correct flag is `--skip-branch`.
|
|
204
|
+
|
|
159
205
|
---
|
|
160
206
|
|
|
161
207
|
## CLI Quick Reference
|
|
@@ -179,6 +225,10 @@ todo close <id> Shorthand: transition to done
|
|
|
179
225
|
--commit --test --note --checkout
|
|
180
226
|
todo work <id> Start/resume work on a ticket
|
|
181
227
|
--branch --actor
|
|
228
|
+
--skip-branch Activate on current branch, no git ops (orchestrator mode)
|
|
229
|
+
todo next <parent-id> Activate next open child on current branch
|
|
230
|
+
stdout: ticket ID stderr: summary exit 1: all done
|
|
231
|
+
--actor
|
|
182
232
|
todo analyze <id> Add analysis entry
|
|
183
233
|
--type blame|hypothesis|evidence|conclusion (required)
|
|
184
234
|
--content <text> (required)
|
|
@@ -193,15 +243,15 @@ todo scan Scan source tree for TODO/FIXME comments
|
|
|
193
243
|
todo dedup Find duplicate tickets
|
|
194
244
|
--strategy fingerprint|file-line|semantic
|
|
195
245
|
--apply
|
|
196
|
-
todo export
|
|
197
|
-
--
|
|
246
|
+
todo export Export tickets as JSON
|
|
247
|
+
--state --type
|
|
198
248
|
```
|
|
199
249
|
|
|
200
250
|
---
|
|
201
251
|
|
|
202
252
|
## Skill Interface
|
|
203
253
|
|
|
204
|
-
Agents operate via
|
|
254
|
+
Agents operate via five named skills. Each maps to a phase of the lifecycle.
|
|
205
255
|
|
|
206
256
|
**todo-capture** — Create a ticket from any signal (log, test failure, comment, agent observation). Use `todo new` with `--source` and `--pipe` as appropriate. Always commit the result.
|
|
207
257
|
|
|
@@ -209,4 +259,6 @@ Agents operate via four named skills. Each maps to a phase of the lifecycle.
|
|
|
209
259
|
|
|
210
260
|
**todo-analyze** — Build up the understanding of a bug or requirement. Use `todo analyze` with sequenced entries: hypothesis → evidence → conclusion. Reference supporting indices. Commit when done.
|
|
211
261
|
|
|
212
|
-
**todo-
|
|
262
|
+
**todo-plan** — Decompose a feature or spec into a parent ticket with ordered children. Use `todo new --parent` to wire children. Commit the full structure before handing off. Children are worked sequentially on the parent's branch via `todo next`.
|
|
263
|
+
|
|
264
|
+
**todo-implement** — Run `todo work` to branch, implement, commit with the `todo:<id>` prefix, then `todo close`. Use `todo next` to advance through children on a shared branch. Always commit `.todo/` after close.
|
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ const export_js_1 = require("./commands/export.js");
|
|
|
10
10
|
const init_js_1 = require("./commands/init.js");
|
|
11
11
|
const link_js_1 = require("./commands/link.js");
|
|
12
12
|
const list_js_1 = require("./commands/list.js");
|
|
13
|
+
const next_js_1 = require("./commands/next.js");
|
|
13
14
|
const new_js_1 = require("./commands/new.js");
|
|
14
15
|
const scan_js_1 = require("./commands/scan.js");
|
|
15
16
|
const show_js_1 = require("./commands/show.js");
|
|
@@ -29,6 +30,7 @@ program
|
|
|
29
30
|
(0, transition_js_1.registerTransition)(program);
|
|
30
31
|
(0, close_js_1.registerClose)(program);
|
|
31
32
|
(0, work_js_1.registerWork)(program);
|
|
33
|
+
(0, next_js_1.registerNext)(program);
|
|
32
34
|
(0, analyze_js_1.registerAnalyze)(program);
|
|
33
35
|
(0, link_js_1.registerLink)(program);
|
|
34
36
|
(0, scan_js_1.registerScan)(program);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerNext = registerNext;
|
|
4
|
+
const context_js_1 = require("../context.js");
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
6
|
+
const git_js_1 = require("../git.js");
|
|
7
|
+
const state_js_1 = require("../state.js");
|
|
8
|
+
const ticket_js_1 = require("../ticket.js");
|
|
9
|
+
function registerNext(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("next <parent-id>")
|
|
12
|
+
.description("Activate the next open child of a parent ticket on the current branch (orchestrator mode)")
|
|
13
|
+
.option("--actor <name>", "override actor (also reads TODO_ACTOR env)")
|
|
14
|
+
.action((parentId, opts) => {
|
|
15
|
+
const ctx = (0, context_js_1.getContext)(true);
|
|
16
|
+
const { repoRoot } = ctx;
|
|
17
|
+
try {
|
|
18
|
+
// Resolve actor
|
|
19
|
+
let actor;
|
|
20
|
+
if (opts.actor) {
|
|
21
|
+
actor = opts.actor;
|
|
22
|
+
}
|
|
23
|
+
else if (process.env["TODO_ACTOR"]) {
|
|
24
|
+
actor = process.env["TODO_ACTOR"];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
try {
|
|
28
|
+
actor = (0, git_js_1.getGitUserName)(repoRoot);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
actor = "unknown";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Load the parent ticket to get the children list (ordered by creation)
|
|
35
|
+
const parent = (0, ticket_js_1.readTicket)(repoRoot, parentId);
|
|
36
|
+
const children = parent.relationships?.children ?? [];
|
|
37
|
+
if (children.length === 0) {
|
|
38
|
+
console.error(`Error: ticket ${parent.id} has no children.`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
// Find the first child that is not in a terminal state
|
|
42
|
+
const openTickets = (0, ticket_js_1.listTickets)(repoRoot, "open");
|
|
43
|
+
const openIds = new Set(openTickets.map((t) => t.id));
|
|
44
|
+
const nextId = children.find((id) => openIds.has(id));
|
|
45
|
+
if (!nextId) {
|
|
46
|
+
console.error(`All children of ${parent.id} are done. Close the parent with: todo close ${parent.id}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
// Activate the child on the current branch (--skip-branch semantics)
|
|
50
|
+
const ticket = (0, ticket_js_1.readTicket)(repoRoot, nextId);
|
|
51
|
+
const currentBranch = (0, git_js_1.getCurrentBranch)(repoRoot);
|
|
52
|
+
const defaultBranch = (0, git_js_1.getDefaultBranch)(repoRoot);
|
|
53
|
+
if (ticket.state !== "active") {
|
|
54
|
+
const now = new Date().toISOString();
|
|
55
|
+
let updated;
|
|
56
|
+
try {
|
|
57
|
+
updated = (0, state_js_1.applyTransition)(ticket, "active", { actor }, repoRoot);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.error(`Error: ${err.message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
updated.work = {
|
|
64
|
+
branch: currentBranch,
|
|
65
|
+
base_branch: defaultBranch,
|
|
66
|
+
started_at: now,
|
|
67
|
+
started_by: actor,
|
|
68
|
+
};
|
|
69
|
+
updated.updated_at = now;
|
|
70
|
+
(0, ticket_js_1.writeTicket)(repoRoot, updated);
|
|
71
|
+
}
|
|
72
|
+
// Print the ticket ID on stdout — scriptable (while next=$(todo next <parent>))
|
|
73
|
+
process.stdout.write(`${nextId}\n`);
|
|
74
|
+
// Print summary on stderr so it doesn't pollute the captured value
|
|
75
|
+
process.stderr.write(`Activated ${nextId}: ${ticket.summary}\n`);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
(0, errors_js_1.handleError)(err);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
package/dist/commands/work.js
CHANGED
|
@@ -11,6 +11,7 @@ function registerWork(program) {
|
|
|
11
11
|
.command("work <id>")
|
|
12
12
|
.description("Start or resume work on a ticket")
|
|
13
13
|
.option("--branch <name>", "override branch name")
|
|
14
|
+
.option("--skip-branch", "activate ticket without any git branch operations (orchestrator mode)")
|
|
14
15
|
.option("--actor <name>", "override actor (also reads TODO_ACTOR env)")
|
|
15
16
|
.action((id, opts) => {
|
|
16
17
|
const ctx = (0, context_js_1.getContext)(true);
|
|
@@ -50,7 +51,31 @@ function registerWork(program) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
const defaultBranch = (0, git_js_1.getDefaultBranch)(repoRoot);
|
|
53
|
-
if (
|
|
54
|
+
if (opts.skipBranch) {
|
|
55
|
+
// --no-branch: orchestrator mode — activate on current branch without any git ops
|
|
56
|
+
const currentBranch = (0, git_js_1.getCurrentBranch)(repoRoot);
|
|
57
|
+
if (ticket.state !== "active") {
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
let updated;
|
|
60
|
+
try {
|
|
61
|
+
updated = (0, state_js_1.applyTransition)(ticket, "active", { actor }, repoRoot);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`Error: ${err.message}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
updated.work = {
|
|
68
|
+
branch: currentBranch,
|
|
69
|
+
base_branch: defaultBranch,
|
|
70
|
+
started_at: now,
|
|
71
|
+
started_by: actor,
|
|
72
|
+
};
|
|
73
|
+
updated.updated_at = now;
|
|
74
|
+
(0, ticket_js_1.writeTicket)(repoRoot, updated);
|
|
75
|
+
}
|
|
76
|
+
console.log(`Activated ticket ${ticket.id} on current branch ${currentBranch}.`);
|
|
77
|
+
}
|
|
78
|
+
else if ((0, git_js_1.branchExists)(branch, repoRoot)) {
|
|
54
79
|
// Resume
|
|
55
80
|
(0, git_js_1.checkoutBranch)(branch, repoRoot);
|
|
56
81
|
// Ensure ticket is active
|
package/dist/git.js
CHANGED
|
@@ -29,7 +29,11 @@ class GitError extends Error {
|
|
|
29
29
|
exports.GitError = GitError;
|
|
30
30
|
function exec(args, cwd) {
|
|
31
31
|
try {
|
|
32
|
-
return (0, node_child_process_1.execFileSync)("git", args, {
|
|
32
|
+
return (0, node_child_process_1.execFileSync)("git", args, {
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
cwd,
|
|
35
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
36
|
+
}).trim();
|
|
33
37
|
}
|
|
34
38
|
catch (err) {
|
|
35
39
|
const msg = err instanceof Error ? err.message : String(err);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4-r-c-4-n-4/todo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Git-native work tracking for coding agents",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"todo": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
10
|
+
"node": ">=20"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist/",
|