openwolf-enhanced 1.26.0 → 1.27.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/CHANGELOG.md +175 -0
- package/dist/dashboard/assets/{AISuggestions-DZmzksSA.js → AISuggestions-DBUq3WQW.js} +1 -1
- package/dist/dashboard/assets/{ActivityLog-BJ0omuzu.js → ActivityLog-DzvER0xw.js} +1 -1
- package/dist/dashboard/assets/{ActivityTimeline-BWhxretI.js → ActivityTimeline-DVx1o9Of.js} +1 -1
- package/dist/dashboard/assets/{AnatomyBrowser-DNVp6-_r.js → AnatomyBrowser-CQGTp2MR.js} +1 -1
- package/dist/dashboard/assets/{BugLog-DMJTuU9p.js → BugLog-Ka23MuzX.js} +1 -1
- package/dist/dashboard/assets/{CerebrumViewer-BwcGr6pu.js → CerebrumViewer-2SO_QPQn.js} +1 -1
- package/dist/dashboard/assets/{CronStatus-Dwf_M4-S.js → CronStatus-BSJ7o-Xc.js} +1 -1
- package/dist/dashboard/assets/{DesignQC-DzHkh94I.js → DesignQC-D7Btjv4l.js} +1 -1
- package/dist/dashboard/assets/{LinkGraph-kqQD--Yj.js → LinkGraph-Cj0m-9HI.js} +1 -1
- package/dist/dashboard/assets/{MemoryViewer-BCn9Fr-w.js → MemoryViewer-DQ_OGTaW.js} +1 -1
- package/dist/dashboard/assets/{NativeMemory-DPMZaP5E.js → NativeMemory-BLa2fOaz.js} +1 -1
- package/dist/dashboard/assets/{ProjectOverview-PWENJiC4.js → ProjectOverview-DK4qaJsJ.js} +1 -1
- package/dist/dashboard/assets/{ProjectsAggregate-Baj44DVk.js → ProjectsAggregate-DW8TBuH2.js} +1 -1
- package/dist/dashboard/assets/{StatusBadge-Db61D51H.js → StatusBadge-Bclc5R-9.js} +1 -1
- package/dist/dashboard/assets/{TokenUsage-UefDh9w4.js → TokenUsage-BwhsNFH4.js} +1 -1
- package/dist/dashboard/assets/index-BNi8lsJE.css +2 -0
- package/dist/dashboard/assets/{index-e2PfOI0b.js → index-DsimHkSD.js} +5 -5
- package/dist/dashboard/index.html +2 -2
- package/dist/hooks/post-bash.js +6 -1
- package/dist/hooks/post-read.js +20 -20
- package/dist/hooks/post-write.js +22 -18
- package/dist/hooks/pre-read.js +39 -30
- package/dist/hooks/session-start.js +5 -3
- package/dist/hooks/shared.js +83 -2
- package/dist/hooks/stop.js +25 -5
- package/dist/src/cli/daemon-cmd.js +9 -7
- package/dist/src/cli/daemon-cmd.js.map +1 -1
- package/dist/src/cli/doctor-cmd.js +2 -1
- package/dist/src/cli/doctor-cmd.js.map +1 -1
- package/dist/src/cli/export-cmd.js +8 -0
- package/dist/src/cli/export-cmd.js.map +1 -1
- package/dist/src/cli/init.js +25 -6
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/update.js +10 -2
- package/dist/src/cli/update.js.map +1 -1
- package/dist/src/daemon/cron-engine.js +47 -3
- package/dist/src/daemon/cron-engine.js.map +1 -1
- package/dist/src/daemon/llm-provider.js +9 -6
- package/dist/src/daemon/llm-provider.js.map +1 -1
- package/dist/src/daemon/wolf-daemon.js +52 -12
- package/dist/src/daemon/wolf-daemon.js.map +1 -1
- package/dist/src/designqc/designqc-capture.js +116 -6
- package/dist/src/designqc/designqc-capture.js.map +1 -1
- package/dist/src/designqc/designqc-engine.js +9 -3
- package/dist/src/designqc/designqc-engine.js.map +1 -1
- package/dist/src/hooks/post-bash.js +6 -1
- package/dist/src/hooks/post-bash.js.map +1 -1
- package/dist/src/hooks/post-read.js +20 -20
- package/dist/src/hooks/post-read.js.map +1 -1
- package/dist/src/hooks/post-write.js +22 -18
- package/dist/src/hooks/post-write.js.map +1 -1
- package/dist/src/hooks/pre-read.js +39 -30
- package/dist/src/hooks/pre-read.js.map +1 -1
- package/dist/src/hooks/session-start.js +5 -3
- package/dist/src/hooks/session-start.js.map +1 -1
- package/dist/src/hooks/shared.js +83 -2
- package/dist/src/hooks/shared.js.map +1 -1
- package/dist/src/hooks/stop.js +25 -5
- package/dist/src/hooks/stop.js.map +1 -1
- package/dist/src/utils/embeddings.js +9 -1
- package/dist/src/utils/embeddings.js.map +1 -1
- package/dist/src/utils/fs-safe.js +15 -1
- package/dist/src/utils/fs-safe.js.map +1 -1
- package/dist/src/utils/logger.js +21 -6
- package/dist/src/utils/logger.js.map +1 -1
- package/dist/src/utils/maintenance.js +44 -1
- package/dist/src/utils/maintenance.js.map +1 -1
- package/dist/src/utils/platform.js +39 -0
- package/dist/src/utils/platform.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/config.json +2 -1
- package/dist/dashboard/assets/index-CVfvTpSA.css +0 -2
|
@@ -7,8 +7,8 @@
|
|
|
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-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-DsimHkSD.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BNi8lsJE.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
package/dist/hooks/post-bash.js
CHANGED
|
@@ -66,7 +66,12 @@ async function main() {
|
|
|
66
66
|
// COMMAND position of each shell segment, not the whole string — the old `\bopenwolf\b` skipped
|
|
67
67
|
// every command that merely mentioned an openwolf PATH, which made the write counter blind for
|
|
68
68
|
// exactly the sessions that work on an openwolf checkout.
|
|
69
|
-
|
|
69
|
+
// Newlines separate commands exactly like `;` does, and they were missing from this split: a
|
|
70
|
+
// two-line Bash block whose second line ran `openwolf push` was tested as ONE segment starting
|
|
71
|
+
// with the first line's command, so the filter saw no openwolf invocation and captured the whole
|
|
72
|
+
// thing. Multi-line blocks are the normal shape of an agent's shell call, so this was the common
|
|
73
|
+
// case, not the edge one.
|
|
74
|
+
const invokesOpenwolf = cmd.split(/&&|\|\||[;|\n\r]/).some(segmentInvokesOpenwolf);
|
|
70
75
|
if (invokesOpenwolf) {
|
|
71
76
|
process.exit(0);
|
|
72
77
|
return;
|
package/dist/hooks/post-read.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { getWolfDir, ensureWolfDir,
|
|
2
|
+
import { getWolfDir, ensureWolfDir, updateSession, readMarkdown, parseAnatomy, estimateFileTokens, getTokenRatios, readStdin, normalizePath, loadIgnore, isSecretFile, matchesAnatomyEntry } from "./shared.js";
|
|
3
3
|
async function main() {
|
|
4
4
|
ensureWolfDir();
|
|
5
5
|
const wolfDir = getWolfDir();
|
|
@@ -57,7 +57,7 @@ async function main() {
|
|
|
57
57
|
for (const [sectionKey, entries] of sections) {
|
|
58
58
|
for (const entry of entries) {
|
|
59
59
|
const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
|
|
60
|
-
if (
|
|
60
|
+
if (matchesAnatomyEntry(normalizedFile, entryRelPath)) {
|
|
61
61
|
tokens = entry.tokens;
|
|
62
62
|
break;
|
|
63
63
|
}
|
|
@@ -66,24 +66,24 @@ async function main() {
|
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
69
|
+
updateSession(sessionFile, { files_read: {} }, (session) => {
|
|
70
|
+
if (session.files_read[normalizedFile]) {
|
|
71
|
+
// Never let a re-read shrink the estimate to zero. A repeat read often arrives with an
|
|
72
|
+
// empty tool_output.content, and if the file has no anatomy entry the fallback above
|
|
73
|
+
// cannot recover a number — overwriting unconditionally would wipe the good first-read
|
|
74
|
+
// estimate. That deflates inputTokens in stop.ts and, because savedFromRepeats multiplies
|
|
75
|
+
// by (count - 1), silently zeroes the repeat-savings metric too.
|
|
76
|
+
const prev = session.files_read[normalizedFile].tokens ?? 0;
|
|
77
|
+
session.files_read[normalizedFile].tokens = Math.max(prev, tokens);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
session.files_read[normalizedFile] = {
|
|
81
|
+
count: 1,
|
|
82
|
+
tokens,
|
|
83
|
+
first_read: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
87
|
process.exit(0);
|
|
88
88
|
}
|
|
89
89
|
main().catch(() => process.exit(0));
|
package/dist/hooks/post-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,
|
|
3
|
+
import { getWolfDir, ensureWolfDir, writeJSON, writeAtomic, parseAnatomy, serializeAnatomy, extractDescription, estimateFileTokens, getTokenRatios, appendMarkdown, timeShort, readStdin, normalizePath, getRetention, loadIgnore, readBugLog, isSecretFile, updateSession, isOutsideProject } from "./shared.js";
|
|
4
4
|
async function main() {
|
|
5
5
|
ensureWolfDir();
|
|
6
6
|
const wolfDir = getWolfDir();
|
|
@@ -37,10 +37,10 @@ async function main() {
|
|
|
37
37
|
// nothing happened, and the STATUS.md reminder never fires. That is exactly how a handoff doc goes
|
|
38
38
|
// stale for eleven slices without anyone noticing. The counter is a number only: no path, no name,
|
|
39
39
|
// so #56 still holds.
|
|
40
|
-
if (relPath
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
if (isOutsideProject(relPath)) {
|
|
41
|
+
updateSession(sessionFile, { files_written: [], edit_counts: {} }, (session) => {
|
|
42
|
+
session.external_writes = (session.external_writes ?? 0) + 1;
|
|
43
|
+
});
|
|
44
44
|
process.exit(0);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -133,24 +133,28 @@ async function main() {
|
|
|
133
133
|
catch { }
|
|
134
134
|
// 3. Record in session tracker + track edit counts
|
|
135
135
|
try {
|
|
136
|
-
const session = readJSON(sessionFile, { files_written: [], edit_counts: {} });
|
|
137
|
-
if (!session.edit_counts)
|
|
138
|
-
session.edit_counts = {};
|
|
139
136
|
const normalizedFile = normalizePath(filePath);
|
|
140
137
|
const action = toolName === "Write" ? "create" : "edit";
|
|
141
138
|
const fileContent = input.tool_input?.content ?? "";
|
|
142
139
|
const tokens = estimateFileTokens(fileContent || writtenText, absolutePath, getTokenRatios(wolfDir));
|
|
143
|
-
session.files_written.push({
|
|
144
|
-
file: normalizedFile,
|
|
145
|
-
action,
|
|
146
|
-
tokens,
|
|
147
|
-
at: new Date().toISOString(),
|
|
148
|
-
});
|
|
149
140
|
const editKey = normalizePath(path.relative(projectRoot, absolutePath));
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
141
|
+
let edits = 0;
|
|
142
|
+
updateSession(sessionFile, { files_written: [], edit_counts: {} }, (session) => {
|
|
143
|
+
if (!session.files_written)
|
|
144
|
+
session.files_written = [];
|
|
145
|
+
if (!session.edit_counts)
|
|
146
|
+
session.edit_counts = {};
|
|
147
|
+
session.files_written.push({
|
|
148
|
+
file: normalizedFile,
|
|
149
|
+
action,
|
|
150
|
+
tokens,
|
|
151
|
+
at: new Date().toISOString(),
|
|
152
|
+
});
|
|
153
|
+
session.edit_counts[editKey] = (session.edit_counts[editKey] || 0) + 1;
|
|
154
|
+
edits = session.edit_counts[editKey];
|
|
155
|
+
});
|
|
156
|
+
if (edits >= 3) {
|
|
157
|
+
process.stderr.write(`⚠️ OpenWolf: ${baseName} has been edited ${edits} times this session. If you're fixing a bug, remember to log it to .wolf/buglog.json.\n`);
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
catch { }
|
package/dist/hooks/pre-read.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, readJSON,
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, updateSession, readMarkdown, parseAnatomy, readStdin, normalizePath, loadIgnore, isSecretFile, matchesAnatomyEntry } from "./shared.js";
|
|
4
|
+
const SESSION_FALLBACK = {
|
|
5
|
+
session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0, repeated_reads_warned: 0,
|
|
6
|
+
};
|
|
4
7
|
async function main() {
|
|
5
8
|
ensureWolfDir();
|
|
6
9
|
const wolfDir = getWolfDir();
|
|
@@ -56,27 +59,34 @@ async function main() {
|
|
|
56
59
|
process.exit(0);
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
62
|
+
// Snapshot read, used only to DECIDE what to print. Every mutation below re-reads under the lock
|
|
63
|
+
// and applies a delta, so a concurrent hook's update is never overwritten with this snapshot.
|
|
64
|
+
const session = readJSON(sessionFile, SESSION_FALLBACK);
|
|
63
65
|
// Check if already read this session
|
|
64
66
|
if (session.files_read[normalizedFile]) {
|
|
65
67
|
const prev = session.files_read[normalizedFile];
|
|
66
68
|
// If the file changed since we last read it (e.g. it was edited during the session),
|
|
67
69
|
// a re-read is legitimate — refresh state and don't warn about a repeated read (upstream #41).
|
|
68
70
|
if (fileMtime !== undefined && prev.mtime !== undefined && fileMtime !== prev.mtime) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
updateSession(sessionFile, SESSION_FALLBACK, (s) => {
|
|
72
|
+
const e = s.files_read[normalizedFile];
|
|
73
|
+
if (e) {
|
|
74
|
+
e.count++;
|
|
75
|
+
e.mtime = fileMtime;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
72
78
|
process.exit(0);
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
81
|
process.stderr.write(`⚡ OpenWolf: ${path.basename(normalizedFile)} was already read this session (~${prev.tokens} tokens). Consider using your existing knowledge of this file.\n`);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
updateSession(sessionFile, SESSION_FALLBACK, (s) => {
|
|
83
|
+
const e = s.files_read[normalizedFile];
|
|
84
|
+
if (e) {
|
|
85
|
+
e.count++;
|
|
86
|
+
e.mtime = fileMtime ?? e.mtime;
|
|
87
|
+
}
|
|
88
|
+
s.repeated_reads_warned++;
|
|
89
|
+
});
|
|
80
90
|
process.exit(0);
|
|
81
91
|
return;
|
|
82
92
|
}
|
|
@@ -90,7 +100,7 @@ async function main() {
|
|
|
90
100
|
for (const entry of entries) {
|
|
91
101
|
// Build the full relative path from the section key + filename for accurate matching
|
|
92
102
|
const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
|
|
93
|
-
if (
|
|
103
|
+
if (matchesAnatomyEntry(normalizedFile, entryRelPath)) {
|
|
94
104
|
process.stderr.write(`📋 OpenWolf anatomy: ${entry.file} — ${entry.description} (~${entry.tokens} tok)\n`);
|
|
95
105
|
// If we indexed this big file's symbols, offer line ranges so the agent can read one
|
|
96
106
|
// function with offset/limit instead of the whole file.
|
|
@@ -107,23 +117,22 @@ async function main() {
|
|
|
107
117
|
if (found)
|
|
108
118
|
break;
|
|
109
119
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
};
|
|
126
|
-
writeJSON(sessionFile, session);
|
|
120
|
+
updateSession(sessionFile, SESSION_FALLBACK, (s) => {
|
|
121
|
+
if (found)
|
|
122
|
+
s.anatomy_hits++;
|
|
123
|
+
else
|
|
124
|
+
s.anatomy_misses++;
|
|
125
|
+
// Record initial read entry (tokens will be updated in post-read)
|
|
126
|
+
s.files_read[normalizedFile] = {
|
|
127
|
+
count: 1,
|
|
128
|
+
tokens: 0,
|
|
129
|
+
first_read: new Date().toISOString(),
|
|
130
|
+
// Whether anatomy.md already described this file. The ledger used to hardcode this as
|
|
131
|
+
// false, which made the waste detector's "anatomy would have sufficed" pattern unreachable.
|
|
132
|
+
anatomy_had_description: found,
|
|
133
|
+
mtime: fileMtime,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
127
136
|
process.exit(0);
|
|
128
137
|
}
|
|
129
138
|
main().catch(() => process.exit(0));
|
|
@@ -35,8 +35,10 @@ async function main() {
|
|
|
35
35
|
catch { }
|
|
36
36
|
const continuing = (source === "compact" || source === "resume") && fs.existsSync(sessionFile);
|
|
37
37
|
if (!continuing) {
|
|
38
|
-
// Create fresh session state
|
|
39
|
-
|
|
38
|
+
// Create fresh session state. Under the lock like every other writer of this file: the reset is
|
|
39
|
+
// a whole-file replacement, so an unlocked write here could land in the middle of another
|
|
40
|
+
// hook's read-modify-write and hand it a half-written file to parse.
|
|
41
|
+
withLock(sessionFile, () => writeJSON(sessionFile, {
|
|
40
42
|
session_id: sessionId,
|
|
41
43
|
started: timestamp(),
|
|
42
44
|
files_read: {},
|
|
@@ -47,7 +49,7 @@ async function main() {
|
|
|
47
49
|
repeated_reads_warned: 0,
|
|
48
50
|
cerebrum_warnings: 0,
|
|
49
51
|
stop_count: 0,
|
|
50
|
-
});
|
|
52
|
+
}));
|
|
51
53
|
// Append session header to memory.md
|
|
52
54
|
const memoryPath = path.join(wolfDir, "memory.md");
|
|
53
55
|
const header = `\n## Session: ${now.toISOString().slice(0, 10)} ${timeShort()}\n\n${SESSION_SUMMARY_SCAFFOLD}\n\n| Time | Action | File(s) | Outcome | ~Tokens |\n|------|--------|---------|---------|--------|\n`;
|
package/dist/hooks/shared.js
CHANGED
|
@@ -126,6 +126,26 @@ export function withLock(targetPath, fn) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Read-modify-write `_session.json` under the lock, in one place.
|
|
131
|
+
*
|
|
132
|
+
* Every hook in a session mutates this one file, and until now only two of the seven writers took
|
|
133
|
+
* the lock at all: the rest did `readJSON` … `writeJSON` around unrelated work, so a PostToolUse
|
|
134
|
+
* hook finishing while PreToolUse was mid-cycle silently dropped one of the two updates. Reads and
|
|
135
|
+
* writes get counted for the token ledger, so a lost update is a wrong number in a report nobody
|
|
136
|
+
* can audit afterwards.
|
|
137
|
+
*
|
|
138
|
+
* The mutator receives the state as it is ON DISK at the moment the lock is held — not a snapshot
|
|
139
|
+
* taken earlier — and must apply a DELTA (increment, add a key), never assign a whole object it
|
|
140
|
+
* captured before. That is what makes the update safe against a concurrent writer.
|
|
141
|
+
*/
|
|
142
|
+
export function updateSession(sessionFile, fallback, mutate) {
|
|
143
|
+
withLock(sessionFile, () => {
|
|
144
|
+
const session = readJSON(sessionFile, fallback);
|
|
145
|
+
mutate(session);
|
|
146
|
+
writeJSON(sessionFile, session);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
129
149
|
// A missing file is normal (first run) and silently yields the fallback. An EXISTING file that
|
|
130
150
|
// fails to parse is a different situation: whatever it held is about to be treated as empty, and
|
|
131
151
|
// a read-modify-write caller would then overwrite it with defaults. That must at least be visible.
|
|
@@ -201,10 +221,24 @@ export function readJSON(filePath, fallback) {
|
|
|
201
221
|
return JSON.parse(raw);
|
|
202
222
|
}
|
|
203
223
|
catch (e) {
|
|
204
|
-
|
|
224
|
+
// The caller is about to treat this file as empty and, in a read-modify-write, overwrite it
|
|
225
|
+
// with defaults — so a corrupt token-ledger.json meant months of usage history vanished on the
|
|
226
|
+
// next Stop hook, with one line on stderr as the only trace. Move it aside first. Recovery is
|
|
227
|
+
// then a possibility rather than a hope, and because the path no longer exists, the next read
|
|
228
|
+
// takes the ordinary "missing file" branch instead of warning forever.
|
|
229
|
+
quarantineCorrupt(filePath);
|
|
230
|
+
process.stderr.write(`[openwolf] ${path.basename(filePath)} was not valid JSON — moved aside, using defaults (${e.message})\n`);
|
|
205
231
|
return fallback;
|
|
206
232
|
}
|
|
207
233
|
}
|
|
234
|
+
/** Rename a corrupt file to <name>.corrupt-<stamp>. Best-effort: never throws, never blocks a read. */
|
|
235
|
+
function quarantineCorrupt(filePath) {
|
|
236
|
+
try {
|
|
237
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
238
|
+
fs.renameSync(filePath, `${filePath}.corrupt-${stamp}`);
|
|
239
|
+
}
|
|
240
|
+
catch { /* unwritable directory, race with another process — defaults still apply */ }
|
|
241
|
+
}
|
|
208
242
|
// Duplicated from utils/fs-safe.ts (separate build roots). Never throws — a hook must not kill a
|
|
209
243
|
// session over a failed journal write — but a total failure is reported on stderr instead of
|
|
210
244
|
// vanishing: a silently dropped write once hid a completely broken persistence path (bug-183).
|
|
@@ -298,12 +332,17 @@ export function makeIgnoreMatcher(patterns) {
|
|
|
298
332
|
if (pat.startsWith("*.") && norm.endsWith(pat.slice(1)))
|
|
299
333
|
return true;
|
|
300
334
|
}
|
|
301
|
-
|
|
335
|
+
// `?` is a gitignore wildcard for exactly one character, and it also has to be handled BEFORE
|
|
336
|
+
// this becomes a regex — it used to be neither escaped nor translated, so it survived into the
|
|
337
|
+
// pattern as a regex quantifier and made the preceding character OPTIONAL. `build?/*.js` then
|
|
338
|
+
// matched `buil/x.js` and missed `build2/x.js`: the rule silently covered the wrong files.
|
|
339
|
+
if (pat.includes("*") || pat.includes("?")) {
|
|
302
340
|
const re = new RegExp("^" +
|
|
303
341
|
pat
|
|
304
342
|
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
305
343
|
.replace(/\*\*/g, "\u0000")
|
|
306
344
|
.replace(/\*/g, "[^/]*")
|
|
345
|
+
.replace(/\?/g, "[^/]")
|
|
307
346
|
.replace(/\u0000/g, ".*") +
|
|
308
347
|
"$");
|
|
309
348
|
if (re.test(norm) || (!anchored && re.test(base)))
|
|
@@ -1054,6 +1093,48 @@ export function readStdin() {
|
|
|
1054
1093
|
export function normalizePath(p) {
|
|
1055
1094
|
return p.replace(/\\/g, "/");
|
|
1056
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Does an absolute (forward-slashed) file path correspond to this anatomy.md entry?
|
|
1098
|
+
*
|
|
1099
|
+
* anatomy entries are project-relative ("src/api/client.ts"); the hooks see an absolute path. The
|
|
1100
|
+
* old test was `abs.endsWith(rel) || abs.endsWith("/" + rel)`, and the first half of that had no
|
|
1101
|
+
* path-segment boundary: `/home/me/vendor/src/api/client.ts` and `/tmp/other-src/api/client.ts`
|
|
1102
|
+
* both "matched" an entry for `src/api/client.ts`, so the hook printed another file's description
|
|
1103
|
+
* and token estimate and booked an anatomy HIT for a file anatomy.md had never seen. The second
|
|
1104
|
+
* half of the old expression was already correct, so this keeps that and drops the loose one — an
|
|
1105
|
+
* exact equality covers the case where the path is already relative.
|
|
1106
|
+
*/
|
|
1107
|
+
export function matchesAnatomyEntry(filePath, entryRelPath) {
|
|
1108
|
+
return filePath === entryRelPath || filePath.endsWith("/" + entryRelPath);
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Does this project-relative path point OUTSIDE the project root?
|
|
1112
|
+
*
|
|
1113
|
+
* Input is the output of `normalizePath(path.relative(root, file))`. The obvious test —
|
|
1114
|
+
* "does it start with `..`" — is complete on POSIX and wrong on Windows, because `path.relative`
|
|
1115
|
+
* cannot express a route between two drives and gives up by returning the target ABSOLUTE:
|
|
1116
|
+
*
|
|
1117
|
+
* path.win32.relative("C:\\proj", "F:\\secrets\\x") === "F:\\secrets\\x"
|
|
1118
|
+
*
|
|
1119
|
+
* That has no leading `..`, so the guard passed it and the full foreign path was written into
|
|
1120
|
+
* anatomy.md and memory.md — precisely the leak (upstream #56) the guard exists to prevent, on the
|
|
1121
|
+
* one platform nobody tested it on. An absolute result means "no relative route exists", i.e. the
|
|
1122
|
+
* strongest possible outside-the-root signal.
|
|
1123
|
+
*
|
|
1124
|
+
* The `..` test is also anchored to a whole segment now: a file genuinely named `..config` in the
|
|
1125
|
+
* root is inside the project, and used to be counted as external.
|
|
1126
|
+
*/
|
|
1127
|
+
export function isOutsideProject(relPath) {
|
|
1128
|
+
if (relPath === "")
|
|
1129
|
+
return true; // the path IS the root, not a file in it
|
|
1130
|
+
if (relPath === ".." || relPath.startsWith("../"))
|
|
1131
|
+
return true;
|
|
1132
|
+
if (path.isAbsolute(relPath))
|
|
1133
|
+
return true; // win32 cross-drive ("F:/secrets/x")
|
|
1134
|
+
if (/^[a-zA-Z]:/.test(relPath))
|
|
1135
|
+
return true; // drive-relative ("F:x"), also not ours
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1057
1138
|
// Count non-mechanical (semantic) entries written to memory.md today. Mechanical rows
|
|
1058
1139
|
// (auto file-op / session-end lines) don't count. Used by the stop hook to detect whether
|
|
1059
1140
|
// a meaningful session summary was written. (upstream #55)
|
package/dist/hooks/stop.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, appendMarkdown, timeShort, getRetention, compactMemoryIfLarge, countSemanticEntries, withLock, readStdin, readTranscriptUsage, detectAgent, bookInjection } from "./shared.js";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, appendMarkdown, timeShort, getRetention, compactMemoryIfLarge, countSemanticEntries, withLock, updateSession, readStdin, readTranscriptUsage, detectAgent, bookInjection } from "./shared.js";
|
|
4
4
|
import { estimateTokens, getTokenRatios } from "./token-estimator.js";
|
|
5
5
|
const NOTHING_BOOKED = {
|
|
6
6
|
reads: 0, writes: 0, tokens: 0, anatomy_hits: 0, anatomy_misses: 0, repeated_reads_blocked: 0,
|
|
@@ -49,7 +49,11 @@ async function main() {
|
|
|
49
49
|
const unnamedWrites = externalWrites + bashWrites;
|
|
50
50
|
const totalWrites = writeCount + unnamedWrites;
|
|
51
51
|
if (readCount === 0 && totalWrites === 0) {
|
|
52
|
-
|
|
52
|
+
// Only stop_count moved; apply it as a delta so an idle turn cannot overwrite a sibling
|
|
53
|
+
// hook's concurrent update with this hook's stale snapshot.
|
|
54
|
+
updateSession(sessionFile, session, (s) => {
|
|
55
|
+
s.stop_count = Math.max(s.stop_count ?? 0, session.stop_count);
|
|
56
|
+
});
|
|
53
57
|
process.exit(0);
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
@@ -180,8 +184,15 @@ async function main() {
|
|
|
180
184
|
lt.real_cache_creation_tokens = (lt.real_cache_creation_tokens ?? 0) + delta(realUsage.cache_creation_input_tokens, booked.real_cache_creation);
|
|
181
185
|
lt.real_api_calls = (lt.real_api_calls ?? 0) + delta(realUsage.api_calls, booked.real_api_calls);
|
|
182
186
|
}
|
|
183
|
-
// Mark what is now booked.
|
|
184
|
-
//
|
|
187
|
+
// Mark what is now booked.
|
|
188
|
+
//
|
|
189
|
+
// [fix] Setting this field inside the lock was never the point — PERSISTING it was. The write to
|
|
190
|
+
// _session.json used to sit ~30 lines further down, past two memory.md appends and a memory
|
|
191
|
+
// compaction, outside this lock. Anything that ended the process in that window (a hook timeout,
|
|
192
|
+
// the user quitting, a full disk during compactMemoryIfLarge) left the ledger credited and the
|
|
193
|
+
// session file still carrying the OLD booked values, so the next Stop re-booked every delta on
|
|
194
|
+
// top. The comment claimed that could not happen. It is now written under the same lock, one
|
|
195
|
+
// statement after the ledger, which is what the comment always described.
|
|
185
196
|
session.booked = {
|
|
186
197
|
reads: Math.max(readCount, booked.reads),
|
|
187
198
|
writes: Math.max(totalWritesNow, booked.writes),
|
|
@@ -197,6 +208,16 @@ async function main() {
|
|
|
197
208
|
real_api_calls: Math.max(realUsage?.api_calls ?? 0, booked.real_api_calls),
|
|
198
209
|
};
|
|
199
210
|
writeJSON(ledgerPath, ledger);
|
|
211
|
+
// Persist the session's own bookkeeping here, still holding the ledger lock. The deltas are
|
|
212
|
+
// re-applied to whatever is on disk right now, so a PostToolUse hook that wrote between this
|
|
213
|
+
// hook's initial read and this line does not lose its update.
|
|
214
|
+
const bookedNow = session.booked;
|
|
215
|
+
const remindersNow = session.reminders_shown;
|
|
216
|
+
updateSession(sessionFile, session, (s) => {
|
|
217
|
+
s.booked = bookedNow;
|
|
218
|
+
s.reminders_shown = remindersNow;
|
|
219
|
+
s.stop_count = Math.max(s.stop_count ?? 0, session.stop_count);
|
|
220
|
+
});
|
|
200
221
|
});
|
|
201
222
|
// Write a session summary line to memory.md if there was meaningful activity
|
|
202
223
|
const memoryPath = path.join(wolfDir, "memory.md");
|
|
@@ -227,7 +248,6 @@ async function main() {
|
|
|
227
248
|
compactMemoryIfLarge(wolfDir, ret.memory_max_bytes);
|
|
228
249
|
}
|
|
229
250
|
catch { }
|
|
230
|
-
writeJSON(sessionFile, session);
|
|
231
251
|
// Surface reminders into Claude's next context window (Stop hooks can inject via stdout JSON).
|
|
232
252
|
if (reminders.length > 0) {
|
|
233
253
|
const additionalContext = `⚠️ OpenWolf end-of-turn reminders:\n${reminders.map((r) => `• ${r}`).join("\n")}`;
|
|
@@ -6,7 +6,7 @@ import { findProjectRoot } from "../scanner/project-root.js";
|
|
|
6
6
|
import { readJSON, writeJSON, withLock } from "../utils/fs-safe.js";
|
|
7
7
|
import { readDashboardToken } from "../utils/dashboard-auth.js";
|
|
8
8
|
import { isPortFree } from "../utils/ports.js";
|
|
9
|
-
import { isWindows } from "../utils/platform.js";
|
|
9
|
+
import { isWindows, execShim } from "../utils/platform.js";
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
12
|
function getDashboardPort() {
|
|
@@ -15,7 +15,9 @@ function getDashboardPort() {
|
|
|
15
15
|
const config = readJSON(path.join(wolfDir, "config.json"), { openwolf: { dashboard: { port: 18791 } } });
|
|
16
16
|
return config.openwolf.dashboard.port;
|
|
17
17
|
}
|
|
18
|
-
// On Windows the pm2 shim is pm2.cmd
|
|
18
|
+
// On Windows the pm2 shim is pm2.cmd. Node cannot spawn a .cmd without a shell (CVE-2024-27980
|
|
19
|
+
// hardening), so every call goes through execShim(), which routes it via cmd.exe with the
|
|
20
|
+
// arguments still quoted individually rather than joined into a parseable string.
|
|
19
21
|
const PM2 = isWindows() ? "pm2.cmd" : "pm2";
|
|
20
22
|
function getPm2Name() {
|
|
21
23
|
const projectRoot = findProjectRoot();
|
|
@@ -157,11 +159,11 @@ export function daemonStart() {
|
|
|
157
159
|
// Resolve daemon script relative to openwolf's install dir, not the target project
|
|
158
160
|
const daemonScript = path.resolve(__dirname, "..", "daemon", "wolf-daemon.js");
|
|
159
161
|
try {
|
|
160
|
-
|
|
162
|
+
execShim(PM2, ["start", daemonScript, "--name", name, "--cwd", projectRoot, "--", "--env", `OPENWOLF_PROJECT_ROOT=${projectRoot}`], {
|
|
161
163
|
stdio: "inherit",
|
|
162
164
|
env: { ...process.env, OPENWOLF_PROJECT_ROOT: projectRoot },
|
|
163
165
|
});
|
|
164
|
-
|
|
166
|
+
execShim(PM2, ["save"], { stdio: "ignore" });
|
|
165
167
|
console.log(`\n ✓ Daemon started: ${name}`);
|
|
166
168
|
if (isWindows()) {
|
|
167
169
|
console.log(" Tip: Run 'pm2-windows-startup' for boot persistence.");
|
|
@@ -185,7 +187,7 @@ export async function daemonStop() {
|
|
|
185
187
|
if (hasPm2()) {
|
|
186
188
|
const name = getPm2Name();
|
|
187
189
|
try {
|
|
188
|
-
|
|
190
|
+
execShim(PM2, ["stop", name], { stdio: "ignore" });
|
|
189
191
|
markDaemonStopped(wolfDir);
|
|
190
192
|
console.log(` ✓ Daemon stopped (PM2): ${name}`);
|
|
191
193
|
return;
|
|
@@ -227,7 +229,7 @@ export async function daemonRestart() {
|
|
|
227
229
|
if (hasPm2()) {
|
|
228
230
|
const name = getPm2Name();
|
|
229
231
|
try {
|
|
230
|
-
|
|
232
|
+
execShim(PM2, ["restart", name], { stdio: "ignore" });
|
|
231
233
|
console.log(` ✓ Daemon restarted (PM2): ${name}`);
|
|
232
234
|
return;
|
|
233
235
|
}
|
|
@@ -264,7 +266,7 @@ export function daemonLogs() {
|
|
|
264
266
|
}
|
|
265
267
|
const name = getPm2Name();
|
|
266
268
|
try {
|
|
267
|
-
|
|
269
|
+
execShim(PM2, ["logs", name, "--lines", "50", "--nostream"], { stdio: "inherit" });
|
|
268
270
|
}
|
|
269
271
|
catch {
|
|
270
272
|
console.error("Failed to get daemon logs.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-cmd.js","sourceRoot":"","sources":["../../../src/cli/daemon-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon-cmd.js","sourceRoot":"","sources":["../../../src/cli/daemon-cmd.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { isWindows } from "../utils/platform.js";
|
|
|
7
7
|
import { dashboardTokenPath } from "../utils/dashboard-auth.js";
|
|
8
8
|
import { remoteTokenPath } from "../utils/remote.js";
|
|
9
9
|
import { readRegistry } from "./registry.js";
|
|
10
|
-
import { getRetention, footprint, humanBytes, compactLedger, consolidateMemory, dedupeAndCapBuglog, pruneBackups, cleanTmp, rotateDaemonLog, dirSize, suggestIgnores, nativeMemoryHealth, findDuplicateEntries, repairNativeMemoryIndex, } from "../utils/maintenance.js";
|
|
10
|
+
import { getRetention, footprint, humanBytes, compactLedger, consolidateMemory, dedupeAndCapBuglog, pruneBackups, pruneProposals, cleanTmp, rotateDaemonLog, dirSize, suggestIgnores, nativeMemoryHealth, findDuplicateEntries, repairNativeMemoryIndex, } from "../utils/maintenance.js";
|
|
11
11
|
import { nativeMemoryDir } from "../hooks/shared.js";
|
|
12
12
|
// Does this file carry permissions of its own, or does it just inherit the folder's?
|
|
13
13
|
//
|
|
@@ -226,6 +226,7 @@ export async function doctorCommand(opts) {
|
|
|
226
226
|
consolidateMemory(wolfDir, ret.memory_consolidate_after_days),
|
|
227
227
|
dedupeAndCapBuglog(wolfDir, ret.buglog_max_entries),
|
|
228
228
|
pruneBackups(wolfDir, ret.backups_keep),
|
|
229
|
+
pruneProposals(wolfDir, ret.proposals_keep),
|
|
229
230
|
rotateDaemonLog(wolfDir, ret.daemon_log_max_bytes),
|
|
230
231
|
cleanTmp(wolfDir),
|
|
231
232
|
];
|