ruvnet-brain 4.5.15 → 4.5.17

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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/bin/install.mjs +1 -1
  3. package/config/practical-rule-catalog.json +111 -0
  4. package/package.json +2 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/hooks/codex-hooks.json +13 -1
  8. package/plugin/hooks/hook-contracts.json +20 -4
  9. package/plugin/scripts/adr-currency-gate.mjs +75 -31
  10. package/plugin/scripts/advocacy-outcomes.mjs +18 -4
  11. package/plugin/scripts/agentdb-recall.mjs +161 -26
  12. package/plugin/scripts/capability-inventory-receipt.mjs +16 -7
  13. package/plugin/scripts/capability-registry.mjs +55 -114
  14. package/plugin/scripts/capacity-aware-parallel-work.mjs +1 -1
  15. package/plugin/scripts/codex-hook-adapter.mjs +33 -6
  16. package/plugin/scripts/codex-hook-wrapper.mjs +23 -3
  17. package/plugin/scripts/continuity-brief.mjs +55 -22
  18. package/plugin/scripts/continuity-events.mjs +93 -17
  19. package/plugin/scripts/continuity-hook-policy.mjs +1 -1
  20. package/plugin/scripts/continuity-journal.mjs +77 -29
  21. package/plugin/scripts/decision-gate.mjs +63 -22
  22. package/plugin/scripts/decision-outcomes.mjs +192 -199
  23. package/plugin/scripts/detach.mjs +53 -13
  24. package/plugin/scripts/doc-currency.mjs +993 -0
  25. package/plugin/scripts/ground-ruvnet.sh +16 -3
  26. package/plugin/scripts/grounding-answer.mjs +18 -16
  27. package/plugin/scripts/grounding-stamp.sh +3 -0
  28. package/plugin/scripts/grounding-turn-evidence.mjs +60 -65
  29. package/plugin/scripts/grounding-turn-gate.mjs +34 -29
  30. package/plugin/scripts/grounding-turn-mark.mjs +101 -17
  31. package/plugin/scripts/hook-context-budget.mjs +90 -0
  32. package/plugin/scripts/hook-registry.mjs +129 -1
  33. package/plugin/scripts/hook-shim.mjs +20 -2
  34. package/plugin/scripts/lesson-gate.mjs +1 -1
  35. package/plugin/scripts/project-capture-queue.mjs +22 -15
  36. package/plugin/scripts/project-progression-producer.mjs +15 -8
  37. package/plugin/scripts/project-progression-reader.mjs +14 -4
  38. package/plugin/scripts/project-progression-session-start.mjs +29 -17
  39. package/plugin/scripts/project-progression-sources.mjs +24 -10
  40. package/plugin/scripts/project-progression-store.mjs +7 -2
  41. package/plugin/scripts/project-store-resolver.mjs +10 -7
  42. package/plugin/scripts/project-transition-hook.mjs +20 -12
  43. package/plugin/scripts/session-snapshot-hook.mjs +20 -9
  44. package/plugin/scripts/session-start-budget.mjs +18 -0
  45. package/plugin/scripts/session-start-core.mjs +32 -6
  46. package/plugin/scripts/session-start-trace.mjs +2 -1
  47. package/plugin/scripts/turn-outcome-capture.mjs +7 -5
  48. package/plugin/scripts/unprompted-runtime.mjs +36 -10
  49. package/plugin/scripts/user-settings.mjs +7 -1
  50. package/scripts/claude-controlled-terminal.mjs +119 -33
  51. package/scripts/codex-managed-terminal.mjs +76 -8
  52. package/scripts/doc-currency.mjs +6 -990
  53. package/scripts/hook-qualify-core.mjs +1 -1
  54. package/scripts/model-managed-prompt.mjs +17 -4
  55. package/scripts/model-managed-workflow-service.mjs +121 -15
  56. package/scripts/model-routing-controller.mjs +112 -21
  57. package/scripts/model-routing-execution-adapters.mjs +10 -5
  58. package/scripts/model-routing-gateway.mjs +1 -1
  59. package/scripts/model-terminal-launchers.mjs +1 -0
  60. package/scripts/practical-rule-selector.mjs +51 -0
  61. package/scripts/product-integrity-contract.mjs +11 -4
  62. package/scripts/source-scope-receipt.mjs +9 -4
@@ -41,7 +41,7 @@
41
41
  import fs from 'node:fs';
42
42
  import os from 'node:os';
43
43
  import path from 'node:path';
44
- import { spawn } from 'node:child_process';
44
+ import { spawn, spawnSync } from 'node:child_process';
45
45
  import { fileURLToPath } from 'node:url';
46
46
 
47
47
  const SELF = fileURLToPath(import.meta.url);
@@ -73,7 +73,8 @@ function receipt(row) {
73
73
  const p = receiptPath();
74
74
  fs.mkdirSync(path.dirname(p), { recursive: true });
75
75
  fs.appendFileSync(p, `${JSON.stringify({ ts: new Date().toISOString(), ...row })}\n`);
76
- } catch { /* a maintenance job must not die because its log directory is read-only */ }
76
+ return true;
77
+ } catch { return false; }
77
78
  }
78
79
 
79
80
  /** Open the job's log, or /dev/null. Never throws — an unwritable log is not a reason to skip work. */
@@ -149,6 +150,8 @@ if (!SUPERVISOR) {
149
150
  // ── SUPERVISOR HALF (already outside the session's process group). Runs the real job, holds the
150
151
  // clock, and is the only thing that can end it early.
151
152
  const out = openLog();
153
+ // Keep an unresolved retirement requirement if any later receipt cannot be written.
154
+ if (!receipt({ state: 'launching', supervisorPid: process.pid, ttlSec, cmd, retirementRequired: true })) process.exit(0);
152
155
  const job = spawn(cmd[0], cmd.slice(1), {
153
156
  detached: true, // its own group again, so the TTL kill reaches ITS children too (npm, git, node)
154
157
  stdio: ['ignore', out, out],
@@ -157,26 +160,63 @@ const job = spawn(cmd[0], cmd.slice(1), {
157
160
  });
158
161
 
159
162
  let killedAtTtl = false;
160
- receipt({ state: 'started', pid: job.pid ?? null, ttlSec, cmd });
163
+ let finished = false;
164
+ let grace;
165
+ const probe = (group = process.platform !== 'win32') => {
166
+ if (!job.pid) return { state: 'unknown', error: 'missing-job-pid' };
167
+ try { process.kill(group ? -job.pid : job.pid, 0); return { state: 'alive' }; }
168
+ catch (error) { return error.code === 'ESRCH' ? { state: 'gone' } : { state: 'unknown', error: error.code || 'probe-failed' }; }
169
+ };
170
+ const finish = (state, detail = {}) => {
171
+ if (finished) return;
172
+ finished = true; clearTimeout(deadline); clearTimeout(grace);
173
+ // Group absence cannot certify descendants that escaped into another group. Keep that fence.
174
+ receipt({ state, pid: job.pid ?? null, supervisorPid: process.pid, ttlSec, cmd,
175
+ retirementRequired: true, retirementConfirmed: false, retirementState: 'UNKNOWN',
176
+ scope: process.platform === 'win32' ? 'windows-taskkill-tree-attempt' : 'posix-process-group', ...detail });
177
+ process.exit(0);
178
+ };
179
+ receipt({ state: 'started', pid: job.pid ?? null, supervisorPid: process.pid, ttlSec, cmd, retirementRequired: true });
161
180
 
162
181
  const deadline = setTimeout(() => {
163
182
  killedAtTtl = true;
164
- try { process.kill(-job.pid, 'SIGTERM'); } catch { /* already gone */ }
165
- setTimeout(() => {
166
- try { process.kill(-job.pid, 'SIGKILL'); } catch { /* already gone */ }
167
- receipt({ state: 'killed-at-ttl', pid: job.pid ?? null, ttlSec, cmd });
168
- process.exit(0);
169
- }, GRACE_MS).unref();
183
+ if (process.platform === 'win32') {
184
+ if (job.exitCode !== null || job.signalCode !== null) return finish('retirement-unknown', { reason: 'parent-exited; descendant identity unavailable', rootObservation: probe(false) });
185
+ const killed = spawnSync('taskkill', ['/PID', String(job.pid), '/T', '/F'], {
186
+ timeout: 1000, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 });
187
+ return finish('retirement-unknown', { reason: 'complete Windows descendant retirement not observed',
188
+ taskkill: { status: killed.status, error: killed.error?.code || null }, rootObservation: probe(false) });
189
+ }
190
+ const errors = [];
191
+ const signal = name => {
192
+ try { process.kill(-job.pid, name); }
193
+ catch (error) { if (error.code !== 'ESRCH') errors.push({ signal: name, error: error.code || 'kill-failed' }); }
194
+ };
195
+ signal('SIGTERM');
196
+ grace = setTimeout(() => {
197
+ if (probe().state !== 'gone') signal('SIGKILL');
198
+ // Ref'd timers retain supervision after the direct parent exits; observe native reaping too.
199
+ grace = setTimeout(() => {
200
+ const groupObservation = probe();
201
+ finish(groupObservation.state === 'gone' && !errors.length ? 'group-retired-at-ttl' : 'retirement-unknown',
202
+ { groupObservation, groupRetirementConfirmed: groupObservation.state === 'gone', killErrors: errors,
203
+ reason: 'escaped or unobserved descendants are not certified' });
204
+ }, 100);
205
+ }, GRACE_MS);
170
206
  }, ttlSec * 1000);
171
207
 
172
208
  job.on('error', (e) => {
173
209
  clearTimeout(deadline);
174
- receipt({ state: 'spawn-failed', pid: null, ttlSec, cmd, detail: e.message });
210
+ receipt({ state: 'spawn-failed', pid: null, ttlSec, cmd, detail: e.message, retirementRequired: false, launched: false });
175
211
  process.exit(0);
176
212
  });
177
213
  job.on('exit', (code, signal) => {
178
- clearTimeout(deadline);
179
214
  if (killedAtTtl) return; // the TTL path writes its own, more specific receipt
180
- receipt({ state: 'exited', pid: job.pid ?? null, code, signal, cmd });
181
- process.exit(0);
215
+ receipt({ state: 'parent-exited', pid: job.pid ?? null, code, signal, cmd, retirementRequired: true, retirementConfirmed: false });
216
+ if (process.platform === 'win32') return finish('retirement-unknown', { reason: 'parent exit does not establish descendant retirement', rootObservation: probe(false) });
217
+ const groupObservation = probe();
218
+ if (groupObservation.state === 'gone') finish('group-retired', { code, signal, groupObservation, groupRetirementConfirmed: true,
219
+ reason: 'escaped or unobserved descendants are not certified' });
220
+ else if (groupObservation.state === 'unknown') finish('retirement-unknown', { code, signal, groupObservation });
221
+ // A surviving owned group stays supervised until the original TTL.
182
222
  });