openwolf-enhanced 1.9.3

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +386 -0
  2. package/LICENSE +663 -0
  3. package/NOTICE +48 -0
  4. package/README.md +283 -0
  5. package/dist/bin/openwolf.js +10 -0
  6. package/dist/bin/openwolf.js.map +1 -0
  7. package/dist/dashboard/assets/AISuggestions-DHXOgHMM.js +9 -0
  8. package/dist/dashboard/assets/ActivityTimeline-BXLwacZV.js +1 -0
  9. package/dist/dashboard/assets/AnatomyBrowser-CUyC8M2I.js +1 -0
  10. package/dist/dashboard/assets/BugLog-Bxvy1sPW.js +1 -0
  11. package/dist/dashboard/assets/CerebrumViewer-DCySVmzA.js +1 -0
  12. package/dist/dashboard/assets/CronStatus-BA4fmQX3.js +1 -0
  13. package/dist/dashboard/assets/DesignQC-BhNEKryS.js +1 -0
  14. package/dist/dashboard/assets/MemoryViewer-0y9lsDUO.js +1 -0
  15. package/dist/dashboard/assets/ProjectOverview-CC51rVRe.js +1 -0
  16. package/dist/dashboard/assets/StatusBadge-4NJI_F2G.js +1 -0
  17. package/dist/dashboard/assets/TokenUsage-7pCxlnCX.js +55 -0
  18. package/dist/dashboard/assets/index-4O5rk-qB.css +2 -0
  19. package/dist/dashboard/assets/index-jfgWxxsn.js +13 -0
  20. package/dist/dashboard/assets/utils-BqE_aDxV.js +1 -0
  21. package/dist/dashboard/index.html +16 -0
  22. package/dist/hooks/post-read.js +85 -0
  23. package/dist/hooks/post-write.js +520 -0
  24. package/dist/hooks/pre-read.js +107 -0
  25. package/dist/hooks/pre-write.js +120 -0
  26. package/dist/hooks/session-start.js +76 -0
  27. package/dist/hooks/shared.js +0 -0
  28. package/dist/hooks/stop.js +219 -0
  29. package/dist/src/buglog/bug-matcher.js +3 -0
  30. package/dist/src/buglog/bug-matcher.js.map +1 -0
  31. package/dist/src/buglog/bug-tracker.js +89 -0
  32. package/dist/src/buglog/bug-tracker.js.map +1 -0
  33. package/dist/src/cli/bug-cmd.js +31 -0
  34. package/dist/src/cli/bug-cmd.js.map +1 -0
  35. package/dist/src/cli/cron-cmd.js +107 -0
  36. package/dist/src/cli/cron-cmd.js.map +1 -0
  37. package/dist/src/cli/daemon-cmd.js +180 -0
  38. package/dist/src/cli/daemon-cmd.js.map +1 -0
  39. package/dist/src/cli/dashboard.js +88 -0
  40. package/dist/src/cli/dashboard.js.map +1 -0
  41. package/dist/src/cli/designqc-cmd.js +31 -0
  42. package/dist/src/cli/designqc-cmd.js.map +1 -0
  43. package/dist/src/cli/doctor-cmd.js +94 -0
  44. package/dist/src/cli/doctor-cmd.js.map +1 -0
  45. package/dist/src/cli/index.js +158 -0
  46. package/dist/src/cli/index.js.map +1 -0
  47. package/dist/src/cli/init.js +493 -0
  48. package/dist/src/cli/init.js.map +1 -0
  49. package/dist/src/cli/registry.js +93 -0
  50. package/dist/src/cli/registry.js.map +1 -0
  51. package/dist/src/cli/scan.js +39 -0
  52. package/dist/src/cli/scan.js.map +1 -0
  53. package/dist/src/cli/status.js +98 -0
  54. package/dist/src/cli/status.js.map +1 -0
  55. package/dist/src/cli/update.js +435 -0
  56. package/dist/src/cli/update.js.map +1 -0
  57. package/dist/src/daemon/cron-engine.js +321 -0
  58. package/dist/src/daemon/cron-engine.js.map +1 -0
  59. package/dist/src/daemon/file-watcher.js +64 -0
  60. package/dist/src/daemon/file-watcher.js.map +1 -0
  61. package/dist/src/daemon/health.js +23 -0
  62. package/dist/src/daemon/health.js.map +1 -0
  63. package/dist/src/daemon/wolf-daemon.js +440 -0
  64. package/dist/src/daemon/wolf-daemon.js.map +1 -0
  65. package/dist/src/designqc/designqc-capture.js +278 -0
  66. package/dist/src/designqc/designqc-capture.js.map +1 -0
  67. package/dist/src/designqc/designqc-engine.js +159 -0
  68. package/dist/src/designqc/designqc-engine.js.map +1 -0
  69. package/dist/src/designqc/designqc-types.js +5 -0
  70. package/dist/src/designqc/designqc-types.js.map +1 -0
  71. package/dist/src/hooks/post-read.js +86 -0
  72. package/dist/src/hooks/post-read.js.map +1 -0
  73. package/dist/src/hooks/post-write.js +521 -0
  74. package/dist/src/hooks/post-write.js.map +1 -0
  75. package/dist/src/hooks/pre-read.js +108 -0
  76. package/dist/src/hooks/pre-read.js.map +1 -0
  77. package/dist/src/hooks/pre-write.js +121 -0
  78. package/dist/src/hooks/pre-write.js.map +1 -0
  79. package/dist/src/hooks/session-start.js +77 -0
  80. package/dist/src/hooks/session-start.js.map +1 -0
  81. package/dist/src/hooks/shared.js +0 -0
  82. package/dist/src/hooks/shared.js.map +1 -0
  83. package/dist/src/hooks/stop.js +220 -0
  84. package/dist/src/hooks/stop.js.map +1 -0
  85. package/dist/src/scanner/anatomy-scanner.js +278 -0
  86. package/dist/src/scanner/anatomy-scanner.js.map +1 -0
  87. package/dist/src/scanner/description-extractor.js +1007 -0
  88. package/dist/src/scanner/description-extractor.js.map +1 -0
  89. package/dist/src/scanner/project-root.js +52 -0
  90. package/dist/src/scanner/project-root.js.map +1 -0
  91. package/dist/src/tracker/token-estimator.js +20 -0
  92. package/dist/src/tracker/token-estimator.js.map +1 -0
  93. package/dist/src/tracker/token-ledger.js +55 -0
  94. package/dist/src/tracker/token-ledger.js.map +1 -0
  95. package/dist/src/tracker/waste-detector.js +101 -0
  96. package/dist/src/tracker/waste-detector.js.map +1 -0
  97. package/dist/src/utils/dashboard-auth.js +50 -0
  98. package/dist/src/utils/dashboard-auth.js.map +1 -0
  99. package/dist/src/utils/fs-safe.js +135 -0
  100. package/dist/src/utils/fs-safe.js.map +1 -0
  101. package/dist/src/utils/hooks-deploy.js +70 -0
  102. package/dist/src/utils/hooks-deploy.js.map +1 -0
  103. package/dist/src/utils/logger.js +48 -0
  104. package/dist/src/utils/logger.js.map +1 -0
  105. package/dist/src/utils/maintenance.js +385 -0
  106. package/dist/src/utils/maintenance.js.map +1 -0
  107. package/dist/src/utils/paths.js +23 -0
  108. package/dist/src/utils/paths.js.map +1 -0
  109. package/dist/src/utils/platform.js +14 -0
  110. package/dist/src/utils/platform.js.map +1 -0
  111. package/dist/src/utils/seed.js +61 -0
  112. package/dist/src/utils/seed.js.map +1 -0
  113. package/package.json +82 -0
  114. package/src/templates/OPENWOLF.md +147 -0
  115. package/src/templates/STATUS.md +48 -0
  116. package/src/templates/anatomy.md +5 -0
  117. package/src/templates/buglog.json +4 -0
  118. package/src/templates/cerebrum.md +22 -0
  119. package/src/templates/claude-md-snippet.md +5 -0
  120. package/src/templates/claude-rules-openwolf.md +17 -0
  121. package/src/templates/config.json +83 -0
  122. package/src/templates/cron-manifest.json +97 -0
  123. package/src/templates/cron-state.json +7 -0
  124. package/src/templates/identity.md +9 -0
  125. package/src/templates/memory.md +4 -0
  126. package/src/templates/reframe-frameworks.md +597 -0
  127. package/src/templates/token-ledger.json +21 -0
@@ -0,0 +1,158 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Command } from "commander";
5
+ import { initCommand } from "./init.js";
6
+ import { statusCommand } from "./status.js";
7
+ import { scanCommand } from "./scan.js";
8
+ import { dashboardCommand } from "./dashboard.js";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ function getVersion() {
12
+ try {
13
+ const pkgPath = path.resolve(__dirname, "../../../package.json");
14
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
15
+ return pkg.version || "unknown";
16
+ }
17
+ catch {
18
+ return "unknown";
19
+ }
20
+ }
21
+ export function createProgram() {
22
+ const program = new Command();
23
+ program
24
+ .name("openwolf")
25
+ .description("Token-conscious AI brain for Claude Code projects")
26
+ .version(getVersion());
27
+ program
28
+ .command("init")
29
+ .description("Initialize .wolf/ in current project")
30
+ .action(initCommand);
31
+ program
32
+ .command("status")
33
+ .description("Show daemon health, last session stats, file integrity")
34
+ .action(statusCommand);
35
+ program
36
+ .command("scan")
37
+ .description("Force full anatomy rescan")
38
+ .option("--check", "Verify anatomy.md matches filesystem (no changes)")
39
+ .action(scanCommand);
40
+ program
41
+ .command("dashboard")
42
+ .description("Open browser to dashboard")
43
+ .action(dashboardCommand);
44
+ const daemon = program
45
+ .command("daemon")
46
+ .description("Daemon management");
47
+ daemon
48
+ .command("start")
49
+ .description("Start daemon via pm2")
50
+ .action(async () => {
51
+ const { daemonStart } = await import("./daemon-cmd.js");
52
+ daemonStart();
53
+ });
54
+ daemon
55
+ .command("stop")
56
+ .description("Stop daemon")
57
+ .action(async () => {
58
+ const { daemonStop } = await import("./daemon-cmd.js");
59
+ daemonStop();
60
+ });
61
+ daemon
62
+ .command("restart")
63
+ .description("Restart daemon")
64
+ .action(async () => {
65
+ const { daemonRestart } = await import("./daemon-cmd.js");
66
+ daemonRestart();
67
+ });
68
+ daemon
69
+ .command("logs")
70
+ .description("Show last 50 lines of daemon log")
71
+ .action(async () => {
72
+ const { daemonLogs } = await import("./daemon-cmd.js");
73
+ daemonLogs();
74
+ });
75
+ const cron = program
76
+ .command("cron")
77
+ .description("Cron task management");
78
+ cron
79
+ .command("list")
80
+ .description("Show all cron tasks with next run times")
81
+ .action(async () => {
82
+ const { cronList } = await import("./cron-cmd.js");
83
+ cronList();
84
+ });
85
+ cron
86
+ .command("run <id>")
87
+ .description("Manually trigger a cron task")
88
+ .action(async (id) => {
89
+ const { cronRun } = await import("./cron-cmd.js");
90
+ await cronRun(id);
91
+ });
92
+ cron
93
+ .command("retry <id>")
94
+ .description("Retry a dead-lettered task")
95
+ .action(async (id) => {
96
+ const { cronRetry } = await import("./cron-cmd.js");
97
+ cronRetry(id);
98
+ });
99
+ // --- Update command ---
100
+ program
101
+ .command("update")
102
+ .description("Update all registered OpenWolf projects to latest version")
103
+ .option("--dry-run", "Show what would be updated without making changes")
104
+ .option("--project <name>", "Update only a specific project (partial name match)")
105
+ .option("--list", "List all registered projects")
106
+ .action(async (opts) => {
107
+ const { updateCommand, listProjects } = await import("./update.js");
108
+ if (opts.list) {
109
+ listProjects();
110
+ }
111
+ else {
112
+ await updateCommand(opts);
113
+ }
114
+ });
115
+ // --- Restore command ---
116
+ program
117
+ .command("restore [backup]")
118
+ .description("Restore .wolf from a backup (run in project dir). Without args, lists available backups.")
119
+ .action(async (backup) => {
120
+ const { restoreCommand } = await import("./update.js");
121
+ restoreCommand(backup);
122
+ });
123
+ // --- Design QC command ---
124
+ program
125
+ .command("designqc [target]")
126
+ .description("Capture full-page screenshots for design evaluation by Claude Code")
127
+ .option("--url <url>", "Dev server URL (auto-starts server if omitted)")
128
+ .option("--routes <routes...>", "Specific routes to check")
129
+ .option("--quality <n>", "JPEG quality 1-100 (lower = fewer tokens)", "70")
130
+ .option("--max-width <n>", "Max capture width in px", "1200")
131
+ .option("--desktop-only", "Skip mobile viewport captures")
132
+ .action(async (target, opts) => {
133
+ const { designqcCommand } = await import("./designqc-cmd.js");
134
+ await designqcCommand(target, opts);
135
+ });
136
+ // --- Doctor command ---
137
+ program
138
+ .command("doctor")
139
+ .description("Report .wolf/ health & footprint, then compact (daemon-independent)")
140
+ .option("--dry-run", "Only report sizes/warnings; write nothing")
141
+ .action(async (opts) => {
142
+ const { doctorCommand } = await import("./doctor-cmd.js");
143
+ await doctorCommand(opts);
144
+ });
145
+ // --- Bug command ---
146
+ const bug = program
147
+ .command("bug")
148
+ .description("Bug memory management");
149
+ bug
150
+ .command("search <term>")
151
+ .description("Search buglog for matching entries")
152
+ .action(async (term) => {
153
+ const { bugSearch } = await import("./bug-cmd.js");
154
+ bugSearch(term);
155
+ });
156
+ return program;
157
+ }
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,493 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { execFileSync } from "node:child_process";
5
+ import { findProjectRoot } from "../scanner/project-root.js";
6
+ import { scanProject } from "../scanner/anatomy-scanner.js";
7
+ import { readJSON, writeJSON, readText, writeText, safeCopyFile } from "../utils/fs-safe.js";
8
+ import { ensureDir } from "../utils/paths.js";
9
+ import { isWindows } from "../utils/platform.js";
10
+ import { copyHookScripts } from "../utils/hooks-deploy.js";
11
+ import { detectProjectName, applyTemplatePlaceholders } from "../utils/seed.js";
12
+ import { registerProject, getRegisteredProjects } from "./registry.js";
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ // Read version from package.json
16
+ function getVersion() {
17
+ try {
18
+ const pkgPath = path.resolve(__dirname, "../../../package.json");
19
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
20
+ return pkg.version || "unknown";
21
+ }
22
+ catch {
23
+ return "unknown";
24
+ }
25
+ }
26
+ // Files that are safe to overwrite on upgrade (config/protocol, not user data)
27
+ const ALWAYS_OVERWRITE = [
28
+ "OPENWOLF.md",
29
+ "config.json",
30
+ "reframe-frameworks.md",
31
+ ];
32
+ // Files that contain user/session data — only create if missing, never overwrite
33
+ const CREATE_IF_MISSING = [
34
+ "identity.md",
35
+ "cerebrum.md",
36
+ "memory.md",
37
+ "anatomy.md",
38
+ "STATUS.md",
39
+ "token-ledger.json",
40
+ "buglog.json",
41
+ "cron-manifest.json",
42
+ "cron-state.json",
43
+ "designqc-report.json",
44
+ "suggestions.json",
45
+ ];
46
+ // Use $CLAUDE_PROJECT_DIR so hooks resolve correctly even if CWD changes during a session
47
+ const HOOK_SETTINGS = {
48
+ hooks: {
49
+ SessionStart: [
50
+ {
51
+ matcher: "",
52
+ hooks: [
53
+ {
54
+ type: "command",
55
+ _managedBy: "openwolf",
56
+ command: 'node "$CLAUDE_PROJECT_DIR/.wolf/hooks/session-start.js"',
57
+ timeout: 5,
58
+ },
59
+ ],
60
+ },
61
+ ],
62
+ PreToolUse: [
63
+ {
64
+ matcher: "Read",
65
+ hooks: [
66
+ {
67
+ type: "command",
68
+ _managedBy: "openwolf",
69
+ command: 'node "$CLAUDE_PROJECT_DIR/.wolf/hooks/pre-read.js"',
70
+ timeout: 5,
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ matcher: "Write|Edit|MultiEdit",
76
+ hooks: [
77
+ {
78
+ type: "command",
79
+ _managedBy: "openwolf",
80
+ command: 'node "$CLAUDE_PROJECT_DIR/.wolf/hooks/pre-write.js"',
81
+ timeout: 5,
82
+ },
83
+ ],
84
+ },
85
+ ],
86
+ PostToolUse: [
87
+ {
88
+ matcher: "Read",
89
+ hooks: [
90
+ {
91
+ type: "command",
92
+ _managedBy: "openwolf",
93
+ command: 'node "$CLAUDE_PROJECT_DIR/.wolf/hooks/post-read.js"',
94
+ timeout: 5,
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ matcher: "Write|Edit|MultiEdit",
100
+ hooks: [
101
+ {
102
+ type: "command",
103
+ _managedBy: "openwolf",
104
+ command: 'node "$CLAUDE_PROJECT_DIR/.wolf/hooks/post-write.js"',
105
+ timeout: 10,
106
+ },
107
+ ],
108
+ },
109
+ ],
110
+ Stop: [
111
+ {
112
+ matcher: "",
113
+ hooks: [
114
+ {
115
+ type: "command",
116
+ _managedBy: "openwolf",
117
+ command: 'node "$CLAUDE_PROJECT_DIR/.wolf/hooks/stop.js"',
118
+ timeout: 10,
119
+ },
120
+ ],
121
+ },
122
+ ],
123
+ },
124
+ };
125
+ export async function initCommand() {
126
+ // Check Node.js version
127
+ const nodeVersion = parseInt(process.version.slice(1), 10);
128
+ if (nodeVersion < 20) {
129
+ console.error(`Node.js 20+ required. Current: ${process.version}`);
130
+ process.exit(1);
131
+ }
132
+ // Detect project root
133
+ const projectRoot = findProjectRoot();
134
+ console.log(`Project root: ${projectRoot}`);
135
+ const wolfDir = path.join(projectRoot, ".wolf");
136
+ const isUpgrade = fs.existsSync(wolfDir);
137
+ const version = getVersion();
138
+ if (isUpgrade) {
139
+ console.log(`Upgrading OpenWolf to v${version}...`);
140
+ }
141
+ // Create .wolf/ directory
142
+ ensureDir(wolfDir);
143
+ ensureDir(path.join(wolfDir, "hooks"));
144
+ // Find templates directory
145
+ const actualTemplatesDir = findTemplatesDir();
146
+ // --- Template files ---
147
+ let createdCount = 0;
148
+ let skippedCount = 0;
149
+ for (const file of ALWAYS_OVERWRITE) {
150
+ writeTemplateFile(actualTemplatesDir, wolfDir, file);
151
+ createdCount++;
152
+ }
153
+ const newlyCreated = new Set();
154
+ for (const file of CREATE_IF_MISSING) {
155
+ const destPath = path.join(wolfDir, file);
156
+ if (fs.existsSync(destPath)) {
157
+ skippedCount++;
158
+ }
159
+ else {
160
+ writeTemplateFile(actualTemplatesDir, wolfDir, file);
161
+ createdCount++;
162
+ newlyCreated.add(file);
163
+ }
164
+ }
165
+ // --- Cerebrum: seed project info only if fresh ---
166
+ if (!isUpgrade) {
167
+ seedCerebrum(wolfDir, projectRoot);
168
+ seedIdentity(wolfDir, projectRoot);
169
+ }
170
+ // --- STATUS.md: substitute {{PROJECT_NAME}} / {{DATE}} when freshly created ---
171
+ if (newlyCreated.has("STATUS.md")) {
172
+ seedStatus(wolfDir, projectRoot);
173
+ }
174
+ // --- Token ledger: set created_at only if empty ---
175
+ const ledgerPath = path.join(wolfDir, "token-ledger.json");
176
+ const ledger = readJSON(ledgerPath, {});
177
+ if (!ledger.created_at) {
178
+ ledger.created_at = new Date().toISOString();
179
+ writeJSON(ledgerPath, ledger);
180
+ }
181
+ // --- Hook scripts: always update (bug fixes, new features) ---
182
+ copyHookScripts(wolfDir);
183
+ // --- Claude settings: replace OpenWolf hooks (upgrade old paths) ---
184
+ const claudeDir = path.join(projectRoot, ".claude");
185
+ ensureDir(claudeDir);
186
+ const settingsPath = path.join(claudeDir, "settings.json");
187
+ if (fs.existsSync(settingsPath)) {
188
+ const existing = readJSON(settingsPath, {});
189
+ const merged = replaceOpenWolfHooks(existing, HOOK_SETTINGS);
190
+ writeJSON(settingsPath, merged);
191
+ }
192
+ else {
193
+ writeJSON(settingsPath, HOOK_SETTINGS);
194
+ }
195
+ // --- Claude rules: always update ---
196
+ const rulesDir = path.join(claudeDir, "rules");
197
+ ensureDir(rulesDir);
198
+ const rulesContent = readTemplateContent("claude-rules-openwolf.md", actualTemplatesDir);
199
+ writeText(path.join(rulesDir, "openwolf.md"), rulesContent);
200
+ // --- CLAUDE.md: add snippet if missing ---
201
+ const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
202
+ const snippetContent = readTemplateContent("claude-md-snippet.md", actualTemplatesDir);
203
+ if (fs.existsSync(claudeMdPath)) {
204
+ const existing = readText(claudeMdPath);
205
+ if (!existing.includes("OpenWolf")) {
206
+ writeText(claudeMdPath, snippetContent + "\n\n" + existing);
207
+ }
208
+ }
209
+ else {
210
+ writeText(claudeMdPath, snippetContent);
211
+ }
212
+ // --- Anatomy scan: only on fresh init ---
213
+ let fileCount = 0;
214
+ if (!isUpgrade) {
215
+ try {
216
+ fileCount = scanProject(wolfDir, projectRoot);
217
+ }
218
+ catch {
219
+ console.log(" Anatomy scan deferred — will run on first session.");
220
+ }
221
+ }
222
+ else {
223
+ // On upgrade, read existing count
224
+ try {
225
+ const anatomyContent = readText(path.join(wolfDir, "anatomy.md"));
226
+ const m = anatomyContent.match(/Files:\s*(\d+)/);
227
+ fileCount = m ? parseInt(m[1], 10) : 0;
228
+ }
229
+ catch {
230
+ fileCount = 0;
231
+ }
232
+ }
233
+ // --- Avoid dashboard/daemon port collisions with other projects (upstream #20) ---
234
+ if (!isUpgrade) {
235
+ try {
236
+ resolvePortCollisions(wolfDir, projectRoot);
237
+ }
238
+ catch { /* non-fatal */ }
239
+ }
240
+ // --- Daemon ---
241
+ let daemonStatus = "start manually with: openwolf daemon start";
242
+ try {
243
+ const pm2Bin = isWindows() ? "pm2.cmd" : "pm2";
244
+ execFileSync(isWindows() ? "where" : "which", ["pm2"], { stdio: "ignore" });
245
+ // Sanitize the project name so it can't inject pm2/shell args.
246
+ const name = `openwolf-${path.basename(projectRoot).replace(/[^A-Za-z0-9_.-]/g, "-")}`;
247
+ // Resolve daemon script relative to openwolf's install dir, not the target project
248
+ const daemonScript = path.resolve(__dirname, "..", "daemon", "wolf-daemon.js");
249
+ try {
250
+ execFileSync(pm2Bin, ["start", daemonScript, "--name", name, "--cwd", projectRoot], {
251
+ stdio: "ignore",
252
+ env: { ...process.env, OPENWOLF_PROJECT_ROOT: projectRoot },
253
+ });
254
+ execFileSync(pm2Bin, ["save"], { stdio: "ignore" });
255
+ daemonStatus = "running via pm2";
256
+ }
257
+ catch {
258
+ daemonStatus = "pm2 found but daemon start failed. Try: openwolf daemon start";
259
+ }
260
+ }
261
+ catch {
262
+ daemonStatus = "pm2 not found. Install with: pnpm add -g pm2";
263
+ }
264
+ // --- Register in central registry (skip if this IS the openwolf source repo) ---
265
+ try {
266
+ const projectName = detectProjectName(projectRoot);
267
+ if (projectName === "openwolf") {
268
+ // Don't register the openwolf dev repo — it would get updated by `openwolf update`
269
+ }
270
+ else {
271
+ registerProject(projectRoot, projectName, version);
272
+ }
273
+ }
274
+ catch {
275
+ // Non-fatal — registry is a convenience feature
276
+ }
277
+ // --- Summary ---
278
+ console.log("");
279
+ if (isUpgrade) {
280
+ console.log(` ✓ OpenWolf upgraded to v${version}`);
281
+ console.log(` ✓ All .wolf data preserved (${skippedCount} files: cerebrum, memory, anatomy, buglog, ledger)`);
282
+ console.log(` ✓ Hook scripts updated (6 hooks)`);
283
+ console.log(` ✓ ${createdCount} config files updated`);
284
+ console.log(` ✓ Anatomy: ${fileCount} files tracked (unchanged)`);
285
+ }
286
+ else {
287
+ console.log(` ✓ OpenWolf v${version} initialized`);
288
+ console.log(` ✓ .wolf/ created with ${createdCount} files`);
289
+ console.log(` ✓ Claude Code hooks registered (6 hooks)`);
290
+ console.log(` ✓ CLAUDE.md updated`);
291
+ console.log(` ✓ .claude/rules/openwolf.md created`);
292
+ console.log(` ✓ Anatomy scan: ${fileCount} files indexed`);
293
+ }
294
+ console.log(` ✓ Daemon: ${daemonStatus}`);
295
+ console.log("");
296
+ console.log(" You're ready. Just use 'claude' as normal — OpenWolf is watching.");
297
+ console.log("");
298
+ }
299
+ // ─── Helpers ─────────────────────────────────────────────────
300
+ function findTemplatesDir() {
301
+ const candidates = [
302
+ path.resolve(__dirname, "..", "..", "..", "src", "templates"),
303
+ path.resolve(__dirname, "..", "..", "src", "templates"),
304
+ path.resolve(__dirname, "..", "templates"),
305
+ path.resolve(__dirname, "templates"),
306
+ ];
307
+ for (const dir of candidates) {
308
+ if (fs.existsSync(dir))
309
+ return dir;
310
+ }
311
+ return candidates[0]; // fallback — generateTemplate will handle missing files
312
+ }
313
+ function writeTemplateFile(templatesDir, wolfDir, file) {
314
+ const srcPath = path.join(templatesDir, file);
315
+ const destPath = path.join(wolfDir, file);
316
+ if (fs.existsSync(srcPath)) {
317
+ safeCopyFile(srcPath, destPath);
318
+ }
319
+ else {
320
+ generateTemplate(destPath, file);
321
+ }
322
+ }
323
+ function readTemplateContent(filename, templatesDir) {
324
+ const filePath = path.join(templatesDir, filename);
325
+ if (fs.existsSync(filePath)) {
326
+ return fs.readFileSync(filePath, "utf-8");
327
+ }
328
+ return getEmbeddedTemplate(filename);
329
+ }
330
+ function getEmbeddedTemplate(filename) {
331
+ const templates = {
332
+ "claude-md-snippet.md": `# OpenWolf\n\n@.wolf/OPENWOLF.md\n\nThis project uses OpenWolf for context management. Read and follow .wolf/OPENWOLF.md every session. Check .wolf/cerebrum.md before generating code. Check .wolf/anatomy.md before reading files.`,
333
+ "claude-rules-openwolf.md": `---\ndescription: OpenWolf protocol enforcement — active on all files\nglobs: **/*\n---\n\n- Read .wolf/STATUS.md FIRST when resuming a session — it holds the current quest, next steps, and decisions\n- Update .wolf/STATUS.md (✅ done / 🚀 next quest) when a quest finishes or before suggesting /clear\n- Check .wolf/anatomy.md before reading any project file\n- Check .wolf/cerebrum.md Do-Not-Repeat list before generating code\n- After writing or editing files, update .wolf/anatomy.md and append to .wolf/memory.md\n- After receiving a user correction, update .wolf/cerebrum.md immediately (Preferences, Learnings, or Do-Not-Repeat)\n- LEARN from every interaction: if you discover a convention, user preference, or project pattern, add it to .wolf/cerebrum.md. Low threshold — when in doubt, log it.\n- BEFORE fixing any bug or error: read .wolf/buglog.json for known fixes\n- AFTER fixing any bug, error, failed test, failed build, or user-reported problem: ALWAYS log to .wolf/buglog.json with error_message, root_cause, fix, and tags\n- If you edit a file more than twice in a session, that likely indicates a bug — log it to .wolf/buglog.json\n- When the user asks to check/evaluate UI design: run \`openwolf designqc\` to capture screenshots, then read them from .wolf/designqc-captures/\n- When the user asks to change/pick/migrate UI framework: read .wolf/reframe-frameworks.md, ask decision questions, recommend a framework, then execute with the framework's prompt`,
334
+ };
335
+ return templates[filename] ?? "";
336
+ }
337
+ // Bump this project's daemon/dashboard ports if another registered project already uses them.
338
+ function resolvePortCollisions(wolfDir, projectRoot) {
339
+ const configPath = path.join(wolfDir, "config.json");
340
+ let cfg;
341
+ try {
342
+ cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
343
+ }
344
+ catch {
345
+ return;
346
+ }
347
+ const daemonPort = cfg.openwolf?.daemon?.port ?? 18790;
348
+ const dashPort = cfg.openwolf?.dashboard?.port ?? 18791;
349
+ const here = path.resolve(projectRoot).toLowerCase();
350
+ const used = new Set();
351
+ for (const p of getRegisteredProjects()) {
352
+ if (path.resolve(p.root).toLowerCase() === here)
353
+ continue;
354
+ try {
355
+ const other = JSON.parse(fs.readFileSync(path.join(p.root, ".wolf", "config.json"), "utf-8"));
356
+ const d = other?.openwolf?.daemon?.port;
357
+ const w = other?.openwolf?.dashboard?.port;
358
+ if (typeof d === "number")
359
+ used.add(d);
360
+ if (typeof w === "number")
361
+ used.add(w);
362
+ }
363
+ catch { /* skip unreadable project config */ }
364
+ }
365
+ let offset = 0;
366
+ while (used.has(daemonPort + offset) || used.has(dashPort + offset))
367
+ offset += 2;
368
+ if (offset === 0)
369
+ return;
370
+ cfg.openwolf = cfg.openwolf ?? {};
371
+ cfg.openwolf.daemon = cfg.openwolf.daemon ?? {};
372
+ cfg.openwolf.dashboard = cfg.openwolf.dashboard ?? {};
373
+ cfg.openwolf.daemon.port = daemonPort + offset;
374
+ cfg.openwolf.dashboard.port = dashPort + offset;
375
+ try {
376
+ fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2), "utf-8");
377
+ }
378
+ catch { /* non-fatal */ }
379
+ }
380
+ function generateTemplate(destPath, file) {
381
+ const templates = {
382
+ "OPENWOLF.md": `# OpenWolf Operating Protocol\n\nYou are working in an OpenWolf-managed project. These rules apply every turn.\n\n## File Navigation\n\n1. Check \`.wolf/anatomy.md\` BEFORE reading any file.\n2. If the description is sufficient, do NOT read the full file.\n3. If a file is not in anatomy.md, search with Grep/Glob.\n\n## Code Generation\n\n1. Read \`.wolf/cerebrum.md\` and respect every entry.\n2. Check \`## Do-Not-Repeat\` section.\n\n## After Actions\n\n1. Append to \`.wolf/memory.md\`.\n2. After file changes: update \`.wolf/anatomy.md\`.\n\n## Token Discipline\n\n- Never re-read a file already read this session.\n- Prefer anatomy.md descriptions over full reads.\n`,
383
+ "identity.md": `# Identity\n\n- **Name:** Wolf\n- **Role:** AI development assistant for this project\n- **Tone:** Direct, concise, technically precise\n`,
384
+ "cerebrum.md": `# Cerebrum\n\n> OpenWolf's learning memory.\n\n## User Preferences\n\n## Key Learnings\n\n## Do-Not-Repeat\n\n## Decision Log\n`,
385
+ "memory.md": `# Memory\n\n> Chronological action log.\n`,
386
+ "anatomy.md": `# anatomy.md\n\n> Project structure index. Pending initial scan.\n`,
387
+ "STATUS.md": `# STATUS — {{PROJECT_NAME}}\n\n> Single source of truth for resuming work. Read this FIRST when starting a session.\n> Update at the end of every quest so the next \\\`/clear\\\` resumes in one read.\n>\n> Last updated: {{DATE}}\n\n---\n\n## ✅ Done\n\n- (nothing yet — move items here as quests complete)\n\n## 🚀 Next phase\n\n**Goal:** _<what we're building next>_\n\n### Acceptance criteria\n1. _<concrete, user-visible outcome>_\n\n### Files to create / edit\n- _<path — purpose>_\n\n## 📁 Active architecture\n\n- **Stack:** _<frameworks / languages>_\n\n## ⚠️ Pending / gotchas\n\n- _<known issues, blockers>_\n\n## 🔧 Useful commands\n\n\\\`\\\`\\\`bash\n# add the commands you run most often here\n\\\`\\\`\\\`\n`,
388
+ "config.json": JSON.stringify({
389
+ version: 1,
390
+ openwolf: {
391
+ enabled: true,
392
+ anatomy: { auto_scan_on_init: true, rescan_interval_hours: 6, max_description_length: 100, max_files: 500, exclude_patterns: ["node_modules", ".git", "dist", "build", ".wolf", ".next", ".nuxt", "coverage", "__pycache__", ".cache", "target", ".vscode", ".idea", ".turbo", ".vercel", ".netlify", ".output", "*.min.js", "*.min.css"] },
393
+ token_audit: { enabled: true, report_frequency: "weekly", waste_threshold_percent: 15, chars_per_token_code: 3.5, chars_per_token_prose: 4.0 },
394
+ cron: { enabled: true, max_retry_attempts: 3, dead_letter_enabled: true, heartbeat_interval_minutes: 30, use_claude_p: true, api_key_env: null },
395
+ memory: { consolidation_after_days: 7, max_entries_before_consolidation: 200 },
396
+ retention: { token_ledger_max_sessions: 200, session_io_max: 100, buglog_max_entries: 200, backups_keep: 10, memory_consolidate_after_days: 7, memory_max_bytes: 262144, daemon_log_max_bytes: 524288 },
397
+ cerebrum: { max_tokens: 2000, reflection_frequency: "weekly" },
398
+ daemon: { port: 18790, log_level: "info" },
399
+ dashboard: { enabled: true, port: 18791, host: "127.0.0.1" },
400
+ designqc: { enabled: true, viewports: [{ name: "desktop", width: 1440, height: 900 }, { name: "mobile", width: 375, height: 812 }], max_screenshots: 6, chrome_path: null },
401
+ },
402
+ }, null, 2),
403
+ "token-ledger.json": JSON.stringify({ version: 1, created_at: "", lifetime: { total_tokens_estimated: 0, total_reads: 0, total_writes: 0, total_sessions: 0, anatomy_hits: 0, anatomy_misses: 0, repeated_reads_blocked: 0, estimated_savings_vs_bare_cli: 0 }, sessions: [], daemon_usage: [], waste_flags: [], optimization_report: { last_generated: null, patterns: [] } }, null, 2),
404
+ "buglog.json": JSON.stringify({ version: 1, bugs: [] }, null, 2),
405
+ "cron-manifest.json": JSON.stringify({ version: 1, tasks: [] }, null, 2),
406
+ "cron-state.json": JSON.stringify({ last_heartbeat: null, engine_status: "initialized", execution_log: [], dead_letter_queue: [], upcoming: [] }, null, 2),
407
+ "designqc-report.json": JSON.stringify({ captured_at: null, captures: [], total_size_kb: 0, estimated_tokens: 0 }, null, 2),
408
+ "suggestions.json": JSON.stringify({ suggestions: [], generated_at: null }, null, 2),
409
+ };
410
+ const content = templates[file] ?? "";
411
+ fs.writeFileSync(destPath, content, "utf-8");
412
+ }
413
+ function seedStatus(wolfDir, projectRoot) {
414
+ applyTemplatePlaceholders(path.join(wolfDir, "STATUS.md"), projectRoot);
415
+ }
416
+ function seedCerebrum(wolfDir, projectRoot) {
417
+ const projectName = detectProjectName(projectRoot);
418
+ const projectDescription = detectProjectDescription(projectRoot);
419
+ if (!projectName && !projectDescription)
420
+ return;
421
+ const cerebrumPath = path.join(wolfDir, "cerebrum.md");
422
+ let cerebrum = readText(cerebrumPath);
423
+ const projectInfo = [
424
+ `- **Project:** ${projectName || path.basename(projectRoot)}`,
425
+ projectDescription ? `- **Description:** ${projectDescription}` : "",
426
+ ].filter(Boolean).join("\n");
427
+ // Insert after ## Key Learnings section
428
+ cerebrum = cerebrum.replace(/## Key Learnings\n\n<!-- Project-specific conventions discovered during development\. -->/, `## Key Learnings\n\n${projectInfo}`);
429
+ // Fallback: if the comment wasn't found (embedded template), try simpler pattern
430
+ if (!cerebrum.includes("**Project:**")) {
431
+ cerebrum = cerebrum.replace(/## Key Learnings\n/, `## Key Learnings\n\n${projectInfo}\n`);
432
+ }
433
+ cerebrum = cerebrum.replace(/Last updated: —/, `Last updated: ${new Date().toISOString().slice(0, 10)}`);
434
+ writeText(cerebrumPath, cerebrum);
435
+ }
436
+ function seedIdentity(wolfDir, projectRoot) {
437
+ const projectName = detectProjectName(projectRoot);
438
+ if (!projectName)
439
+ return;
440
+ const identityPath = path.join(wolfDir, "identity.md");
441
+ let content = readText(identityPath);
442
+ content = content.replace(/\*\*Name:\*\* Wolf/, `**Name:** ${projectName}`);
443
+ content = content.replace(/\*\*Role:\*\* AI development assistant for this project/, `**Role:** AI development assistant for ${projectName}`);
444
+ writeText(identityPath, content);
445
+ }
446
+ /**
447
+ * Replace all OpenWolf hook entries in settings.json with the current version.
448
+ * Removes old-style relative-path hooks and inserts the new $CLAUDE_PROJECT_DIR hooks.
449
+ * Preserves any non-OpenWolf hooks the user may have added.
450
+ */
451
+ function replaceOpenWolfHooks(existing, hookSettings) {
452
+ const merged = { ...existing };
453
+ if (!merged.hooks) {
454
+ merged.hooks = {};
455
+ }
456
+ const hooks = merged.hooks;
457
+ for (const [event, newMatchers] of Object.entries(hookSettings.hooks)) {
458
+ if (!hooks[event]) {
459
+ hooks[event] = [];
460
+ }
461
+ // Remove any existing OpenWolf hook entries (match by .wolf/hooks/ in command)
462
+ hooks[event] = hooks[event].filter((entry) => {
463
+ const isOpenWolfHook = entry.hooks?.some((h) => (h.command && h.command.includes(".wolf/hooks/")) || h._managedBy === "openwolf");
464
+ return !isOpenWolfHook;
465
+ });
466
+ // Add the new OpenWolf hooks
467
+ for (const matcher of newMatchers) {
468
+ hooks[event].push(matcher);
469
+ }
470
+ }
471
+ return merged;
472
+ }
473
+ function detectProjectDescription(projectRoot) {
474
+ // Try package.json
475
+ try {
476
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
477
+ if (pkg.description)
478
+ return pkg.description;
479
+ }
480
+ catch { }
481
+ // Try README first line/paragraph
482
+ for (const readme of ["README.md", "readme.md", "README.rst", "README.txt"]) {
483
+ try {
484
+ const content = fs.readFileSync(path.join(projectRoot, readme), "utf-8");
485
+ const lines = content.split("\n").filter(l => l.trim() && !l.startsWith("#") && !l.startsWith("=") && !l.startsWith("-") && !l.startsWith("!["));
486
+ if (lines.length > 0)
487
+ return lines[0].trim().slice(0, 200);
488
+ }
489
+ catch { }
490
+ }
491
+ return "";
492
+ }
493
+ //# sourceMappingURL=init.js.map