Decisions

  • Decided: .claude/rules/ stays open — only CLAUDE.md and .claude/settings.json are strict. Rules are low-risk and frequently edited.
  • Decided: Dual source — protected: true in file frontmatter is the source of truth, .claude/protected-files.yml is a generated cache for fast hook lookups.
  • Decided: Sync script in PreToolUse hook with 24h staleness check — only rescans frontmatter if yml is older than 24 hours.
  • Decided: Hook blocks the edit and tells Claude to write a proposal instead. Claude writes the proposed change to vault/90_inbox/proposals/protected/. User accepts or rejects via skill.
  • Decided: Two protection modes — locked (no create, no edit → proposal) and watch (edit OK, changes logged + notified). No separate files/dirs sections — path with extension = file, without = dir.
  • Decided: locked dirs: vault/00_system/docs/, vault/00_system/extern/, vault/00_system/designs/approved/, vault/_templates/. watch dirs: .claude/hooks/, .claude/agents/, .claude/rules/, .claude/skills/, vault/00_system/designs/drafts/.
  • Decided: watch notifications via all three channels: (1) changelog file vault/00_system/logs/watch-changelog.md, (2) surfaced in /status and daily briefing, (3) terminal message after each edit.
  • Decided: decision-log.md and dashboards/ stay open — Claude updates these as part of regular work.

User Tasks


FR-003: Protected Files System

Summary

Files have two protection modes: locked (changes → proposal for user review) and watch (changes allowed but logged and notified). Unprotected files are freely editable.

Problem / Motivation

Some files (CLAUDE.md, settings.json, system docs) should not be modified without explicit user approval. Other files can be modified but the user wants visibility on changes. Instead of just blocking edits (which stops work), we redirect them to proposals, and for watched files we log and notify.

Proposed Solution

  1. Audit all paths and finalize the protected list
  2. Add protected: true to frontmatter of protected files (source of truth)
  3. Build a sync script that scans frontmatter → generates .claude/protected-files.yml
  4. Build a PreToolUse hook that:
    • Runs sync if yml is older than 24 hours
    • Detects Edit/Write to a protected path
    • For locked paths: blocks all create/edit (path with extension = file, without = dir)
    • Blocks with exit non-zero + message telling Claude to write a proposal instead
    • watch dirs are handled by PostToolUse hook (Phase 4), not blocked
  5. Build a PostToolUse hook for watch paths that:
    • Logs changes to vault/00_system/logs/watch-changelog.md (file, timestamp, summary)
    • Prints a terminal notification after each edit
  6. Surface unreviewed changelog entries in /status skill and daily briefing
  7. Claude writes the proposed change to vault/90_inbox/proposals/protected/
  8. Build an /accept-proposal skill that applies the change and cleans up
  9. Document the system in CLAUDE.md

Phase Overview

PhaseDescriptionStatus
Phase 1Audit & decide final protected + watch listin-progress
Phase 2Frontmatter + sync script + generated yml
Phase 3PreToolUse protect hook (with 24h sync)
Phase 4PostToolUse watch hook (changelog + terminal)
Phase 5Watch in /status + daily briefing
Phase 6Proposal format + /accept-proposal skill
Phase 7CLAUDE.md documentation

Phase 1: Audit & Decide —

Goal: Review all paths and finalize which files need protection.

File / FeatureDetailsOwnerStatus
Review current protected listCheck each path — is protection still needed?opus
Include vault/my-todos.mdUser-only file — Claude must never write to itopus
Include .claude/settings.jsonIf Claude removes the hook, protection breaksopus
Include CLAUDE.mdMaster instructions — changes need user sign-offopus
Identify workflow frictionWhere does protection slow down normal work?mv
Propose final listBinary: protected (→ proposal) or openopus
User approvalUser signs off on final listmv

Phase 2: Frontmatter & Sync —

Goal: Add protected: true frontmatter to protected files and build a sync script that generates the yml cache.

File / FeatureDetailsOwnerStatus
Add protected: true to frontmatterAdd field to all files from the approved listopus
Write sync script (.claude/hooks/sync-protected-list.sh)Scans all vault + config files for protected: true in frontmatter, writes .claude/protected-files.ymlopus
Test sync scriptVerify yml matches frontmatter across repomv

Phase 3: Protect Hook —

Goal: PreToolUse hook that checks yml and redirects edits to proposals.

File / FeatureDetailsOwnerStatus
Write PreToolUse hook (.claude/hooks/protect-files.sh)On Edit/Write: run sync if yml > 24h old, then check target against yml. locked paths: block all create/edit → redirect to proposal. Has extension = file match, no extension = dir match (startswith).opus
Register hook in settings.jsonAdd to PreToolUse arrayopus
Test hook redirects protected editsVerify hook blocks and Claude writes proposal insteadmv

Phase 4: Watch Hook —

Goal: PostToolUse hook that logs and notifies on changes to watched paths.

File / FeatureDetailsOwnerStatus
Write PostToolUse hook (.claude/hooks/watch-notify.sh)After Edit/Write to a watch path: append entry to vault/00_system/logs/watch-changelog.md (file, timestamp, brief summary) and print terminal notificationopus
Register hook in settings.jsonAdd to PostToolUse arrayopus
Create vault/00_system/logs/watch-changelog.mdChangelog file with reviewed/unreviewed markersopus
Test watch loggingEdit a watched file → entry appears in changelog + terminal messagemv

Phase 5: Watch in Status & Briefing —

Goal: Surface unreviewed changelog entries in /status skill and daily briefing.

File / FeatureDetailsOwnerStatus
Update /status skillShow count of unreviewed changelog entries, with summaryopus
Update daily briefingInclude unreviewed watch changesopus
Mark-as-reviewed mechanismUser can mark entries as reviewed (e.g., checkbox or /reviewed command)opus

Phase 6: Proposal Workflow —

Goal: Define proposal format and build accept/reject skill.

File / FeatureDetailsOwnerStatus
Define proposal file formatTarget file, change type (edit/create/delete), proposed content or diffopus
Create vault/90_inbox/proposals/protected/Landing folder for protected file proposalsopus
Build /accept-proposal skillApplies the proposed change to the target file, deletes the proposalopus
Reject workflowUser deletes the proposal file (or /reject-proposal skill)opus
Test full flowEdit protected → proposal created → accept → change appliedmv

Phase 7: Documentation —

Goal: Document the system in CLAUDE.md.

File / FeatureDetailsOwnerStatus
Add Protected Files section to CLAUDE.mdHow it works, all three modes, how to add/remove paths, proposal workflow, watch notificationsopus

Acceptance Criteria:

  • Every protected path has a clear reason for being protected
  • Protected files have protected: true in frontmatter
  • Sync script generates .claude/protected-files.yml from frontmatter
  • PreToolUse hook runs sync if yml > 24h old, then checks target
  • locked dirs block all create/edit operations
  • Blocked edits result in a proposal file
  • Proposal files contain enough info to apply the change (target path, content/diff)
  • /accept-proposal applies the change correctly
  • watch paths: edits succeed but are logged to changelog and shown in terminal
  • /status and daily briefing show unreviewed watch changes
  • User can mark changelog entries as reviewed
  • Unprotected files can still be edited freely
  • Adding protection = add protected: true (or watch: true) to frontmatter (next sync picks it up)

Notes:

  • Previously tracked as two FRs (FR-003 organize + FR-037 enforce), merged 2026-02-27.
  • Key insight: blocking edits stops work; redirecting to proposals keeps momentum while maintaining control.
  • Frontmatter is source of truth; yml is generated cache. 24h staleness check keeps overhead minimal.

Prerequisites / Gap Analysis

Requirements

RequirementDescription
REQ-1.claude/settings.json supports PreToolUse hooks

Current State

ComponentStatusDetails
Protected files list in CLAUDE.mddoneExists but not enforced
PreToolUse hookNot implemented

Gap (What’s missing?)

GapEffortBlocker?
Audit of current listLowNo
Sync scriptLowNo
PreToolUse protect hookMedNo
PostToolUse watch hookMedNo
Watch in /status + briefingLowNo
/accept-proposal skillMedNo

Test

Manual tests

TestExpectedActualLast
Edit a locked file in conversationHook blocks, Claude writes proposalpending-
Run /accept-proposal on a pending proposalChange applied, proposal marked appliedpending-
Reject a proposal (delete file)Target file unchangedpending-
Run /status with unreviewed changelog entriesShows changelog countpending-
Mark changelog entry as reviewed ([x])No longer counted in /statuspending-

AI-verified tests

Run inside git snapshot: git stash -u before, git restore . && git clean -fd && git stash pop after. Tests may freely mutate the repo.

ScenarioExpected behaviorVerification method
Edit a locked file in conversationHook blocks, Claude writes proposalAttempt edit on CLAUDE.md, verify blocked + proposal created
Run /accept-proposal on a pending proposalChange applied, proposal marked appliedCreate test proposal, approve, run skill, verify target updated
Reject a proposal (delete file)Target file unchangedCreate test proposal, delete it, verify target unchanged
/status with unreviewed changelog entriesShows changelog countAdd unchecked entries to watch-changelog.md, run /status, verify count shown
Mark changelog entry as reviewed ([x])No longer counted in /statusCheck entry in changelog, run /status, verify count decreased

E2E tests — tests/test_protected_files_e2e.py

ScenarioAssertion
Edit locked file → blockedexit 2, proposal path in stderr
Edit watched file → allowed + loggedexit 0, changelog entry created
Edit unprotected file → allowed silentlyexit 0, no changelog entry
/accept-proposal applies changeTarget file updated, proposal status → applied

Integration tests — tests/test_protected_files.py::TestIntegration*

ComponentCoverage
protect-files.py subprocessLocked blocked, CLAUDE.md bypass check, watched allowed, unprotected allowed, yml self-protection, settings.json blocked, non-Edit passes
watch-notify.py subprocessWatched logs [WATCH], unprotected silent, locked not watched, daily changelog split (changelog/{today}.md), append to existing day file
sync-protected-list.pyIdempotent, required locked entries present, watch entries present, locked frontmatter consistent, watched frontmatter consistent, no conflicting protection values

Unit tests — tests/test_protected_files.py

ComponentCoverage
protect-files.pyLoad paths, is_file_entry, matches_locked, get_relative_path, self-protection, proposal bypass, staleness, new-file-allowed, main edge cases
watch-notify.pyLoad paths, match paths, changelog append/format/create, daily split (per-day files in changelog/), main edge cases
sync-protected-list.pyLoad yml, parse frontmatter, update frontmatter, remove key, path matching, resolve files, conflict resolution, bidirectional sync, save yml

History

DateEventDetails
2026-02-26CreatedAs FR-018 + FR-027
2026-02-27RenumberedTo FR-003 + FR-037
2026-02-27MergedFR-037 into FR-003
2026-02-28ReformattedAligned to feature-request template
2026-03-16RedesignedProposal-based approach: edits → proposal files → user accept/reject. Frontmatter as source of truth with 24h sync to yml cache. Two dir modes: locked and watch. Watch notifies via changelog, terminal, and /status.
2026-03-19Test gap foundwatch-notify tests still reference single watch-changelog.md but implementation writes daily split files to changelog/{today}.md. Tests need updating.

References

  • FR-011 (Hook Scripts) — protect-files hook is part of this system