@dreki-gg/taskman 0.2.0 → 0.2.1

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,85 @@
1
+ # @dreki-gg/taskman
2
+
3
+ A standalone task-management engine — and a `taskman` CLI — over a plain
4
+ `.plans/` JSONL ledger. It is the core extracted from
5
+ [`@dreki-gg/pi-plan-mode`](../plan-mode), so any harness (not just pi) can drive
6
+ the same plans, initiatives, and tasks.
7
+
8
+ ## Why it exists
9
+
10
+ Planning agents need durable, file-based task state that survives across
11
+ sessions and tools. `taskman` owns that state machine — task status, plan and
12
+ initiative lifecycle, and the projection rules that keep them consistent — with
13
+ **no dependency on any specific agent harness**. Use it from a shell, a CI job,
14
+ a different agent, or as a library.
15
+
16
+ ## The ledger (the one durable contract)
17
+
18
+ Everything lives under `.plans/` in the current working directory:
19
+
20
+ - `.plans/plans.jsonl` — the plan registry.
21
+ - `.plans/initiatives.jsonl` — the initiative registry (initiatives group plans).
22
+ - `.plans/<plan>/tasks.jsonl` — one plan's task list (first line is metadata).
23
+ - `.plans/<plan>/HANDOFF.md`, `.plans/<initiative>/INITIATIVE.md` — prose docs.
24
+
25
+ Three invariants are worth knowing; everything else is mechanism:
26
+
27
+ 1. **Status is a projection, not a flag.** A plan is `done` when its active
28
+ tasks are all resolved (and no follow-ups remain); an initiative is `done`
29
+ when every member plan is terminal. Writing task state re-derives the
30
+ registry — you do not set plan status by hand for the normal path.
31
+ 2. **Plan resolution is stateless.** A command targets a plan via `--plan
32
+ <name>` (accepts `.plans/<name>` too), else the *single* in-progress plan.
33
+ Ambiguous or missing → it exits non-zero and lists the candidates.
34
+ 3. **Terminal statuses set manually are never auto-reverted.** `reconcile` only
35
+ moves `in-progress ⇄ done`; it never resurrects a `superseded`/`abandoned`
36
+ plan or regresses a finished one.
37
+
38
+ ## Usage
39
+
40
+ ```bash
41
+ taskman --help # full, always-current command list
42
+ taskman <command> --help # flags + arguments for one command
43
+ ```
44
+
45
+ `--help` is the source of truth for commands and flags — this README does not
46
+ duplicate it (so it cannot drift). Every command prints human text by default
47
+ and accepts `--json` for machine consumption.
48
+
49
+ A typical execution loop:
50
+
51
+ ```bash
52
+ taskman status # what's the active plan and its tasks?
53
+ taskman update-task t-003 done # mark progress (auto-reconciles the plan)
54
+ taskman add-task "handle empty case" --reason "found gap while implementing"
55
+ taskman reconcile --apply # repair safe status drift
56
+ ```
57
+
58
+ ## As a library
59
+
60
+ ```ts
61
+ import { makePlanRuntime, resolvePlanByName, setTaskStatus } from '@dreki-gg/taskman';
62
+
63
+ const run = makePlanRuntime(); // bridges the Effect programs to the live filesystem
64
+ const { planDir } = await run(resolvePlanByName({ name: 'my-plan' }));
65
+ await run(setTaskStatus(planDir!, 't-001', 'done'));
66
+ ```
67
+
68
+ The public surface (storage, schema, reconcile, initiative projection,
69
+ resolution, and composite write flows) is exported from the package root. The
70
+ engine is built on [Effect](https://effect.website) with a single `FileSystem`
71
+ seam, so it is straightforward to test and to run against an alternate backend.
72
+
73
+ ## Agent skill
74
+
75
+ This package ships a [TanStack Intent](https://tanstack.com/intent) skill
76
+ (`skills/taskman/core`) — versioned guidance that AI coding agents discover from
77
+ `node_modules`. If you use an AI agent, run:
78
+
79
+ ```bash
80
+ npx @tanstack/intent@latest install
81
+ ```
82
+
83
+ ## License
84
+
85
+ MIT
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@dreki-gg/taskman",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Standalone task-management engine + CLI over a .plans/ JSONL ledger — the plan-mode core, usable from any Node harness",
5
5
  "keywords": [
6
6
  "tasks",
7
7
  "planning",
8
- "cli"
8
+ "cli",
9
+ "tanstack-intent"
9
10
  ],
10
11
  "author": "Juan Albarran <jalbarrandev@gmail.com>",
11
12
  "license": "MIT",
@@ -26,11 +27,14 @@
26
27
  "taskman": "./dist/cli.mjs"
27
28
  },
28
29
  "files": [
29
- "dist"
30
+ "dist",
31
+ "README.md",
32
+ "skills"
30
33
  ],
31
34
  "scripts": {
32
35
  "build": "tsdown",
33
36
  "test": "bun test",
37
+ "intent:validate": "intent validate",
34
38
  "typecheck": "tsc --noEmit",
35
39
  "lint": "oxlint src",
36
40
  "format": "oxfmt --write src",
@@ -41,6 +45,7 @@
41
45
  "effect": "^3.21.2"
42
46
  },
43
47
  "devDependencies": {
48
+ "@tanstack/intent": "^0.1.0",
44
49
  "@types/node": "24",
45
50
  "bun-types": "latest",
46
51
  "oxfmt": "^0.43.0",
@@ -0,0 +1,165 @@
1
+ ---
2
+ name: taskman/core
3
+ description: >
4
+ Drive the taskman CLI and engine over a .plans/ JSONL ledger — plans,
5
+ initiatives, and tasks. Load when running `taskman` commands (status, list,
6
+ update-task, add-task, reconcile, close), tracking plan/task progress across
7
+ sessions or harnesses, or calling @dreki-gg/taskman as a library. Covers the
8
+ status-is-a-projection model, stateless plan resolution, and reconcile.
9
+ type: core
10
+ library: "@dreki-gg/taskman"
11
+ library_version: "0.2.1"
12
+ sources:
13
+ - "dreki-gg/pi-extensions:packages/taskman/README.md"
14
+ - "dreki-gg/pi-extensions:packages/taskman/src/cli.ts"
15
+ - "dreki-gg/pi-extensions:packages/taskman/src/engine.ts"
16
+ ---
17
+
18
+ # taskman
19
+
20
+ `taskman` manages plan/task state on disk under `.plans/` so planning agents
21
+ keep durable progress across sessions and tools. It ships both a CLI (`taskman`)
22
+ and a library (`@dreki-gg/taskman`).
23
+
24
+ The command and flag inventory is **not** reproduced here — it lives in
25
+ `--help`, which is always current for the installed version:
26
+
27
+ ```bash
28
+ taskman --help # all commands
29
+ taskman <command> --help # flags + arguments for one command
30
+ ```
31
+
32
+ This skill teaches the model that `--help` cannot: the data contract and the
33
+ three invariants that decide whether a command does what you expect.
34
+
35
+ ## The ledger
36
+
37
+ Everything is plain JSONL/Markdown under `.plans/` in the current directory:
38
+
39
+ - `.plans/plans.jsonl` — plan registry
40
+ - `.plans/initiatives.jsonl` — initiative registry (initiatives group plans)
41
+ - `.plans/<plan>/tasks.jsonl` — one plan's tasks (first line is metadata)
42
+ - `.plans/<plan>/HANDOFF.md`, `.plans/<initiative>/INITIATIVE.md` — prose
43
+
44
+ ## Invariants (read before acting)
45
+
46
+ 1. **Status is a projection of task state, not a manual flag.** A plan becomes
47
+ `done` when its active tasks are all resolved and no follow-ups remain; an
48
+ initiative becomes `done` when every member plan is terminal. `update-task`
49
+ re-derives the registry automatically.
50
+ 2. **Plan resolution is stateless.** A command targets a plan via `--plan
51
+ <name>` (accepts `.plans/<name>`), else the *single* in-progress plan.
52
+ Ambiguous or missing → non-zero exit listing the candidates.
53
+ 3. **Manual terminal statuses are never auto-reverted.** `reconcile` only moves
54
+ `in-progress ⇄ done`; it never resurrects a `superseded`/`abandoned` plan or
55
+ regresses a finished one.
56
+
57
+ ## Core Patterns
58
+
59
+ ### Execution loop
60
+
61
+ ```bash
62
+ taskman status # active plan + task ids/statuses
63
+ taskman update-task t-003 done # mark progress; plan status re-derived
64
+ taskman add-task "handle empty input" --reason "found gap while implementing"
65
+ taskman reconcile --apply # repair safe (in-progress→done) drift
66
+ ```
67
+
68
+ ### Machine-readable output
69
+
70
+ Every command prints human text by default and accepts `--json`:
71
+
72
+ ```bash
73
+ taskman status --json # { active, plan_name, title, total, counts, task_ids }
74
+ taskman list --json # array of plan items
75
+ ```
76
+
77
+ ### Library usage
78
+
79
+ ```ts
80
+ import { makePlanRuntime, resolvePlanByName, setTaskStatus } from '@dreki-gg/taskman';
81
+
82
+ const run = makePlanRuntime(); // bridges Effect programs to the live filesystem
83
+ const { planName, planDir, candidates } = await run(resolvePlanByName({ name: 'my-plan' }));
84
+ if (!planDir) throw new Error(`Unresolved; candidates: ${candidates.join(', ')}`);
85
+ await run(setTaskStatus(planDir, 't-001', 'done')); // also reconciles the registry
86
+ ```
87
+
88
+ ## Common Mistakes
89
+
90
+ ### HIGH Setting plan status by hand to mark progress
91
+
92
+ Wrong:
93
+
94
+ ```bash
95
+ taskman close done --plan my-plan # to "finish" a plan whose tasks are open
96
+ ```
97
+
98
+ Correct:
99
+
100
+ ```bash
101
+ taskman update-task t-007 done # resolve the tasks; status follows
102
+ ```
103
+
104
+ `close` forces a lifecycle status and is for `superseded`/`abandoned` (or an
105
+ explicit override). For normal completion, resolve the tasks — the `done`
106
+ projection is derived from task state, so a forced `done` will be re-opened the
107
+ moment another task write reconciles the plan.
108
+
109
+ ### HIGH Assuming a bare command finds any plan
110
+
111
+ Wrong:
112
+
113
+ ```bash
114
+ taskman status # expecting it to pick "the plan I just finished"
115
+ ```
116
+
117
+ Correct:
118
+
119
+ ```bash
120
+ taskman status --plan my-plan # name it explicitly when not uniquely in-progress
121
+ ```
122
+
123
+ With no `--plan`, resolution only succeeds when exactly one plan is
124
+ `in-progress`. A just-completed plan is `done`, so it drops out of resolution
125
+ and a bare command exits non-zero — pass `--plan`.
126
+
127
+ ### MEDIUM Treating reconcile as a force-sync in both directions
128
+
129
+ Wrong:
130
+
131
+ ```bash
132
+ taskman reconcile --apply # expecting it to flip a done plan back to in-progress
133
+ ```
134
+
135
+ Correct:
136
+
137
+ ```bash
138
+ # A `done` plan with unfinished tasks is reported as downgrade drift, NOT fixed.
139
+ # Resolve it by marking the tasks instead:
140
+ taskman update-task t-004 done --plan my-plan
141
+ ```
142
+
143
+ `reconcile --apply` only performs the safe upgrade (`in-progress → done`). A
144
+ `done`→`in-progress` downgrade almost always means work merged without marking
145
+ tasks; it is surfaced for a human, never auto-applied.
146
+
147
+ ### MEDIUM Expecting add-task to queue active work
148
+
149
+ Wrong:
150
+
151
+ ```bash
152
+ taskman add-task "refactor parser" --reason "messy"
153
+ # ...then expecting it to show as pending work to do now
154
+ ```
155
+
156
+ Correct:
157
+
158
+ ```bash
159
+ taskman add-task "refactor parser" --reason "messy" # captured as a deferred follow-up
160
+ # Review/triage later; it is intentionally kept out of the active queue and
161
+ # keeps the plan non-finalizable until resolved.
162
+ ```
163
+
164
+ `add-task` records a `deferred`, `discovered` task — a follow-up for later
165
+ triage, not an active to-do. It does not become pending automatically.