* fix(agents): unbreak the scratch-dir guards on macOS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): fold case in the scratch-guard exclusion list
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): match the MCP cache roots exactly, not by prefix
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(agents): pin the MCP cache class on the fileops guard
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): prove scratch file ops per command segment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: describe the checkout root in the scratch guidance
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): close two auto-allow holes in the scratch guards
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): keep redirects and chained writes off the allow path
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): never prove a command carrying a substitution or relative cd
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): treat sibling checkouts as separate roots
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): prove where a directory-form cp or mv actually lands
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): leave directory-form cp and mv unproved
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: state the one-write-per-line rule in the scratch guidance
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: prefer Edit/Write over shell edits in agent guidance
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): stop a failed cd from hiding the directory form
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(agents): state the glob and cd rationale once
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): stop the scratch-dir guards prompting on quoted text
* fix(agents): keep prompting past wrapper flags and quoted heredoc markers
* fix(agents): only treat a line-ending delimiter as a heredoc opener
* fix(agents): refuse a heredoc opener whose redirect carries a quote
* fix(agents): stop the wrapper scan at a quoted word instead of a word count
* fix(agents): scan a wrapper's operands to the end of the segment
* fix(agents): scan a heredoc body that is piped into a shell
* fix(agents): only treat a quoted, unexecuted heredoc body as data
* fix(agents): split separators before looking for the shell running a heredoc
* fix(agents): require a reading consumer before treating a body as data
* fix: serve getJob in the ai evals benchmark api catalog
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: scope the frontend format hook to the frontend dir
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: answer the run-by-path endpoints and mirror the real getJob entry
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate the format hook on a repo-root frontend, not the project dir
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: auto-allow scratch file ops confined to /tmp
The /tmp entries in .claude/settings.json used a `Bash(cmd:/tmp/*)` form, but
the colon is only meaningful as a trailing `:*` wildcard — each was matched as
a literal command string no invocation produces, so mkdir, cp, mv, touch,
chmod, tar and unzip all prompted despite the rules being present.
mkdir and touch become working prefix rules. The rest move to a PreToolUse
hook, which is required for mv and chmod (both sit in the `ask` list, which
outranks any allow rule) and preferable for cp/tar/unzip: a prefix rule can
only constrain the first operand, so `cp /tmp/x ~/.zshrc` would match a
`cp /tmp/` prefix. The hook instead requires every path operand to resolve
under /tmp, which also keeps it from becoming a way around the
`Read(**/.env)` deny rules by copying a project file into readable scratch.
tar and unzip get a separate parser: their destination arrives as a flag value
(`-C`, `-d`) and a bundle like `-xzf` consumes the following token. Flags are
an allowlist, so `-P`/`--absolute-names`, which disable tar's refusal to
extract `..` and absolute member paths, defer rather than needing enumeration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: close three symlink and prefix-rule escapes in the /tmp hook
Review findings on the previous commit, all three genuine:
Drop the `Bash(mkdir|touch /tmp/:*)` allow rules. A prefix rule constrains
only the first operand, so they accepted `mkdir /tmp/../etc/evil` and
`touch /tmp/a ~/.bashrc` — the hook already deferred both, but an allow rule
grants the call before the hook's silence can matter. They were also
redundant: the hook covers mkdir and touch on its own.
Refuse globs outright. Bash expands them only after the hook has decided, so
realpath saw the unexpanded pattern: `chmod 600 /tmp/link*` canonicalized to
itself, passed, then expanded onto a symlink pointing outside /tmp, and chmod
follows command-line symlinks. guard-rm-outside-tmp.sh can allow globs under
/tmp because `rm` unlinks a symlink rather than following it; every command
here follows one instead.
Make options a per-command allowlist. Generic acceptance let `cp -RL` through,
which dereferences while recursing and so copies the content of a symlink
target outside /tmp into a scratch dir that `Read(/tmp/**)` exposes — the same
deny-rule bypass the every-operand rule exists to prevent. Plain `-r` and `-a`
recreate such a symlink as a symlink and stay allowed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: require absolute operands so option words cannot pose as /tmp paths
Both remaining escapes shared a root cause: a token the tool reads as an
option was validated as a path, because resolving it against the cwd made any
bare word look safe whenever that cwd was under /tmp. `tar P -xf /tmp/a.tar
-C /tmp/out` checked out as /tmp/P while tar read P as --absolute-names, and
`cp /tmp/tree -RL /tmp/out` checked out as /tmp/-RL while cp read -RL as
dereferencing recursion.
Accept only absolute operands, which removes the class rather than the two
instances. Also apply the option allowlist at every position, since GNU utils
permute and recognize options after operands.
`unzip -l` no longer requires a destination; listing extracts nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: drop a contradictory comment and stop treating unzip -v as extraction
The sentence justifying the old before-first-operand option check outlived the
check itself, leaving the file asserting both that and the all-position rule
that replaced it. Only the second is true.
`unzip -v` is a verbose listing and writes nothing, so it no longer requires an
extraction destination.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: allow /tmp rm and read-only gmail in local permission rules
* fix: gate rm outside /tmp via PreToolUse hook and gate gmail drafts
* fix: reject shell expansion and multi-line commands in rm guard
* fix: make rm guard allow-only with Bash(rm:*) ask as safety net
* fix: reject quotes and backslashes in rm guard to block obscured traversal
* fix: switch rm guard to deny-by-default whitelist of safe /tmp operands
* fix: treat lone dash as rm operand, not an option flag
* fix: defer option tokens containing glob chars in rm guard
* feat: also auto-allow rm strictly inside git working trees under $HOME
* feat: auto-allow deleting linked worktree root folders, still guard primary checkouts
* fix: restrict globs to /tmp and validate post-operand option tokens in rm guard
* docs: correct rm guard rationale to not overclaim git recoverability
- guard-main-branch.sh: exit 2 on block (was advisory echo), and block
force-push to main from any branch (--force, -f, --force-with-lease, +ref)
- settings.json: gate claude.ai MCP connectors (Stripe, Gmail, Calendar,
Drive, Slack, Linear) behind permissions.ask
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: remove wm-cursor, add local-review skill, update PR skill for EE
- Remove the unused wm-cursor script and all references to it in
README_WORKMUX_DEV.md and worktree-common.sh
- Add /local-review skill for code review (bugs + CLAUDE.md compliance)
- Add EE companion PR workflow to the /pr skill
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add wm-ts-nav tree-sitter navigator and fix format hooks
- Add wm-ts-nav: standalone tree-sitter code navigator with SQLite index
for fast symbol search, definition lookup, and file outlines across
Rust, TypeScript, and Svelte files (~12ms warm, ~1s cold for 482 files)
- Fix format hooks: surface errors instead of swallowing with 2>/dev/null,
use direct prettier path with svelte plugin, add success feedback
- Add wm-ts-nav commands to settings allow list
- Document wm-ts-nav usage in CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(wm-ts-nav): add refs command and --parent filter
- refs: find usages of a symbol in code, skipping comments and strings
(tree-sitter AST walk, ~46ms for 482 files vs grep's 4ms but no noise)
- --parent filter on search: find all methods on a type across all files
(e.g. search "%" --kind function --parent ServiceName)
- Update CLAUDE.md with clearer when-to-use guidance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(wm-ts-nav): index refs in DB with import-path resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(wm-ts-nav): add body, callers, callees commands and refs --file/--caller
- body: extract a symbol's source code from disk using indexed line ranges
- callers: cross-file call graph via SQL join of refs + symbols tables
- callees: list all identifiers referenced within a symbol's body
- refs --file: scope results to files matching a substring
- refs --caller: annotate each ref with the containing function name
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(wm-ts-nav): add auto-rebuilding wrapper script
The `wm-ts-nav/nav` wrapper checks if source files are newer than the
binary and rebuilds automatically. Invoked via `sh wm-ts-nav/nav` to
avoid needing executable permissions after clone.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: tighten CLAUDE.md nav section for actionable guidance
Remove redundant question→command mapping, latency numbers, and
excessive examples. Lead with "prefer wm-ts-nav over Read to save
context window" and keep only the patterns that change behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: revert backend/Cargo.lock to main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: promote wm-ts-nav in workflow, copy binary to worktrees
- CLAUDE.md: integrate wm-ts-nav into Workflow step 1 and Core
Principles so agents use outline/body before full file reads
- workmux: copy built binary via files.copy
- worktree-common.sh: copy binary in wm_copy_dependencies for webmux
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(wm-ts-nav): fix double indexing, add TSX grammar, remove needless clone
- Reuse index stats from the pre-query update instead of indexing twice
on the Index command
- Add Lang::Tsx variant so .tsx/.jsx files use LANGUAGE_TSX instead of
LANGUAGE_TYPESCRIPT (Svelte stays on TS since script blocks are pure TS)
- Remove source.clone() for non-Svelte files — move directly instead
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(wm-ts-nav): fix svelte line numbers, add class methods, innermost caller
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Claude Code hooks for formatting and notifications
- Add PostToolUse hooks to auto-format files after Edit/Write:
- format-frontend.sh: runs prettier on frontend files
- format-backend.sh: runs rustfmt on backend Rust files
- Add Notification hook to alert user when Claude needs input
- Add edition=2021 to rustfmt.toml for proper parsing
- Update .gitignore for symlinked cache directories
- Add additional bash permissions for cargo check and npm scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* remove echo
* notification when in ssh as well
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add permissions test suite covering scripts, flows, apps, resources,
variables, schedules, and folder-based permissions
- Add test for operator restrictions (cannot create/update/archive)
- Restrict operators from archiving scripts (archive_script_by_path,
archive_script_by_hash) for security consistency with flows/apps
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>