code-warden 3.1.1 → 3.3.0

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.
Files changed (41) hide show
  1. package/CONFIGURE.md +39 -39
  2. package/DECISIONS.md +107 -107
  3. package/README.md +199 -137
  4. package/SKILL.md +169 -169
  5. package/bin/code-warden.js +82 -0
  6. package/codewarden.json +14 -14
  7. package/examples/governed-session.md +132 -132
  8. package/install.js +399 -399
  9. package/install.ps1 +32 -32
  10. package/install.sh +33 -33
  11. package/package.json +45 -2
  12. package/references/anti-drift.md +55 -55
  13. package/references/architecture.md +26 -26
  14. package/references/cleanup.md +30 -30
  15. package/references/cognition.md +36 -36
  16. package/references/operations.md +45 -45
  17. package/references/planning-gates.md +83 -83
  18. package/references/research-and-fit.md +51 -51
  19. package/references/safety.md +31 -31
  20. package/templates/ci/github-actions.yml +83 -66
  21. package/tools/auto-detect.js +91 -91
  22. package/tools/auto-targets.js +104 -104
  23. package/tools/auto-windsurf-adapter.js +75 -75
  24. package/tools/get-context.js +50 -50
  25. package/tools/governance-report.js +302 -0
  26. package/tools/hooks/claude/install-hooks.js +112 -112
  27. package/tools/hooks/claude/uninstall-hooks.js +75 -75
  28. package/tools/hooks/claude/warden-lint-hook.js +106 -106
  29. package/tools/hooks/claude/warden-secrets-hook.js +73 -73
  30. package/tools/hooks/codex/install-hooks.js +100 -100
  31. package/tools/hooks/codex/uninstall-hooks.js +53 -53
  32. package/tools/hooks/codex/warden-apply-patch-hook.js +113 -113
  33. package/tools/hooks/codex/warden-bash-hook.js +51 -51
  34. package/tools/lib/config.js +49 -49
  35. package/tools/lib/file-collection.js +72 -72
  36. package/tools/lib/line-count.js +28 -28
  37. package/tools/lib/secret-patterns.js +57 -57
  38. package/tools/tests/fixtures/clean.js +9 -9
  39. package/tools/tests/run-tests.js +210 -210
  40. package/tools/verify-secrets.js +26 -26
  41. package/tools/warden-lint.js +27 -27
@@ -1,106 +1,106 @@
1
- #!/usr/bin/env node
2
- /**
3
- * warden-lint-hook.js
4
- * PreToolUse Claude Code hook: blocks Write/Edit if the resulting file would
5
- * exceed the configured line limit.
6
- *
7
- * Payload (stdin JSON): { tool_name, tool_input: { file_path, content|new_string, ... } }
8
- * On violation: exit 2 + JSON deny response to stdout.
9
- * On pass: exit 0 (no output).
10
- */
11
-
12
- 'use strict';
13
-
14
- const fs = require('fs');
15
- const path = require('path');
16
- const { countLines } = require('../../lib/line-count');
17
- const { loadConfig } = require('../../lib/config');
18
-
19
- // ---------------------------------------------------------------------------
20
- // Config — loaded via shared module; falls back to 400 if missing
21
- // ---------------------------------------------------------------------------
22
-
23
- const { maxFileLength: MAX_LINES } = loadConfig();
24
-
25
- // ---------------------------------------------------------------------------
26
- // Skip list — file types where line counting is meaningless
27
- // ---------------------------------------------------------------------------
28
-
29
- const SKIP_EXTS = new Set([
30
- '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg',
31
- '.woff', '.woff2', '.ttf', '.eot', '.otf',
32
- '.zip', '.tar', '.gz', '.rar', '.7z',
33
- '.pdf', '.doc', '.docx', '.xls', '.xlsx',
34
- '.mp3', '.mp4', '.avi', '.mov', '.wav',
35
- '.map', '.lock',
36
- ]);
37
-
38
- const SKIP_NAMES = new Set(['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']);
39
-
40
- function shouldSkip(filePath) {
41
- if (!filePath) return true;
42
- if (SKIP_NAMES.has(path.basename(filePath))) return true;
43
- return SKIP_EXTS.has(path.extname(filePath).toLowerCase());
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Response helpers
48
- // ---------------------------------------------------------------------------
49
-
50
- function deny(reason) {
51
- process.stdout.write(JSON.stringify({
52
- hookSpecificOutput: {
53
- hookEventName: 'PreToolUse',
54
- permissionDecision: 'deny',
55
- permissionDecisionReason: reason,
56
- },
57
- }));
58
- process.exit(2);
59
- }
60
-
61
- const allow = () => process.exit(0);
62
-
63
- // ---------------------------------------------------------------------------
64
- // Main
65
- // ---------------------------------------------------------------------------
66
-
67
- async function main() {
68
- let payload;
69
- try {
70
- const chunks = [];
71
- for await (const chunk of process.stdin) chunks.push(chunk);
72
- payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
73
- } catch {
74
- allow(); // parse failure is non-blocking
75
- }
76
-
77
- const { tool_name, tool_input = {} } = payload;
78
- const { file_path, content, old_string, new_string, replace_all } = tool_input;
79
-
80
- if (tool_name === 'Write') {
81
- if (shouldSkip(file_path)) allow();
82
- const lines = countLines(content || '');
83
- if (lines > MAX_LINES) {
84
- deny(`[CodeWarden] File length gate: ${path.basename(file_path)} would be ${lines} lines (limit ${MAX_LINES}). Split into modules before writing.`);
85
- }
86
- allow();
87
- }
88
-
89
- if (tool_name === 'Edit') {
90
- if (shouldSkip(file_path)) allow();
91
- if (!fs.existsSync(file_path)) allow(); // new file — Write hook will catch it
92
- const current = fs.readFileSync(file_path, 'utf8');
93
- const patched = replace_all
94
- ? current.split(old_string || '').join(new_string || '')
95
- : current.replace(old_string || '', new_string || '');
96
- const lines = countLines(patched);
97
- if (lines > MAX_LINES) {
98
- deny(`[CodeWarden] File length gate: ${path.basename(file_path)} would be ${lines} lines after edit (limit ${MAX_LINES}). Split into modules before editing.`);
99
- }
100
- allow();
101
- }
102
-
103
- allow(); // unrecognised tool — pass through
104
- }
105
-
106
- main().catch(() => allow());
1
+ #!/usr/bin/env node
2
+ /**
3
+ * warden-lint-hook.js
4
+ * PreToolUse Claude Code hook: blocks Write/Edit if the resulting file would
5
+ * exceed the configured line limit.
6
+ *
7
+ * Payload (stdin JSON): { tool_name, tool_input: { file_path, content|new_string, ... } }
8
+ * On violation: exit 2 + JSON deny response to stdout.
9
+ * On pass: exit 0 (no output).
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { countLines } = require('../../lib/line-count');
17
+ const { loadConfig } = require('../../lib/config');
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Config — loaded via shared module; falls back to 400 if missing
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const { maxFileLength: MAX_LINES } = loadConfig();
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Skip list — file types where line counting is meaningless
27
+ // ---------------------------------------------------------------------------
28
+
29
+ const SKIP_EXTS = new Set([
30
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg',
31
+ '.woff', '.woff2', '.ttf', '.eot', '.otf',
32
+ '.zip', '.tar', '.gz', '.rar', '.7z',
33
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx',
34
+ '.mp3', '.mp4', '.avi', '.mov', '.wav',
35
+ '.map', '.lock',
36
+ ]);
37
+
38
+ const SKIP_NAMES = new Set(['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']);
39
+
40
+ function shouldSkip(filePath) {
41
+ if (!filePath) return true;
42
+ if (SKIP_NAMES.has(path.basename(filePath))) return true;
43
+ return SKIP_EXTS.has(path.extname(filePath).toLowerCase());
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Response helpers
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function deny(reason) {
51
+ process.stdout.write(JSON.stringify({
52
+ hookSpecificOutput: {
53
+ hookEventName: 'PreToolUse',
54
+ permissionDecision: 'deny',
55
+ permissionDecisionReason: reason,
56
+ },
57
+ }));
58
+ process.exit(2);
59
+ }
60
+
61
+ const allow = () => process.exit(0);
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Main
65
+ // ---------------------------------------------------------------------------
66
+
67
+ async function main() {
68
+ let payload;
69
+ try {
70
+ const chunks = [];
71
+ for await (const chunk of process.stdin) chunks.push(chunk);
72
+ payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
73
+ } catch {
74
+ allow(); // parse failure is non-blocking
75
+ }
76
+
77
+ const { tool_name, tool_input = {} } = payload;
78
+ const { file_path, content, old_string, new_string, replace_all } = tool_input;
79
+
80
+ if (tool_name === 'Write') {
81
+ if (shouldSkip(file_path)) allow();
82
+ const lines = countLines(content || '');
83
+ if (lines > MAX_LINES) {
84
+ deny(`[CodeWarden] File length gate: ${path.basename(file_path)} would be ${lines} lines (limit ${MAX_LINES}). Split into modules before writing.`);
85
+ }
86
+ allow();
87
+ }
88
+
89
+ if (tool_name === 'Edit') {
90
+ if (shouldSkip(file_path)) allow();
91
+ if (!fs.existsSync(file_path)) allow(); // new file — Write hook will catch it
92
+ const current = fs.readFileSync(file_path, 'utf8');
93
+ const patched = replace_all
94
+ ? current.split(old_string || '').join(new_string || '')
95
+ : current.replace(old_string || '', new_string || '');
96
+ const lines = countLines(patched);
97
+ if (lines > MAX_LINES) {
98
+ deny(`[CodeWarden] File length gate: ${path.basename(file_path)} would be ${lines} lines after edit (limit ${MAX_LINES}). Split into modules before editing.`);
99
+ }
100
+ allow();
101
+ }
102
+
103
+ allow(); // unrecognised tool — pass through
104
+ }
105
+
106
+ main().catch(() => allow());
@@ -1,73 +1,73 @@
1
- #!/usr/bin/env node
2
- /**
3
- * warden-secrets-hook.js
4
- * PreToolUse Claude Code hook: blocks Write/Edit if content contains
5
- * hardcoded credentials matching zero-trust secret patterns.
6
- *
7
- * Write: scans full content.
8
- * Edit: scans new_string only (old content was already committed).
9
- *
10
- * On violation: exit 2 + JSON deny response to stdout.
11
- * On pass: exit 0 (no output).
12
- */
13
-
14
- 'use strict';
15
-
16
- const path = require('path');
17
- const { scanForSecrets } = require('../../lib/secret-patterns');
18
-
19
- // ---------------------------------------------------------------------------
20
- // Response helpers
21
- // ---------------------------------------------------------------------------
22
-
23
- function deny(reason) {
24
- process.stdout.write(JSON.stringify({
25
- hookSpecificOutput: {
26
- hookEventName: 'PreToolUse',
27
- permissionDecision: 'deny',
28
- permissionDecisionReason: reason,
29
- },
30
- }));
31
- process.exit(2);
32
- }
33
-
34
- const allow = () => process.exit(0);
35
-
36
- // ---------------------------------------------------------------------------
37
- // Main
38
- // ---------------------------------------------------------------------------
39
-
40
- async function main() {
41
- let payload;
42
- try {
43
- const chunks = [];
44
- for await (const chunk of process.stdin) chunks.push(chunk);
45
- payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
46
- } catch {
47
- allow();
48
- }
49
-
50
- const { tool_name, tool_input = {} } = payload;
51
-
52
- if (tool_name === 'Write') {
53
- const hit = scanForSecrets(tool_input.content || '');
54
- if (hit) {
55
- const file = path.basename(tool_input.file_path || 'file');
56
- deny(`[CodeWarden] Hardcoded credential scanner: ${hit.label} detected in ${file}. Use environment variables — no hardcoded credentials.`);
57
- }
58
- allow();
59
- }
60
-
61
- if (tool_name === 'Edit') {
62
- const hit = scanForSecrets(tool_input.new_string || '');
63
- if (hit) {
64
- const file = path.basename(tool_input.file_path || 'file');
65
- deny(`[CodeWarden] Hardcoded credential scanner: ${hit.label} detected in replacement for ${file}. Use environment variables — no hardcoded credentials.`);
66
- }
67
- allow();
68
- }
69
-
70
- allow();
71
- }
72
-
73
- main().catch(() => allow());
1
+ #!/usr/bin/env node
2
+ /**
3
+ * warden-secrets-hook.js
4
+ * PreToolUse Claude Code hook: blocks Write/Edit if content contains
5
+ * hardcoded credentials matching zero-trust secret patterns.
6
+ *
7
+ * Write: scans full content.
8
+ * Edit: scans new_string only (old content was already committed).
9
+ *
10
+ * On violation: exit 2 + JSON deny response to stdout.
11
+ * On pass: exit 0 (no output).
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const path = require('path');
17
+ const { scanForSecrets } = require('../../lib/secret-patterns');
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Response helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function deny(reason) {
24
+ process.stdout.write(JSON.stringify({
25
+ hookSpecificOutput: {
26
+ hookEventName: 'PreToolUse',
27
+ permissionDecision: 'deny',
28
+ permissionDecisionReason: reason,
29
+ },
30
+ }));
31
+ process.exit(2);
32
+ }
33
+
34
+ const allow = () => process.exit(0);
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Main
38
+ // ---------------------------------------------------------------------------
39
+
40
+ async function main() {
41
+ let payload;
42
+ try {
43
+ const chunks = [];
44
+ for await (const chunk of process.stdin) chunks.push(chunk);
45
+ payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
46
+ } catch {
47
+ allow();
48
+ }
49
+
50
+ const { tool_name, tool_input = {} } = payload;
51
+
52
+ if (tool_name === 'Write') {
53
+ const hit = scanForSecrets(tool_input.content || '');
54
+ if (hit) {
55
+ const file = path.basename(tool_input.file_path || 'file');
56
+ deny(`[CodeWarden] Hardcoded credential scanner: ${hit.label} detected in ${file}. Use environment variables — no hardcoded credentials.`);
57
+ }
58
+ allow();
59
+ }
60
+
61
+ if (tool_name === 'Edit') {
62
+ const hit = scanForSecrets(tool_input.new_string || '');
63
+ if (hit) {
64
+ const file = path.basename(tool_input.file_path || 'file');
65
+ deny(`[CodeWarden] Hardcoded credential scanner: ${hit.label} detected in replacement for ${file}. Use environment variables — no hardcoded credentials.`);
66
+ }
67
+ allow();
68
+ }
69
+
70
+ allow();
71
+ }
72
+
73
+ main().catch(() => allow());
@@ -1,100 +1,100 @@
1
- #!/usr/bin/env node
2
- /**
3
- * install-hooks.js — Codex PreToolUse hook installer
4
- *
5
- * Merges code-warden hook entries into ~/.codex/hooks.json.
6
- *
7
- * Codex hooks.json schema:
8
- * { "PreToolUse": [ { "matcher": "...", "description": "...", "command": "...", "args": [...] } ] }
9
- *
10
- * Idempotent: strips existing code-warden entries by description marker,
11
- * then appends current entries. Ensures paths stay current after reinstalls.
12
- *
13
- * Requires the skill to already be installed at skillDir.
14
- */
15
-
16
- 'use strict';
17
-
18
- const fs = require('fs');
19
- const path = require('path');
20
- const os = require('os');
21
-
22
- const MARKER_PREFIX = 'code-warden:';
23
- const HOOKS_PATH = path.join(os.homedir(), '.codex', 'hooks.json');
24
-
25
- // ---------------------------------------------------------------------------
26
- // Hooks file I/O
27
- // ---------------------------------------------------------------------------
28
-
29
- function readHooks() {
30
- if (!fs.existsSync(HOOKS_PATH)) return {};
31
- try {
32
- return JSON.parse(fs.readFileSync(HOOKS_PATH, 'utf8'));
33
- } catch (err) {
34
- throw new Error(`hooks.json exists but could not be parsed (${HOOKS_PATH}): ${err.message}`);
35
- }
36
- }
37
-
38
- function writeHooks(hooks) {
39
- const tmp = `${HOOKS_PATH}.tmp`;
40
- fs.mkdirSync(path.dirname(HOOKS_PATH), { recursive: true });
41
- fs.writeFileSync(tmp, JSON.stringify(hooks, null, 2) + '\n', 'utf8');
42
- fs.renameSync(tmp, HOOKS_PATH);
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // Hook entry helpers
47
- // ---------------------------------------------------------------------------
48
-
49
- function stripCodeWardenHooks(entries) {
50
- return (entries || []).filter(
51
- e => !String(e.description || '').startsWith(MARKER_PREFIX)
52
- );
53
- }
54
-
55
- function buildEntries(skillDir) {
56
- return [
57
- {
58
- matcher: 'apply_patch',
59
- command: 'node',
60
- args: [path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-apply-patch-hook.js')],
61
- description: 'code-warden: apply_patch secrets + size gate',
62
- },
63
- {
64
- matcher: 'Bash',
65
- command: 'node',
66
- args: [path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-bash-hook.js')],
67
- description: 'code-warden: bash secrets gate',
68
- },
69
- ];
70
- }
71
-
72
- // ---------------------------------------------------------------------------
73
- // Install
74
- // ---------------------------------------------------------------------------
75
-
76
- function installHooks(skillDir) {
77
- // Guard: skill must be installed before hooks are written
78
- const required = [
79
- path.join(skillDir, 'SKILL.md'),
80
- path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-apply-patch-hook.js'),
81
- path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-bash-hook.js'),
82
- ];
83
- for (const p of required) {
84
- if (!fs.existsSync(p)) {
85
- console.error('[CodeWarden] Hooks require an installed Codex target.');
86
- console.error(`[CodeWarden] Missing: ${p}`);
87
- console.error('[CodeWarden] Run: node install.js --target=codex --all');
88
- process.exit(1);
89
- }
90
- }
91
-
92
- const hooks = readHooks();
93
- const cleaned = stripCodeWardenHooks(hooks.PreToolUse);
94
- hooks.PreToolUse = [...cleaned, ...buildEntries(skillDir)];
95
-
96
- writeHooks(hooks);
97
- return HOOKS_PATH;
98
- }
99
-
100
- module.exports = { installHooks };
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install-hooks.js — Codex PreToolUse hook installer
4
+ *
5
+ * Merges code-warden hook entries into ~/.codex/hooks.json.
6
+ *
7
+ * Codex hooks.json schema:
8
+ * { "PreToolUse": [ { "matcher": "...", "description": "...", "command": "...", "args": [...] } ] }
9
+ *
10
+ * Idempotent: strips existing code-warden entries by description marker,
11
+ * then appends current entries. Ensures paths stay current after reinstalls.
12
+ *
13
+ * Requires the skill to already be installed at skillDir.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const os = require('os');
21
+
22
+ const MARKER_PREFIX = 'code-warden:';
23
+ const HOOKS_PATH = path.join(os.homedir(), '.codex', 'hooks.json');
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Hooks file I/O
27
+ // ---------------------------------------------------------------------------
28
+
29
+ function readHooks() {
30
+ if (!fs.existsSync(HOOKS_PATH)) return {};
31
+ try {
32
+ return JSON.parse(fs.readFileSync(HOOKS_PATH, 'utf8'));
33
+ } catch (err) {
34
+ throw new Error(`hooks.json exists but could not be parsed (${HOOKS_PATH}): ${err.message}`);
35
+ }
36
+ }
37
+
38
+ function writeHooks(hooks) {
39
+ const tmp = `${HOOKS_PATH}.tmp`;
40
+ fs.mkdirSync(path.dirname(HOOKS_PATH), { recursive: true });
41
+ fs.writeFileSync(tmp, JSON.stringify(hooks, null, 2) + '\n', 'utf8');
42
+ fs.renameSync(tmp, HOOKS_PATH);
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Hook entry helpers
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function stripCodeWardenHooks(entries) {
50
+ return (entries || []).filter(
51
+ e => !String(e.description || '').startsWith(MARKER_PREFIX)
52
+ );
53
+ }
54
+
55
+ function buildEntries(skillDir) {
56
+ return [
57
+ {
58
+ matcher: 'apply_patch',
59
+ command: 'node',
60
+ args: [path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-apply-patch-hook.js')],
61
+ description: 'code-warden: apply_patch secrets + size gate',
62
+ },
63
+ {
64
+ matcher: 'Bash',
65
+ command: 'node',
66
+ args: [path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-bash-hook.js')],
67
+ description: 'code-warden: bash secrets gate',
68
+ },
69
+ ];
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Install
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function installHooks(skillDir) {
77
+ // Guard: skill must be installed before hooks are written
78
+ const required = [
79
+ path.join(skillDir, 'SKILL.md'),
80
+ path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-apply-patch-hook.js'),
81
+ path.join(skillDir, 'tools', 'hooks', 'codex', 'warden-bash-hook.js'),
82
+ ];
83
+ for (const p of required) {
84
+ if (!fs.existsSync(p)) {
85
+ console.error('[CodeWarden] Hooks require an installed Codex target.');
86
+ console.error(`[CodeWarden] Missing: ${p}`);
87
+ console.error('[CodeWarden] Run: node install.js --target=codex --all');
88
+ process.exit(1);
89
+ }
90
+ }
91
+
92
+ const hooks = readHooks();
93
+ const cleaned = stripCodeWardenHooks(hooks.PreToolUse);
94
+ hooks.PreToolUse = [...cleaned, ...buildEntries(skillDir)];
95
+
96
+ writeHooks(hooks);
97
+ return HOOKS_PATH;
98
+ }
99
+
100
+ module.exports = { installHooks };
@@ -1,53 +1,53 @@
1
- #!/usr/bin/env node
2
- /**
3
- * uninstall-hooks.js — Codex PreToolUse hook remover
4
- *
5
- * Removes all code-warden entries from ~/.codex/hooks.json.
6
- * Cleans up empty PreToolUse array and empty top-level object if nothing remains.
7
- */
8
-
9
- 'use strict';
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const os = require('os');
14
-
15
- const MARKER_PREFIX = 'code-warden:';
16
- const HOOKS_PATH = path.join(os.homedir(), '.codex', 'hooks.json');
17
-
18
- function uninstallHooks() {
19
- if (!fs.existsSync(HOOKS_PATH)) {
20
- console.log('[CodeWarden] ~/.codex/hooks.json not found — nothing to remove.');
21
- return;
22
- }
23
-
24
- let hooks;
25
- try {
26
- hooks = JSON.parse(fs.readFileSync(HOOKS_PATH, 'utf8'));
27
- } catch (err) {
28
- console.error(`[CodeWarden] hooks.json could not be parsed: ${err.message}`);
29
- process.exit(1);
30
- }
31
-
32
- const before = (hooks.PreToolUse || []).length;
33
- hooks.PreToolUse = (hooks.PreToolUse || []).filter(
34
- e => !String(e.description || '').startsWith(MARKER_PREFIX)
35
- );
36
- const removed = before - hooks.PreToolUse.length;
37
-
38
- // Clean empty array
39
- if (hooks.PreToolUse.length === 0) delete hooks.PreToolUse;
40
-
41
- // Write back (or remove file if completely empty)
42
- if (Object.keys(hooks).length === 0) {
43
- fs.unlinkSync(HOOKS_PATH);
44
- console.log(`[CodeWarden] Removed ${removed} hook(s) — hooks.json is now empty, file removed.`);
45
- } else {
46
- const tmp = `${HOOKS_PATH}.tmp`;
47
- fs.writeFileSync(tmp, JSON.stringify(hooks, null, 2) + '\n', 'utf8');
48
- fs.renameSync(tmp, HOOKS_PATH);
49
- console.log(`[CodeWarden] Removed ${removed} code-warden hook(s) from ~/.codex/hooks.json.`);
50
- }
51
- }
52
-
53
- module.exports = { uninstallHooks };
1
+ #!/usr/bin/env node
2
+ /**
3
+ * uninstall-hooks.js — Codex PreToolUse hook remover
4
+ *
5
+ * Removes all code-warden entries from ~/.codex/hooks.json.
6
+ * Cleans up empty PreToolUse array and empty top-level object if nothing remains.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+
15
+ const MARKER_PREFIX = 'code-warden:';
16
+ const HOOKS_PATH = path.join(os.homedir(), '.codex', 'hooks.json');
17
+
18
+ function uninstallHooks() {
19
+ if (!fs.existsSync(HOOKS_PATH)) {
20
+ console.log('[CodeWarden] ~/.codex/hooks.json not found — nothing to remove.');
21
+ return;
22
+ }
23
+
24
+ let hooks;
25
+ try {
26
+ hooks = JSON.parse(fs.readFileSync(HOOKS_PATH, 'utf8'));
27
+ } catch (err) {
28
+ console.error(`[CodeWarden] hooks.json could not be parsed: ${err.message}`);
29
+ process.exit(1);
30
+ }
31
+
32
+ const before = (hooks.PreToolUse || []).length;
33
+ hooks.PreToolUse = (hooks.PreToolUse || []).filter(
34
+ e => !String(e.description || '').startsWith(MARKER_PREFIX)
35
+ );
36
+ const removed = before - hooks.PreToolUse.length;
37
+
38
+ // Clean empty array
39
+ if (hooks.PreToolUse.length === 0) delete hooks.PreToolUse;
40
+
41
+ // Write back (or remove file if completely empty)
42
+ if (Object.keys(hooks).length === 0) {
43
+ fs.unlinkSync(HOOKS_PATH);
44
+ console.log(`[CodeWarden] Removed ${removed} hook(s) — hooks.json is now empty, file removed.`);
45
+ } else {
46
+ const tmp = `${HOOKS_PATH}.tmp`;
47
+ fs.writeFileSync(tmp, JSON.stringify(hooks, null, 2) + '\n', 'utf8');
48
+ fs.renameSync(tmp, HOOKS_PATH);
49
+ console.log(`[CodeWarden] Removed ${removed} code-warden hook(s) from ~/.codex/hooks.json.`);
50
+ }
51
+ }
52
+
53
+ module.exports = { uninstallHooks };