openwolf-enhanced 1.20.2 → 1.20.4
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/CHANGELOG.md +59 -0
- package/dist/dashboard/assets/{AISuggestions-CFK_2uAF.js → AISuggestions-CtOxHIl9.js} +1 -1
- package/dist/dashboard/assets/{ActivityLog-CCOHsoDc.js → ActivityLog-CFN5LTgT.js} +1 -1
- package/dist/dashboard/assets/{ActivityTimeline-BhZHCuoG.js → ActivityTimeline-DUFGSBZE.js} +1 -1
- package/dist/dashboard/assets/{AnatomyBrowser-B5Of1HOO.js → AnatomyBrowser-kTbfRkpC.js} +1 -1
- package/dist/dashboard/assets/{BugLog-BqlIbKOd.js → BugLog-B1OZI3_G.js} +1 -1
- package/dist/dashboard/assets/{CerebrumViewer-Do6Mh7gd.js → CerebrumViewer-BBNL_KNO.js} +1 -1
- package/dist/dashboard/assets/{CronStatus-y_GxP3-1.js → CronStatus-GEJfvNJ6.js} +1 -1
- package/dist/dashboard/assets/{DesignQC-BqcR7OWX.js → DesignQC-BL3xQxKL.js} +1 -1
- package/dist/dashboard/assets/{MemoryViewer-Dnj_oixJ.js → MemoryViewer-CM4NJTuH.js} +1 -1
- package/dist/dashboard/assets/{NativeMemory-DotOF3kS.js → NativeMemory-SQQ20A77.js} +1 -1
- package/dist/dashboard/assets/{ProjectOverview-BEzUoJk1.js → ProjectOverview-Bo1BwDba.js} +1 -1
- package/dist/dashboard/assets/{ProjectsAggregate-paEMmTgD.js → ProjectsAggregate-By41p6Ih.js} +1 -1
- package/dist/dashboard/assets/{StatusBadge-Cpbb5e1W.js → StatusBadge-BJrvWVqs.js} +1 -1
- package/dist/dashboard/assets/{TokenUsage-ByUBdw7G.js → TokenUsage-CloSdJB2.js} +1 -1
- package/dist/dashboard/assets/{index-Dp-E2wpx.js → index-HpYzFcBu.js} +3 -3
- package/dist/dashboard/index.html +1 -1
- package/dist/hooks/post-bash.js +11 -6
- package/dist/hooks/post-read.js +4 -2
- package/dist/hooks/post-write.js +24 -30
- package/dist/hooks/pre-read.js +12 -3
- package/dist/hooks/pre-write.js +29 -5
- package/dist/hooks/session-start.js +8 -4
- package/dist/hooks/shared.js +79 -30
- package/dist/hooks/stop.js +4 -1
- package/dist/src/buglog/bug-tracker.js +7 -3
- package/dist/src/buglog/bug-tracker.js.map +1 -1
- package/dist/src/daemon/cron-engine.js +7 -50
- package/dist/src/daemon/cron-engine.js.map +1 -1
- package/dist/src/daemon/file-watcher.js +5 -1
- package/dist/src/daemon/file-watcher.js.map +1 -1
- package/dist/src/hooks/post-bash.js +11 -6
- package/dist/src/hooks/post-bash.js.map +1 -1
- package/dist/src/hooks/post-read.js +4 -2
- package/dist/src/hooks/post-read.js.map +1 -1
- package/dist/src/hooks/post-write.js +24 -30
- package/dist/src/hooks/post-write.js.map +1 -1
- package/dist/src/hooks/pre-read.js +12 -3
- package/dist/src/hooks/pre-read.js.map +1 -1
- package/dist/src/hooks/pre-write.js +29 -5
- package/dist/src/hooks/pre-write.js.map +1 -1
- package/dist/src/hooks/session-start.js +8 -4
- package/dist/src/hooks/session-start.js.map +1 -1
- package/dist/src/hooks/shared.js +79 -30
- package/dist/src/hooks/shared.js.map +1 -1
- package/dist/src/hooks/stop.js +4 -1
- package/dist/src/hooks/stop.js.map +1 -1
- package/dist/src/tracker/token-ledger.js +5 -30
- package/dist/src/tracker/token-ledger.js.map +1 -1
- package/dist/src/utils/maintenance.js +44 -25
- package/dist/src/utils/maintenance.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-HpYzFcBu.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="./assets/index-Cr3yWM5G.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
package/dist/hooks/post-bash.js
CHANGED
|
@@ -12,10 +12,9 @@ function classifyOutcome(resp) {
|
|
|
12
12
|
}
|
|
13
13
|
if (r.interrupted === true || r.is_error === true || r.isError === true)
|
|
14
14
|
return "error";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return "error";
|
|
15
|
+
// NOTE: no "stderr but no stdout" heuristic — plenty of successful commands write only to
|
|
16
|
+
// stderr (git checkout progress, curl -v, warnings). It misclassified them as failures,
|
|
17
|
+
// undercounting bash_writes and mislabeling activity.log entries. Explicit signals only.
|
|
19
18
|
}
|
|
20
19
|
if (typeof resp === "string" && /(^|\s)(error|failed|command not found)\b|exit code [1-9]/i.test(resp)) {
|
|
21
20
|
return "error";
|
|
@@ -39,8 +38,14 @@ async function main() {
|
|
|
39
38
|
process.exit(0);
|
|
40
39
|
return;
|
|
41
40
|
}
|
|
42
|
-
// Never capture the
|
|
43
|
-
|
|
41
|
+
// Never capture invocations of the openwolf CLI itself, to avoid feedback noise. Match the
|
|
42
|
+
// COMMAND position of each shell segment, not the whole string — the old `\bopenwolf\b` skipped
|
|
43
|
+
// every command that merely mentioned an openwolf PATH, which made the write counter blind for
|
|
44
|
+
// exactly the sessions that work on an openwolf checkout.
|
|
45
|
+
const invokesOpenwolf = cmd
|
|
46
|
+
.split(/&&|\|\||[;|]/)
|
|
47
|
+
.some((seg) => /^(?:\s*[A-Za-z_]\w*=\S*)*\s*(?:\S*\/)?openwolf(?:\s|$)/.test(seg));
|
|
48
|
+
if (invokesOpenwolf) {
|
|
44
49
|
process.exit(0);
|
|
45
50
|
return;
|
|
46
51
|
}
|
package/dist/hooks/post-read.js
CHANGED
|
@@ -23,8 +23,10 @@ async function main() {
|
|
|
23
23
|
const normalizedFile = normalizePath(filePath);
|
|
24
24
|
// Skip tracking for .wolf/ internal files — consistent with pre-read
|
|
25
25
|
const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Separator required after the root — see pre-read (sibling-prefix directories, e.g.
|
|
27
|
+
// /root/orderflow2, must not count as inside /root/orderflow).
|
|
28
|
+
const relToProject = normalizedFile.startsWith(projectDir + "/")
|
|
29
|
+
? normalizedFile.slice(projectDir.length + 1)
|
|
28
30
|
: "";
|
|
29
31
|
// Don't track reads of files outside the project root (upstream #56). relToProject is ""
|
|
30
32
|
// both when the path is outside projectDir and when it equals the root itself — neither
|
package/dist/hooks/post-write.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import
|
|
4
|
-
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, parseAnatomy, serializeAnatomy, extractDescription, estimateFileTokens, getTokenRatios, appendMarkdown, timeShort, readStdin, normalizePath, getRetention, loadIgnore, readBugLog, isSecretFile } from "./shared.js";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, writeAtomic, parseAnatomy, serializeAnatomy, extractDescription, estimateFileTokens, getTokenRatios, appendMarkdown, timeShort, readStdin, normalizePath, getRetention, loadIgnore, readBugLog, isSecretFile } from "./shared.js";
|
|
5
4
|
async function main() {
|
|
6
5
|
ensureWolfDir();
|
|
7
6
|
const wolfDir = getWolfDir();
|
|
@@ -113,21 +112,7 @@ async function main() {
|
|
|
113
112
|
hits: 0,
|
|
114
113
|
misses: 0,
|
|
115
114
|
});
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
fs.writeFileSync(tmp, serialized, "utf-8");
|
|
119
|
-
fs.renameSync(tmp, anatomyPath);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
try {
|
|
123
|
-
fs.writeFileSync(anatomyPath, serialized, "utf-8");
|
|
124
|
-
}
|
|
125
|
-
catch { }
|
|
126
|
-
try {
|
|
127
|
-
fs.unlinkSync(tmp);
|
|
128
|
-
}
|
|
129
|
-
catch { }
|
|
130
|
-
}
|
|
115
|
+
writeAtomic(anatomyPath, () => serialized);
|
|
131
116
|
}
|
|
132
117
|
}
|
|
133
118
|
catch { }
|
|
@@ -465,15 +450,17 @@ function detectFixPattern(oldStr, newStr, ext, fileLabel) {
|
|
|
465
450
|
fix: `Added async modifier`,
|
|
466
451
|
};
|
|
467
452
|
}
|
|
468
|
-
// --- Type
|
|
453
|
+
// --- Type assertion fix ---
|
|
454
|
+
// Only an added `as`-assertion counts. The old second branch (`": "` newly appearing in a
|
|
455
|
+
// small edit) fired on adding ANY object property or label and flooded the buglog with
|
|
456
|
+
// fake "Type error" entries.
|
|
469
457
|
if (ext === ".ts" || ext === ".tsx") {
|
|
470
|
-
if (
|
|
471
|
-
(newStr.includes(": ") && !oldStr.includes(": ") && oldLines.length <= 3)) {
|
|
458
|
+
if (newStr.includes(" as ") && !oldStr.includes(" as ")) {
|
|
472
459
|
return {
|
|
473
460
|
category: "type-fix",
|
|
474
461
|
summary: `Type error`,
|
|
475
|
-
rootCause: `Missing or incorrect type
|
|
476
|
-
fix: `Added type assertion
|
|
462
|
+
rootCause: `Missing or incorrect type assertion`,
|
|
463
|
+
fix: `Added type assertion`,
|
|
477
464
|
context: extractChangedLines(oldStr, newStr),
|
|
478
465
|
};
|
|
479
466
|
}
|
|
@@ -509,15 +496,22 @@ function tokenizeCode(code) {
|
|
|
509
496
|
return code.replace(/[^\w$]/g, " ").split(/\s+/).filter(t => t.length > 0);
|
|
510
497
|
}
|
|
511
498
|
function findOperatorChange(oldStr, newStr) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
499
|
+
// Tokenize operators longest-first instead of substring-testing: `"===".includes("==")` is true,
|
|
500
|
+
// so the old check could never see a `===` → `==` (or `!==` → `!=`) change — the most classic
|
|
501
|
+
// operator bug of them all.
|
|
502
|
+
const extractOps = (s) => {
|
|
503
|
+
const counts = new Map();
|
|
504
|
+
for (const m of s.match(/===|!==|==|!=|>=|<=|>>|<<|&&|\|\||\?\?/g) ?? []) {
|
|
505
|
+
counts.set(m, (counts.get(m) ?? 0) + 1);
|
|
520
506
|
}
|
|
507
|
+
return counts;
|
|
508
|
+
};
|
|
509
|
+
const oldOps = extractOps(oldStr);
|
|
510
|
+
const newOps = extractOps(newStr);
|
|
511
|
+
const removed = [...oldOps.keys()].filter((op) => (newOps.get(op) ?? 0) < oldOps.get(op));
|
|
512
|
+
const added = [...newOps.keys()].filter((op) => (oldOps.get(op) ?? 0) < newOps.get(op));
|
|
513
|
+
if (removed.length === 1 && added.length === 1) {
|
|
514
|
+
return { old: removed[0], new: added[0] };
|
|
521
515
|
}
|
|
522
516
|
return null;
|
|
523
517
|
}
|
package/dist/hooks/pre-read.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, readStdin, normalizePath, loadIgnore } from "./shared.js";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, readStdin, normalizePath, loadIgnore, isSecretFile } from "./shared.js";
|
|
4
4
|
async function main() {
|
|
5
5
|
ensureWolfDir();
|
|
6
6
|
const wolfDir = getWolfDir();
|
|
@@ -30,8 +30,10 @@ async function main() {
|
|
|
30
30
|
// Skip tracking for .wolf/ internal files — they're infrastructure, not project files.
|
|
31
31
|
// Counting them inflates anatomy miss rates since .wolf/ is excluded from anatomy scanning.
|
|
32
32
|
const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// Require the path SEPARATOR after the root — a bare startsWith(projectDir) also matched
|
|
34
|
+
// sibling directories sharing the prefix (/root/orderflow2 counted as inside /root/orderflow).
|
|
35
|
+
const relToProject = normalizedFile.startsWith(projectDir + "/")
|
|
36
|
+
? normalizedFile.slice(projectDir.length + 1)
|
|
35
37
|
: "";
|
|
36
38
|
// Don't track files outside the project root (upstream #56).
|
|
37
39
|
if (!relToProject) {
|
|
@@ -47,6 +49,13 @@ async function main() {
|
|
|
47
49
|
process.exit(0);
|
|
48
50
|
return;
|
|
49
51
|
}
|
|
52
|
+
// Never track secret-bearing files (#54). post-read already skips them, but the session entry
|
|
53
|
+
// (with the file's PATH) is created HERE — without this guard a read .env still landed in
|
|
54
|
+
// _session.json and from there in token-ledger.json.
|
|
55
|
+
if (isSecretFile(normalizedFile)) {
|
|
56
|
+
process.exit(0);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
50
59
|
const session = readJSON(sessionFile, {
|
|
51
60
|
session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0,
|
|
52
61
|
repeated_reads_warned: 0,
|
package/dist/hooks/pre-write.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, readMarkdown, readStdin, readBugLog } from "./shared.js";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, readStdin, readBugLog, withLock } from "./shared.js";
|
|
4
4
|
async function main() {
|
|
5
5
|
ensureWolfDir();
|
|
6
6
|
const wolfDir = getWolfDir();
|
|
@@ -24,7 +24,20 @@ async function main() {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
// 1. Cerebrum Do-Not-Repeat check
|
|
27
|
-
checkCerebrum(wolfDir, allContent);
|
|
27
|
+
const cerebrumWarnings = checkCerebrum(wolfDir, allContent);
|
|
28
|
+
// Record fired warnings in the session tracker — the field existed since day one but nothing
|
|
29
|
+
// ever incremented it, so the ledger/dashboard always showed 0.
|
|
30
|
+
if (cerebrumWarnings > 0) {
|
|
31
|
+
try {
|
|
32
|
+
const sessionFile = path.join(wolfDir, "hooks", "_session.json");
|
|
33
|
+
withLock(sessionFile, () => {
|
|
34
|
+
const session = readJSON(sessionFile, {});
|
|
35
|
+
session.cerebrum_warnings = (session.cerebrum_warnings ?? 0) + cerebrumWarnings;
|
|
36
|
+
writeJSON(sessionFile, session);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch { /* best-effort */ }
|
|
40
|
+
}
|
|
28
41
|
// 2. Bug log: search for similar past bugs when editing code
|
|
29
42
|
// This fires when Claude is about to edit a file — if the edit looks like a fix
|
|
30
43
|
// (changing error handling, modifying catch blocks, etc.), check the bug log
|
|
@@ -37,9 +50,10 @@ function checkCerebrum(wolfDir, content) {
|
|
|
37
50
|
const cerebrumContent = readMarkdown(path.join(wolfDir, "cerebrum.md"));
|
|
38
51
|
const doNotRepeatSection = cerebrumContent.split("## Do-Not-Repeat")[1];
|
|
39
52
|
if (!doNotRepeatSection)
|
|
40
|
-
return;
|
|
53
|
+
return 0;
|
|
41
54
|
const entries = doNotRepeatSection.split("## ")[0];
|
|
42
55
|
const lines = entries.split("\n").filter((l) => l.trim().startsWith("[") || l.trim().startsWith("-"));
|
|
56
|
+
let fired = 0;
|
|
43
57
|
for (const line of lines) {
|
|
44
58
|
const trimmed = line.trim().replace(/^[-*]\s*/, "").replace(/^\[[\d-]+\]\s*/, "");
|
|
45
59
|
if (!trimmed)
|
|
@@ -59,11 +73,14 @@ function checkCerebrum(wolfDir, content) {
|
|
|
59
73
|
const regex = new RegExp(`\\b${pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
60
74
|
if (regex.test(content)) {
|
|
61
75
|
process.stderr.write(`⚠️ OpenWolf cerebrum warning: "${trimmed}" — check your code before proceeding.\n`);
|
|
76
|
+
fired++;
|
|
77
|
+
break; // one warning per Do-Not-Repeat entry is enough
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
catch { }
|
|
65
81
|
}
|
|
66
82
|
}
|
|
83
|
+
return fired;
|
|
67
84
|
}
|
|
68
85
|
// Common words that appear in most code — must be excluded from similarity matching
|
|
69
86
|
const STOP_WORDS = new Set([
|
|
@@ -94,8 +111,15 @@ function checkBugLog(wolfDir, filePath, oldStr, newStr, content) {
|
|
|
94
111
|
const editText = (oldStr + " " + newStr + " " + content).toLowerCase();
|
|
95
112
|
const editTokens = tokenize(editText);
|
|
96
113
|
const relevant = fileMatches.filter(bug => {
|
|
97
|
-
// Check if any bug tag appears in the edit content
|
|
98
|
-
|
|
114
|
+
// Check if any bug tag appears in the edit content — as a whole word, and only tags with some
|
|
115
|
+
// substance. A substring match on short tags ("ts", "s1", "api") hit virtually every edit and
|
|
116
|
+
// made this FYI fire constantly for any file that carried one.
|
|
117
|
+
const tagHit = (bug.tags ?? []).some(t => {
|
|
118
|
+
const tag = t.toLowerCase();
|
|
119
|
+
if (tag.length < 4)
|
|
120
|
+
return false;
|
|
121
|
+
return new RegExp(`\\b${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(editText);
|
|
122
|
+
});
|
|
99
123
|
if (tagHit)
|
|
100
124
|
return true;
|
|
101
125
|
// Check meaningful word overlap (excluding stop words)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort, buildResumeDigest, readStdin, SESSION_SUMMARY_SCAFFOLD } from "./shared.js";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort, buildResumeDigest, readStdin, withLock, SESSION_SUMMARY_SCAFFOLD } from "./shared.js";
|
|
4
4
|
async function main() {
|
|
5
5
|
ensureWolfDir();
|
|
6
6
|
const wolfDir = getWolfDir();
|
|
@@ -83,10 +83,14 @@ async function main() {
|
|
|
83
83
|
// Increment total_sessions in token-ledger — only for a genuinely new session, not a
|
|
84
84
|
// resume/compact continuation (which would double-count the same session).
|
|
85
85
|
if (!continuing) {
|
|
86
|
+
// Same lock as the stop hook's ledger write — a session ending in one process while another
|
|
87
|
+
// starts must not clobber the read-modify-write.
|
|
86
88
|
const ledgerPath = path.join(wolfDir, "token-ledger.json");
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
withLock(ledgerPath, () => {
|
|
90
|
+
const ledger = readJSON(ledgerPath, { version: 1, lifetime: { total_sessions: 0 } });
|
|
91
|
+
ledger.lifetime.total_sessions++;
|
|
92
|
+
writeJSON(ledgerPath, ledger);
|
|
93
|
+
});
|
|
90
94
|
}
|
|
91
95
|
// Inject a compact resume digest as additionalContext so the model continues without
|
|
92
96
|
// re-reading STATUS.md/cerebrum/memory. Bounded and config-gated. stdout is reserved
|
package/dist/hooks/shared.js
CHANGED
|
@@ -12,9 +12,28 @@ export function nativeMemoryDir(projectRoot) {
|
|
|
12
12
|
if (override)
|
|
13
13
|
return fs.existsSync(override) ? override : null;
|
|
14
14
|
const root = projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
15
|
+
const base = path.join(os.homedir(), ".claude", "projects");
|
|
15
16
|
const slug = root.replace(/\//g, "-");
|
|
16
|
-
const dir = path.join(
|
|
17
|
-
|
|
17
|
+
const dir = path.join(base, slug, "memory");
|
|
18
|
+
if (fs.existsSync(dir))
|
|
19
|
+
return dir;
|
|
20
|
+
// The exact slugging Claude Code uses varies for paths with dots/underscores — the plain
|
|
21
|
+
// slash→dash guess above misses those and native memory silently vanished from recall. Fall
|
|
22
|
+
// back to matching any project dir whose name equals the root once BOTH are canonicalized
|
|
23
|
+
// (every non-alphanumeric run → "-"). Exact guess still wins; this only runs on a miss.
|
|
24
|
+
const canon = (s) => s.replace(/[^A-Za-z0-9]+/g, "-");
|
|
25
|
+
const want = canon(root);
|
|
26
|
+
try {
|
|
27
|
+
for (const name of fs.readdirSync(base)) {
|
|
28
|
+
if (canon(name) === want) {
|
|
29
|
+
const candidate = path.join(base, name, "memory");
|
|
30
|
+
if (fs.existsSync(candidate))
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch { /* no projects dir */ }
|
|
36
|
+
return null;
|
|
18
37
|
}
|
|
19
38
|
export function getWolfDir() {
|
|
20
39
|
// Prefer an explicit project dir so hooks work even if CWD changes during a session.
|
|
@@ -169,39 +188,58 @@ export function getRetention(wolfDir) {
|
|
|
169
188
|
// and ** globs. (Duplicated in utils/maintenance.ts — separate build roots.)
|
|
170
189
|
// ---------------------------------------------------------------------------
|
|
171
190
|
export function makeIgnoreMatcher(patterns) {
|
|
172
|
-
|
|
191
|
+
// gitignore-lite, evaluated in ORDER with last-match-wins so `!` negations work
|
|
192
|
+
// ("dist/" then "!dist/keep.js"). A leading "/" anchors the pattern to the project root —
|
|
193
|
+
// "/dist" matches only the top-level dist, not src/dist. The old matcher dropped negations
|
|
194
|
+
// entirely and never gave the leading slash any meaning, so "/dist"-style .gitignore lines
|
|
195
|
+
// (the common form) matched nothing at all.
|
|
196
|
+
const rules = patterns
|
|
173
197
|
.map((p) => p.trim())
|
|
174
|
-
.filter((p) => p && !p.startsWith("#")
|
|
175
|
-
|
|
198
|
+
.filter((p) => p && !p.startsWith("#"))
|
|
199
|
+
.map((raw) => {
|
|
200
|
+
const negate = raw.startsWith("!");
|
|
201
|
+
const body = (negate ? raw.slice(1) : raw).trim();
|
|
202
|
+
const anchored = body.startsWith("/");
|
|
203
|
+
const pat = (anchored ? body.slice(1) : body).replace(/\/+$/, "");
|
|
204
|
+
return { negate, anchored, pat };
|
|
205
|
+
})
|
|
206
|
+
.filter((r) => r.pat.length > 0);
|
|
207
|
+
if (rules.length === 0)
|
|
176
208
|
return () => false;
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (norm === pat || base === pat)
|
|
209
|
+
const matchesRule = (norm, parts, base, r) => {
|
|
210
|
+
const { anchored, pat } = r;
|
|
211
|
+
if (norm === pat || norm.startsWith(pat + "/"))
|
|
212
|
+
return true;
|
|
213
|
+
if (!anchored) {
|
|
214
|
+
if (base === pat || parts.includes(pat))
|
|
184
215
|
return true;
|
|
185
|
-
if (parts.includes(pat))
|
|
186
|
-
return true; // dir name anywhere in path
|
|
187
216
|
if (pat.startsWith("*.") && norm.endsWith(pat.slice(1)))
|
|
188
217
|
return true;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
218
|
+
}
|
|
219
|
+
if (pat.includes("*")) {
|
|
220
|
+
const re = new RegExp("^" +
|
|
221
|
+
pat
|
|
222
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
223
|
+
.replace(/\*\*/g, "\u0000")
|
|
224
|
+
.replace(/\*/g, "[^/]*")
|
|
225
|
+
.replace(/\u0000/g, ".*") +
|
|
226
|
+
"$");
|
|
227
|
+
if (re.test(norm) || (!anchored && re.test(base)))
|
|
228
|
+
return true;
|
|
202
229
|
}
|
|
203
230
|
return false;
|
|
204
231
|
};
|
|
232
|
+
return (relPath) => {
|
|
233
|
+
const norm = relPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
234
|
+
const parts = norm.split("/");
|
|
235
|
+
const base = parts[parts.length - 1];
|
|
236
|
+
let ignored = false;
|
|
237
|
+
for (const r of rules) {
|
|
238
|
+
if (matchesRule(norm, parts, base, r))
|
|
239
|
+
ignored = !r.negate;
|
|
240
|
+
}
|
|
241
|
+
return ignored;
|
|
242
|
+
};
|
|
205
243
|
}
|
|
206
244
|
// Secret-bearing files that must never be captured into anatomy.md / memory.md.
|
|
207
245
|
// Upstream only excluded .env* (issue #54) — private keys, certs, keystores and
|
|
@@ -940,11 +978,22 @@ export function normalizePath(p) {
|
|
|
940
978
|
export function countSemanticEntries(wolfDir) {
|
|
941
979
|
try {
|
|
942
980
|
const content = fs.readFileSync(path.join(wolfDir, "memory.md"), "utf-8");
|
|
981
|
+
const lines = content.split(/\r?\n/);
|
|
982
|
+
// Scope to the CURRENT session: everything after the last "## Session" header. The old code
|
|
983
|
+
// counted lines starting with "| YYYY-MM-DD" — a format no writer ever produces (rows start
|
|
984
|
+
// "| HH:MM |"), so the count was always 0 and the summary reminder fired regardless.
|
|
985
|
+
let start = 0;
|
|
986
|
+
for (let i = 0; i < lines.length; i++)
|
|
987
|
+
if (/^## Session\b/.test(lines[i]))
|
|
988
|
+
start = i + 1;
|
|
943
989
|
const mechanical = /^\|\s*[\d:]+\s*\|\s*(Created|Edited|Multi-edited|Session end:|designqc:)/;
|
|
944
|
-
const today = `| ${new Date().toISOString().slice(0, 10)}`;
|
|
945
990
|
let count = 0;
|
|
946
|
-
for (const line of
|
|
947
|
-
if (line.
|
|
991
|
+
for (const line of lines.slice(start)) {
|
|
992
|
+
if (/^\*\*Did:\*\*/.test(line.trim())) {
|
|
993
|
+
count++;
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (/^\|\s*\d{1,2}:\d{2}\s*\|/.test(line) && !mechanical.test(line))
|
|
948
997
|
count++;
|
|
949
998
|
}
|
|
950
999
|
return count;
|
package/dist/hooks/stop.js
CHANGED
|
@@ -90,6 +90,7 @@ async function main() {
|
|
|
90
90
|
writes_count: writeCount,
|
|
91
91
|
repeated_reads_blocked: session.repeated_reads_warned,
|
|
92
92
|
anatomy_lookups: session.anatomy_hits,
|
|
93
|
+
...(unnamedWrites > 0 ? { unnamed_writes: unnamedWrites } : {}),
|
|
93
94
|
},
|
|
94
95
|
};
|
|
95
96
|
// Update token-ledger.json — lock the read-modify-write so concurrent sessions and the cron
|
|
@@ -130,7 +131,9 @@ async function main() {
|
|
|
130
131
|
ledger.sessions = ledger.sessions.slice(-ret.token_ledger_max_sessions);
|
|
131
132
|
}
|
|
132
133
|
ledger.lifetime.total_reads += readCount;
|
|
133
|
-
|
|
134
|
+
// Unnamed writes (shell / other working dirs) count toward the lifetime total — they were real
|
|
135
|
+
// work. They stay OUT of the session's named writes[] list (no path was recorded, by design).
|
|
136
|
+
ledger.lifetime.total_writes += writeCount + unnamedWrites;
|
|
134
137
|
ledger.lifetime.total_tokens_estimated += inputTokens + outputTokens;
|
|
135
138
|
ledger.lifetime.anatomy_hits += session.anatomy_hits;
|
|
136
139
|
ledger.lifetime.anatomy_misses += session.anatomy_misses;
|
|
@@ -62,9 +62,13 @@ export function findSimilarBugs(wolfDir, errorMessage) {
|
|
|
62
62
|
const results = [];
|
|
63
63
|
for (const bug of bugLog.bugs) {
|
|
64
64
|
let score = 0;
|
|
65
|
-
// Exact substring match
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
// Exact substring match — but only when the CONTAINED string has real length. normalize()
|
|
66
|
+
// can reduce a short or punctuation-only message to "" or a few chars, and "".includes / a
|
|
67
|
+
// 3-char fragment matches everything: logBug would then fold unrelated new bugs into that
|
|
68
|
+
// entry as "duplicates".
|
|
69
|
+
const nb = normalize(bug.error_message);
|
|
70
|
+
if ((normalizedInput.length >= 12 && nb.includes(normalizedInput)) ||
|
|
71
|
+
(nb.length >= 12 && normalizedInput.includes(nb))) {
|
|
68
72
|
score += 1.0;
|
|
69
73
|
}
|
|
70
74
|
// Word overlap (jaccard)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bug-tracker.js","sourceRoot":"","sources":["../../../src/buglog/bug-tracker.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"bug-tracker.js","sourceRoot":"","sources":["../../../src/buglog/bug-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import cron from "node-cron";
|
|
4
|
-
import { readJSON, writeJSON,
|
|
4
|
+
import { readJSON, writeJSON, writeText, withLock } from "../utils/fs-safe.js";
|
|
5
5
|
import { scanProject } from "../scanner/anatomy-scanner.js";
|
|
6
6
|
import { detectWaste } from "../tracker/waste-detector.js";
|
|
7
|
+
import { consolidateMemory } from "../utils/maintenance.js";
|
|
7
8
|
import { resolveLlmConfig, callLlmDetailed, requiresApiKey } from "./llm-provider.js";
|
|
8
9
|
/**
|
|
9
10
|
* Split text into chunks that fit a context window — at paragraph boundaries, never mid-sentence.
|
|
@@ -222,7 +223,11 @@ export class CronEngine {
|
|
|
222
223
|
scanProject(this.wolfDir, this.projectRoot);
|
|
223
224
|
break;
|
|
224
225
|
case "consolidate_memory":
|
|
225
|
-
|
|
226
|
+
// Delegates to utils/maintenance — the engine used to carry its own copy, which MISSED the
|
|
227
|
+
// idempotency guard: every daily run re-consolidated already-consolidated blocks and, finding
|
|
228
|
+
// no table rows in them, rewrote every count to "(0 actions)". 127 of 128 blocks in one live
|
|
229
|
+
// memory.md had lost their counts to this. One implementation now, guarded and locked.
|
|
230
|
+
consolidateMemory(this.wolfDir, action.params?.older_than_days ?? 7);
|
|
226
231
|
break;
|
|
227
232
|
case "generate_token_report":
|
|
228
233
|
this.generateTokenReport();
|
|
@@ -234,54 +239,6 @@ export class CronEngine {
|
|
|
234
239
|
throw new Error(`Unknown action type: ${action.type}`);
|
|
235
240
|
}
|
|
236
241
|
}
|
|
237
|
-
consolidateMemory(olderThanDays) {
|
|
238
|
-
const memoryPath = path.join(this.wolfDir, "memory.md");
|
|
239
|
-
const content = readText(memoryPath);
|
|
240
|
-
if (!content)
|
|
241
|
-
return;
|
|
242
|
-
const cutoff = new Date();
|
|
243
|
-
cutoff.setDate(cutoff.getDate() - olderThanDays);
|
|
244
|
-
const lines = content.split("\n");
|
|
245
|
-
const result = [];
|
|
246
|
-
let inOldSession = false;
|
|
247
|
-
let oldSessionLines = [];
|
|
248
|
-
let currentSessionDate = null;
|
|
249
|
-
for (const line of lines) {
|
|
250
|
-
const sessionMatch = line.match(/^## Session: (\d{4}-\d{2}-\d{2})/);
|
|
251
|
-
if (sessionMatch) {
|
|
252
|
-
// Flush previous old session
|
|
253
|
-
if (inOldSession && oldSessionLines.length > 0) {
|
|
254
|
-
const actionCount = oldSessionLines.filter((l) => l.startsWith("|") && !l.startsWith("|--") && !l.startsWith("| Time")).length;
|
|
255
|
-
result.push(`> Consolidated session (${actionCount} actions)`);
|
|
256
|
-
result.push("");
|
|
257
|
-
}
|
|
258
|
-
currentSessionDate = new Date(sessionMatch[1]);
|
|
259
|
-
if (currentSessionDate < cutoff) {
|
|
260
|
-
inOldSession = true;
|
|
261
|
-
oldSessionLines = [];
|
|
262
|
-
result.push(line); // Keep the header
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
inOldSession = false;
|
|
266
|
-
result.push(line);
|
|
267
|
-
}
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (inOldSession) {
|
|
271
|
-
oldSessionLines.push(line);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
result.push(line);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
// Flush last old session
|
|
278
|
-
if (inOldSession && oldSessionLines.length > 0) {
|
|
279
|
-
const actionCount = oldSessionLines.filter((l) => l.startsWith("|") && !l.startsWith("|--") && !l.startsWith("| Time")).length;
|
|
280
|
-
result.push(`> Consolidated session (${actionCount} actions)`);
|
|
281
|
-
result.push("");
|
|
282
|
-
}
|
|
283
|
-
writeText(memoryPath, result.join("\n"));
|
|
284
|
-
}
|
|
285
242
|
generateTokenReport() {
|
|
286
243
|
const flags = detectWaste(this.wolfDir);
|
|
287
244
|
const ledgerPath = path.join(this.wolfDir, "token-ledger.json");
|