Originally created by: fu351
Originally owned by: harshitagrawal2O
DestructiveCommandRule's bulk-delete check (_segment_verdict, src/doberman/engine/rules/commands.py:568) steps up to AUTH only when _count_delete_operands(tokens) >= bulk_threshold (default 25, DEFAULT_BULK_THRESHOLD, line 84).
Rescoped 2026-08-20. AN-1 (shipped in 0.18.0) already added a lexical, filename-only gate for unrecoverable gitignored data: _any_operand_unrecoverable / _rm_targets_unrecoverable_data (commands.py:323-340) match each operand's basename against _UNRECOVERABLE_DELETE_GLOBS (local DB / secret / key filenames), with no filesystem or git access. A directory operand (rm -rf data/) cannot be classified lexically and is deliberately outside that gate (the ponytail: note at line 336 defers it). That directory-level gap is what this issue is now about: the rule still has no awareness of whether a directory target is gitignored-but-uncommitted — deleting build/ or node_modules/ is cheap to regenerate, but deleting an uncommitted .env.local-adjacent working directory or a gitignored data/ folder holding un-backed-up state can be genuinely irrecoverable, and today it gets exactly the same treatment as any other bulk delete. Add a new, additive escalation: when a rm/bulk-delete segment's target(s) are inside a directory git check-ignore reports as ignored and the repo has no committed copy, lower the effective bulk threshold (or force AUTH) for that segment.
Additive engine change (new rule condition), raise-only by construction — it can only turn an existing PASS into AUTH for a specific new case, never the reverse. The judgment is in scoping the git integration cleanly (shell out to git check-ignore, Doberman's established pattern for "ask git rather than reimplement .gitignore parsing" — see how role_boundary.py treats repo boundaries) and handling "not a git repo" / "git not installed" gracefully.
AUTH — it must never lower the verdict for any command the existing bulk_threshold check already escalates, and it must never turn an existing BLOCK (e.g. _rm_is_catastrophic, line 295) into anything weaker, and it must leave the AN-1 lexical gate (_rm_targets_unrecoverable_data) exactly as it is — build beside it, not over it.git check-ignore fails, times out, or the target isn't inside a git repo, the segment must fall back to exactly today's behavior (the plain bulk_threshold check) — never silently skip escalation because gitignore status couldn't be determined.src/doberman/engine/rules/commands.py — _segment_verdict (line 568), _count_delete_operands (line 307), the AN-1 lexical gate to build beside: _any_operand_unrecoverable / _rm_targets_unrecoverable_data (lines 323–340), DestructiveCommandRule (line 756 onward)._targets_gitignored_uncommitted(paths, root) — shells out to git check-ignore -q <path> (and git ls-files --error-unmatch to rule out "ignored but already tracked" false positives), following the "ask git, don't reimplement gitignore parsing" pattern.tests/unit/test_rule_commands.py — existing bulk-threshold tests to extend.rm targeting paths inside a git-ignored, never-committed directory is escalated to AUTH at a lower operand count than DEFAULT_BULK_THRESHOLD (or always, contributor's documented choice — call it out in the PR description).git is unavailable or the target isn't inside a git repo, behavior falls back to today's plain bulk_threshold check (no crash, no silent PASS).explanation.ruff check . && ruff format --check . and lint-imports passpytest passespytest tests/unit/test_rule_commands.py -v
.gitignore pattern matching yourself (no new parsing dependency) — shell out to git check-ignore._rm_is_catastrophic's existing BLOCK conditions or the protected-branch force-push check.pathspec) — this issue is scoped to reusing the git binary already required for git_op actions.New here? Start with CONTRIBUTING.md. Issues are labelled level-1 (docs only) through level-10 (new subsystem) — pick one at your level and climb. Comment to claim an issue before starting.
Ticket changed by: fu351
Ticket changed by: fu351
Originally posted by: harshitagrawal2O
Picking this one up.
Originally posted by: harshitagrawal2O
Thanks for the assignment — starting on this, but I want to flag a real tension before I pick an approach.
Since this issue was filed,
src/doberman/engine/rules/commands.pypicked up_rm_targets_unrecoverable_data(AN-1, the lexical unrecoverable-file gate), and its neighbor_any_operand_unrecoverablecarries this note:That's exactly this issue's scenario (a directory operand), and it reads as a deliberate call to keep the objective decision path free of filesystem/git subprocess calls. It also means the issue's own suggested precedent doesn't hold up as written — I checked
role_boundary.py's repo-boundary handling, and it's pure in-memory path-prefix matching (classify(role, raw_path, root=root)), never a git subprocess; there's no existing "ask git" pattern anywhere inengine/rules/today (confirmed: noimport subprocessin that package).So before I implement
git check-ignore-based detection in the hot path, I'd rather confirm which way you want this resolved:subprocess/filesystem calls out of the synchronous decision path (e.g., a cached/precomputed gitignore-directory set built off-path, or a narrower heuristic that doesn't need git at all), orgit check-ignorecall is acceptable specifically here despite that note — e.g. because AN-1's comment was scoped to justify why that fix stayed lexical-only, not a blanket ban — in which case I'll implement it exactly as the issue describes (shell togit check-ignore -q, fail toward today's plainbulk_thresholdbehavior on any git/subprocess error).Whichever it is, happy to proceed the same day you confirm.
Originally posted by: fu351
Assigned! This one goes deep into the engine's classification path, so take whatever design room you need and ask here before locking in an approach, I'd rather talk it through early than re-review late.