Files
max-sixty__worktrunk/.config/clawpatch/features/feat_custom_remove.json
Maximilian Roos ac958c2326 chore(clawpatch): track hand-authored threat models under .config/clawpatch (#2822)
## Summary

[clawpatch](https://www.npmjs.com/package/clawpatch) is the automated
Codex-driven code reviewer we used for a security/data-safety review
pass over worktrunk. It keyed its state to a gitignored `.clawpatch/`
directory — which means the work vanishes when the worktree is removed.
This relocates that state to `.config/clawpatch/`, alongside the repo's
other tool configs (`insta.yaml`, `nextest.toml`, `wt.toml`, …), and
tracks only the durable, reusable inputs.

The auto-mapper is near-blind to worktrunk's layout (it seeds features
from `src/main.rs`, `src/bin/*`, `tests/*` only — not `src/commands/`,
`src/git/`, `src/config/`), so every high-value finding came from six
**hand-authored** feature definitions. Each is a threat model mapping a
worktrunk security/data-safety invariant to the exact files implementing
it — `approval_gate`, `merge_safety`, `remove`, `config_deprecation`,
`repo_cache`, `switch_resolve`. That mapping is the actual review IP and
is worth keeping in-repo: model- and tool-agnostic, equally usable by a
future review pass.

## What's tracked vs not

Tracked (8 files): the 6 `feat_custom_*.json` threat models, the tuned
`config.json`, and a `.gitignore` allowlist block.

Everything else stays gitignored — derived, machine-specific, or
transient:

- `findings/` — regenerated by `clawpatch review`, and tied to old
branch state
- `project.json` — regenerated by `clawpatch init`, contains an absolute
`rootPath`
- the auto-mapped `feat_cli-command` feature — deterministic from
`clawpatch map`
- `runs/` `reports/` `locks/` `patches/` — per-run transient

The `.gitignore` uses an allowlist (`ignore .config/clawpatch/*`, then
re-include `config.json` and `features/feat_custom_*.json`) so a future
`clawpatch map`/`review` cannot accidentally start tracking regenerated
state.

## Usage note

Run with `--state-dir .config/clawpatch` or
`CLAWPATCH_STATE_DIR=.config/clawpatch`. The in-file `stateDir` alone is
not enough: clawpatch's config discovery only honors the relocated
directory when the state dir is set via flag or env. `config.json` also
adds `.config/clawpatch/**` to `exclude` so clawpatch never reviews its
own state.

## Test plan

No code change — config and gitignore only. Verified `clawpatch status
--state-dir .config/clawpatch` reads the relocated state (7 features, 31
findings) and `git check-ignore` confirms the allowlist tracks exactly
`config.json` + the 6 `feat_custom_*.json`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 20:22:28 -07:00

106 lines
3.1 KiB
JSON

{
"schemaVersion": 1,
"featureId": "feat_custom_remove",
"title": "wt remove and picker-driven removal: data-loss surface",
"summary": "Worktree removal is the highest-stakes destructive operation. Force-remove must require explicit user consent; cleanup must not silently discard uncommitted changes or untracked files. Race conditions between safety checks and the actual delete must fail closed (retain data). The picker also routes alt-r through handle_remove_output - must consult approvals read-only and skip rather than prompt mid-render. See CLAUDE.md > Data Safety, Time-of-check vs time-of-use.",
"kind": "library",
"source": "manual",
"confidence": "high",
"entrypoints": [
{
"path": "src/commands/picker/mod.rs",
"symbol": "do_removal",
"route": null,
"command": "wt remove (interactive)"
}
],
"ownedFiles": [
{
"path": "src/commands/picker/mod.rs",
"reason": "interactive removal entry"
},
{
"path": "src/git/repository/worktrees.rs",
"reason": "worktree list/remove primitives"
},
{
"path": "src/remove_dir.rs",
"reason": "filesystem removal logic"
},
{
"path": "src/commands/relocate.rs",
"reason": "relocate/move worktree (also destructive)"
}
],
"contextFiles": [
{
"path": "src/commands/command_approval.rs",
"reason": "approval system for pre-remove hooks"
},
{
"path": "src/commands/hooks.rs",
"reason": "pre/post-remove hook execution"
},
{
"path": "src/git/repository/working_tree.rs",
"reason": "dirty detection"
},
{
"path": "docs/content/faq.md",
"reason": "documented destructive surface"
},
{
"path": "CLAUDE.md",
"reason": "data safety invariants"
}
],
"tests": [
{
"path": "tests/integration_tests/remove.rs",
"command": "cargo test --test integration remove"
}
],
"tags": [
"rust",
"data-safety",
"destructive"
],
"trustBoundaries": [
"filesystem",
"user-input"
],
"status": "needs-fix",
"lock": null,
"findingIds": [
"fnd_sig-feat-custom-remove-3fc85fbc9_2fb36531bc",
"fnd_sig-feat-custom-remove-d1e281e8e_f4081924e6",
"fnd_sig-feat-custom-remove-54cc3614f_9edd442ab5",
"fnd_sig-feat-custom-remove-1dbe5d2d1_10faa93f43",
"fnd_sig-feat-custom-remove-7e79725d5_26a2834e00",
"fnd_sig-feat-custom-remove-3f4296191_028f92e016",
"fnd_sig-feat-custom-remove-34d4e5259_2210c7a66b",
"fnd_sig-feat-custom-remove-087a0b4bb_5c82e2c2a5"
],
"patchAttemptIds": [],
"analysisHistory": [
{
"runId": "20260516T210804-353f93",
"kind": "review",
"summary": "4 finding(s)",
"provider": "codex",
"model": null,
"createdAt": "2026-05-16T21:13:20.172Z"
},
{
"runId": "20260517T050139-b247d5",
"kind": "review",
"summary": "4 finding(s)",
"provider": "codex",
"model": null,
"createdAt": "2026-05-17T05:10:23.387Z"
}
],
"createdAt": "2026-05-16T21:00:00.000Z",
"updatedAt": "2026-05-17T05:10:23.387Z"
}