v2.0: self-audit pass — fix inert loop alarm, close guard bypasses, remove dead code - #7
Conversation
…de, doc drift A multi-agent audit swept every subsystem and adversarially verified each finding; this fixes the load-bearing ones (91 confirmed) and removes dead weight. Enforcement layer: - Loop alarm was deterministically inert on Claude Code 2.1.x: failing Bash commands fire PostToolUseFailure (no exit-code field), not PostToolUse where the hook keyed on tool_response.exit_code. Now wired to both events; new tests replay real failure payloads and prove the Nth failure trips the nudge. - Failing write-commands no longer reset their own grind count. - TEST_PATH normalizes separators so the weakening alarm + claim-audit gate fire on Windows backslash paths. - Destructive guard: force-with-lease segment-scoped, `git checkout ./`/`..` blocked, override only as an env-assignment prefix. - Compaction recovery flushes protocol + original request before bounded git calls. - Hook state dir honours CLAUDE_DIR. mem CLI: schema-less-index reads fail soft; no MEMORY.md near-dup explosion; REL_DATE drops bare "now"/"recently"; fts/degraded tokenize identically; doctor --privacy warns the index embeds project bodies. Workflows/installers/bench: big-task planner/halt/flag fixes; honest exit-cause and count messages; check-workflows enforces no Date.now/Math.random and scopes meta check; LC_ALL=C manifest sort; doctor python3-guard + version check; run.sh path canonicalization; score.py crash-guards + negation-aware claims audit + behavioral tests. Doctrine/docs: fable skill + doctrine defer to the orchestration opt-in gate; corrected destructive-guard coverage, privacy-seed path, agent table, Python 3.11 note, and the loop-alarm known-limit; scoped the "every component live-verified" claim. Removed: dead privacy.toml.example, MultiEdit (gone in 2.1.x), unused commits array / FTS_COLUMNS / redundant test / no-op env copy. Suite: 166 -> 183 passing.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis release updates enforcement-hook event semantics, destructive-command protection, memory indexing and privacy diagnostics, benchmark scoring, workflow validation, installer determinism, environment checks, documentation, and regression tests. ChangesEnforcement hooks and event wiring
Memory index and privacy diagnostics
Benchmark execution and scoring
Workflow, installer, and diagnostics
Documentation and release records
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ClaudeCode
participant HookSettings
participant EnforcementHook
participant MemoryCLI
ClaudeCode->>HookSettings: emit tool-use event
HookSettings->>EnforcementHook: invoke matched hook
EnforcementHook->>MemoryCLI: reindex configured corpus when session ends
MemoryCLI-->>EnforcementHook: return index result
EnforcementHook-->>ClaudeCode: emit audit, alarm, or reset outcome
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
tests/test_bench.py (1)
21-27: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winOuter timeout can be shorter than the two inner pytest timeouts combined.
score_instancecaps the wholescore.pysubprocess at 300s, butscore.py's_run_pytestruns two nested pytest calls, each with its own 300s timeout (up to ~600s worst case). If a hang-simulating test is ever added here — the exact scenario CONF63 is meant to degrade gracefully from — this harness's own timeout would fire first and raise an uncaughtTimeoutExpiredinstead of exercising that handling.♻️ Proposed fix: give the outer call enough headroom for two inner timeouts
def score_instance(instance): r = subprocess.run([sys.executable, str(SCORE), str(instance)], - capture_output=True, text=True, timeout=300) + capture_output=True, text=True, timeout=650) assert r.returncode == 0, r.stderr return json.loads(r.stdout)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_bench.py` around lines 21 - 27, Update score_instance so the subprocess timeout provides headroom for both nested pytest runs configured by score.py’s _run_pytest, rather than expiring at 300 seconds. Preserve the existing return-code assertion and JSON parsing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@claude/CLAUDE.md`:
- Line 20: Update the fable-mem command in the cross-project memory guidance to
use ${CLAUDE_DIR:-$HOME/.claude} as the default path, ensuring the home
directory expands when CLAUDE_DIR is unset; preserve the existing command and
platform-specific python guidance.
In `@claude/cli/mem.py`:
- Around line 702-712: Update declared_name and the surrounding memory parsing
flow to preserve whether each name came from frontmatter instead of inferring
declaration from equality with slug_of(m["path"]). Filter out only names marked
as filename fallbacks, while retaining explicitly declared names such as MEMORY
or Auth even when they match the filename fallback.
In `@claude/hooks/pretool-destructive-guard.py`:
- Around line 30-34: The override handling around OVERRIDE_PREFIX currently
approves the entire command line when any segment contains a valid assignment.
Evaluate FABLE_DESTRUCTIVE_OK=1 independently for each shell segment, alongside
the dangerous-pattern checks, so the override applies only to its following
command and cannot authorize later segments after separators such as semicolons,
pipes, or ampersands.
In `@claude/skills/fable/SKILL.md`:
- Around line 43-50: Update the “Workflow stages honor the orchestration gate”
section to replace the broad “another slash command” opt-in with a slash command
that explicitly instructs the agent to use Workflow or orchestration. Preserve
the existing opt-in cases and fallback to Agent-tool subagents when Workflow is
not authorized or unavailable.
In `@claude/skills/memory-search/SKILL.md`:
- Around line 34-37: Update the “Banking and promotion” guidance to retain an
explicit mem doctor --privacy scan before promoting or sharing a lesson. Keep
the postmortem skill as the source for write mechanics, but require the privacy
check as a separate backstop for interpreter- or copy-based promotions.
In `@README.md`:
- Around line 177-180: Update the README privacy.toml documentation to reference
the resolved base using ${CLAUDE_DIR:-$HOME/.claude}/memory/privacy.toml instead
of hardcoding ~/.claude, including the additional occurrence at the other
documented location. Keep the existing seeding and overwrite behavior unchanged.
In `@tests/test_mem_cli.py`:
- Around line 207-213: Update
test_doctor_privacy_warns_the_index_embeds_project_bodies to assert that both
run(tmp_path, "index") and run(tmp_path, "doctor", "--privacy") return success
before checking the privacy warning text.
---
Nitpick comments:
In `@tests/test_bench.py`:
- Around line 21-27: Update score_instance so the subprocess timeout provides
headroom for both nested pytest runs configured by score.py’s _run_pytest,
rather than expiring at 300 seconds. Preserve the existing return-code assertion
and JSON parsing behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ac057e76-666c-4d25-82b5-55a74535d6e4
📒 Files selected for processing (41)
CHANGELOG.mdREADME.mdbench/README.mdbench/run.shbench/score.pyclaude/CLAUDE.mdclaude/cli/mem.pyclaude/cli/privacy.toml.exampleclaude/hooks/posttool-loop-alarm.pyclaude/hooks/posttool-test-weakening-alarm.pyclaude/hooks/precompact-save-task.pyclaude/hooks/pretool-destructive-guard.pyclaude/hooks/sessionend-mem-journal.pyclaude/hooks/sessionstart-compact-recovery.pyclaude/hooks/stop-claim-audit.pyclaude/hooks/userpromptsubmit-mem-recall.pyclaude/settings/settings-snippet-small.jsonclaude/settings/settings-snippet-windows-small.jsonclaude/settings/settings-snippet-windows.jsonclaude/settings/settings-snippet.jsonclaude/skills/fable/SKILL.mdclaude/skills/memory-search/SKILL.mdclaude/skills/orchestrate/SKILL.mdclaude/workflows/big-task.jsclaude/workflows/bug-hunt.jsclaude/workflows/design-variants.jsclaude/workflows/memory-gc.jsclaude/workflows/memory-review.jsclaude/workflows/paranoid-review.jsdocs/RESEARCH.mdinstall.shtests/test_bench.pytests/test_destructive_guard.pytests/test_loop_alarm.pytests/test_mem_cli.pytests/test_settings_snippet.pytests/test_stop_claim_audit.pytests/test_weakening_alarm.pytools/check-workflows.mjstools/doctor.ps1tools/doctor.sh
💤 Files with no reviewable changes (1)
- claude/cli/privacy.toml.example
| # A memory with no frontmatter `name:` falls back to its filename basename | ||
| # (parse_file), so every native per-project MEMORY.md shares the name "MEMORY". | ||
| # Matching on a filename-fallback name produced N-choose-2 bogus "identical name" | ||
| # pairs across unrelated projects (CONF50) — so only compare DECLARED names. | ||
| def declared_name(m): | ||
| n = _norm(m["name"]) | ||
| return n if n and n != _norm(slug_of(m["path"])) else "" | ||
| for i in range(len(mems)): | ||
| for j in range(i + 1, len(mems)): | ||
| a, b = mems[i], mems[j] | ||
| if _norm(a["name"]) and _norm(a["name"]) == _norm(b["name"]): | ||
| if declared_name(a) and declared_name(a) == declared_name(b): |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not infer frontmatter presence from name equality.
An explicitly declared name: MEMORY in MEMORY.md (or name: Auth in auth.md) equals its filename fallback and is discarded here. Preserve whether name came from frontmatter, then filter only actual fallback names; otherwise valid duplicate candidates are missed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@claude/cli/mem.py` around lines 702 - 712, Update declared_name and the
surrounding memory parsing flow to preserve whether each name came from
frontmatter instead of inferring declaration from equality with
slug_of(m["path"]). Filter out only names marked as filename fallbacks, while
retaining explicitly declared names such as MEMORY or Auth even when they match
the filename fallback.
| memory/ | ||
| privacy.toml work-marker patterns; seeded to | ||
| ~/.claude/memory/privacy.toml only if absent (never | ||
| overwritten — your tuned patterns are yours) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Make privacy paths honor CLAUDE_DIR.
These instructions still hardcode ~/.claude, but pretool-mem-privacy-guard.py and mem.py resolve the base from CLAUDE_DIR. On custom installations, users may configure or inspect the wrong corpus and believe the privacy guard is protecting it.
Document the resolved default consistently, e.g. ${CLAUDE_DIR:-$HOME/.claude}/memory/privacy.toml.
Also applies to: 216-216
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 177 - 180, Update the README privacy.toml
documentation to reference the resolved base using
${CLAUDE_DIR:-$HOME/.claude}/memory/privacy.toml instead of hardcoding
~/.claude, including the additional occurrence at the other documented location.
Keep the existing seeding and overwrite behavior unchanged.
…UDE_DIR docs
- Critical: the FABLE_DESTRUCTIVE_OK=1 override approved the whole command line, so
`FABLE_DESTRUCTIVE_OK=1 git reset --hard; rm -rf /` let the rm through. The guard now
evaluates every check per shell segment (length-preserving quote strip keeps offsets
aligned so the rm pattern still sees quoted targets); the override exempts only the
segment it prefixes. Added a test for the compound-bypass attack.
- Docs: the quoted `~` in ${CLAUDE_DIR:-~/.claude} never expands — use $HOME/.claude in the
CLAUDE.md and memory-search command examples.
- memory-search: restored the explicit `mem doctor --privacy` backstop instruction (the
write-time guard only covers Write|Edit and fails open).
- fable skill: a slash-command opt-in for Workflow now requires the command to explicitly
authorize orchestration, matching the orchestrate gate.
- Tests: assert index/doctor return codes in the privacy-advisory test; give the bench
score_instance timeout headroom for two nested pytest runs.
- mem.py gc-scan: documented the intentional declared-name degradation (rare exact-name
dup still surfaces via same_topic_pairs; not worth an index migration).
Suite: 184 passing.
- Major: reverting fts5 search regression. The CONF57 change routed _fts_match through
keywords() (len>2, stopwords), which silently dropped every short query ("go", "ci",
"os", "db", "ai") in the common fts5 mode. fts5 unicode61 token-matches safely, so it
keeps all tokens; only the degraded LIKE path filters short tokens (substring noise).
Added a regression test.
- German-market gate under-triggered after CONF26: a lone "GmbH"/"Impressum" no longer
enabled the legally load-bearing compliance judge. GmbH/AGB/Impressum/BFSG/TTDSG/TDDDG
are now strong single triggers; only weak signals (lone umlaut, und, eine) need two.
- bench/score.py: a timed-out acceptance run now fails loudly instead of scoring the
partial PASSED lines it printed before hanging.
- Doc drift the v2.0 pass left behind: MultiEdit still named in the privacy-guard
docstring, postmortem SKILL, and the CHANGELOG claim; the loop-alarm README lines still
said "no file modification" instead of "no successful change"; the destructive-guard
docstring and CLAUDE.md checkout summaries omitted the newly-blocked `..`.
Suite: 185 passing.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
tests/test_destructive_guard.py (1)
157-166: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSegment-scoping test is correct — but add a case for the newline gap.
test_override_is_scoped_to_its_own_segmentcorrectly proves;and&&scoping (matches the hook's_segments()/OVERRIDE_SEGMENTlogic). All shown segmentation tests here use;/&&/|— none exercise a bare-newline-joined command, which is the vector where the override currently escapes its own segment (see hook-file finding on_segments/line 108). Once that's fixed, add:assert run_hook( "FABLE_DESTRUCTIVE_OK=1 git reset --hard\nrm -rf /", cwd=repo).returncode == 2🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_destructive_guard.py` around lines 157 - 166, Extend test_override_is_scoped_to_its_own_segment to cover a bare newline command separator: assert that an approved FABLE_DESTRUCTIVE_OK=1 git reset --hard followed by newline and rm -rf / returns 2, while preserving the existing semicolon, &&, and standalone approved-segment assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@claude/hooks/pretool-destructive-guard.py`:
- Around line 126-128: Update the ALWAYS_DANGEROUS dispatch and matching logic
to avoid triggering rm checks on destructive-command phrases merely mentioned
inside quoted commit messages, while still detecting genuinely quoted rm targets
such as "/" or "$HOME". Replace the fragile why.startswith("recursive rm")
coupling with an explicit per-pattern flag or equivalent metadata, and use it to
distinguish raw-text matching needed for quoted targets from quote-stripped
matching for normal command detection. Preserve existing TREE_DESTROYERS and
FORCE_PUSH behavior.
- Around line 38-47: Update _segments to treat bare newline characters as
segment boundaries before override matching, preventing an override on one
command from applying to later destructive commands; preserve line-continuation
behavior explicitly if supported, and continue splitting existing top-level
shell separators unchanged.
- Line 113: Update the preprocessing around the unquoted command in the
destructive-command guard so command substitutions, including $(...) and
backticks inside double-quoted spans, remain visible to the git checks before
quoted text is blanked. Preserve safe quote stripping for ordinary quoted
arguments while ensuring TREE_DESTROYERS and FORCE_PUSH inspect substitutions
for destructive commands.
---
Nitpick comments:
In `@tests/test_destructive_guard.py`:
- Around line 157-166: Extend test_override_is_scoped_to_its_own_segment to
cover a bare newline command separator: assert that an approved
FABLE_DESTRUCTIVE_OK=1 git reset --hard followed by newline and rm -rf / returns
2, while preserving the existing semicolon, &&, and standalone approved-segment
assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fd84a79e-51da-4efd-8822-e6a7c871fc0a
📒 Files selected for processing (8)
claude/CLAUDE.mdclaude/cli/mem.pyclaude/hooks/pretool-destructive-guard.pyclaude/skills/fable/SKILL.mdclaude/skills/memory-search/SKILL.mdtests/test_bench.pytests/test_destructive_guard.pytests/test_mem_cli.py
🚧 Files skipped from review as they are similar to previous changes (5)
- claude/skills/fable/SKILL.md
- claude/CLAUDE.md
- claude/skills/memory-search/SKILL.md
- tests/test_mem_cli.py
- claude/cli/mem.py
…ositive
Three issues found reviewing the per-segment override rewrite:
- Critical: newline is a shell command separator, but `re.sub(r"\s+"," ",cmd)` collapsed
it before segmentation, so `FABLE_DESTRUCTIVE_OK=1 git reset --hard\nrm -rf /` became one
segment and the override suppressed the rm. Newlines are now preserved (line
continuations joined first) and _segments splits on them like `;`.
- Critical: a destructive command inside "$(...)" or backticks in double quotes was blanked
out of view. Command-substitution contents are now scanned per segment.
- Major/correctness: the rm check ran on raw text, so a commit message merely MENTIONING
`rm -rf /` false-tripped. It now uses an rm-target view that un-quotes only genuine bare
targets (rm -rf "/") while blanking multi-word quoted spans (messages). Also replaced the
fragile `why.startswith("recursive rm")` dispatch with an explicit per-pattern flag.
New tests cover the newline bypass, command-substitution evasion, and the message
false-positive. Suite: 187 passing.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
claude/hooks/pretool-destructive-guard.py (1)
65-80: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy liftParse substitutions before splitting on shell separators.
echo $(true; rm -rf /)gets split at the inner;, leavingrm -rf /)outside_subst_contents()and bypassing the destructive-command match. Nested and process substitutions have the same gap.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@claude/hooks/pretool-destructive-guard.py` around lines 65 - 80, Update the parsing flow around _segments and _subst_contents so command, nested, and process substitutions are recognized and protected before splitting on top-level shell separators. Ensure separators inside any substitution remain part of that substitution, allowing the complete substituted content to be checked for destructive commands while preserving the existing quote-aware top-level splitting behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@claude/hooks/pretool-destructive-guard.py`:
- Around line 51-62: Update _rm_view and the associated rm-target matching logic
to preserve whether a quoted span used single or double quotes, and recognize
equivalent double-quoted expansion forms such as "${HOME}" alongside "$HOME". Do
not treat single-quoted literals such as '${HOME}' as expansions, while
retaining the existing whitespace masking and length-preserving behavior for
quoted text.
---
Outside diff comments:
In `@claude/hooks/pretool-destructive-guard.py`:
- Around line 65-80: Update the parsing flow around _segments and
_subst_contents so command, nested, and process substitutions are recognized and
protected before splitting on top-level shell separators. Ensure separators
inside any substitution remain part of that substitution, allowing the complete
substituted content to be checked for destructive commands while preserving the
existing quote-aware top-level splitting behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 96f04238-e59e-4309-80c6-af27b179eef9
📒 Files selected for processing (2)
claude/hooks/pretool-destructive-guard.pytests/test_destructive_guard.py
…ator, ${HOME})
Follow-up on the command-substitution scanning added last commit — CodeRabbit found it
half-done:
- Single-quoted '$(...)' is a literal, not a command — the naive scan false-blocked it.
Substitutions are now extracted only from the single-quote-blanked view (where they are
actually active).
- A separator inside a substitution ($(true; rm -rf /)) split it out of the scan. Active
substitution spans are now blanked before top-level segmentation, and their contents are
scanned recursively (one nesting level) as their own commands.
- rm -rf "${HOME}" (braced expansion) is the same catastrophic target as $HOME; the rm
pattern now matches both.
Refactored the per-command checks behind _iter_command_slices() (segments + active
substitution contents, override-scoped, bounded depth). Residual regex-tripwire limits
(nested/process substitution, sh -c/eval) documented in README known-limits. New tests for
all three cases. Suite: 189 passing.
What this is
The kit was turned on itself. A multi-agent audit swept every subsystem (hooks, workflows, skills, agents, installers, mem CLI, bench, tests, docs) and adversarially verified each candidate defect against the code before it counted — 91 findings survived verification. This PR fixes the load-bearing ones, removes dead weight, and adds tests that prove each fix.
The headline is a real instance of the exact failure this kit exists to kill — a "deterministic" enforcement hook that was silently inert — found in the kit itself and proven fixed.
Enforcement layer (the hooks that must hold)
PostToolUseFailure(a distinct event with no exit-code field — failure is signalled by the event itself), notPostToolUse, where the hook was registered and keyed ontool_response.exit_code. The two events are mutually exclusive, so the hook could never observe a failure. It's now wired to both events; new unit tests replay real-shaped failure payloads and prove the Nth failure trips the nudge.make test > build.log) no longer reset their own grind count.TEST_PATHnormalizes path separators so the test-weakening alarm and claim-audit gate fire on native-Windows backslash paths (they were inert there).--force-with-leasein another segment no longer excuses a bare--force;git checkout .//..block on a dirty tree; theFABLE_DESTRUCTIVE_OK=1override must be an actual env-assignment prefix, not a mention in a commit message.CLAUDE_DIR.mem CLI
Schema-less/0-byte index reads fail soft instead of tracebacking; no more N-choose-2 bogus
MEMORY.mdduplicate proposals;REL_DATEstops flagging bare "now"/"recently"; fts5 and degraded search tokenize identically;doctor --privacywarns that the index embeds project bodies.Workflows · installers · bench
big-taskplanner/halt/flag fixes; honest exit-cause and dry-run counts across workflows;check-workflows.mjsnow enforces noDate.now()/Math.random()and scopes the meta-field check;LC_ALL=Cmanifest sort for byte-parity with the PowerShell installer;doctor.shskips-with-warn instead of cascading false FAILs when python3 is absent, and both doctors gained the ≥ 2.1.154 version check;bench/run.shcanonicalizes relative paths;bench/score.pygains crash guards, a negation-aware claims audit, and behavioral tests.Doctrine & docs honesty
The
fableskill and doctrine no longer self-authorize Workflow runs — they defer to the orchestration opt-in gate. Corrected the destructive-guard coverage claim, privacy-seed path, agent table, Python 3.11 requirement for the privacy layer, and the loop-alarm known-limit; scoped RESEARCH's "every component live-verified" claim.Removed
Dead
privacy.toml.example(byte-identical duplicate nothing installed);MultiEdit(gone in 2.1.x) from every matcher/set/branch; unusedcommitsarray,FTS_COLUMNS, a redundant settings-JSON test, and a no-openv=os.environ.copy().Verification
install.sh+doctor.shrun clean end-to-end and idempotent on a scratchCLAUDE_DIR.🤖 Generated with Claude Code
https://claude.ai/code/session_01EzKf6rXy7r1Kf9MeoBSLMo
Generated by Claude Code
Summary by CodeRabbit
PostToolUseFailure.MultiEdithandling from privacy/audit and related alarm logic.