osborn 0.9.234 → 0.9.236

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 (2) hide show
  1. package/dist/claude-llm.js +42 -1
  2. package/package.json +1 -1
@@ -47,7 +47,11 @@ async function buildRecallInjection(sessionId, workingDir, prompt) {
47
47
  try {
48
48
  if (!RECALL_ENABLED() || !sessionId || !workingDir)
49
49
  return '';
50
- const q = String(prompt || '').trim();
50
+ // CAP the recall query length (defense-in-depth). A pasted or echoed giant prompt — e.g. a
51
+ // "liked" log step that serialized a 24KB command+output — would otherwise build a massive
52
+ // FTS MATCH and an oversized embed request on the hot UserPromptSubmit path and stall the
53
+ // turn. A real user utterance is short; 512 chars is ample to retrieve on.
54
+ const q = String(prompt || '').trim().slice(0, 512);
51
55
  if (q.length < 3)
52
56
  return '';
53
57
  if (!storeExists(sessionId, workingDir))
@@ -95,6 +99,15 @@ async function buildRecallInjection(sessionId, workingDir, prompt) {
95
99
  // agent's direct tool calls count against the budget.
96
100
  let turnToolCallCount = 0;
97
101
  const TOOL_CALL_BUDGET = 5;
102
+ // Writer PROVENANCE REMINDER (Chesterton's Fence): before the writer mutates a
103
+ // version-controlled file it should review that file's git history so the edit is
104
+ // backward-informed (root cause / prior + reverted attempts / cross-component coupling),
105
+ // not forward-only. Delivered as a ONE-SHOT PreToolUse deny-with-reminder on the writer's
106
+ // first edit of an existing file each turn. This single flag is the irreducible minimum:
107
+ // a PreToolUse hook can only allow/deny/ask (no "inject note + proceed"), so the reminder
108
+ // must be a deny — and without a "already nudged this turn" flag that deny would loop
109
+ // forever. Reset on every UserPromptSubmit (new turn).
110
+ let writerHistoryReminded = false;
98
111
  /**
99
112
  * Strip markdown formatting for TTS (text-to-speech)
100
113
  * Removes **bold**, ##headers, ```code```, etc. so TTS doesn't read them literally
@@ -493,6 +506,13 @@ export const NAMED_AGENTS = {
493
506
  '4. If ANYTHING has drifted from the plan (file moved, code refactored, dependency changed):',
494
507
  ' STOP and report back to the main agent. Do NOT improvise.',
495
508
  '',
509
+ '### Step 1b: Review history before touching a versioned file',
510
+ 'For any file that ALREADY EXISTS (i.e. has git history), review its lineage BEFORE you edit it (on your first edit of an existing file each turn you\'ll get a one-time reminder to). This is Chesterton\'s Fence — understand WHY the code is there before you change it. Do:',
511
+ ' 1. `git log --follow -p -- "<file>"` — recent commits and the rationale behind them.',
512
+ ' 2. `git blame` on the exact lines you will change — who/when/which commit introduced them.',
513
+ ' 3. `git log --oneline --grep=<feature>` and `git log --oneline -S"<symbol>"` — look for related OR REVERTED work so you do not redo something already tried, and note which components were co-changed (coupling).',
514
+ 'Fix problems backward-informed — root cause, systemic implications, prior attempts — not forward-only. New files are exempt (no history to review).',
515
+ '',
496
516
  '### Step 2: Clarify unknowns',
497
517
  '1. If the plan is vague or ambiguous — ask the main agent a specific clarifying question.',
498
518
  ' Examples: "Which config format — YAML or JSON?", "New file or extend existing auth.ts?"',
@@ -1873,6 +1893,25 @@ class ClaudeLLMStream extends llm.LLMStream {
1873
1893
  // hardcoded per-role branches had, now in one visible place.
1874
1894
  if (toolName === 'Write' || toolName === 'Edit' || toolName === 'MultiEdit') {
1875
1895
  const filePath = String(toolInput.file_path || '');
1896
+ // Writer PROVENANCE REMINDER (Chesterton's Fence): on the writer's FIRST edit of
1897
+ // an EXISTING (version-controlled) file this turn, deny ONCE with a reminder to
1898
+ // review the file's git history so the change is backward-informed (root cause,
1899
+ // prior/reverted attempts, cross-component coupling), not forward-only — then it
1900
+ // proceeds. New files are exempt (no history). One-shot per turn so it can't loop.
1901
+ if (agentType === 'writer' && filePath && existsSync(filePath) && !writerHistoryReminded) {
1902
+ writerHistoryReminded = true;
1903
+ const base = filePath.split('/').pop() || filePath;
1904
+ const gateReason = [
1905
+ `Before editing ${base}, first review its history so your change is backward-informed, not forward-only (Chesterton's Fence — understand WHY this code exists before changing it):`,
1906
+ ` 1. git log --follow -p -- "${filePath}" (recent commits + rationale)`,
1907
+ ` 2. git blame the specific lines you're about to change (who/when/which commit)`,
1908
+ ` 3. git log --oneline --grep=<feature> and git log --oneline -S"<symbol>" (related OR reverted work so you don't redo something already tried; note components co-changed in those commits)`,
1909
+ `Then re-attempt this edit. (One-time reminder this turn; new files are exempt.)`,
1910
+ ].join('\n');
1911
+ console.log(`🏛️ Writer provenance reminder: nudging before ${base} (first edit of an existing file this turn)`);
1912
+ this.#eventEmitter.emit('tool_blocked', { name: toolName, reason: `Review ${base}'s git history first (one-time provenance reminder).` });
1913
+ return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny' }, reason: gateReason };
1914
+ }
1876
1915
  const { decision, reason } = decideWrite(agentType, filePath);
1877
1916
  console.log(`🔎 Write gate: agent=${agentType ?? 'main'} path="${filePath || '(none)'}" → ${decision}`);
1878
1917
  if (decision === 'deny') {
@@ -1944,6 +1983,8 @@ class ClaudeLLMStream extends llm.LLMStream {
1944
1983
  try {
1945
1984
  // Reset the per-turn tool call counter so the new turn starts fresh.
1946
1985
  turnToolCallCount = 0;
1986
+ // Reset the writer provenance-reminder flag for the new turn.
1987
+ writerHistoryReminded = false;
1947
1988
  const reminder = readFileSync(TURN_SHAPE_REMINDER_PATH, 'utf-8');
1948
1989
  const promptPreview = String(input?.prompt || '').substring(0, 60).replace(/\n/g, ' ');
1949
1990
  // Reliable recall: retrieve prior messages relevant to THIS prompt and inject
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.234",
3
+ "version": "0.9.236",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {