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.
- package/dist/bin/hebbian.js +68 -8
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/digest.d.ts +42 -0
- package/dist/digest.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +66 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/hebbian.js
CHANGED
|
@@ -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.
|
|
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
|
|