openwolf 1.0.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/LICENSE +663 -0
- package/README.md +232 -0
- package/dist/bin/openwolf.js +10 -0
- package/dist/bin/openwolf.js.map +1 -0
- package/dist/dashboard/assets/AISuggestions-DzE-DQkR.js +1 -0
- package/dist/dashboard/assets/ActivityTimeline-DGVjujnt.js +1 -0
- package/dist/dashboard/assets/AnatomyBrowser-S-2rmYtw.js +1 -0
- package/dist/dashboard/assets/BugLog-CG2zDHJc.js +1 -0
- package/dist/dashboard/assets/CerebrumViewer-Dlgoy69U.js +1 -0
- package/dist/dashboard/assets/CronStatus-DxUF1iW_.js +1 -0
- package/dist/dashboard/assets/DesignQC-BGXn_aq8.js +1 -0
- package/dist/dashboard/assets/MemoryViewer-CGqkTyvQ.js +1 -0
- package/dist/dashboard/assets/ProjectOverview-DlFhu69i.js +1 -0
- package/dist/dashboard/assets/TokenUsage-DDsQiVIq.js +68 -0
- package/dist/dashboard/assets/index-CzK9GUjV.css +1 -0
- package/dist/dashboard/assets/index-PYeNGjkN.js +52 -0
- package/dist/dashboard/index.html +16 -0
- package/dist/hooks/post-read.js +68 -0
- package/dist/hooks/post-write.js +502 -0
- package/dist/hooks/pre-read.js +79 -0
- package/dist/hooks/pre-write.js +120 -0
- package/dist/hooks/session-start.js +76 -0
- package/dist/hooks/shared.js +613 -0
- package/dist/hooks/stop.js +146 -0
- package/dist/src/buglog/bug-matcher.js +3 -0
- package/dist/src/buglog/bug-matcher.js.map +1 -0
- package/dist/src/buglog/bug-tracker.js +81 -0
- package/dist/src/buglog/bug-tracker.js.map +1 -0
- package/dist/src/cli/bug-cmd.js +28 -0
- package/dist/src/cli/bug-cmd.js.map +1 -0
- package/dist/src/cli/cron-cmd.js +106 -0
- package/dist/src/cli/cron-cmd.js.map +1 -0
- package/dist/src/cli/daemon-cmd.js +177 -0
- package/dist/src/cli/daemon-cmd.js.map +1 -0
- package/dist/src/cli/dashboard.js +84 -0
- package/dist/src/cli/dashboard.js.map +1 -0
- package/dist/src/cli/designqc-cmd.js +31 -0
- package/dist/src/cli/designqc-cmd.js.map +1 -0
- package/dist/src/cli/index.js +149 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/init.js +506 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/registry.js +93 -0
- package/dist/src/cli/registry.js.map +1 -0
- package/dist/src/cli/scan.js +39 -0
- package/dist/src/cli/scan.js.map +1 -0
- package/dist/src/cli/status.js +85 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/update.js +414 -0
- package/dist/src/cli/update.js.map +1 -0
- package/dist/src/daemon/cron-engine.js +300 -0
- package/dist/src/daemon/cron-engine.js.map +1 -0
- package/dist/src/daemon/file-watcher.js +53 -0
- package/dist/src/daemon/file-watcher.js.map +1 -0
- package/dist/src/daemon/health.js +23 -0
- package/dist/src/daemon/health.js.map +1 -0
- package/dist/src/daemon/wolf-daemon.js +294 -0
- package/dist/src/daemon/wolf-daemon.js.map +1 -0
- package/dist/src/designqc/designqc-capture.js +235 -0
- package/dist/src/designqc/designqc-capture.js.map +1 -0
- package/dist/src/designqc/designqc-engine.js +141 -0
- package/dist/src/designqc/designqc-engine.js.map +1 -0
- package/dist/src/designqc/designqc-types.js +5 -0
- package/dist/src/designqc/designqc-types.js.map +1 -0
- package/dist/src/hooks/post-read.js +69 -0
- package/dist/src/hooks/post-read.js.map +1 -0
- package/dist/src/hooks/post-write.js +503 -0
- package/dist/src/hooks/post-write.js.map +1 -0
- package/dist/src/hooks/pre-read.js +80 -0
- package/dist/src/hooks/pre-read.js.map +1 -0
- package/dist/src/hooks/pre-write.js +121 -0
- package/dist/src/hooks/pre-write.js.map +1 -0
- package/dist/src/hooks/session-start.js +77 -0
- package/dist/src/hooks/session-start.js.map +1 -0
- package/dist/src/hooks/shared.js +614 -0
- package/dist/src/hooks/shared.js.map +1 -0
- package/dist/src/hooks/stop.js +147 -0
- package/dist/src/hooks/stop.js.map +1 -0
- package/dist/src/scanner/anatomy-scanner.js +260 -0
- package/dist/src/scanner/anatomy-scanner.js.map +1 -0
- package/dist/src/scanner/description-extractor.js +1007 -0
- package/dist/src/scanner/description-extractor.js.map +1 -0
- package/dist/src/scanner/project-root.js +42 -0
- package/dist/src/scanner/project-root.js.map +1 -0
- package/dist/src/tracker/token-estimator.js +20 -0
- package/dist/src/tracker/token-estimator.js.map +1 -0
- package/dist/src/tracker/token-ledger.js +45 -0
- package/dist/src/tracker/token-ledger.js.map +1 -0
- package/dist/src/tracker/waste-detector.js +101 -0
- package/dist/src/tracker/waste-detector.js.map +1 -0
- package/dist/src/utils/fs-safe.js +74 -0
- package/dist/src/utils/fs-safe.js.map +1 -0
- package/dist/src/utils/logger.js +48 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/paths.js +23 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/platform.js +14 -0
- package/dist/src/utils/platform.js.map +1 -0
- package/package.json +77 -0
- package/src/templates/OPENWOLF.md +135 -0
- package/src/templates/anatomy.md +5 -0
- package/src/templates/buglog.json +4 -0
- package/src/templates/cerebrum.md +22 -0
- package/src/templates/claude-md-snippet.md +5 -0
- package/src/templates/claude-rules-openwolf.md +15 -0
- package/src/templates/config.json +73 -0
- package/src/templates/cron-manifest.json +97 -0
- package/src/templates/cron-state.json +7 -0
- package/src/templates/identity.md +9 -0
- package/src/templates/memory.md +4 -0
- package/src/templates/reframe-frameworks.md +597 -0
- package/src/templates/token-ledger.json +21 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, readMarkdown, readStdin } from "./shared.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
ensureWolfDir();
|
|
6
|
+
const wolfDir = getWolfDir();
|
|
7
|
+
const raw = await readStdin();
|
|
8
|
+
let input;
|
|
9
|
+
try {
|
|
10
|
+
input = JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
process.exit(0);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// For Edit tool, the meaningful content is old_string + new_string
|
|
17
|
+
const content = input.tool_input?.content ?? "";
|
|
18
|
+
const oldStr = input.tool_input?.old_string ?? "";
|
|
19
|
+
const newStr = input.tool_input?.new_string ?? "";
|
|
20
|
+
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
|
|
21
|
+
const allContent = [content, oldStr, newStr].join("\n");
|
|
22
|
+
if (!allContent.trim()) {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 1. Cerebrum Do-Not-Repeat check
|
|
27
|
+
checkCerebrum(wolfDir, allContent);
|
|
28
|
+
// 2. Bug log: search for similar past bugs when editing code
|
|
29
|
+
// This fires when Claude is about to edit a file — if the edit looks like a fix
|
|
30
|
+
// (changing error handling, modifying catch blocks, etc.), check the bug log
|
|
31
|
+
if (filePath && (oldStr || content)) {
|
|
32
|
+
checkBugLog(wolfDir, filePath, oldStr, newStr, content);
|
|
33
|
+
}
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
function checkCerebrum(wolfDir, content) {
|
|
37
|
+
const cerebrumContent = readMarkdown(path.join(wolfDir, "cerebrum.md"));
|
|
38
|
+
const doNotRepeatSection = cerebrumContent.split("## Do-Not-Repeat")[1];
|
|
39
|
+
if (!doNotRepeatSection)
|
|
40
|
+
return;
|
|
41
|
+
const entries = doNotRepeatSection.split("## ")[0];
|
|
42
|
+
const lines = entries.split("\n").filter((l) => l.trim().startsWith("[") || l.trim().startsWith("-"));
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const trimmed = line.trim().replace(/^[-*]\s*/, "").replace(/^\[[\d-]+\]\s*/, "");
|
|
45
|
+
if (!trimmed)
|
|
46
|
+
continue;
|
|
47
|
+
const patterns = [];
|
|
48
|
+
const quotedMatches = trimmed.match(/"([^"]+)"/g) || trimmed.match(/'([^']+)'/g) || trimmed.match(/`([^`]+)`/g);
|
|
49
|
+
if (quotedMatches) {
|
|
50
|
+
for (const qm of quotedMatches) {
|
|
51
|
+
patterns.push(qm.replace(/["'`]/g, ""));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const neverMatch = trimmed.match(/(?:never use|avoid|don't use|do not use)\s+(\w+)/i);
|
|
55
|
+
if (neverMatch)
|
|
56
|
+
patterns.push(neverMatch[1]);
|
|
57
|
+
for (const pattern of patterns) {
|
|
58
|
+
try {
|
|
59
|
+
const regex = new RegExp(`\\b${pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
60
|
+
if (regex.test(content)) {
|
|
61
|
+
process.stderr.write(`⚠️ OpenWolf cerebrum warning: "${trimmed}" — check your code before proceeding.\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Common words that appear in most code — must be excluded from similarity matching
|
|
69
|
+
const STOP_WORDS = new Set([
|
|
70
|
+
"error", "function", "return", "const", "this", "that", "with", "from",
|
|
71
|
+
"import", "export", "class", "interface", "type", "undefined", "null",
|
|
72
|
+
"true", "false", "string", "number", "object", "array", "value",
|
|
73
|
+
"file", "path", "name", "data", "response", "request", "result",
|
|
74
|
+
"should", "must", "does", "have", "been", "will", "would", "could",
|
|
75
|
+
"when", "then", "else", "each", "some", "every", "only",
|
|
76
|
+
]);
|
|
77
|
+
function checkBugLog(wolfDir, filePath, oldStr, newStr, content) {
|
|
78
|
+
const bugLogPath = path.join(wolfDir, "buglog.json");
|
|
79
|
+
if (!fs.existsSync(bugLogPath))
|
|
80
|
+
return;
|
|
81
|
+
const bugLog = readJSON(bugLogPath, { version: 1, bugs: [] });
|
|
82
|
+
if (bugLog.bugs.length === 0)
|
|
83
|
+
return;
|
|
84
|
+
const basename = path.basename(filePath);
|
|
85
|
+
// ONLY surface bugs that match the SAME file being edited.
|
|
86
|
+
// Cross-file matching is too noisy and risks misdirecting Claude.
|
|
87
|
+
const fileMatches = bugLog.bugs.filter(b => {
|
|
88
|
+
const bugBasename = path.basename(b.file);
|
|
89
|
+
return bugBasename === basename;
|
|
90
|
+
});
|
|
91
|
+
if (fileMatches.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
// Further filter: require tag or error_message overlap with the edit content
|
|
94
|
+
const editText = (oldStr + " " + newStr + " " + content).toLowerCase();
|
|
95
|
+
const editTokens = tokenize(editText);
|
|
96
|
+
const relevant = fileMatches.filter(bug => {
|
|
97
|
+
// Check if any bug tag appears in the edit content
|
|
98
|
+
const tagHit = bug.tags.some(t => editText.includes(t.toLowerCase()));
|
|
99
|
+
if (tagHit)
|
|
100
|
+
return true;
|
|
101
|
+
// Check meaningful word overlap (excluding stop words)
|
|
102
|
+
const bugTokens = tokenize(bug.error_message + " " + bug.root_cause);
|
|
103
|
+
const overlap = [...editTokens].filter(t => bugTokens.has(t));
|
|
104
|
+
// Require at least 3 meaningful overlapping words
|
|
105
|
+
return overlap.length >= 3;
|
|
106
|
+
});
|
|
107
|
+
if (relevant.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
// Surface as a FYI, not a directive — Claude should evaluate, not blindly apply
|
|
110
|
+
process.stderr.write(`📋 OpenWolf buglog: ${relevant.length} past bug(s) found for ${basename} — review for context, do NOT apply blindly:\n`);
|
|
111
|
+
for (const bug of relevant.slice(0, 2)) {
|
|
112
|
+
process.stderr.write(` [${bug.id}] "${bug.error_message.slice(0, 70)}"\n Cause: ${bug.root_cause.slice(0, 80)}\n Fix: ${bug.fix.slice(0, 80)}\n`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function tokenize(text) {
|
|
116
|
+
return new Set(text.replace(/[^\w\s]/g, " ").split(/\s+/)
|
|
117
|
+
.filter(w => w.length > 3 && !STOP_WORDS.has(w.toLowerCase()))
|
|
118
|
+
.map(w => w.toLowerCase()));
|
|
119
|
+
}
|
|
120
|
+
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort } from "./shared.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
ensureWolfDir();
|
|
6
|
+
const wolfDir = getWolfDir();
|
|
7
|
+
// Clean up stale .tmp files left from failed atomic writes
|
|
8
|
+
try {
|
|
9
|
+
const files = fs.readdirSync(wolfDir);
|
|
10
|
+
for (const f of files) {
|
|
11
|
+
if (f.endsWith(".tmp")) {
|
|
12
|
+
try {
|
|
13
|
+
fs.unlinkSync(path.join(wolfDir, f));
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
const hooksDir = path.join(wolfDir, "hooks");
|
|
21
|
+
const sessionFile = path.join(hooksDir, "_session.json");
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const sessionId = `session-${now.toISOString().slice(0, 10)}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
24
|
+
// Create fresh session state
|
|
25
|
+
writeJSON(sessionFile, {
|
|
26
|
+
session_id: sessionId,
|
|
27
|
+
started: timestamp(),
|
|
28
|
+
files_read: {},
|
|
29
|
+
files_written: [],
|
|
30
|
+
edit_counts: {},
|
|
31
|
+
anatomy_hits: 0,
|
|
32
|
+
anatomy_misses: 0,
|
|
33
|
+
repeated_reads_warned: 0,
|
|
34
|
+
cerebrum_warnings: 0,
|
|
35
|
+
stop_count: 0,
|
|
36
|
+
});
|
|
37
|
+
// Append session header to memory.md
|
|
38
|
+
const memoryPath = path.join(wolfDir, "memory.md");
|
|
39
|
+
const header = `\n## Session: ${now.toISOString().slice(0, 10)} ${timeShort()}\n\n| Time | Action | File(s) | Outcome | ~Tokens |\n|------|--------|---------|---------|--------|\n`;
|
|
40
|
+
appendMarkdown(memoryPath, header);
|
|
41
|
+
// Check cerebrum freshness — remind Claude to learn
|
|
42
|
+
try {
|
|
43
|
+
const cerebrumPath = path.join(wolfDir, "cerebrum.md");
|
|
44
|
+
const cerebrumContent = fs.readFileSync(cerebrumPath, "utf-8");
|
|
45
|
+
const stat = fs.statSync(cerebrumPath);
|
|
46
|
+
const daysSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
|
|
47
|
+
// Count actual entries (non-comment, non-empty lines in content sections)
|
|
48
|
+
const entryLines = cerebrumContent.split("\n").filter(l => {
|
|
49
|
+
const t = l.trim();
|
|
50
|
+
return t.startsWith("- ") || t.startsWith("* ") || (t.startsWith("[") && t.includes("]"));
|
|
51
|
+
});
|
|
52
|
+
if (entryLines.length < 3) {
|
|
53
|
+
process.stderr.write(`💡 OpenWolf: cerebrum.md has only ${entryLines.length} entries. Learn from this session — record user preferences, project conventions, and mistakes to .wolf/cerebrum.md.\n`);
|
|
54
|
+
}
|
|
55
|
+
else if (daysSinceUpdate > 3) {
|
|
56
|
+
process.stderr.write(`💡 OpenWolf: cerebrum.md hasn't been updated in ${Math.floor(daysSinceUpdate)} days. Look for opportunities to add learnings this session.\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch { }
|
|
60
|
+
// Check buglog — remind if empty
|
|
61
|
+
try {
|
|
62
|
+
const buglogPath = path.join(wolfDir, "buglog.json");
|
|
63
|
+
const buglog = readJSON(buglogPath, { bugs: [] });
|
|
64
|
+
if (buglog.bugs.length === 0) {
|
|
65
|
+
process.stderr.write(`📋 OpenWolf: buglog.json is empty. If you encounter or fix any bugs, errors, or failed tests this session, log them to .wolf/buglog.json.\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
// Increment total_sessions in token-ledger
|
|
70
|
+
const ledgerPath = path.join(wolfDir, "token-ledger.json");
|
|
71
|
+
const ledger = readJSON(ledgerPath, { version: 1, lifetime: { total_sessions: 0 } });
|
|
72
|
+
ledger.lifetime.total_sessions++;
|
|
73
|
+
writeJSON(ledgerPath, ledger);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
main().catch(() => process.exit(0));
|