@getpipher/armory-todo 0.5.1 → 0.5.2
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/extensions/todo.ts +12 -1
- package/package.json +1 -1
- package/src/backup.ts +41 -1
- package/src/todo-store.ts +2 -1
package/extensions/todo.ts
CHANGED
|
@@ -42,6 +42,7 @@ import { autoPruneOnSessionStart } from "../src/auto-prune";
|
|
|
42
42
|
import { loadConfig } from "../src/config";
|
|
43
43
|
import { projectsOverview } from "../src/projects";
|
|
44
44
|
import { renameProject } from "../src/registry";
|
|
45
|
+
import { readAndClearWipeAlert } from "../src/backup";
|
|
45
46
|
|
|
46
47
|
const ACTIONS = ["list", "add", "update", "get", "complete", "delete", "clear", "park", "prune", "restore", "health", "projects", "project_rename"] as const;
|
|
47
48
|
|
|
@@ -79,6 +80,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
79
80
|
// Warm + report on session start (every new/resume/fork/reload).
|
|
80
81
|
pi.on("session_start", async (_event, ctx) => {
|
|
81
82
|
try {
|
|
83
|
+
// v0.5.2: surface a pending wipe-alert sentinel FIRST (most prominent).
|
|
84
|
+
let wipeMsg = "";
|
|
85
|
+
try {
|
|
86
|
+
const alert = readAndClearWipeAlert();
|
|
87
|
+
if (alert) {
|
|
88
|
+
wipeMsg = `⚠ WIPE RECOVERED: ${alert.before}→${alert.after} at ${alert.at}${alert.snap ? `, snap=${alert.snap}` : ""} — check ~/.pi/agent/todo/todo-audit.log + run \`ps aux | grep -iE 'tsx|pi'\` to catch the wiper live`;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// alert optional
|
|
92
|
+
}
|
|
82
93
|
let autoMsg = "";
|
|
83
94
|
let ageDays = 7;
|
|
84
95
|
try {
|
|
@@ -101,7 +112,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
101
112
|
} catch {
|
|
102
113
|
// health check optional
|
|
103
114
|
}
|
|
104
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "info");
|
|
115
|
+
if (ctx.hasUI) ctx.ui.notify((wipeMsg ? wipeMsg + "\n" : "") + msg, "info");
|
|
105
116
|
} catch {
|
|
106
117
|
// store unavailable — never crash the session
|
|
107
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpipher/armory-todo",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|
package/src/backup.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// Pure fs helpers (no pi imports) so they're unit-testable in isolation.
|
|
16
16
|
|
|
17
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync, appendFileSync, statSync, readFileSync } from "node:fs";
|
|
17
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, appendFileSync, statSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
18
18
|
import { dirname, join } from "node:path";
|
|
19
19
|
import { getTodoDir } from "./paths.ts";
|
|
20
20
|
|
|
@@ -80,4 +80,44 @@ export function countTodosInFile(path: string): number {
|
|
|
80
80
|
|
|
81
81
|
function readJson(path: string): any {
|
|
82
82
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- v0.5.2 wipe alert (one-shot sentinel) ---
|
|
86
|
+
const WIPE_ALERT_PATH = join(getTodoDir(), ".wipe-alert");
|
|
87
|
+
|
|
88
|
+
/** On a drop, write a one-shot sentinel at <TODO_DIR>/.wipe-alert so the next
|
|
89
|
+
* pi session_start can surface the recovery prominently. Overwritten by each
|
|
90
|
+
* new drop (the latest wins). The session_start handler reads + deletes it. */
|
|
91
|
+
export function writeWipeAlert(before: number, after: number, snap: string | null): void {
|
|
92
|
+
try {
|
|
93
|
+
const dir = getTodoDir();
|
|
94
|
+
mkdirSync(dir, { recursive: true });
|
|
95
|
+
const path = join(dir, ".wipe-alert");
|
|
96
|
+
const payload = {
|
|
97
|
+
at: new Date().toISOString(),
|
|
98
|
+
before,
|
|
99
|
+
after,
|
|
100
|
+
snap: snap ? snap.split("/").pop() : null,
|
|
101
|
+
snapPath: snap,
|
|
102
|
+
};
|
|
103
|
+
writeFileSync(path, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
104
|
+
try { chmodSync(path, 0o600); } catch { /* fs may ignore mode */ }
|
|
105
|
+
} catch {
|
|
106
|
+
// best-effort; alert must not block the write
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Read + delete the wipe-alert sentinel (one-shot). Returns the payload if a
|
|
111
|
+
* pending alert exists, else null. The caller surfaces it in session_start
|
|
112
|
+
* then it's gone (until the next drop). */
|
|
113
|
+
export function readAndClearWipeAlert(): { at: string; before: number; after: number; snap: string | null; snapPath: string | null } | null {
|
|
114
|
+
try {
|
|
115
|
+
const path = join(getTodoDir(), ".wipe-alert");
|
|
116
|
+
if (!existsSync(path)) return null;
|
|
117
|
+
const payload = JSON.parse(readFileSync(path, "utf8"));
|
|
118
|
+
try { unlinkSync(path); } catch { /* best-effort clear */ }
|
|
119
|
+
return payload;
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
83
123
|
}
|
package/src/todo-store.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { migrateIfNeeded, migrateV2ToV3 } from "./migrate.ts";
|
|
|
22
22
|
import { loadConfig } from "./config.ts";
|
|
23
23
|
import { loadRegistry, getProjectEntry } from "./registry.ts";
|
|
24
24
|
import { checkNotesCap, checkProjectCap, overBudgetProjects } from "./caps.ts";
|
|
25
|
-
import { backupFile, snapshotOnDrop, appendAudit, countTodosInFile } from "./backup.ts";
|
|
25
|
+
import { backupFile, snapshotOnDrop, appendAudit, countTodosInFile, writeWipeAlert } from "./backup.ts";
|
|
26
26
|
|
|
27
27
|
export type Priority = "low" | "med" | "high" | "critical";
|
|
28
28
|
export type Status = "open" | "in_progress" | "parked" | "done" | "cancelled";
|
|
@@ -168,6 +168,7 @@ export function saveStore(store: Store): void {
|
|
|
168
168
|
}
|
|
169
169
|
renameSync(tmp, path);
|
|
170
170
|
appendAudit("todo", before, after, dropSnap);
|
|
171
|
+
if (after < before) writeWipeAlert(before, after, dropSnap);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
function assertPriority(p: unknown): asserts p is Priority {
|