greprag 5.45.0 → 5.47.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/dist/commands/arm-reminder.d.ts +25 -0
- package/dist/commands/arm-reminder.js +65 -0
- package/dist/commands/arm-reminder.js.map +1 -0
- package/dist/commands/email.js +20 -2
- package/dist/commands/email.js.map +1 -1
- package/dist/commands/fix.d.ts +20 -2
- package/dist/commands/fix.js +21 -4
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/friction-reminder.d.ts +92 -0
- package/dist/commands/friction-reminder.js +147 -0
- package/dist/commands/friction-reminder.js.map +1 -0
- package/dist/commands/init.js +20 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mechanic.d.ts +18 -0
- package/dist/commands/mechanic.js +77 -9
- package/dist/commands/mechanic.js.map +1 -1
- package/dist/commands/memory-format.d.ts +11 -2
- package/dist/commands/memory-format.js +22 -8
- package/dist/commands/memory-format.js.map +1 -1
- package/dist/commands/memory.d.ts +44 -0
- package/dist/commands/memory.js +73 -9
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/poll-registry.d.ts +9 -52
- package/dist/commands/poll-registry.js +9 -176
- package/dist/commands/poll-registry.js.map +1 -1
- package/dist/commands/reminder-registry.d.ts +20 -0
- package/dist/commands/reminder-registry.js +59 -0
- package/dist/commands/reminder-registry.js.map +1 -0
- package/dist/commands/reminder-types.d.ts +49 -0
- package/dist/commands/reminder-types.js +8 -0
- package/dist/commands/reminder-types.js.map +1 -0
- package/dist/hook.js +174 -77
- package/dist/hook.js.map +1 -1
- package/dist/inbox-attachments.d.ts +37 -0
- package/dist/inbox-attachments.js +64 -0
- package/dist/inbox-attachments.js.map +1 -0
- package/dist/index.js +16 -70
- package/dist/index.js.map +1 -1
- package/dist/session-id.d.ts +2 -2
- package/dist/session-id.js +2 -2
- package/dist/session-id.js.map +1 -1
- package/dist/worktree-state.d.ts +14 -0
- package/dist/worktree-state.js +61 -0
- package/dist/worktree-state.js.map +1 -0
- package/package.json +1 -1
- package/skill/mechanic/SKILL.md +22 -10
- package/skill/templates/chip-spawn.md +1 -1
- package/skill/templates/reflex-chip.md +58 -0
|
@@ -1,56 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* polls (`greprag inbox poll`), kept in its OWN namespace `~/.greprag/polls/`
|
|
3
|
-
* so it NEVER touches the Monitor watcher's `~/.greprag/watchers/` registry.
|
|
1
|
+
/** Inbox cursor store — the per-session `~/.greprag/polls/<short>.cursor` file.
|
|
4
2
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
* `capSurplusVerdict`): per session, keep the K freshest LIVE polls, reap only
|
|
13
|
-
* the surplus, never below K. No death-judgment — a count + a floor — so it can
|
|
14
|
-
* never false-kill the live poll. Reload-orphans (if asyncRewake does not adopt
|
|
15
|
-
* a process across reload) are bounded here AND self-clean via the poll's own
|
|
16
|
-
* max-lifetime ceiling. */
|
|
17
|
-
/** Default per-session poll floor: keep this many freshest live polls, reap the
|
|
18
|
-
* surplus, never below it. K=2 = the live one + one margin (a reload overlap). */
|
|
19
|
-
export declare const DEFAULT_POLL_CAP = 2;
|
|
20
|
-
export interface PollRecord {
|
|
21
|
-
short: string;
|
|
22
|
-
pid: number;
|
|
23
|
-
startedAt: number;
|
|
24
|
-
}
|
|
25
|
-
/** APPEND this poll's entry (pruning dead entries as it goes). Best-effort. */
|
|
26
|
-
export declare function registerPoll(short: string, pid: number): void;
|
|
27
|
-
/** Remove THIS poll's entry (on the poll's own exit — message-wake or ceiling). */
|
|
28
|
-
export declare function deregisterPoll(short: string, pid: number): void;
|
|
29
|
-
/** Does this session have ANY live poll right now? The re-arm gate: a gated arm
|
|
30
|
-
* (PostToolUse / UserPromptSubmit) skips when true so per-tool fires don't pile
|
|
31
|
-
* up; SessionStart never gates (it always arms a fresh, reload-adopted poll).
|
|
32
|
-
* Sweeps dead entries as it reads. */
|
|
33
|
-
export declare function isPollArmed(short: string): boolean;
|
|
34
|
-
/** Every live poll on this machine, freshest first. */
|
|
35
|
-
export declare function listLivePolls(): PollRecord[];
|
|
36
|
-
/** Last message id this session's polls have seen, or null (first poll → live
|
|
37
|
-
* tail only; no replay of pre-existing history). */
|
|
3
|
+
* HISTORY: this module WAS the asyncRewake idle-liveness POLL registry (pidfiles,
|
|
4
|
+
* count-cap, liveness probes). The poll was REMOVED — it cost ~1 long-lived node
|
|
5
|
+
* process per session (~1.1 GB across the operator's concurrent sessions); the
|
|
6
|
+
* Monitor watcher + the SessionStart drain are the delivery path. See
|
|
7
|
+
* adr/monitor-resilience.md. Only the cursor helpers survive, still used by the
|
|
8
|
+
* `drain` hook to track a stable last-seen id. */
|
|
9
|
+
/** Last message id this session has seen, or null (first read → live tail only). */
|
|
38
10
|
export declare function readPollCursor(short: string): string | null;
|
|
39
11
|
export declare function writePollCursor(short: string, id: string): void;
|
|
40
|
-
/** Drop the cursor (→ next
|
|
41
|
-
* rejects the stored `since` (the message it points to was pruned/deleted): a
|
|
42
|
-
* stale cursor would otherwise 4xx forever, permanently wedging delivery for
|
|
43
|
-
* this session. Clearing it trades replay of the gap for restored liveness. */
|
|
12
|
+
/** Drop the cursor (→ next read resumes from the live tail). */
|
|
44
13
|
export declare function clearPollCursor(short: string): void;
|
|
45
|
-
export interface PollCapResult {
|
|
46
|
-
scanned: number;
|
|
47
|
-
surplus: number;
|
|
48
|
-
killed: number[];
|
|
49
|
-
}
|
|
50
|
-
/** Count-cap every session's polls: keep the K freshest LIVE polls, kill only the
|
|
51
|
-
* surplus, never below K. Snapshot-free (per-session file reads + pidAlive); a
|
|
52
|
-
* `taskkill` only on genuine surplus. Run at SessionStart (recap hook) and via
|
|
53
|
-
* `greprag inbox poll-reap`. Reuses the watcher's pure `capSurplusVerdict` — the
|
|
54
|
-
* same liveness-safety guarantee, applied to the poll namespace.
|
|
55
|
-
* adr: adr/monitor-resilience.md */
|
|
56
|
-
export declare function reapSurplusPolls(K?: number): PollCapResult;
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* polls (`greprag inbox poll`), kept in its OWN namespace `~/.greprag/polls/`
|
|
4
|
-
* so it NEVER touches the Monitor watcher's `~/.greprag/watchers/` registry.
|
|
2
|
+
/** Inbox cursor store — the per-session `~/.greprag/polls/<short>.cursor` file.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* The cleanup model mirrors the SHIPPED watcher count-cap (and reuses its pure
|
|
13
|
-
* `capSurplusVerdict`): per session, keep the K freshest LIVE polls, reap only
|
|
14
|
-
* the surplus, never below K. No death-judgment — a count + a floor — so it can
|
|
15
|
-
* never false-kill the live poll. Reload-orphans (if asyncRewake does not adopt
|
|
16
|
-
* a process across reload) are bounded here AND self-clean via the poll's own
|
|
17
|
-
* max-lifetime ceiling. */
|
|
4
|
+
* HISTORY: this module WAS the asyncRewake idle-liveness POLL registry (pidfiles,
|
|
5
|
+
* count-cap, liveness probes). The poll was REMOVED — it cost ~1 long-lived node
|
|
6
|
+
* process per session (~1.1 GB across the operator's concurrent sessions); the
|
|
7
|
+
* Monitor watcher + the SessionStart drain are the delivery path. See
|
|
8
|
+
* adr/monitor-resilience.md. Only the cursor helpers survive, still used by the
|
|
9
|
+
* `drain` hook to track a stable last-seen id. */
|
|
18
10
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
11
|
if (k2 === undefined) k2 = k;
|
|
20
12
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -49,23 +41,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
49
41
|
};
|
|
50
42
|
})();
|
|
51
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
-
exports.DEFAULT_POLL_CAP = void 0;
|
|
53
|
-
exports.registerPoll = registerPoll;
|
|
54
|
-
exports.deregisterPoll = deregisterPoll;
|
|
55
|
-
exports.isPollArmed = isPollArmed;
|
|
56
|
-
exports.listLivePolls = listLivePolls;
|
|
57
44
|
exports.readPollCursor = readPollCursor;
|
|
58
45
|
exports.writePollCursor = writePollCursor;
|
|
59
46
|
exports.clearPollCursor = clearPollCursor;
|
|
60
|
-
exports.reapSurplusPolls = reapSurplusPolls;
|
|
61
47
|
const fs = __importStar(require("fs"));
|
|
62
48
|
const path = __importStar(require("path"));
|
|
63
|
-
const child_process_1 = require("child_process");
|
|
64
|
-
const watcher_registry_1 = require("./watcher-registry");
|
|
65
49
|
const POLL_DIRNAME = 'polls';
|
|
66
|
-
/** Default per-session poll floor: keep this many freshest live polls, reap the
|
|
67
|
-
* surplus, never below it. K=2 = the live one + one margin (a reload overlap). */
|
|
68
|
-
exports.DEFAULT_POLL_CAP = 2;
|
|
69
50
|
function grepragHome() {
|
|
70
51
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
71
52
|
return home ? path.join(home, '.greprag') : null;
|
|
@@ -74,105 +55,11 @@ function pollsDir() {
|
|
|
74
55
|
const h = grepragHome();
|
|
75
56
|
return h ? path.join(h, POLL_DIRNAME) : null;
|
|
76
57
|
}
|
|
77
|
-
function pollfilePath(short) {
|
|
78
|
-
const dir = pollsDir();
|
|
79
|
-
return dir ? path.join(dir, `${short}.json`) : null;
|
|
80
|
-
}
|
|
81
|
-
/** True iff `pid` is a live process. `process.kill(pid, 0)` probes existence
|
|
82
|
-
* without signalling; EPERM = exists (another user), ESRCH = gone. */
|
|
83
|
-
function pidAlive(pid) {
|
|
84
|
-
if (!Number.isFinite(pid) || pid <= 0)
|
|
85
|
-
return false;
|
|
86
|
-
try {
|
|
87
|
-
process.kill(pid, 0);
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
catch (e) {
|
|
91
|
-
return e?.code === 'EPERM';
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function readPollEntries(short) {
|
|
95
|
-
try {
|
|
96
|
-
const p = pollfilePath(short);
|
|
97
|
-
if (!p)
|
|
98
|
-
return [];
|
|
99
|
-
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
100
|
-
const arr = Array.isArray(parsed) ? parsed : [parsed];
|
|
101
|
-
return arr.filter((r) => !!r && typeof r.pid === 'number');
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function writePollEntries(short, entries) {
|
|
108
|
-
try {
|
|
109
|
-
const dir = pollsDir();
|
|
110
|
-
if (!dir)
|
|
111
|
-
return;
|
|
112
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
113
|
-
const p = path.join(dir, `${short}.json`);
|
|
114
|
-
if (entries.length === 0) {
|
|
115
|
-
fs.rmSync(p, { force: true });
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
fs.writeFileSync(p, JSON.stringify(entries));
|
|
119
|
-
}
|
|
120
|
-
catch { /* best-effort — a failed write only means a re-arm, never a crash */ }
|
|
121
|
-
}
|
|
122
|
-
function allShorts() {
|
|
123
|
-
try {
|
|
124
|
-
const dir = pollsDir();
|
|
125
|
-
if (!dir || !fs.existsSync(dir))
|
|
126
|
-
return [];
|
|
127
|
-
return fs.readdirSync(dir).filter(f => f.endsWith('.json')).map(f => f.slice(0, -5));
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/** APPEND this poll's entry (pruning dead entries as it goes). Best-effort. */
|
|
134
|
-
function registerPoll(short, pid) {
|
|
135
|
-
const entries = readPollEntries(short).filter(r => r.pid !== pid && pidAlive(r.pid));
|
|
136
|
-
entries.push({ short, pid, startedAt: Date.now() });
|
|
137
|
-
writePollEntries(short, entries);
|
|
138
|
-
}
|
|
139
|
-
/** Remove THIS poll's entry (on the poll's own exit — message-wake or ceiling). */
|
|
140
|
-
function deregisterPoll(short, pid) {
|
|
141
|
-
writePollEntries(short, readPollEntries(short).filter(r => r.pid !== pid));
|
|
142
|
-
}
|
|
143
|
-
/** Does this session have ANY live poll right now? The re-arm gate: a gated arm
|
|
144
|
-
* (PostToolUse / UserPromptSubmit) skips when true so per-tool fires don't pile
|
|
145
|
-
* up; SessionStart never gates (it always arms a fresh, reload-adopted poll).
|
|
146
|
-
* Sweeps dead entries as it reads. */
|
|
147
|
-
function isPollArmed(short) {
|
|
148
|
-
const entries = readPollEntries(short);
|
|
149
|
-
const alive = entries.filter(r => pidAlive(r.pid));
|
|
150
|
-
if (alive.length !== entries.length)
|
|
151
|
-
writePollEntries(short, alive);
|
|
152
|
-
return alive.length > 0;
|
|
153
|
-
}
|
|
154
|
-
/** Every live poll on this machine, freshest first. */
|
|
155
|
-
function listLivePolls() {
|
|
156
|
-
const out = [];
|
|
157
|
-
for (const short of allShorts()) {
|
|
158
|
-
const entries = readPollEntries(short);
|
|
159
|
-
const alive = entries.filter(r => pidAlive(r.pid));
|
|
160
|
-
if (alive.length !== entries.length)
|
|
161
|
-
writePollEntries(short, alive);
|
|
162
|
-
out.push(...alive);
|
|
163
|
-
}
|
|
164
|
-
return out.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
|
|
165
|
-
}
|
|
166
|
-
// ---- Cursor (gap-recovery so the long-poll never loses a message) ----------
|
|
167
|
-
// Each poll resumes the SSE stream from the last id it persisted (`?since=`),
|
|
168
|
-
// so a message that arrives between one poll exiting and the next arming is
|
|
169
|
-
// REPLAYED, not lost. The cursor is per-session, shared across poll invocations.
|
|
170
58
|
function cursorPath(short) {
|
|
171
59
|
const dir = pollsDir();
|
|
172
60
|
return dir ? path.join(dir, `${short}.cursor`) : null;
|
|
173
61
|
}
|
|
174
|
-
/** Last message id this session
|
|
175
|
-
* tail only; no replay of pre-existing history). */
|
|
62
|
+
/** Last message id this session has seen, or null (first read → live tail only). */
|
|
176
63
|
function readPollCursor(short) {
|
|
177
64
|
try {
|
|
178
65
|
const p = cursorPath(short);
|
|
@@ -195,10 +82,7 @@ function writePollCursor(short, id) {
|
|
|
195
82
|
}
|
|
196
83
|
catch { /* best-effort — at worst one message is re-delivered */ }
|
|
197
84
|
}
|
|
198
|
-
/** Drop the cursor (→ next
|
|
199
|
-
* rejects the stored `since` (the message it points to was pruned/deleted): a
|
|
200
|
-
* stale cursor would otherwise 4xx forever, permanently wedging delivery for
|
|
201
|
-
* this session. Clearing it trades replay of the gap for restored liveness. */
|
|
85
|
+
/** Drop the cursor (→ next read resumes from the live tail). */
|
|
202
86
|
function clearPollCursor(short) {
|
|
203
87
|
try {
|
|
204
88
|
const p = cursorPath(short);
|
|
@@ -207,55 +91,4 @@ function clearPollCursor(short) {
|
|
|
207
91
|
}
|
|
208
92
|
catch { /* best-effort */ }
|
|
209
93
|
}
|
|
210
|
-
/** Count-cap every session's polls: keep the K freshest LIVE polls, kill only the
|
|
211
|
-
* surplus, never below K. Snapshot-free (per-session file reads + pidAlive); a
|
|
212
|
-
* `taskkill` only on genuine surplus. Run at SessionStart (recap hook) and via
|
|
213
|
-
* `greprag inbox poll-reap`. Reuses the watcher's pure `capSurplusVerdict` — the
|
|
214
|
-
* same liveness-safety guarantee, applied to the poll namespace.
|
|
215
|
-
* adr: adr/monitor-resilience.md */
|
|
216
|
-
function reapSurplusPolls(K = exports.DEFAULT_POLL_CAP) {
|
|
217
|
-
let scanned = 0;
|
|
218
|
-
let surplus = 0;
|
|
219
|
-
const killed = [];
|
|
220
|
-
for (const short of allShorts()) {
|
|
221
|
-
const alive = readPollEntries(short).filter(r => pidAlive(r.pid));
|
|
222
|
-
scanned += alive.length;
|
|
223
|
-
const { keep, kill } = (0, watcher_registry_1.capSurplusVerdict)(alive, K);
|
|
224
|
-
if (kill.length === 0) {
|
|
225
|
-
writePollEntries(short, alive);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
surplus += kill.length;
|
|
229
|
-
const survivors = keep.slice();
|
|
230
|
-
for (const w of kill) {
|
|
231
|
-
if (killProcess(w.pid))
|
|
232
|
-
killed.push(w.pid);
|
|
233
|
-
else
|
|
234
|
-
survivors.push(w); // kill failed → keep tracking, retry next pass
|
|
235
|
-
}
|
|
236
|
-
writePollEntries(short, survivors);
|
|
237
|
-
}
|
|
238
|
-
return { scanned, surplus, killed };
|
|
239
|
-
}
|
|
240
|
-
/** Force-kill a poll process. A poll has no children (single SSE process), so a
|
|
241
|
-
* plain kill suffices — but `taskkill /T` is used on Windows for symmetry with
|
|
242
|
-
* the watcher and to catch any incidental child. */
|
|
243
|
-
function killProcess(pid) {
|
|
244
|
-
try {
|
|
245
|
-
if (process.platform === 'win32') {
|
|
246
|
-
(0, child_process_1.execFileSync)('taskkill.exe', ['/PID', String(pid), '/T', '/F'], // proc-allow: poll count-cap surplus kill, mirrors watcher-registry
|
|
247
|
-
{ timeout: 5_000, stdio: 'ignore', windowsHide: true });
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
try {
|
|
251
|
-
process.kill(pid, 'SIGKILL');
|
|
252
|
-
}
|
|
253
|
-
catch { /* already gone */ }
|
|
254
|
-
}
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
94
|
//# sourceMappingURL=poll-registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poll-registry.js","sourceRoot":"","sources":["../../src/commands/poll-registry.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"poll-registry.js","sourceRoot":"","sources":["../../src/commands/poll-registry.ts"],"names":[],"mappings":";AAAA;;;;;;;mDAOmD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBnD,wCAOC;AAED,0CAOC;AAGD,0CAKC;AA7CD,uCAAyB;AACzB,2CAA6B;AAE7B,MAAM,YAAY,GAAG,OAAO,CAAC;AAE7B,SAAS,WAAW;IAClB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;IAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,SAAS,QAAQ;IACf,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED,oFAAoF;AACpF,SAAgB,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,OAAO,CAAC,IAAI,IAAI,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAC1B,CAAC;AAED,SAAgB,eAAe,CAAC,KAAa,EAAE,EAAU;IACvD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE;YAAE,OAAO;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC,CAAC,wDAAwD,CAAC,CAAC;AACtE,CAAC;AAED,gEAAgE;AAChE,SAAgB,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC;YAAE,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The reminder-interrupt registry CONTAINER (docs/reminder-interrupt.md §Registry
|
|
2
|
+
* spec). One loop over all modules: per turn, fire each module's thin reminder while
|
|
3
|
+
* its detector says deficient; at SessionStart, emit each announce. Stacking welcome.
|
|
4
|
+
* PURE over ReminderEnv — the hook assembles env (i/o) and emits the returned lines;
|
|
5
|
+
* a broken module never blocks a turn (fail-open per module). */
|
|
6
|
+
import { ReminderEnv, ReminderModule, Detection } from './reminder-types';
|
|
7
|
+
/** Registry order = display order. Detector-gated modules first (watcher-arm is the
|
|
8
|
+
* reference), graded/ambient after. Add a module here to wire it everywhere. */
|
|
9
|
+
export declare const REGISTRY: ReminderModule[];
|
|
10
|
+
export interface FiredReminder {
|
|
11
|
+
id: string;
|
|
12
|
+
tier: Detection['tier'];
|
|
13
|
+
line: string;
|
|
14
|
+
}
|
|
15
|
+
/** Per-turn: every module's live reminder line (skipping silent), in registry order.
|
|
16
|
+
* Stacking — a turn may carry several. A detector or reminder that throws drops that
|
|
17
|
+
* module for the turn; it never blocks the others or the turn. */
|
|
18
|
+
export declare function collectReminders(env: ReminderEnv, registry?: ReminderModule[]): FiredReminder[];
|
|
19
|
+
/** SessionStart: every module's announce (skipping null), in registry order. */
|
|
20
|
+
export declare function collectAnnounces(env: ReminderEnv, registry?: ReminderModule[]): string[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** The reminder-interrupt registry CONTAINER (docs/reminder-interrupt.md §Registry
|
|
3
|
+
* spec). One loop over all modules: per turn, fire each module's thin reminder while
|
|
4
|
+
* its detector says deficient; at SessionStart, emit each announce. Stacking welcome.
|
|
5
|
+
* PURE over ReminderEnv — the hook assembles env (i/o) and emits the returned lines;
|
|
6
|
+
* a broken module never blocks a turn (fail-open per module). */
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.REGISTRY = void 0;
|
|
9
|
+
exports.collectReminders = collectReminders;
|
|
10
|
+
exports.collectAnnounces = collectAnnounces;
|
|
11
|
+
const arm_reminder_1 = require("./arm-reminder");
|
|
12
|
+
const friction_reminder_1 = require("./friction-reminder");
|
|
13
|
+
/** Registry order = display order. Detector-gated modules first (watcher-arm is the
|
|
14
|
+
* reference), graded/ambient after. Add a module here to wire it everywhere. */
|
|
15
|
+
exports.REGISTRY = [arm_reminder_1.watcherArmModule, friction_reminder_1.mechanicFrictionModule];
|
|
16
|
+
/** Per-turn: every module's live reminder line (skipping silent), in registry order.
|
|
17
|
+
* Stacking — a turn may carry several. A detector or reminder that throws drops that
|
|
18
|
+
* module for the turn; it never blocks the others or the turn. */
|
|
19
|
+
function collectReminders(env, registry = exports.REGISTRY) {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const m of registry) {
|
|
22
|
+
let d;
|
|
23
|
+
try {
|
|
24
|
+
d = m.detect(env);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!d || d.tier === 'silent')
|
|
30
|
+
continue;
|
|
31
|
+
let line = null;
|
|
32
|
+
try {
|
|
33
|
+
line = m.reminder(d, env);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (line)
|
|
39
|
+
out.push({ id: m.id, tier: d.tier, line });
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** SessionStart: every module's announce (skipping null), in registry order. */
|
|
44
|
+
function collectAnnounces(env, registry = exports.REGISTRY) {
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const m of registry) {
|
|
47
|
+
let a = null;
|
|
48
|
+
try {
|
|
49
|
+
a = m.announce(env);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (a)
|
|
55
|
+
out.push(a);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=reminder-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reminder-registry.js","sourceRoot":"","sources":["../../src/commands/reminder-registry.ts"],"names":[],"mappings":";AAAA;;;;kEAIkE;;;AAmBlE,4CAaC;AAGD,4CAUC;AA1CD,iDAAkD;AAClD,2DAA6D;AAE7D;iFACiF;AACpE,QAAA,QAAQ,GAAqB,CAAC,+BAAgB,EAAE,0CAAsB,CAAC,CAAC;AAQrF;;mEAEmE;AACnE,SAAgB,gBAAgB,CAC9B,GAAgB,EAAE,WAA6B,gBAAQ;IAEvD,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAY,CAAC;QACjB,IAAI,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QAC9C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QACxC,IAAI,IAAI,GAAkB,IAAI,CAAC;QAC/B,IAAI,CAAC;YAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QACtD,IAAI,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,SAAgB,gBAAgB,CAC9B,GAAgB,EAAE,WAA6B,gBAAQ;IAEvD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,GAAkB,IAAI,CAAC;QAC5B,IAAI,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QAChD,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Shared types for the reminder-interrupt registry (docs/reminder-interrupt.md
|
|
2
|
+
* §Registry spec — the detector model). Modeled on the WORKING arm-check loop:
|
|
3
|
+
* detect a live external deficiency → fire while it holds → auto-silence the
|
|
4
|
+
* instant it resolves. `detect(env)` is the primitive (the arm-check's
|
|
5
|
+
* `isLocallyArmed`, generalized); a turn counter is not. */
|
|
6
|
+
/** Firing-tier ladder. `silent` = the deficiency is resolved (the auto-stop); the
|
|
7
|
+
* rest escalate wording intensity with the cost of inaction (ambient = soft
|
|
8
|
+
* pointer, nudge = imperative + reason, nag = LOUD forced-decision). */
|
|
9
|
+
export type Tier = 'silent' | 'ambient' | 'nudge' | 'nag';
|
|
10
|
+
/** The live-state bag the hook assembles ONCE per turn — ALL i/o lives in the hook
|
|
11
|
+
* so modules stay pure (and their detect→silent transition stays unit-testable).
|
|
12
|
+
* Grows as modules need new signals. */
|
|
13
|
+
export interface ReminderEnv {
|
|
14
|
+
/** 8-hex session id. */
|
|
15
|
+
short: string;
|
|
16
|
+
/** Turns elapsed this session (for any ambient-cadence module). */
|
|
17
|
+
turnCount: number;
|
|
18
|
+
/** Live behavioral stress (docs/stress-trigger.md): 0 calm / 1 elevated / 2 high. */
|
|
19
|
+
stress: number;
|
|
20
|
+
/** Live local watcher present? (isLocallyArmed) — the arm-check's signal. */
|
|
21
|
+
armed: boolean;
|
|
22
|
+
/** Unread messages addressed to THIS session (own mail — NOT the project-wide
|
|
23
|
+
* v5.6.1 eavesdrop count). */
|
|
24
|
+
sessionUnread: number;
|
|
25
|
+
/** Owning claude.exe PID — baked into the arm command. */
|
|
26
|
+
ownerPid?: number | null;
|
|
27
|
+
/** Identity handle (alias before @) for reply-address templating. */
|
|
28
|
+
alias?: string | null;
|
|
29
|
+
/** Assistant project → elevated watcher arm. */
|
|
30
|
+
assistant?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** A module's per-turn read of its deficiency. `tier:'silent'` = resolved → no fire. */
|
|
33
|
+
export interface Detection {
|
|
34
|
+
tier: Tier;
|
|
35
|
+
/** Optional grounding data the reminder line interpolates (e.g. unread count). */
|
|
36
|
+
detail?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
/** A reminder-interrupt registry module (docs/reminder-interrupt.md §Registry spec).
|
|
39
|
+
* PURE over ReminderEnv — the container/hook owns all i/o, so detect→silent is the
|
|
40
|
+
* unit-testable regression-lock. */
|
|
41
|
+
export interface ReminderModule {
|
|
42
|
+
id: string;
|
|
43
|
+
/** Read the live deficiency. The gate: `silent` ⇒ the module is quiet this turn. */
|
|
44
|
+
detect: (env: ReminderEnv) => Detection;
|
|
45
|
+
/** SessionStart schema, loaded once (teach method + why). null = nothing to announce. */
|
|
46
|
+
announce: (env: ReminderEnv) => string | null;
|
|
47
|
+
/** Per-turn thin line for a live detection; null when silent. */
|
|
48
|
+
reminder: (d: Detection, env: ReminderEnv) => string | null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Shared types for the reminder-interrupt registry (docs/reminder-interrupt.md
|
|
3
|
+
* §Registry spec — the detector model). Modeled on the WORKING arm-check loop:
|
|
4
|
+
* detect a live external deficiency → fire while it holds → auto-silence the
|
|
5
|
+
* instant it resolves. `detect(env)` is the primitive (the arm-check's
|
|
6
|
+
* `isLocallyArmed`, generalized); a turn counter is not. */
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
//# sourceMappingURL=reminder-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reminder-types.js","sourceRoot":"","sources":["../../src/commands/reminder-types.ts"],"names":[],"mappings":";AAAA;;;;6DAI6D"}
|