@wipcomputer/wip-file-guard 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1 (2026-02-21)
4
+
5
+ Align description, add SKILL.md, add badges, agent-driven install, REFERENCE.md
6
+
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ ###### WIP Computer
2
+ # File Guard
3
+
4
+ PreToolUse hook that blocks destructive edits to protected files. When an AI agent tries to overwrite or strip content from files like CLAUDE.md, SHARED-CONTEXT.md, or SOUL.md... it gets blocked with a clear explanation of what went wrong.
5
+
6
+ ## The Problem
7
+
8
+ AI agents replace content instead of extending it. After context compaction, behavioral rules like "don't delete things" vanish. The agent rewrites your CLAUDE.md, strips 30 lines from SHARED-CONTEXT.md, or replaces your SOUL.md with a shorter version. Every time.
9
+
10
+ File Guard is a technical guardrail. It doesn't ask the agent to be careful. It blocks the operation before it happens.
11
+
12
+ ## How It Works
13
+
14
+ Two rules:
15
+
16
+ 1. **Write is blocked** on protected files. Always. Use Edit instead.
17
+ 2. **Edit is blocked** when it removes more than 2 net lines from a protected file.
18
+
19
+ The agent gets a deny message explaining what happened and telling it to re-read the file and add content instead of replacing it.
20
+
21
+ ### Protected Files
22
+
23
+ | File | What it protects |
24
+ |------|-----------------|
25
+ | `CLAUDE.md` | Project instructions, boot sequence, system docs |
26
+ | `SHARED-CONTEXT.md` | Cross-agent shared state |
27
+ | `SOUL.md` | Agent identity |
28
+ | `IDENTITY.md` | Agent identity (alternate format) |
29
+ | `CONTEXT.md` | Current state snapshot |
30
+ | `TOOLS.md` | Tool and workflow rules |
31
+ | `MEMORY.md` | Persistent memory and preferences |
32
+
33
+ ## Install
34
+
35
+ Open your AI coding tool and say:
36
+
37
+ ```
38
+ Read the README at github.com/wipcomputer/wip-file-guard.
39
+ Then explain to me:
40
+ 1. What is this tool?
41
+ 2. What does it do?
42
+ 3. What would it change or fix in our current system?
43
+
44
+ Then ask me:
45
+ - Do you have more questions?
46
+ - Do you want to integrate it into our system?
47
+ - Do you want to clone it (use as-is) or fork it (so you can contribute back if you find bugs)?
48
+ ```
49
+
50
+ Your agent will read the repo, explain the tool, and walk you through integration interactively.
51
+
52
+ Also see **[wip-release](https://github.com/wipcomputer/wip-release)** ... one-command release pipeline for agent-native software.
53
+
54
+ See [REFERENCE.md](REFERENCE.md) for manual install instructions (Claude Code, OpenClaw, CLI).
55
+
56
+ ## Four Interfaces
57
+
58
+ One core, four interfaces into the same guard logic.
59
+
60
+ | Interface | File | What it does |
61
+ |-----------|------|-------------|
62
+ | **Core** | `guard.mjs` | Pure guard logic. Reads stdin JSON, decides allow/deny. |
63
+ | **Claude Code** | `guard.mjs` (PreToolUse hook) | Hooks into CC's PreToolUse event. Blocks before the edit happens. |
64
+ | **OpenClaw** | `openclaw.plugin.json` | Lifecycle hook for OpenClaw agents. Same rules, different runtime. |
65
+ | **CLI** | `guard.mjs --list`, `test.sh` | Testing and inspection from the command line. |
66
+
67
+ See [REFERENCE.md](REFERENCE.md) for customization (adding protected files, changing thresholds).
68
+
69
+ ## Tests
70
+
71
+ ```bash
72
+ bash test.sh
73
+ ```
74
+
75
+ ```
76
+ wip-file-guard tests
77
+ ===================
78
+
79
+ PASS: Block Write to CLAUDE.md
80
+ PASS: Block Write to SHARED-CONTEXT.md
81
+ PASS: Allow Write to random file
82
+ PASS: Block Edit removing 5 lines from CLAUDE.md
83
+ PASS: Allow Edit adding lines to CLAUDE.md
84
+ PASS: Allow Edit on non-protected file (even removing lines)
85
+ PASS: Allow Edit with small removal (2 lines)
86
+ PASS: Block Edit with 4 line removal from SOUL.md
87
+ PASS: Block Write to IDENTITY.md
88
+ PASS: Block Write to TOOLS.md
89
+
90
+ Results: 10 passed, 0 failed
91
+ ```
92
+
93
+ ## Why This Exists
94
+
95
+ Context compaction erases behavioral rules. An agent that was told "never delete content from CLAUDE.md" forgets that instruction after compaction. It then proceeds to replace 50 lines with 10, confident it's improving the file.
96
+
97
+ This happened five times in one session. The fix isn't better prompting. It's a hook that blocks the operation before it executes. Behavioral rules degrade. Technical guards don't.
98
+
99
+ ---
100
+
101
+ ## License
102
+
103
+ MIT
104
+
105
+ Built by Parker Todd Brooks, with Claude Code and Lēsa (OpenClaw).
package/REFERENCE.md ADDED
@@ -0,0 +1,79 @@
1
+ ###### WIP Computer
2
+ # wip-file-guard ... Reference
3
+
4
+ Manual install instructions, CLI usage, and customization.
5
+
6
+ ## Claude Code
7
+
8
+ Add to `~/.claude/settings.json`:
9
+
10
+ ```json
11
+ {
12
+ "hooks": {
13
+ "PreToolUse": [
14
+ {
15
+ "matcher": "Edit|Write",
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "node \"/path/to/wip-file-guard/guard.mjs\"",
20
+ "timeout": 5
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ Replace `/path/to/wip-file-guard/` with where you cloned the repo.
30
+
31
+ ## OpenClaw
32
+
33
+ Add to your OpenClaw installation's `extensions/` directory:
34
+
35
+ ```bash
36
+ cp -r wip-file-guard ~/.openclaw/extensions/wip-file-guard
37
+ ```
38
+
39
+ The `openclaw.plugin.json` registers a `before_tool_use` lifecycle hook that applies the same rules.
40
+
41
+ ## CLI
42
+
43
+ ```bash
44
+ # List protected files
45
+ node guard.mjs --list
46
+
47
+ # Test the guard with a simulated input
48
+ echo '{"tool_name":"Write","tool_input":{"file_path":"/foo/CLAUDE.md"}}' | node guard.mjs
49
+
50
+ # Run the test suite
51
+ bash test.sh
52
+ ```
53
+
54
+ ## Customization
55
+
56
+ ### Adding Protected Files
57
+
58
+ Edit the `PROTECTED` set in `guard.mjs`:
59
+
60
+ ```javascript
61
+ const PROTECTED = new Set([
62
+ 'CLAUDE.md',
63
+ 'SHARED-CONTEXT.md',
64
+ 'SOUL.md',
65
+ 'IDENTITY.md',
66
+ 'CONTEXT.md',
67
+ 'TOOLS.md',
68
+ 'MEMORY.md',
69
+ 'YOUR-FILE-HERE.md', // add yours
70
+ ]);
71
+ ```
72
+
73
+ ### Changing the Line Threshold
74
+
75
+ The default blocks edits that remove more than 2 net lines. Change the threshold in the Edit handler:
76
+
77
+ ```javascript
78
+ if (removed > 2) { // change 2 to your threshold
79
+ ```
package/SKILL.md ADDED
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: WIP.file-guard
3
+ version: 1.0.1
4
+ description: Hook that blocks destructive edits to protected identity files. For Claude Code CLI and OpenClaw.
5
+ homepage: https://github.com/wipcomputer/wip-file-guard
6
+ metadata:
7
+ category: dev-tools
8
+ capabilities:
9
+ - file-protection
10
+ - edit-blocking
11
+ - identity-guard
12
+ dependencies: []
13
+ interface: Claude Code Hook
14
+ requires:
15
+ binaries: [node]
16
+ openclaw:
17
+ emoji: "🛡️"
18
+ install:
19
+ env: []
20
+ author:
21
+ name: Parker Todd Brooks
22
+ ---
23
+
24
+ # wip-file-guard
25
+
26
+ Hook that blocks destructive edits to protected identity files. For Claude Code CLI and OpenClaw.
27
+
28
+ ## When to Use This Skill
29
+
30
+ **Use wip-file-guard for:**
31
+ - Protecting CLAUDE.md, SOUL.md, IDENTITY.md, MEMORY.md, and other identity files from being overwritten
32
+ - Blocking AI agents from replacing file content instead of extending it
33
+ - Surviving context compaction (behavioral rules get erased, but hooks don't)
34
+
35
+ **This is a technical guardrail, not a prompt.** It blocks the operation before it happens.
36
+
37
+ ### Do NOT Use For
38
+
39
+ - Protecting binary files or images
40
+ - Blocking all edits (it allows small edits, only blocks destructive ones)
41
+ - Repos without identity files
42
+
43
+ ## How It Works
44
+
45
+ Two rules:
46
+
47
+ 1. **Write is blocked** on protected files. Always. Use Edit instead.
48
+ 2. **Edit is blocked** when it removes more than 2 net lines from a protected file.
49
+
50
+ ### Protected Files
51
+
52
+ CLAUDE.md, SHARED-CONTEXT.md, SOUL.md, IDENTITY.md, CONTEXT.md, TOOLS.md, MEMORY.md
53
+
54
+ ### Protected Patterns
55
+
56
+ Any file matching: memory, memories, journal, diary, daily log
57
+
58
+ ## API Reference
59
+
60
+ ### CLI
61
+
62
+ ```bash
63
+ node guard.mjs --list # list protected files
64
+ bash test.sh # run test suite
65
+ ```
66
+
67
+ ### Claude Code Hook
68
+
69
+ Add to `~/.claude/settings.json`:
70
+
71
+ ```json
72
+ {
73
+ "hooks": {
74
+ "PreToolUse": [
75
+ {
76
+ "matcher": "Edit|Write",
77
+ "hooks": [
78
+ {
79
+ "type": "command",
80
+ "command": "node \"/path/to/wip-file-guard/guard.mjs\"",
81
+ "timeout": 5
82
+ }
83
+ ]
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## Troubleshooting
91
+
92
+ ### Agent keeps trying to Write
93
+
94
+ The deny message tells the agent to re-read the file and use Edit instead. If the agent ignores it, it's likely post-compaction and has lost context. The hook will keep blocking.
95
+
96
+ ### Edit blocked unexpectedly
97
+
98
+ Check the net line removal. Edits that remove more than 2 lines from a protected file are blocked. Small edits (adding or replacing 1-2 lines) are allowed.
package/guard.mjs ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ // cc-file-guard/guard.mjs
3
+ // PreToolUse hook for Claude Code.
4
+ // Blocks destructive edits to protected files.
5
+ // - Blocks Write tool on protected files entirely
6
+ // - Blocks Edit when net line removal > 2 lines
7
+
8
+ import { basename } from 'node:path';
9
+ import { existsSync } from 'node:fs';
10
+
11
+ // Exact basename matches
12
+ export const PROTECTED = new Set([
13
+ 'CLAUDE.md',
14
+ 'SHARED-CONTEXT.md',
15
+ 'SOUL.md',
16
+ 'IDENTITY.md',
17
+ 'CONTEXT.md',
18
+ 'TOOLS.md',
19
+ 'MEMORY.md',
20
+ ]);
21
+
22
+ // Pattern matches (case-insensitive, checked against full path and basename)
23
+ export const PROTECTED_PATTERNS = [
24
+ /memory/i,
25
+ /memories/i,
26
+ /journal/i,
27
+ /diary/i,
28
+ /daily.*log/i,
29
+ ];
30
+
31
+ function isProtected(filePath) {
32
+ const name = basename(filePath);
33
+ if (PROTECTED.has(name)) return name;
34
+ for (const pattern of PROTECTED_PATTERNS) {
35
+ if (pattern.test(filePath)) return name + ` (matched pattern: ${pattern})`;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function deny(reason) {
41
+ const output = {
42
+ hookSpecificOutput: {
43
+ hookEventName: 'PreToolUse',
44
+ permissionDecision: 'deny',
45
+ permissionDecisionReason: reason,
46
+ },
47
+ };
48
+ process.stdout.write(JSON.stringify(output));
49
+ }
50
+
51
+ function countLines(str) {
52
+ if (!str) return 0;
53
+ return str.split('\n').length;
54
+ }
55
+
56
+ // CLI mode: node guard.mjs --list
57
+ if (process.argv.includes('--list')) {
58
+ console.log('Protected files (exact):');
59
+ for (const f of PROTECTED) console.log(` ${f}`);
60
+ console.log('Protected patterns:');
61
+ for (const p of PROTECTED_PATTERNS) console.log(` ${p}`);
62
+ process.exit(0);
63
+ }
64
+
65
+ async function main() {
66
+ let raw = '';
67
+ for await (const chunk of process.stdin) {
68
+ raw += chunk;
69
+ }
70
+
71
+ let input;
72
+ try {
73
+ input = JSON.parse(raw);
74
+ } catch {
75
+ // Can't parse input, allow by default
76
+ process.exit(0);
77
+ }
78
+
79
+ const toolName = input.tool_name || '';
80
+ const toolInput = input.tool_input || {};
81
+ const filePath = toolInput.file_path || toolInput.filePath || '';
82
+ const fileName = basename(filePath);
83
+
84
+ // Only check protected files
85
+ const match = isProtected(filePath);
86
+ if (!match) {
87
+ process.exit(0);
88
+ }
89
+
90
+ // Block Write on protected files
91
+ // Exact matches: always block Write (use Edit instead)
92
+ // Pattern matches: only block if file already exists (allow creating new files)
93
+ if (toolName === 'Write') {
94
+ const isExactMatch = PROTECTED.has(fileName);
95
+ if (isExactMatch || existsSync(filePath)) {
96
+ deny(`BLOCKED: Write tool on ${match} is not allowed. Use Edit to make specific changes. Never overwrite protected files.`);
97
+ process.exit(0);
98
+ }
99
+ // Pattern match but file doesn't exist yet — allow creation
100
+ process.exit(0);
101
+ }
102
+
103
+ // For Edit, check line removal AND large replacements
104
+ if (toolName === 'Edit') {
105
+ const oldString = toolInput.old_string || '';
106
+ const newString = toolInput.new_string || '';
107
+ const oldLines = countLines(oldString);
108
+ const newLines = countLines(newString);
109
+ const removed = oldLines - newLines;
110
+
111
+ // Block net removal of more than 2 lines
112
+ if (removed > 2) {
113
+ deny(`BLOCKED: You are removing ${removed} lines from ${match} (old: ${oldLines} lines, new: ${newLines} lines). Re-read the file and add content instead of replacing it.`);
114
+ process.exit(0);
115
+ }
116
+
117
+ // Block large replacements (swapping big chunks even if line count is similar)
118
+ if (oldLines > 4 && oldString !== newString) {
119
+ deny(`BLOCKED: You are replacing ${oldLines} lines in ${match}. Edit smaller sections or append new content instead of replacing existing content.`);
120
+ process.exit(0);
121
+ }
122
+ }
123
+
124
+ // Allow
125
+ process.exit(0);
126
+ }
127
+
128
+ main().catch(() => process.exit(0));
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "wip-file-guard",
3
+ "version": "1.0.0",
4
+ "description": "Blocks destructive edits to protected files (CLAUDE.md, SHARED-CONTEXT.md, SOUL.md, etc.)",
5
+ "lifecycle": {
6
+ "before_tool_use": "./guard.mjs"
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@wipcomputer/wip-file-guard",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "description": "Hook that blocks destructive edits to protected identity files. For Claude Code CLI and OpenClaw.",
6
+ "main": "guard.mjs",
7
+ "bin": {
8
+ "wip-file-guard": "./guard.mjs"
9
+ },
10
+ "scripts": {
11
+ "test": "bash test.sh"
12
+ },
13
+ "keywords": [
14
+ "claude-code",
15
+ "openclaw",
16
+ "hook",
17
+ "file-guard",
18
+ "ai-safety",
19
+ "pretooluse"
20
+ ],
21
+ "author": "Parker Todd Brooks",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/wipcomputer/wip-file-guard.git"
26
+ }
27
+ }
package/test.sh ADDED
@@ -0,0 +1,119 @@
1
+ #!/bin/bash
2
+ # test.sh - Test wip-file-guard hook
3
+ # Run: bash test.sh
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ GUARD="$SCRIPT_DIR/guard.mjs"
7
+ PASS=0
8
+ FAIL=0
9
+
10
+ check() {
11
+ local desc="$1"
12
+ local input="$2"
13
+ local expect="$3" # "block" or "allow"
14
+
15
+ local output
16
+ output=$(echo "$input" | node "$GUARD" 2>/dev/null)
17
+ local code=$?
18
+
19
+ if [ "$expect" = "block" ]; then
20
+ if echo "$output" | grep -q "deny"; then
21
+ echo "PASS: $desc"
22
+ ((PASS++))
23
+ else
24
+ echo "FAIL: $desc (expected block, got allow)"
25
+ ((FAIL++))
26
+ fi
27
+ else
28
+ if [ -z "$output" ] && [ $code -eq 0 ]; then
29
+ echo "PASS: $desc"
30
+ ((PASS++))
31
+ else
32
+ echo "FAIL: $desc (expected allow, got: $output)"
33
+ ((FAIL++))
34
+ fi
35
+ fi
36
+ }
37
+
38
+ echo "wip-file-guard tests"
39
+ echo "==================="
40
+ echo ""
41
+
42
+ # Write tests
43
+ check "Block Write to CLAUDE.md" \
44
+ '{"tool_name":"Write","tool_input":{"file_path":"/foo/CLAUDE.md","content":"new"}}' \
45
+ "block"
46
+
47
+ check "Block Write to SHARED-CONTEXT.md" \
48
+ '{"tool_name":"Write","tool_input":{"file_path":"/bar/workspace/SHARED-CONTEXT.md","content":"new"}}' \
49
+ "block"
50
+
51
+ check "Allow Write to random file" \
52
+ '{"tool_name":"Write","tool_input":{"file_path":"/foo/bar.js","content":"new"}}' \
53
+ "allow"
54
+
55
+ # Edit tests - line removal
56
+ check "Block Edit removing 5 lines from CLAUDE.md" \
57
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/CLAUDE.md","old_string":"a\nb\nc\nd\ne\nf","new_string":"replaced"}}' \
58
+ "block"
59
+
60
+ check "Allow Edit adding lines to CLAUDE.md" \
61
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/CLAUDE.md","old_string":"a","new_string":"a\nb\nc"}}' \
62
+ "allow"
63
+
64
+ check "Allow Edit on non-protected file (even removing lines)" \
65
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/bar.js","old_string":"a\nb\nc\nd\ne","new_string":"x"}}' \
66
+ "allow"
67
+
68
+ check "Allow Edit with small removal (2 lines)" \
69
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/SOUL.md","old_string":"a\nb\nc","new_string":"x"}}' \
70
+ "allow"
71
+
72
+ check "Block Edit with 4 line removal from SOUL.md" \
73
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/SOUL.md","old_string":"a\nb\nc\nd\ne\nf","new_string":"x\ny"}}' \
74
+ "block"
75
+
76
+ # Protected file coverage
77
+ check "Block Write to IDENTITY.md" \
78
+ '{"tool_name":"Write","tool_input":{"file_path":"/any/path/IDENTITY.md","content":"new"}}' \
79
+ "block"
80
+
81
+ check "Block Write to TOOLS.md" \
82
+ '{"tool_name":"Write","tool_input":{"file_path":"/any/path/TOOLS.md","content":"new"}}' \
83
+ "block"
84
+
85
+ # Large replacement (same line count, different content)
86
+ check "Block Edit replacing 8 lines with 8 different lines in SHARED-CONTEXT.md" \
87
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/SHARED-CONTEXT.md","old_string":"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8","new_string":"new1\nnew2\nnew3\nnew4\nnew5\nnew6\nnew7\nnew8"}}' \
88
+ "block"
89
+
90
+ check "Allow Edit replacing 3 lines in CLAUDE.md" \
91
+ '{"tool_name":"Edit","tool_input":{"file_path":"/foo/CLAUDE.md","old_string":"a\nb\nc","new_string":"x\ny\nz"}}' \
92
+ "allow"
93
+
94
+ # Pattern matching - Write
95
+ # Pattern-matched files: allow Write for NEW files, block for existing files
96
+ check "Allow Write to NEW file in memory/ directory (file doesn't exist)" \
97
+ '{"tool_name":"Write","tool_input":{"file_path":"/nonexistent/memory/2099-01-01.md","content":"new"}}' \
98
+ "allow"
99
+
100
+ check "Allow Write to NEW journal file (file doesn't exist)" \
101
+ '{"tool_name":"Write","tool_input":{"file_path":"/nonexistent/journals/new-entry.md","content":"new"}}' \
102
+ "allow"
103
+
104
+ # Pattern matching - Edit (existing files still protected from destructive edits)
105
+ check "Block Edit removing lines from daily log" \
106
+ '{"tool_name":"Edit","tool_input":{"file_path":"/memory/daily/2026-02-19.md","old_string":"a\nb\nc\nd\ne","new_string":"x"}}' \
107
+ "block"
108
+
109
+ # Block Write to pattern-matched file that EXISTS on disk
110
+ check "Block Write to existing test.sh (matched pattern: memory in path)" \
111
+ "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$SCRIPT_DIR/test.sh\",\"content\":\"new\"}}" \
112
+ "allow"
113
+
114
+ check "Allow Write to unrelated file with no pattern match" \
115
+ '{"tool_name":"Write","tool_input":{"file_path":"/src/utils/helper.js","content":"new"}}' \
116
+ "allow"
117
+
118
+ echo ""
119
+ echo "Results: $PASS passed, $FAIL failed"