@getpipher/armory-todo 0.3.1 → 0.5.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.
package/README.md CHANGED
@@ -15,6 +15,10 @@
15
15
  <img src="https://img.shields.io/badge/dependencies-0-9aa7a1" alt="no dependencies">
16
16
  </p>
17
17
 
18
+ <p align="center">
19
+ <strong>lifecycle boxes</strong> · <strong>title + notes</strong> · <strong>auto-prune</strong> · <strong>interactive panel</strong> · <strong>health diagnostics</strong> · <strong>hard-prune gate</strong>
20
+ </p>
21
+
18
22
  ---
19
23
 
20
24
  ## The problem
@@ -99,21 +103,40 @@ todos. It's gated three ways:
99
103
  Everything else in armory-todo is reversible. `prune --hard` is the one
100
104
  irreversible escape hatch, always user-confirmed.
101
105
 
102
- ## Title + notes (v0.3.0)
106
+ ## Project-scope management (v0.4.0)
107
+
108
+ A project registry (`~/.pi/agent/todo/projects.json`, lazy-synced on read) tracks known projects + an advisory per-project **`maxOpen`** cap slot. The `todo` tool gains two actions: **`projects`** (per-project scope overview — open/in_progress/parked/done counts + `maxOpen` + `OVER`/`?typo` markers + last-updated) and **`project_rename`** (rename or merge a project; rewrites live + archive + registry — the typo-cleanup path, e.g. `getpither` → `getpipher`).
109
+
110
+ `health` gains four per-project flags: **`PROJECT_OVER`** (open > a project's `maxOpen` slot), **`PROJECT_LARGE`** (open > `health.perProjectDefaultMax`, default 8 — fires out-of-the-box, no per-project config needed), **`PROJECT_STALE`** (project untouched > `activeStaleDays`), **`PROJECT_TYPO`** (1-todo project with a near-named sibling, Levenshtein ≤ 2). The `/todo health` report + the `todo` `health` action both show a `projects:` section.
111
+
112
+ The interactive `/todo` panel gains a 6th tab — **Projects** — listing the overview rows + a `(no project)` summary, with an action submenu per project: **Rename / merge** (inline input), **Set maxOpen** (number or `clear`), **Filter active to project** (jump to the Active tab scoped). A thin `/todo projects` slash mirrors the overview as text.
113
+
114
+ **Advisory in v0.4.0 → enforced in v0.5.0** — `maxOpen` now blocks `add` (and project-move) when a project is at its cap. See the [Caps enforcement (v0.5.0)](#caps-enforcement-v050) section below.
115
+
116
+ ## Caps enforcement (v0.5.0)
117
+
118
+ Three caps keep the store (and its auto-injected prompt block) from bloating silently — the forcing-function half of [issue #1](https://github.com/getpither/armory-todo/issues/1):
119
+
120
+ 1. **Count cap (per-project `maxOpen`, enforced).** A project's `maxOpen` slot (set via the Projects tab → Set maxOpen, or `setProjectMaxOpen`) **blocks `add`** when the project is at its cap, and **blocks a project-move** of an `open`/`in_progress` todo into a capped project. The cap is on the `open` count (matches the `PROJECT_OVER` health flag); `in_progress` doesn't count. Un-park (`parked→open`) is intentionally **not** blocked — reactivating deferred work isn't adding new work. The block message tells you how to raise/clear the cap. `maxOpen: null` (default) = uncapped.
121
+
122
+ 2. **Notes cap (`health.maxNotesBytes`, default 8192 bytes, enforced).** Oversize notes are rejected at `add`/`update` (only when `notes` is being written — a title edit on a grandfathered oversize note isn't trapped). Byte-length, not char-length (notes can hold Unicode). Existing oversize notes are grandfathered; `health` surfaces the worst offender via the `NOTES_OVER` flag + an actionable `todo update <id> notes:…` suggestion.
123
+
124
+ 3. **Over-cap injection truncation.** When actionable > `health.activeMaxOpen` (default 15), the auto-injected `## Open TODOs (N)` block collapses to a ~4-line summary (total + project span + over-budget projects + a `todo list` pointer) instead of the row list. Under the cap → the familiar row list. `activeMaxOpen` itself stays **advisory** (it drives the `ACTIVE_LARGE` flag and the truncation trigger; it is not a hard global block).
103
125
 
104
- The single `text` field is split into **`title`** (≤120 chars, one-line summary the only thing injected into the prompt or shown in compact lists) and **`notes`** (any length, the running detail/log — never auto-injected). A hard cap rejects `title` >120 chars at `add`/`update` so the junk-drawer pattern can't re-form. `list` shows titles + a `•` marker when notes exist; `get <id>` reads a todo's full notes before acting on it. v2 `text`-only stores migrate on first load (curated for the 2 known todos + a first-line fallback).
126
+ **Backwards-compat:** zero migration (store v3, config v1, registry v1 unchanged in shape). Oversize notes grandfathered. The `maxOpen` advisory→enforced graduation is a documented behavior change for any v0.4.0 user who set a slot (the block message tells them how to raise/clear).
105
127
 
106
128
  ## Interactive panel (SPEC-3)
107
129
 
108
130
  Run `/todo` (no arg) in a TUI session to open the interactive triage panel:
109
131
 
110
- - **Box tabs** (Tab / Shift+Tab): Active · Parked · **Done** · Archive · Config
132
+ - **Box tabs** (Tab / Shift+Tab): Active · Parked · **Done** · Archive · **Projects** · Config
111
133
  - **Filter input**: type to search by text (live filter)
112
134
  - **SelectList**: arrow keys navigate, Enter selects
113
135
  - **Action submenu** (on Enter): View detail / Complete / Park / Re-activate / Restore / Edit title / Delete
114
136
  - **Done tab** (v0.3.1): all finished work (`status: done`) unified across live + archive, location-tagged (`[live Nd]` / `[archived YYYY-MM-DD]`), filterable; Enter → View detail, or Restore-from-archive. Excludes `cancelled` (that's in the Archive tab).
115
137
  - **Detail view** (View detail, or Enter on a row): renders the title + full `notes` read-only, with a footer hint on editing notes via the `todo` tool
116
138
  - **Archive box**: summary-first (counts by project + month) → Enter on a bucket to drill down
139
+ - **Projects tab** (v0.4.0): per-project scope overview (open/in_progress/parked/done counts + `maxOpen` + `OVER`/`?typo` markers) + a `(no project)` summary row; Enter on a project → action submenu: Rename / merge · Set maxOpen · Filter active to project.
117
140
  - **Config box**: SettingsList with prune ages + health thresholds — edit live, persists to `todo.config.json`
118
141
  - **Escape**: exit the panel
119
142
 
@@ -134,7 +157,7 @@ Typed subcommands (`/todo park <id>`, `/todo prune`, etc.) all still work alongs
134
157
  /todo list open + in-progress TODOs
135
158
  /todo all include parked/done/cancelled
136
159
  /todo add <title> quick add (priority: med; notes via the todo tool)
137
- /todo finished list all done todos (live + archived, recent first)
160
+ /todo finished list all done todos (live + archived, recent first)
138
161
  /todo done <id> mark done
139
162
  /todo rm <id> cancel (tombstone)
140
163
  /todo park <id> defer (parked — not injected, recoverable)
@@ -168,13 +191,15 @@ Each TODO carries `id, title (≤120 chars), notes (any length), project, tags,
168
191
 
169
192
  ## How it works
170
193
 
171
- - **Disk store** — `~/.pi/agent/todo/` folder: `todo.json` (live: active + parked), `todo-archive.json` (sealed: done + cancelled), `todo.config.json` (prune ages + health thresholds). Atomic `0600` writes, corrupt-file auto-recovery, `version: 3` schema (`title` ≤120 chars + `notes` any length; v2 `text`-only stores migrate to v3 on first load). Not pi session entries, so it outlives any conversation.
194
+ - **Disk store** — `~/.pi/agent/todo/` folder: `todo.json` (live: active + parked), `todo-archive.json` (sealed: done + cancelled), `todo.config.json` (prune ages + health thresholds), `projects.json` (project registry: canonical names + advisory `maxOpen` slots, v0.4.0). Atomic `0600` writes, corrupt-file auto-recovery, `version: 3` store schema (`title` ≤120 chars + `notes` any length; v2 `text`-only stores migrate to v3 on first load). Not pi session entries, so it outlives any conversation.
172
195
  - **`todo` tool** — model CRUD + lifecycle (above).
173
196
  - **`/todo` command** — human triage (above).
174
- - **Auto-inject** — on every `before_agent_start`, a compact `## Open TODOs (N)` block (titles + ids, capped at 15, sorted by priority) is appended to the system prompt, so the agent starts every turn already aware of pending work. Only `open` + `in_progress` are injected — `parked` and archived todos are excluded (the lifecycle-box boundary). Mutations refresh it on the next turn.
197
+ - **Auto-inject** — on every `before_agent_start`, a compact `## Open TODOs (N)` block (titles + ids, sorted by priority) is appended to the system prompt, so the agent starts every turn already aware of pending work. The block is **cap-aware** (v0.5.0): under `health.activeMaxOpen` (default 15) it lists the rows; **over** the cap it collapses to a lean summary (counts + over-budget projects + a `todo list` pointer) so the prompt stays bounded when the store bloats. Only `open` + `in_progress` are injected — `parked` and archived todos are excluded (the lifecycle-box boundary). Mutations refresh it on the next turn.
175
198
  - **Archive query** — `list` with `archived:true` is summary-first (counts by project + month) then filtered/paginated on demand, so a large archive never bloats a single query.
176
199
 
177
200
  Full design + decisions:
201
+ - v0.4.0 (project-scope management): [`docs/superpowers/specs/2026-07-21-project-scope-management-design.md`](docs/superpowers/specs/2026-07-21-project-scope-management-design.md)
202
+ - v0.3.1 (auto-prune + unified Done view): [`docs/superpowers/specs/2026-07-21-auto-prune-done-view-design.md`](docs/superpowers/specs/2026-07-21-auto-prune-done-view-design.md)
178
203
  - v0.3.0 (title + notes split): [`docs/superpowers/specs/2026-07-21-title-notes-split-design.md`](docs/superpowers/specs/2026-07-21-title-notes-split-design.md)
179
204
  - v0.2.0 (lifecycle boxes + prune + health): [`docs/superpowers/specs/2026-07-20-lifecycle-boxes-prune-design.md`](docs/superpowers/specs/2026-07-20-lifecycle-boxes-prune-design.md)
180
205
  - Original v0.1.0 spec: [`docs/todo-SPEC.md`](docs/todo-SPEC.md)
@@ -185,12 +210,12 @@ Full design + decisions:
185
210
  |---|---|---|
186
211
  | `TODO_DIR` | `~/.pi/agent/todo/` | override the store folder (tests / multiple profiles) |
187
212
 
188
- Run the store tests: `npm test` (255/255 across 9 suites).
213
+ Run the store tests: `npm test` (315/315 across 11 suites).
189
214
 
190
215
  ## Known issues
191
216
 
192
217
  - **No in-panel multi-line `notes` editing.** The panel's inline Edit is single-line (`Input`) for `title` only; `ctx.ui.editor()` from inside `ctx.ui.custom()` triggers a nested-UI bug (`/todo` won't reopen). `notes` is model-managed via the `todo` tool (`action: update, id, notes`). When a safe `ctx.ui.editor()`-from-`custom()` pattern lands in pi-tui, in-panel notes editing is a clean follow-up.
193
- - **Preventive caps-on-add** (notes length cap + project registry) are deferred to Workstream C (v0.4.0). Until then, `health` reports notes-bytes as a read-only diagnostic.
218
+ - **Caps enforcement shipped in v0.5.0.** Per-project `maxOpen` blocks `add`/move; `health.maxNotesBytes` (default 8KB) rejects oversize notes at write; the auto-injected block collapses to a lean summary over `activeMaxOpen`. See [Caps enforcement (v0.5.0)](#caps-enforcement-v050) above.
194
219
 
195
220
  ## Security
196
221
 
package/assets/hero.svg CHANGED
@@ -103,11 +103,11 @@
103
103
  <path d="M14,8 L14,18 M11,8 L17,8 M11,18 L17,18" fill="none" stroke="#8a9791" stroke-width="1.4" stroke-linecap="round"/>
104
104
  <text x="27" y="17.5" fill="#c2cfc9" letter-spacing="0.4">MIT</text>
105
105
  </g>
106
- <!-- v0.1.0 -->
106
+ <!-- v0.3.1 -->
107
107
  <g transform="translate(234, 0)">
108
108
  <rect x="0" y="0" rx="13" ry="13" width="98" height="26" fill="#10171a" stroke="#3a4a44" stroke-width="1"/>
109
109
  <path d="M14,9 L14,17 M14,9 L11,12 M14,9 L17,12" fill="none" stroke="#8a9791" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
110
- <text x="27" y="17.5" fill="#c2cfc9" letter-spacing="0.4">v0.1.0</text>
110
+ <text x="27" y="17.5" fill="#c2cfc9" letter-spacing="0.4">v0.3.1</text>
111
111
  </g>
112
112
  <!-- no dependencies -->
113
113
  <g transform="translate(342, 0)">
@@ -152,7 +152,7 @@
152
152
  <rect x="0" y="0" rx="6" ry="6" width="40" height="20" fill="#3a2a0e" stroke="#fbbf24" stroke-width="1"/>
153
153
  <text x="20" y="14.5" font-size="11" font-weight="700" fill="#fcd34d" text-anchor="middle" letter-spacing="0.4">high</text>
154
154
  <text x="52" y="14.5" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="12" fill="#b8c4be">[td-…m6pjxhj]</text>
155
- <text x="184" y="14.5" font-size="13" fill="#f4f7f4">⏵ Decouple global rules → AGENTS.md</text>
155
+ <text x="184" y="14.5" font-size="13" fill="#f4f7f4">⏵ Decouple global rules → AGENTS.md •</text>
156
156
  <rect x="408" y="2" rx="8" ry="8" width="44" height="16" fill="#13202b" stroke="#2b4250"/>
157
157
  <text x="430" y="13.5" font-size="10.5" fill="#a8b5af" text-anchor="middle">(pi)</text>
158
158
  </g>
@@ -162,7 +162,7 @@
162
162
  <rect x="0" y="0" rx="6" ry="6" width="34" height="20" fill="#0e1f33" stroke="#60a5fa" stroke-width="1"/>
163
163
  <text x="17" y="14.5" font-size="11" font-weight="700" fill="#93c5fd" text-anchor="middle" letter-spacing="0.4">med</text>
164
164
  <text x="46" y="14.5" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="12" fill="#b8c4be">[td-…mzr0qs9]</text>
165
- <text x="178" y="14.5" font-size="13" fill="#f4f7f4">Research browser-use vs Chrome MCP</text>
165
+ <text x="178" y="14.5" font-size="13" fill="#f4f7f4">Research browser-use vs Chrome MCP •</text>
166
166
  </g>
167
167
 
168
168
  <!-- row 3: low -->