Port: develop branch — 10 ported PRs#15
Conversation
* security: bind dashboard to loopback, reject cross-origin WS upgrades The dashboard server previously called app.listen(port) with no host argument, binding to 0.0.0.0. Combined with the fact that none of the HTTP or WebSocket endpoints require authentication, this meant the dashboard was reachable from the LAN and cross-origin from any webpage the user visited in a browser. Exposed surface included: - GET /api/files — contents of cerebrum.md, memory.md, buglog.json, token-ledger.json, and suggestions.json. - POST /api/cron/run/:taskId and the WebSocket "trigger_task" handler — both execute cron tasks, including ai_task actions that shell out to claude -p in the project root. This change: 1. Binds the HTTP/WebSocket server to 127.0.0.1 by default. The bind address is read from openwolf.dashboard.bind in .wolf/config.json (new optional field), defaulting to "127.0.0.1" when the field is absent so existing installs become loopback-only on restart. 2. Adds a verifyClient check on the WebSocket upgrade that allows same-origin connections (dashboard loaded from http://127.0.0.1:<port> or http://localhost:<port>) and non-browser clients (no Origin header), while rejecting any other Origin. 3. Logs a warning when the dashboard is bound to a non-loopback address, to make the security implication explicit for anyone who sets bind: "0.0.0.0" on purpose. 4. Documents the new default and the daemon.dashboard.bind opt-in in the README. Users who were intentionally exposing the dashboard to their network will need to set "bind": "0.0.0.0" under openwolf.dashboard in their .wolf/config.json after upgrading. * fix(daemon): correct WebSocket origin check for non-loopback bind Three issues addressed in isAllowedOrigin / WebSocket CSRF guard: 1. (Bug) When bind="0.0.0.0", browsers send Origin: http://<actual-ip>:<port>, never http://0.0.0.0:<port>. The old code added the literal bind address to the allowed set, so remote WebSocket connections always failed the check even for users who explicitly opted into network access. Fix: use the Host request header to dynamically match whatever IP the client connected on. 2. (Warning) Absent Origin header was unconditionally allowed, letting any remote machine bypass the CSRF check with a non-browser client when bind="0.0.0.0". Fix: restrict no-Origin connections to loopback remote addresses only. 3. (Warning) The loopback identity check was duplicated between isAllowedOrigin and the startup warning logger. Fix: extract isLoopback() helper and use it in both places. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: autofix PR #2 per review comments --------- Co-authored-by: Saad Khan <skhan8@mail.einstein.yu.edu> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: extend scanner language support and sync CODE_EXTS Adds Flutter-critical languages (Kotlin, Swift, Objective-C) plus common gaps (C++ variants, C#, Ruby, PHP, Lua, Vue, Svelte, HTML, Protobuf, GraphQL, Terraform, shell variants) to both extension sets. Also brings src/tracker/token-estimator.ts CODE_EXTS back in sync with src/scanner/anatomy-scanner.ts CODE_EXTENSIONS — the two sets had drifted apart since only CODE_EXTENSIONS gets the .dart addition from #10. Adds a one-line "Keep in sync with ..." comment above each so future additions hit both places. These sets control the chars-per-token ratio (3.5 for code vs 3.75 fallback) used by estimateTokens; the net effect is ~7% more accurate token accounting in anatomy.md and detectContentType() consumers for projects written in these languages. * refactor: extract shared CODE_EXTENSIONS to utils/extensions.ts; fix HTML ratio Addresses two PR review findings: WR-01 — Eliminate duplicate parallel Sets Both `anatomy-scanner.ts` and `token-estimator.ts` maintained byte-for-byte identical `CODE_EXTENSIONS`/`CODE_EXTS` sets linked only by a "Keep in sync" comment — an unenforceable convention that would silently produce divergent token-ratio behaviour the moment a future contributor updated one file and missed the other. Extract to `src/utils/extensions.ts` as a single source of truth and import from both consumers. WR-02 — Remove .html/.htm from CODE_EXTENSIONS HTML is markup with prose content and attribute text; classifying it as `code` applies a 3.5 chars/token ratio that consistently under-counts tokens, risking budget overruns. Dropping .html/.htm from CODE_EXTENSIONS lets them fall through to the default `mixed` ratio (3.75), which is more accurate. A comment in extensions.ts records the rationale. * chore: autofix PR #3 per review comments --------- Co-authored-by: Saad Khan <skhan8@mail.einstein.yu.edu>
* fix(readJSON): deep-merge defaults to survive partial configs readJSON's fallback was whole-file-only: it returned the fallback object only when the file read/parse threw. If .wolf/config.json exists and parses but is missing a nested section (e.g. an older config written before dashboard/daemon/cron were added), every accessor like `config.openwolf.dashboard.port` throws `TypeError: Cannot read properties of undefined`. Make readJSON deep-merge the parsed value over the fallback: loaded values always win, but missing nested keys fall through to the caller-supplied defaults. Arrays and scalars are replaced wholesale — only plain objects are merged. File read/parse failures still return the fallback as-is (unchanged behavior). Net effect: every existing readJSON call site becomes tolerant of older/partial configs without any call-site changes. Fixes `openwolf dashboard` and `openwolf daemon *` crashes when a user's .wolf/config.json predates a section the current release reads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: autofix PR #6 per review comments --------- Co-authored-by: Michael Minor <mikeminor.creativetechnologist@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* init/update: tag hook entries with _managedBy: "openwolf" Adds `_managedBy: "openwolf"` to every hook object in `HOOK_SETTINGS` so Claude Code's settings round-tripper recognizes them as third-party managed entries and preserves them through `/effort`, `/config`, and similar rewrites. Without the tag, entries get silently dropped: a working OpenWolf install can be de-wired by typing `/effort medium` once, since Claude Code's merge logic only preserves entries it recognizes as owned (claude-hooks uses the same field, for example). Also tightens `replaceOpenWolfHooks` to recognize the new tag in addition to the legacy `.wolf/hooks/` substring — defensive against future path schema changes, and keeps the dedupe correct for installs upgrading from a pre-tag version. The two changes are minimal and backward-compatible: untagged entries from older installs still match the substring fallback, so upgrades clean up cleanly. New installs get tagged from the start. Fixes cytostack#31. * fix(hook-settings): address PR #8 review comments - isOpenWolfHook: check _managedBy as primary signal, path substring as backward-compat fallback for pre-tag installs - Add comment documenting that _managedBy is empirically observed passthrough, not a guaranteed Claude Code field (Warning #2) - Add comment in replaceOpenWolfHooks documenting co-location assumption: one inner hook per outer entry unsupported (Warning #1) - Reformat HOOK_SETTINGS to multi-line expanded style, respects 80-char line length rule (Info #4) - Code duplication between init.ts and update.ts already resolved by prior extraction to hook-settings.ts (Info #3) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: ManniX-ITA <20623405+mann1x@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: strip \r before parsing anatomy.md lines On Windows with core.autocrlf=true, files checked out from git have CRLF line endings. The parseAnatomy regex uses $ anchors that fail to match when \r precedes \n, causing all entries to silently drop. This results in anatomy.md being rewritten with empty sections on every post-write hook invocation, losing all tracked file entries. Fix: strip trailing \r from each line before regex matching in all three copies of parseAnatomy (hooks/shared, scanner, dashboard). * chore: autofix PR #4 per review comments --------- Co-authored-by: Ferhat Şener <ruless@gmail.com>
* fix: use safe config access with defaults in CLI and daemon readJSON's fallback only applies when the file fails to read/parse. If .wolf/config.json exists but is missing a nested section (e.g. an older config written before dashboard/daemon/cron keys were added), every accessor like `config.openwolf.dashboard.port` throws `TypeError: Cannot read properties of undefined`. Replace the 8 unsafe accessors with optional chaining + nullish coalescing to sensible defaults, matching the pattern already used in src/cli/designqc-cmd.ts:33. Fixes `openwolf dashboard` and `openwolf daemon *` crashes when config.json predates a section the current release reads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(pr-review): CR-01/WR-02 full exclude_patterns fallback with shared constants Add DEFAULT_EXCLUDE_PATTERNS (19 patterns, matching config.json template) and DEFAULT_MAX_FILES constants to anatomy-scanner.ts. Replace the truncated 5-pattern inline list in both the readJSON fallback and the ?? expression. Make WolfConfig fields optional to match runtime reality. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(pr-review): WR-01/WR-03 make WolfConfig interfaces match optional-access reality Mark all nested WolfConfig fields as optional (?:) across wolf-daemon.ts, dashboard.ts, and cron-cmd.ts. TypeScript will now warn if any future code accesses these fields without ?. or null-coalescing, preventing recurrence of the crash this PR originally fixed. Also add clarifying comment to the cron.enabled ?? true guard in wolf-daemon.ts explaining why absent key defaults to enabled (matches template default). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: autofix PR #5 per review comments * fix(daemon): add bind to WolfConfig type and use safe access Add `bind?: string` to the dashboard interface in WolfConfig so the property is recognized by TypeScript, and use optional chaining when reading it to satisfy strict null checks introduced after merging develop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Michael Minor <mikeminor.creativetechnologist@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* security: patch command injection, path traversal, and unauthorized access - Fix critical command injection in CLI by using execFileSync - Bind dashboard to localhost and add token-based authentication - Prevent path traversal in CronEngine via path resolution validation - Mitigate DoS in file watcher with 1MB broadcast limit - Add security test harness using node:test * fix: WR-03 remove unused spawnSync import from daemon-cmd.ts * fix: BL-01/WR-01/WR-02 fix auth middleware order and token file security - Move express.static before auth middleware so dashboard assets load without a token (static files contain no sensitive data) - Restrict auth middleware to /api/ routes only (BL-01) - Add mkdirSync before writeFileSync to prevent ENOENT on cold init (WR-01) - Write daemon-token.tmp with mode 0o600 to prevent world-readable token (WR-02) * fix: WR-05 normalize paths to lowercase before traversal check startsWith comparison was case-sensitive and bypassable on macOS (APFS/HFS+ case-insensitive) and Windows filesystems. * fix: WR-06 document token URL exposure with TODO for sessionStorage migration * fix: WR-07/WR-08 remove AI-generated artifacts and gitignore them directives.md was auto-generated by Gemini CLI on a different developer's machine (/home/rwolf/) and leaked their home path, OS, and workspace. summary.md was a Gemini CLI session summary. Neither belongs in source control. Both added to .gitignore to prevent recurrence. --------- Co-authored-by: river wolf <riverwolf67@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Use safeCopyFile shim to avoid copy_file_range EPERM on WSL2 + EFS fs.copyFileSync (and the underlying libuv uv_fs_copyfile) uses Linux's copy_file_range syscall as a fast path. That syscall fails with EPERM when the destination is on a Windows volume mounted via WSL2 9P AND the destination directory has the EFS Encrypted attribute. This makes `openwolf init` and `openwolf update` unusable on any Windows-EFS path opened from WSL. Plain read+write avoids copy_file_range and works in all cases. Add safeCopyFile to utils/fs-safe.ts (matching the existing safe-write pattern) and replace all 12 fs.copyFileSync call sites in cli/init.ts and cli/update.ts. Reproduction: 1. On Windows, mark a directory EFS-encrypted (cipher /e <dir>) 2. Open WSL2, cd into the directory via /mnt/<drive> 3. openwolf init -> EPERM at fs.copyFileSync of OPENWOLF.md After this change: init and update succeed; new files inherit the parent's Encrypted attribute correctly via standard NTFS inheritance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: stage pnpm-lock.yaml from merge with main Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: make safeCopyFile atomic and document contract differences (CR-01, WR-01, WR-02) - Wrap write in temp+rename pattern (matches writeJSON/writeText) so an interrupted copy leaves no partially-written hook script at the destination (CR-01) - On write/rename failure, unlink the tmp file before re-throwing so no orphan is left behind - Add explanatory comment to the empty chmod catch block (WR-01) - Update docstring to note the two semantic differences from fs.copyFileSync: read+write bypass and silent dest-dir creation (WR-02) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Tony Cirigliano <tony@cirigliano.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(update): preserve config.json across `openwolf update` (fixes cytostack#37) `update.ts` was unconditionally copying `src/templates/config.json` over every registered project's `.wolf/config.json`. That file is not template content — it carries each project's daemon port, dashboard port, scan intervals, and exclude patterns. Overwriting it across all projects in one shot resets every registered project to the same defaults (`daemon=18790`, `dashboard=18791`), so only the first daemon to start binds and the rest crash-loop on `EADDRINUSE`. Move `config.json` from `ALWAYS_OVERWRITE` to `USER_DATA_FILES`. It is still included in `BACKUP_FILES` via the spread, so `openwolf restore` can still recover it. Reproduced on Linux (PVE 7) and Windows 11. After local apply, ten projects on one host kept their unique ports through a simulated update; pre-patch, all ten fell to defaults inside one second. No behavior change for `OPENWOLF.md` or `reframe-frameworks.md` — those remain protocol-doc overwrites. * fix: address PR #12 review comments in update.ts - Fix stale comment on line 177: remove config.json reference since it is no longer in ALWAYS_OVERWRITE (now lists OPENWOLF.md and reframe-frameworks.md accurately) - Add "create if missing" seed block for config.json after the template copy loop so older projects that predate the config.json template addition receive the file on their next `openwolf update` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: ManniX-ITA <20623405+mann1x@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add STATUS.md as session handoff document Adds a `.wolf/STATUS.md` file that acts as the single source of truth for resuming work across sessions. Reduces session-resume cost from ~6 file reads (memory.md + cerebrum.md + plan files + code) to a single read. Changes: - New template `src/templates/STATUS.md` with sections for ✅ Concluído, 🚀 Próxima fase, 📁 Arquitetura ativa,⚠️ Pendências, 🔧 Comandos. - `src/templates/OPENWOLF.md`: new "STATUS.md — Single Source of Truth" section at top + bumped step in Session End to update STATUS.md first. - `src/templates/claude-rules-openwolf.md`: rules to read STATUS.md first and to update it when a quest finishes or before /clear. - `src/cli/init.ts`: register STATUS.md in CREATE_IF_MISSING and add an embedded fallback in generateTemplate. - `src/hooks/stop.ts`: new `checkStatusFreshness()` — emits a stderr reminder when 3+ code writes happen but STATUS.md mtime predates the session start (or when STATUS.md is missing entirely). Result: at the end of a quest the AI moves done items to ✅ and writes the next quest in 🚀, so /clear is cheap and resumes in 1 read. * fix: translate Portuguese STATUS.md headers, add seedStatus(), fix stop-hook path/catch bugs - CR-01: add seedStatus() to init.ts — substitutes {{PROJECT_NAME}} and {{DATE}} placeholders immediately after STATUS.md is written on fresh init - CR-02: translate all Portuguese section headers in STATUS.md to English (Concluído→Done, Próxima fase→Next Phase, Arquitetura ativa→Active Architecture, Pendências externas→External Dependencies, etc.) and update matching references in OPENWOLF.md instructions - WR-01: fix path-separator assumption in checkStatusFreshness — add path.sep-based check alongside the Unix slash so .wolf/ writes are correctly excluded on Windows - WR-03: narrow bare catch{} in checkStatusFreshness to ENOENT only; permission errors and other fs failures now pass through silently instead of emitting a misleading "STATUS.md missing" nudge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: techopcgamer <techopcgamer@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds .planning/config.json with model_profile set to 'inherit'. This configuration file tracks project-level settings for GSD workflows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR Review Summary - Critical Issues FoundOverall Rating: 7.8/10 (Good, but critical issues need addressing before merge) 🚨 Critical Issues (Must Fix)1. Silent Failures in Error HandlingFiles: 2. Missing Integration TestsFiles: 3. Inadequate Error Logging in DaemonFiles: 📊 Test Coverage Gaps4. Windows Platform TestingSeverity: MEDIUM (Rating: 7/10) 5. Authentication TestingSeverity: MEDIUM (Rating: 7/10) ✅ Positive Aspects
📋 Next StepsI recommend addressing the critical issues before merging:
Full review details are available in the generated reports. |
- Fixed silent failures in fs-safe.ts with proper error logging - Added error logging to all empty catch blocks in wolf-daemon.ts - Enhanced error messages in dashboard.ts browser opening - Added 8 new integration tests for security-critical code paths - All 54 tests passing, build successful Addresses critical issues identified in PR #15 review: - Silent failures in error handling (Rating: 9) - Missing integration tests for security (Rating: 9) - Inadequate error logging in daemon (Rating: 8) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The security test file was using Node's built-in node:test module instead of vitest's API, causing test failures. This commit converts all test assertions to use vitest's API: - test() → it() - assert.*() → expect().*() - before() → beforeAll() - after() → afterAll() All 62 tests now pass successfully. Fixes: test failures in PR #15 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR Review Complete ✅Status: Ready to Merge Review Summary
Token-conscious AI brain for Claude Code projects Options: Commands:
Changes MadeFixed one minor issue in :
Key Improvements in PR #15
Recommendation🟢 APPROVED - This PR is ready to merge and brings significant value:
Reviewed by Claude Code on 2026-05-14 |
Critical fixes:
- wolf-daemon.ts: pass bind address to app.listen (was always binding 0.0.0.0)
- wolf-daemon.ts: add server.on('error') handler for clean EADDRINUSE messages
- wolf-daemon.ts: wrap startup token write in try/catch with process.exit(1)
- dashboard.ts + main.tsx + useWolfData.ts: strip ?token= from URL via
history.replaceState on first load; store in sessionStorage; send via
X-Api-Token header on all subsequent API calls
- init.ts: move config.json from ALWAYS_OVERWRITE to CREATE_IF_MISSING so
user port assignments are preserved on upgrade
- fs-safe.ts: differentiate ENOENT (silent) from parse/permission errors
(logged to stderr) in readJSON
- cron-engine.ts: log retry failures instead of swallowing with catch(() => {})
Important fixes:
- cron-engine.ts: log non-ENOENT errors reading AI task context files
- file-watcher.ts: log broadcast failures at debug level (was empty catch)
- daemon-cmd.ts: show actual error message in daemonStart catch
- daemon-cmd.ts: log non-ENOENT errors from findPidOnPort
- daemon-cmd.ts: distinguish PM2 "not found" from permission errors in
daemonStop and daemonRestart catch blocks
- dashboard.ts: add error and exit event handlers on forked daemon child
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C6: readText now differentiates ENOENT (expected - file not found) from
permission/I-O errors (unexpected - logs to stderr), matching readJSON.
C5: safeCopyFile's two bare catch{} blocks now log unexpected errors:
- temp-file cleanup failure logs the leaked .tmp path
- chmod failure logs non-EPERM/ENOTSUP codes (Windows/WSL2 expected)
…ding fs.copyFileSync uses copy_file_range on Linux which fails with EPERM on EFS/WSL2 mounts. safeCopyFile was added to fix exactly this scenario and was already used everywhere else in update.ts — this one call was missed.
When upgrading from a version that predated STATUS.md, the file is newly
written into CREATE_IF_MISSING but the seedStatus() call was inside
if (!isUpgrade) — leaving raw {{PROJECT_NAME}}/{{DATE}} placeholders in the
file. Track newly-created files and seed STATUS.md whenever it is first
written, regardless of upgrade vs fresh-init path.
url contains ?token=<hex> for the initial dashboard page load. Passing it directly to logger.error wrote the live session token to dashboard.log, where anyone with log read access could use it to bypass auth. Strip the query string before logging; the user-facing console.log(URL) is intentional (they need it to open the browser manually).
C1: verifyClient now validates ?token= from the WS upgrade URL in addition
to origin, using safeCompareToken. Unauthenticated WS connections are
rejected before the socket is established.
I2: request_full_state replies only to the requesting sender (sender.send)
instead of broadcasting all .wolf/ file contents to every connected client.
I3: shutdown() now unlinks daemon-token.tmp. server.on('error') also unlinks
it so a bind failure (EADDRINUSE) doesn't leave a stale token file.
I4: API auth middleware accepts x-api-token header only. ?token= query param
is removed — it exposed the token in browser history and Referer headers.
I6: Token comparison uses crypto.timingSafeEqual via safeCompareToken helper
instead of !== to prevent timing side-channel attacks.
S4: WebSocketServer maxPayload set to 4 MB to cap memory use from large
JSON messages.
wolf-client.ts: constructor accepts optional token parameter and appends ?token=<value> to the WS upgrade URL (required by C1 fix in wolf-daemon.ts). useWolfData.ts: reads wolf_token from sessionStorage and passes it to WolfClient so the WebSocket handshake is authenticated.
I7: killPid bare catch{} now logs EPERM separately (needs elevated privileges)
vs other errors, rather than silently returning false with no diagnostics.
S3: daemonLogs catch now includes the error message from pm2 instead of
discarding it — matches the error-handling upgrade applied elsewhere in this PR.
…checkCerebrumFreshness I8: shared.ts readMarkdown now logs non-ENOENT errors to stderr instead of silently returning empty string. Identical fix to readText in fs-safe.ts (C6). I9: stop.ts checkCerebrumFreshness now distinguishes ENOENT (cerebrum.md not yet created — expected) from EACCES/I/O errors (unexpected). Mirrors the pattern checkStatusFreshness already uses in the same file.
…rtifacts These files are generated by the GSD code-review workflow and should not be committed to the repository. Also add a *-REVIEW.md and *-REVIEW-FIX.md pattern to catch phase-prefixed variants from planning workflows.
…n modules C7: All four test suites now import production code (readJSON, readText, safeCopyFile, execFileSync) rather than testing inline lambdas. Removing the production guard now causes the corresponding test to fail. I10: Added new test suites covering: - readJSON: ENOENT silent, non-ENOENT logged, malformed JSON logged - readText: ENOENT silent, reads existing files correctly - safeCopyFile: correct copy, dest-dir creation, no stale .tmp on success or failure (atomicity/cleanup) - Auth token comparison: timingSafeEqual logic from wolf-daemon.ts (correct token accepted, wrong token rejected, short/empty tokens don't throw) - Path traversal: ../ and absolute-path escapes are blocked, safe paths pass - File watcher DoS cap: real filesystem stat mirrors the 1 MB guard
Summary
Batch merge of 10 ported PRs from the original repo into the open-source fork:
openwolf updatepreserves user config.json valuesmanaged-by: openwolftag in generated filesKey changes (26 files, +1116 / -324)
src/hooks/stop.tshook andsrc/templates/STATUS.mdsrc/utils/fs-safe.tsexpanded with cross-platform copy helperssrc/utils/extensions.tsadded for scanner language supportsrc/scanner/anatomy-scanner.tsrefactored for new extensionssrc/daemon/wolf-daemon.tshardened (loopback bind, graceful shutdown)src/cli/subcommands updated (init, update, hook-settings, dashboard)tests/security.test.tsaddedpnpm-lock.yamlrefreshedTest plan
pnpm build && node dist/bin/openwolf.js --helpopenwolf initcopies new STATUS.md templateopenwolf updatepreserves existing config.json values🤖 Generated with Claude Code