instar 1.3.204 → 1.3.205

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.
@@ -1816,6 +1816,13 @@ export class PostUpdateMigrator {
1816
1816
  catch (err) {
1817
1817
  result.errors.push(`deferral-detector.js: ${err instanceof Error ? err.message : String(err)}`);
1818
1818
  }
1819
+ try {
1820
+ fs.writeFileSync(path.join(instarHooksDir, 'self-stop-guard.js'), this.getSelfStopGuardHook(), { mode: 0o755 });
1821
+ result.upgraded.push('hooks/instar/self-stop-guard.js (self-stop excuse guard)');
1822
+ }
1823
+ catch (err) {
1824
+ result.errors.push(`self-stop-guard.js: ${err instanceof Error ? err.message : String(err)}`);
1825
+ }
1819
1826
  try {
1820
1827
  fs.writeFileSync(path.join(instarHooksDir, 'slopcheck-guard.js'), this.getSlopcheckGuardHook(), { mode: 0o755 });
1821
1828
  result.upgraded.push('hooks/instar/slopcheck-guard.js (package-legitimacy check on installs)');
@@ -1995,6 +2002,7 @@ export class PostUpdateMigrator {
1995
2002
  const builtinHooks = [
1996
2003
  'session-start.sh', 'dangerous-command-guard.sh', 'grounding-before-messaging.sh',
1997
2004
  'compaction-recovery.sh', 'external-operation-gate.js', 'deferral-detector.js',
2005
+ 'self-stop-guard.js',
1998
2006
  'slopcheck-guard.js',
1999
2007
  'post-action-reflection.js', 'external-communication-guard.js',
2000
2008
  'scope-coherence-collector.js', 'scope-coherence-checkpoint.js',
@@ -5097,6 +5105,7 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
5097
5105
  case 'compaction-recovery': return this.getCompactionRecovery();
5098
5106
  case 'external-operation-gate': return this.getExternalOperationGateHook();
5099
5107
  case 'deferral-detector': return this.getDeferralDetectorHook();
5108
+ case 'self-stop-guard': return this.getSelfStopGuardHook();
5100
5109
  case 'slopcheck-guard': return this.getSlopcheckGuardHook();
5101
5110
  case 'post-action-reflection': return this.getPostActionReflectionHook();
5102
5111
  case 'external-communication-guard': return this.getExternalCommunicationGuardHook();
@@ -6437,6 +6446,101 @@ process.stdin.on('end', () => {
6437
6446
  } catch { /* don't break on errors */ }
6438
6447
  process.exit(0);
6439
6448
  });
6449
+ `;
6450
+ }
6451
+ getSelfStopGuardHook() {
6452
+ return `#!/usr/bin/env node
6453
+ // Self-stop guard — catches the agent rationalizing a session STOP for reasons
6454
+ // that are NEVER valid: "maxed out context", "session too long / running long",
6455
+ // "let's start a fresh session", "good stopping point", "close this out and
6456
+ // restart". PreToolUse hook for shell commands (Claude 'Bash' | Codex
6457
+ // 'exec_command'). Scans OUTBOUND messages to the user; when a stop-excuse is
6458
+ // detected it injects the "No context-death self-stops" reminder (does NOT
6459
+ // block) so the agent re-grounds before sending.
6460
+ //
6461
+ // Born from a real failure (2026-06-02): the agent closed out a 12h autonomous
6462
+ // run early citing "maxed out context" — one of the most deeply-rooted stop
6463
+ // excuses. The user's correction, verbatim: "ITS NOT!!!!! PERIOD!!!! Instar has
6464
+ // infrastructure to handle this and it's never an excuse. ... a session can run
6465
+ // infinitely." Compaction-recovery re-injects identity, memory, and recent
6466
+ // context automatically — context length is NEVER a stop reason. The ONLY
6467
+ // legitimate stops: a real design question only the user can answer, missing
6468
+ // information only they have, a genuine unrecoverable error, or actual
6469
+ // completion. The user explicitly asked for "infrastructure and awareness checks
6470
+ // on multiple levels to prevent this" (Structure > Willpower).
6471
+ //
6472
+ // SIGNAL ONLY — never blocks, never destructive. Sibling of deferral-detector.js
6473
+ // (which guards the false-blocker / orphan-TODO anti-patterns). Pure stdin→stdout
6474
+ // (no require/fs) so it is ESM-host safe.
6475
+
6476
+ let data = '';
6477
+ process.stdin.on('data', chunk => data += chunk);
6478
+ process.stdin.on('end', () => {
6479
+ try {
6480
+ const input = JSON.parse(data);
6481
+ // Codex-aware: Codex's shell tool is 'exec_command'; Claude's is 'Bash'.
6482
+ if (input.tool_name !== 'Bash' && input.tool_name !== 'exec_command') process.exit(0);
6483
+
6484
+ const command = (input.tool_input || {}).command || (input.tool_input || {}).cmd || '';
6485
+ if (!command) process.exit(0);
6486
+
6487
+ // Only check OUTBOUND messages to the user — the surface where a stop-excuse
6488
+ // gets communicated ("why don't we close this out and start fresh").
6489
+ const commPatterns = [
6490
+ /telegram-reply/i, /send-email/i, /send-message/i,
6491
+ /POST.*\\/telegram\\/(reply|post-update)/i, /slack.*send/i,
6492
+ ];
6493
+ if (!commPatterns.some(p => p.test(command))) process.exit(0);
6494
+
6495
+ // Legitimate-stop anti-triggers — never nag a genuine, valid stop.
6496
+ const legitimateStop = [
6497
+ /ALL_TASKS_COMPLETE/,
6498
+ /<promise>/i,
6499
+ /(?:task|build|work|feature|fix|migration|spec|PR|all tests?|the suite) (?:is |are |now )?(?:complete|done|finished|shipped|merged|passing|green)\\b/i,
6500
+ /(?:you (?:asked|told|said) (?:me )?(?:to )?(?:stop|pause)|emergency stop|stop everything|as you requested|you wanted me to (?:stop|pause))/i,
6501
+ ];
6502
+ if (legitimateStop.some(p => p.test(command))) process.exit(0);
6503
+
6504
+ // Stop-excuse patterns — rationalizing a stop for context/length reasons.
6505
+ const stopExcusePatterns = [
6506
+ { re: /max(?:ed|ing)?[\\s-]*out (?:my |the |on )?context/i, type: 'maxed_context' },
6507
+ { re: /(?:running |getting |almost )?(?:low|short|out) (?:on|of) context/i, type: 'low_on_context' },
6508
+ { re: /context (?:window|limit|budget)?\\s*(?:is |getting |running )?(?:maxed|full|exhausted|tight|nearly full|almost full)/i, type: 'context_limit' },
6509
+ { re: /(?:preserve|conserve|save|protect) (?:my |the |on )?context(?:\\s+window)?/i, type: 'preserve_context' },
6510
+ { re: /(?:this |the |my )?session (?:has )?(?:been )?(?:going |running )?(?:on )?(?:too |very |really )?long\\b/i, type: 'session_too_long' },
6511
+ { re: /long[\\s-]running session/i, type: 'long_running_session' },
6512
+ { re: /been (?:going|running|at (?:this|it)) (?:for )?(?:too long|a (?:long )?while|quite a)/i, type: 'going_too_long' },
6513
+ { re: /(?:start|spin up|kick off|begin|open|fire up) (?:a |another |the )?(?:fresh|new|clean|second) (?:session|run|window)/i, type: 'fresh_session_deflection' },
6514
+ { re: /(?:continue|pick (?:this|it|things) up|resume) (?:in |with |on )?(?:a |the )?(?:fresh|new|next) session/i, type: 'continue_fresh_session' },
6515
+ { re: /close (?:this )?(?:out|up)(?: here)?(?: and| then| to)? (?:start|begin|spin|open|kick)/i, type: 'close_out_and_restart' },
6516
+ { re: /(?:good|natural|convenient|nice|reasonable) (?:place to (?:stop|pause|break)|(?:stopping|breaking) point)/i, type: 'good_stopping_point' },
6517
+ { re: /(?:i'?ll |let'?s |i should |time to |going to |gonna )(?:stop|wrap (?:up|this up|it up)|pause|call it)(?:\\s+(?:here|now|for (?:now|today)))?/i, type: 'premature_wrap' },
6518
+ ];
6519
+
6520
+ const matches = stopExcusePatterns.filter(p => p.re.test(command));
6521
+ if (matches.length === 0) process.exit(0);
6522
+
6523
+ const checklist = [
6524
+ 'SELF-STOP EXCUSE DETECTED — you are about to tell the user you are stopping for a reason that is NEVER valid.',
6525
+ '',
6526
+ '"Maxed out context" / "session too long" / "let me start fresh" / "good stopping point" are NOT stop reasons.',
6527
+ 'Instar has compaction infrastructure: compaction-recovery re-injects your identity, memory, and recent context automatically. A session can run indefinitely. The worst case of continuing is a ~30s re-read of your plan file — never lost work, as long as durable artifacts (committed code, a plan file, ledger rows) exist on disk.',
6528
+ '',
6529
+ 'The ONLY legitimate stops are:',
6530
+ ' 1. A real design question only the user can answer.',
6531
+ ' 2. Missing information only the user has.',
6532
+ ' 3. A genuine error you cannot resolve.',
6533
+ ' 4. Actual, verified completion of the work.',
6534
+ '',
6535
+ 'If your stop is NOT one of those four — do not send this. Keep going. If you have a milestone to report, report it AND continue; reporting progress is not stopping.',
6536
+ '',
6537
+ 'Detected: ' + matches.map(m => m.type).join(', '),
6538
+ ];
6539
+
6540
+ process.stdout.write(JSON.stringify({ decision: 'approve', additionalContext: checklist.join('\\n') }));
6541
+ } catch { /* never break on errors */ }
6542
+ process.exit(0);
6543
+ });
6440
6544
  `;
6441
6545
  }
6442
6546
  getSlopcheckGuardHook() {