@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 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 work <child-id> # checks out todo/<feature-id> branch
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
- Step by step:
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
- For child tickets: `todo work <child-id>` checks out the parent's branch, not a new one.
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 <id> Export ticket as JSON/markdown
197
- --format json|markdown
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 four named skills. Each maps to a phase of the lifecycle.
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-implement** — Run `todo work` to branch, implement, commit with the `todo:<id>` prefix, then `todo close`. Always commit `.todo/` after close.
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
@@ -1,2 +1,4 @@
1
1
  # todo
2
2
  Making working on work work
3
+
4
+ https://www.npmjs.com/package/@4-r-c-4-n-4/todo
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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerNext(program: Command): void;
@@ -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
+ }
@@ -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 ((0, git_js_1.branchExists)(branch, repoRoot)) {
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, { encoding: "utf8", cwd }).trim();
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.2",
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": ">=22"
10
+ "node": ">=20"
11
11
  },
12
12
  "files": [
13
13
  "dist/",