@getpipher/armory-todo 0.5.3 → 0.5.5
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 +28 -0
- package/extensions/todo.ts +23 -11
- package/package.json +9 -3
- package/src/config.ts +14 -0
- package/src/index.d.ts +78 -0
- package/src/index.ts +39 -0
package/README.md
CHANGED
|
@@ -21,6 +21,17 @@
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
+
## Public API
|
|
25
|
+
|
|
26
|
+
The package `exports` entry (`./src/index.ts`) is the **stable public surface** —
|
|
27
|
+
`addTodo`, `listTodos`, `updateTodo`, `getTodo`, `completeTodo`, `parkTodo`,
|
|
28
|
+
`deleteTodo`, and the `Todo` / `AddInput` / `UpdateInput` / `ListFilter` /
|
|
29
|
+
`Priority` / `Status` / `Store` types (plus `TodoError`). Other `src/*` paths
|
|
30
|
+
are internal and may change without notice. Depend on `@getpipher/armory-todo`
|
|
31
|
+
(the public entry), never deep-import `src/*`.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
24
35
|
## The problem
|
|
25
36
|
|
|
26
37
|
pi sessions are ephemeral conversation branches. A TODO you tell to session A is invisible to session B unless you manually write it to a notes file *and* remember to read it next time. Every existing pi todo extension (`@juicesharp/rpiv-todo`, `@xynogen/pix-todo`, `@gonrocca/zero-pi-todo`, …) is **conversation-branch-scoped** — they persist via pi's `appendEntry()` and survive compaction + `/reload` *within a single session*. None bridge across separate sessions, and none make a fresh session aware of pending work on its own.
|
|
@@ -210,6 +221,23 @@ Full design + decisions:
|
|
|
210
221
|
|---|---|---|
|
|
211
222
|
| `TODO_DIR` | `~/.pi/agent/todo/` | override the store folder (tests / multiple profiles) |
|
|
212
223
|
|
|
224
|
+
`todo.config.json` (in `TODO_DIR`) holds prune ages, health thresholds, and notify toggles. All values are optional — missing fields are merged with defaults on load.
|
|
225
|
+
|
|
226
|
+
```jsonc
|
|
227
|
+
{
|
|
228
|
+
"version": 1,
|
|
229
|
+
"prune": { "defaultAgeDays": 7, "hardAgeDays": 180, "statuses": ["done", "cancelled"] },
|
|
230
|
+
"health": { "activeMaxOpen": 15, "activeStaleDays": 30, "parkedMax": 10,
|
|
231
|
+
"parkedStaleDays": 60, "archiveMax": 200, "archiveOldDays": 180,
|
|
232
|
+
"perProjectDefaultMax": 8, "maxNotesBytes": 8192 },
|
|
233
|
+
"notify": { "sessionStartCount": true }
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| `notify.*` | default | purpose |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| `sessionStartCount` | `true` | Show the `armory-todo: N open TODOs` startup line. Set `false` to silence it — safety messages (wipe-recovery alert, auto-prune undo info) still surface. |
|
|
240
|
+
|
|
213
241
|
Run the store tests: `npm test` (315/315 across 11 suites).
|
|
214
242
|
|
|
215
243
|
## Known issues
|
package/extensions/todo.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { healthReport } from "../src/health";
|
|
|
39
39
|
import { hardPrune } from "../src/hard-prune";
|
|
40
40
|
import { TodoPanel } from "../src/panel";
|
|
41
41
|
import { autoPruneOnSessionStart } from "../src/auto-prune";
|
|
42
|
-
import { loadConfig } from "../src/config";
|
|
42
|
+
import { loadConfig, type TodoConfig } from "../src/config";
|
|
43
43
|
import { projectsOverview } from "../src/projects";
|
|
44
44
|
import { renameProject } from "../src/registry";
|
|
45
45
|
import { readAndClearWipeAlert } from "../src/backup";
|
|
@@ -90,10 +90,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
90
|
} catch {
|
|
91
91
|
// alert optional
|
|
92
92
|
}
|
|
93
|
+
// One config load reused for the prune age + the notify-suppress flag.
|
|
94
|
+
let cfg: TodoConfig | undefined;
|
|
95
|
+
try { cfg = loadConfig(); } catch { /* config optional */ }
|
|
93
96
|
let autoMsg = "";
|
|
94
|
-
let ageDays = 7;
|
|
97
|
+
let ageDays = cfg?.prune.defaultAgeDays ?? 7;
|
|
95
98
|
try {
|
|
96
|
-
ageDays = loadConfig().prune.defaultAgeDays;
|
|
97
99
|
const ap = autoPruneOnSessionStart();
|
|
98
100
|
if (ap) {
|
|
99
101
|
const lines = ap.items.map((i) => ` [${i.id}] ${i.status} ${i.title}`);
|
|
@@ -102,17 +104,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
102
104
|
} catch {
|
|
103
105
|
// auto-prune optional — don't crash the session notify
|
|
104
106
|
}
|
|
107
|
+
const showCount = cfg?.notify?.sessionStartCount !== false;
|
|
105
108
|
const open = listTodos();
|
|
106
|
-
let msg =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
let msg = "";
|
|
110
|
+
if (showCount) {
|
|
111
|
+
msg = `armory-todo: ${open.length} open TODO${open.length === 1 ? "" : "s"}${autoMsg}`;
|
|
112
|
+
try {
|
|
113
|
+
const report = healthReport();
|
|
114
|
+
if (report.flags.length > 0) {
|
|
115
|
+
msg += `${autoMsg ? "\n" : " — "}` + `⚠ ${report.flags.length} bloat signal${report.flags.length === 1 ? "" : "s"} (run /todo health)`;
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// health check optional
|
|
111
119
|
}
|
|
112
|
-
}
|
|
113
|
-
//
|
|
120
|
+
} else if (autoMsg) {
|
|
121
|
+
// Count line suppressed; still surface the auto-prune undo info.
|
|
122
|
+
msg = `armory-todo${autoMsg}`;
|
|
123
|
+
}
|
|
124
|
+
if (ctx.hasUI) {
|
|
125
|
+
const out = (wipeMsg ? wipeMsg + "\n" : "") + msg;
|
|
126
|
+
if (out) ctx.ui.notify(out, "info");
|
|
114
127
|
}
|
|
115
|
-
if (ctx.hasUI) ctx.ui.notify((wipeMsg ? wipeMsg + "\n" : "") + msg, "info");
|
|
116
128
|
} catch {
|
|
117
129
|
// store unavailable — never crash the session
|
|
118
130
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpipher/armory-todo",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Global, cross-session TODO for pi \u2014 persists across all sessions and is auto-injected into every prompt. The disk-backed counterpart to branch-scoped pi todo extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
"bugs": {
|
|
22
22
|
"url": "https://github.com/getpipher/armory-todo/issues"
|
|
23
23
|
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./src/index.d.ts",
|
|
27
|
+
"default": "./src/index.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
24
30
|
"files": [
|
|
25
31
|
"extensions",
|
|
26
32
|
"src",
|
|
@@ -35,7 +41,7 @@
|
|
|
35
41
|
]
|
|
36
42
|
},
|
|
37
43
|
"scripts": {
|
|
38
|
-
"test": "for t in todo-store todo-title-notes todo-archive todo-config todo-migrate todo-health todo-hard-prune todo-auto-prune registry projects panel-data todo-caps todo-backup; do node test/$t.test.mts || exit 1; done"
|
|
44
|
+
"test": "for t in todo-store todo-title-notes todo-archive todo-config todo-migrate todo-health todo-hard-prune todo-auto-prune registry projects panel-data todo-caps todo-backup public-api; do node test/$t.test.mts || exit 1; done"
|
|
39
45
|
},
|
|
40
46
|
"peerDependencies": {
|
|
41
47
|
"@earendil-works/pi-ai": "*",
|
|
@@ -53,4 +59,4 @@
|
|
|
53
59
|
"optional": true
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
|
-
}
|
|
62
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -28,10 +28,18 @@ export interface HealthConfig {
|
|
|
28
28
|
maxNotesBytes: number; // v0.5.0: per-todo notes byte cap (hard-reject at add/update)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export interface NotifyConfig {
|
|
32
|
+
/** Show the `armory-todo: N open TODOs` session-start count line.
|
|
33
|
+
* Safety messages (wipe-recovery alert, auto-prune undo info) still surface
|
|
34
|
+
* when this is false. Default true. */
|
|
35
|
+
sessionStartCount: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
export interface TodoConfig {
|
|
32
39
|
version: 1;
|
|
33
40
|
prune: PruneConfig;
|
|
34
41
|
health: HealthConfig;
|
|
42
|
+
notify: NotifyConfig;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
export const DEFAULT_CONFIG: TodoConfig = {
|
|
@@ -51,6 +59,9 @@ export const DEFAULT_CONFIG: TodoConfig = {
|
|
|
51
59
|
perProjectDefaultMax: 8,
|
|
52
60
|
maxNotesBytes: 8192,
|
|
53
61
|
},
|
|
62
|
+
notify: {
|
|
63
|
+
sessionStartCount: true,
|
|
64
|
+
},
|
|
54
65
|
};
|
|
55
66
|
|
|
56
67
|
/** Deep clone of DEFAULT_CONFIG (so callers can't mutate the constant). */
|
|
@@ -77,10 +88,13 @@ export function loadConfig(): TodoConfig {
|
|
|
77
88
|
if (health.maxNotesBytes === undefined || typeof health.maxNotesBytes !== "number" || Number.isNaN(health.maxNotesBytes) || health.maxNotesBytes < 0) {
|
|
78
89
|
health.maxNotesBytes = DEFAULT_CONFIG.health.maxNotesBytes;
|
|
79
90
|
}
|
|
91
|
+
const notify = { ...DEFAULT_CONFIG.notify, ...(parsed.notify ?? {}) };
|
|
92
|
+
if (typeof notify.sessionStartCount !== "boolean") notify.sessionStartCount = true;
|
|
80
93
|
return {
|
|
81
94
|
version: 1,
|
|
82
95
|
prune: { ...DEFAULT_CONFIG.prune, ...parsed.prune },
|
|
83
96
|
health,
|
|
97
|
+
notify,
|
|
84
98
|
};
|
|
85
99
|
} catch {
|
|
86
100
|
try {
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed declarations for the public stable API of @getpipher/armory-todo.
|
|
4
|
+
*
|
|
5
|
+
* Consumed via the package `exports` `types` condition (see package.json) so
|
|
6
|
+
* that TypeScript consumers type-check against this declaration instead of
|
|
7
|
+
* pulling the raw `.ts` implementation source. Runtime (tsx/jiti/node) uses the
|
|
8
|
+
* `default` condition (`./src/index.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Hand-written (no build step — getpipher convention). Keep in sync with
|
|
11
|
+
* src/index.ts re-exports.
|
|
12
|
+
*/
|
|
13
|
+
export type Priority = "low" | "med" | "high" | "critical";
|
|
14
|
+
export type Status = "open" | "in_progress" | "parked" | "done" | "cancelled";
|
|
15
|
+
|
|
16
|
+
export interface Todo {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
notes: string;
|
|
20
|
+
project: string;
|
|
21
|
+
tags: string[];
|
|
22
|
+
priority: Priority;
|
|
23
|
+
status: Status;
|
|
24
|
+
source: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
closedAt: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AddInput {
|
|
31
|
+
title: string;
|
|
32
|
+
notes?: string;
|
|
33
|
+
project?: string;
|
|
34
|
+
tags?: string[];
|
|
35
|
+
priority?: Priority;
|
|
36
|
+
source?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UpdateInput {
|
|
40
|
+
title?: string;
|
|
41
|
+
notes?: string;
|
|
42
|
+
project?: string;
|
|
43
|
+
tags?: string[];
|
|
44
|
+
priority?: Priority;
|
|
45
|
+
status?: Status;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ListFilter {
|
|
49
|
+
status?: Status | "all";
|
|
50
|
+
project?: string;
|
|
51
|
+
tag?: string;
|
|
52
|
+
text?: string;
|
|
53
|
+
since?: string;
|
|
54
|
+
before?: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
page?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface Store {
|
|
60
|
+
version: 3;
|
|
61
|
+
updatedAt: string;
|
|
62
|
+
todos: Todo[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class TodoError extends Error {}
|
|
66
|
+
|
|
67
|
+
export function addTodo(input: AddInput): Todo;
|
|
68
|
+
export function listTodos(filter?: ListFilter): Todo[];
|
|
69
|
+
export function updateTodo(id: string, patch: UpdateInput): Todo;
|
|
70
|
+
export function getTodo(id: string): Todo;
|
|
71
|
+
export function completeTodo(id: string): Todo;
|
|
72
|
+
export function parkTodo(id: string): Todo;
|
|
73
|
+
export function deleteTodo(id: string): Todo;
|
|
74
|
+
export function clearTodos(status?: Status): number;
|
|
75
|
+
export function renderOpenBlock(max?: number): string;
|
|
76
|
+
export function getStorePath(): string;
|
|
77
|
+
export function loadStore(): Store;
|
|
78
|
+
export function saveStore(store: Store): void;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Public stable API of @getpipher/armory-todo.
|
|
4
|
+
*
|
|
5
|
+
* This `exports` entry (see package.json) is the stable surface — the functions
|
|
6
|
+
* and types re-exported here. Other `src/*` paths are internal and may change
|
|
7
|
+
* without notice. Depend on `@getpipher/armory-todo` (this public entry), never
|
|
8
|
+
* deep-import `src/*`.
|
|
9
|
+
*
|
|
10
|
+
* Consumers (e.g. @getpipher/armory-fleet) import from here so that evolution
|
|
11
|
+
* of armory-todo is decoupled: a breaking change can only touch a consumer's
|
|
12
|
+
* adapter, never its core, and the version pin + CI typecheck catch drift.
|
|
13
|
+
*/
|
|
14
|
+
export {
|
|
15
|
+
addTodo,
|
|
16
|
+
listTodos,
|
|
17
|
+
updateTodo,
|
|
18
|
+
getTodo,
|
|
19
|
+
completeTodo,
|
|
20
|
+
parkTodo,
|
|
21
|
+
deleteTodo,
|
|
22
|
+
clearTodos,
|
|
23
|
+
renderOpenBlock,
|
|
24
|
+
getStorePath,
|
|
25
|
+
loadStore,
|
|
26
|
+
saveStore,
|
|
27
|
+
} from "./todo-store.ts";
|
|
28
|
+
|
|
29
|
+
export type {
|
|
30
|
+
Todo,
|
|
31
|
+
AddInput,
|
|
32
|
+
UpdateInput,
|
|
33
|
+
ListFilter,
|
|
34
|
+
Priority,
|
|
35
|
+
Status,
|
|
36
|
+
Store,
|
|
37
|
+
} from "./todo-store.ts";
|
|
38
|
+
|
|
39
|
+
export { TodoError } from "./todo-store.ts";
|