code-warden 3.3.0 → 3.3.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/CONFIGURE.md +39 -39
- package/DECISIONS.md +107 -107
- package/README.md +6 -0
- package/SKILL.md +169 -169
- package/bin/code-warden.js +82 -82
- package/codewarden.json +14 -14
- package/examples/governed-session.md +132 -132
- package/install.js +399 -399
- package/install.ps1 +32 -32
- package/install.sh +33 -33
- package/package.json +62 -62
- package/references/anti-drift.md +55 -55
- package/references/architecture.md +26 -26
- package/references/cleanup.md +30 -30
- package/references/cognition.md +36 -36
- package/references/operations.md +45 -45
- package/references/planning-gates.md +83 -83
- package/references/research-and-fit.md +51 -51
- package/references/safety.md +31 -31
- package/tools/auto-detect.js +91 -91
- package/tools/auto-targets.js +104 -104
- package/tools/auto-windsurf-adapter.js +75 -75
- package/tools/get-context.js +50 -50
- package/tools/governance-report.js +302 -302
- package/tools/hooks/claude/install-hooks.js +112 -112
- package/tools/hooks/claude/uninstall-hooks.js +75 -75
- package/tools/hooks/claude/warden-lint-hook.js +106 -106
- package/tools/hooks/claude/warden-secrets-hook.js +73 -73
- package/tools/hooks/codex/install-hooks.js +100 -100
- package/tools/hooks/codex/uninstall-hooks.js +53 -53
- package/tools/hooks/codex/warden-apply-patch-hook.js +113 -113
- package/tools/hooks/codex/warden-bash-hook.js +51 -51
- package/tools/lib/config.js +49 -49
- package/tools/lib/file-collection.js +5 -2
- package/tools/lib/line-count.js +28 -28
- package/tools/lib/secret-patterns.js +57 -57
- package/tools/tests/fixtures/clean.js +9 -9
- package/tools/tests/run-tests.js +210 -210
- package/tools/verify-secrets.js +26 -26
- package/tools/warden-lint.js +27 -27
|
@@ -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 };
|