hebbian 0.5.2 → 0.6.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.
@@ -2065,8 +2065,10 @@ var init_hooks = __esm({
2065
2065
  // src/digest.ts
2066
2066
  var digest_exports = {};
2067
2067
  __export(digest_exports, {
2068
+ detectToolFailure: () => detectToolFailure,
2068
2069
  digestTranscript: () => digestTranscript,
2069
2070
  extractCorrections: () => extractCorrections,
2071
+ parseToolResults: () => parseToolResults,
2070
2072
  readHookInput: () => readHookInput
2071
2073
  });
2072
2074
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
@@ -2093,14 +2095,25 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
2093
2095
  const logPath = join16(logDir, `${resolvedSessionId}.jsonl`);
2094
2096
  if (existsSync15(logPath)) {
2095
2097
  console.log(`\u23ED already digested session ${resolvedSessionId}, skip`);
2096
- return { corrections: 0, skipped: 0, transcriptPath, sessionId: resolvedSessionId };
2098
+ return { corrections: 0, skipped: 0, toolFailures: 0, transcriptPath, sessionId: resolvedSessionId };
2097
2099
  }
2098
2100
  const messages = parseTranscript(transcriptPath);
2101
+ const toolFailures = parseToolResults(transcriptPath);
2102
+ for (const failure of toolFailures) {
2103
+ logEpisode(brainRoot, "tool-failure", failure.toolName, failure.errorText);
2104
+ }
2105
+ if (toolFailures.length > 0) {
2106
+ console.log(`\u{1F527} digest: ${toolFailures.length} tool failure(s) logged as episodes`);
2107
+ }
2099
2108
  const corrections = extractCorrections(messages);
2100
- if (corrections.length === 0) {
2109
+ if (corrections.length === 0 && toolFailures.length === 0) {
2101
2110
  console.log(`\u{1F4DD} digest: no corrections found in session ${resolvedSessionId}`);
2102
2111
  writeAuditLog(brainRoot, resolvedSessionId, []);
2103
- return { corrections: 0, skipped: messages.length, transcriptPath, sessionId: resolvedSessionId };
2112
+ return { corrections: 0, skipped: messages.length, toolFailures: toolFailures.length, transcriptPath, sessionId: resolvedSessionId };
2113
+ }
2114
+ if (corrections.length === 0) {
2115
+ writeAuditLog(brainRoot, resolvedSessionId, []);
2116
+ return { corrections: 0, skipped: messages.length, toolFailures: toolFailures.length, transcriptPath, sessionId: resolvedSessionId };
2104
2117
  }
2105
2118
  let applied = 0;
2106
2119
  const auditEntries = [];
@@ -2120,6 +2133,7 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
2120
2133
  return {
2121
2134
  corrections: applied,
2122
2135
  skipped: messages.length - corrections.length,
2136
+ toolFailures: toolFailures.length,
2123
2137
  transcriptPath,
2124
2138
  sessionId: resolvedSessionId
2125
2139
  };
@@ -2151,6 +2165,47 @@ function extractText(content) {
2151
2165
  }
2152
2166
  return null;
2153
2167
  }
2168
+ function parseToolResults(transcriptPath) {
2169
+ const content = readFileSync7(transcriptPath, "utf8");
2170
+ const lines = content.split("\n").filter(Boolean);
2171
+ const failures = [];
2172
+ for (const line of lines) {
2173
+ if (failures.length >= MAX_FAILURES_PER_SESSION) break;
2174
+ let entry;
2175
+ try {
2176
+ entry = JSON.parse(line);
2177
+ } catch {
2178
+ continue;
2179
+ }
2180
+ if (entry.type !== "user") continue;
2181
+ if (!entry.message || !Array.isArray(entry.message.content)) continue;
2182
+ for (const block of entry.message.content) {
2183
+ if (block.type !== "tool_result") continue;
2184
+ if (!block.is_error) continue;
2185
+ const failure = detectToolFailure(block, entry.toolUseResult);
2186
+ if (failure) failures.push(failure);
2187
+ }
2188
+ }
2189
+ return failures;
2190
+ }
2191
+ function detectToolFailure(block, toolUseResult) {
2192
+ if (!block.is_error) return null;
2193
+ let errorText = "";
2194
+ if (typeof block.content === "string") {
2195
+ errorText = block.content;
2196
+ } else if (Array.isArray(block.content)) {
2197
+ errorText = block.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n");
2198
+ }
2199
+ if (!errorText && typeof toolUseResult === "string") {
2200
+ errorText = toolUseResult;
2201
+ }
2202
+ if (!errorText) return null;
2203
+ const exitMatch = errorText.match(/^Exit code (\d+)/);
2204
+ const exitCode = exitMatch ? parseInt(exitMatch[1], 10) : 1;
2205
+ const firstLine = errorText.split("\n").find((l) => l.trim() && !l.startsWith("Exit code")) || "unknown";
2206
+ const toolName = firstLine.trim().slice(0, 80);
2207
+ return { toolName, exitCode, errorText: errorText.slice(0, 500) };
2208
+ }
2154
2209
  function extractCorrections(messages) {
2155
2210
  const corrections = [];
2156
2211
  for (const text of messages) {
@@ -2160,6 +2215,7 @@ function extractCorrections(messages) {
2160
2215
  if (text.trim().endsWith("?")) continue;
2161
2216
  if (/^<[a-zA-Z]/.test(text.trim())) continue;
2162
2217
  if (/^Base directory for this skill:/i.test(text.trim())) continue;
2218
+ if (/^[•·▸▶\-\*]\s/.test(text.trim())) continue;
2163
2219
  const correction = detectCorrection(text);
2164
2220
  if (correction) {
2165
2221
  corrections.push(correction);
@@ -2342,7 +2398,7 @@ function writeAuditLog(brainRoot, sessionId, entries) {
2342
2398
  );
2343
2399
  writeFileSync12(logPath, lines.join("\n") + (lines.length > 0 ? "\n" : ""), "utf8");
2344
2400
  }
2345
- var NEGATION_PATTERNS, AFFIRMATION_PATTERNS, MUST_PATTERNS, WARN_PATTERNS;
2401
+ var NEGATION_PATTERNS, AFFIRMATION_PATTERNS, MUST_PATTERNS, WARN_PATTERNS, MAX_FAILURES_PER_SESSION;
2346
2402
  var init_digest = __esm({
2347
2403
  "src/digest.ts"() {
2348
2404
  "use strict";
@@ -2358,12 +2414,15 @@ var init_digest = __esm({
2358
2414
  /^no[,.\s!]/i,
2359
2415
  /\bdon[''\u2019]?t\s+use\b/i,
2360
2416
  /\bavoid\b/i,
2361
- // Korean negation
2417
+ // Korean negation — specific verb forms only to avoid matching incidental 않/대신 in explanations
2362
2418
  /하지\s*마/,
2363
2419
  /안\s*돼/,
2364
- /대신/,
2365
2420
  /쓰지\s*마/,
2366
- /않/
2421
+ /[가-힣]+지\s*마/,
2422
+ /하지\s*않/,
2423
+ // "do not" specifically
2424
+ /쓰지\s*않/
2425
+ // "do not use" specifically
2367
2426
  ];
2368
2427
  AFFIRMATION_PATTERNS = [
2369
2428
  /\balways\b/i,
@@ -2385,6 +2444,7 @@ var init_digest = __esm({
2385
2444
  // Korean
2386
2445
  /주의/
2387
2446
  ];
2447
+ MAX_FAILURES_PER_SESSION = 20;
2388
2448
  }
2389
2449
  });
2390
2450
 
@@ -3056,7 +3116,7 @@ var init_doctor = __esm({
3056
3116
  init_constants();
3057
3117
  import { parseArgs } from "util";
3058
3118
  import { resolve as resolve3 } from "path";
3059
- var VERSION = "0.5.2";
3119
+ var VERSION = "0.6.0";
3060
3120
  var HELP = `
3061
3121
  hebbian v${VERSION} \u2014 Folder-as-neuron brain for any AI agent.
3062
3122