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.
- package/CONFIGURE.md +39 -39
- package/DECISIONS.md +107 -107
- package/README.md +199 -137
- package/SKILL.md +169 -169
- package/bin/code-warden.js +82 -0
- 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 +45 -2
- 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/templates/ci/github-actions.yml +83 -66
- 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 -0
- 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 +72 -72
- 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,113 +1,113 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* warden-apply-patch-hook.js — Codex PreToolUse hook
|
|
4
|
-
*
|
|
5
|
-
* Fires on apply_patch tool calls. Scans added lines for hardcoded credentials
|
|
6
|
-
* and estimates resulting file size where the target path can be extracted.
|
|
7
|
-
*
|
|
8
|
-
* Codex hook payload (stdin, JSON):
|
|
9
|
-
* { tool: "apply_patch", toolInput: { patch: "<patch text>" } }
|
|
10
|
-
*
|
|
11
|
-
* Exit codes:
|
|
12
|
-
* 0 — proceed
|
|
13
|
-
* 2 — block (writes JSON deny to stdout)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
const fs = require('fs');
|
|
19
|
-
const path = require('path');
|
|
20
|
-
const { scanForSecrets } = require('../../lib/secret-patterns');
|
|
21
|
-
const { countLines } = require('../../lib/line-count');
|
|
22
|
-
const { loadConfig } = require('../../lib/config');
|
|
23
|
-
|
|
24
|
-
const { maxFileLength: maxLines } = loadConfig();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function deny(reason) {
|
|
28
|
-
process.stdout.write(JSON.stringify({ deny: true, message: `[CodeWarden] ${reason}` }) + '\n');
|
|
29
|
-
process.exit(2);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Patch parsing helpers
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Extract lines added by the patch (lines starting with '+', not '+++').
|
|
38
|
-
*/
|
|
39
|
-
function extractAddedLines(patch) {
|
|
40
|
-
return patch.split('\n')
|
|
41
|
-
.filter(l => l.startsWith('+') && !l.startsWith('+++'))
|
|
42
|
-
.map(l => l.slice(1));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Extract target file path from patch header (*** path or +++ path or --- path).
|
|
47
|
-
* Returns null if not detectable.
|
|
48
|
-
*/
|
|
49
|
-
function extractTargetPath(patch) {
|
|
50
|
-
for (const line of patch.split('\n')) {
|
|
51
|
-
const m = line.match(/^\+{3}\s+(.+?)(\s+\d{4}-\d{2}-\d{2}.*)?$/) ||
|
|
52
|
-
line.match(/^\*{3}\s+(.+?)(\s+\d{4}-\d{2}-\d{2}.*)?$/);
|
|
53
|
-
if (m) {
|
|
54
|
-
const p = m[1].trim();
|
|
55
|
-
if (p !== '/dev/null') return p;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Estimate resulting line count: base file lines + added lines - removed lines.
|
|
63
|
-
* Returns null if file not readable.
|
|
64
|
-
*/
|
|
65
|
-
function estimateResultLines(patch, targetPath) {
|
|
66
|
-
let baseLines = 0;
|
|
67
|
-
if (targetPath && fs.existsSync(targetPath)) {
|
|
68
|
-
try {
|
|
69
|
-
baseLines = countLines(fs.readFileSync(targetPath, 'utf8'));
|
|
70
|
-
} catch { return null; }
|
|
71
|
-
} else if (!targetPath) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
// Count added and removed lines
|
|
75
|
-
let added = 0;
|
|
76
|
-
let removed = 0;
|
|
77
|
-
for (const line of patch.split('\n')) {
|
|
78
|
-
if (line.startsWith('+') && !line.startsWith('+++')) added++;
|
|
79
|
-
else if (line.startsWith('-') && !line.startsWith('---')) removed++;
|
|
80
|
-
}
|
|
81
|
-
return baseLines + added - removed;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Main
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
let raw = '';
|
|
89
|
-
process.stdin.setEncoding('utf8');
|
|
90
|
-
process.stdin.on('data', chunk => { raw += chunk; });
|
|
91
|
-
process.stdin.on('end', () => {
|
|
92
|
-
let payload;
|
|
93
|
-
try { payload = JSON.parse(raw); } catch { process.exit(0); }
|
|
94
|
-
|
|
95
|
-
const input = payload.toolInput || payload.tool_input || {};
|
|
96
|
-
const patch = String(input.patch || '');
|
|
97
|
-
if (!patch) process.exit(0);
|
|
98
|
-
|
|
99
|
-
// --- Secrets check on added lines ---
|
|
100
|
-
const added = extractAddedLines(patch);
|
|
101
|
-
const addedText = added.join('\n');
|
|
102
|
-
const hit = scanForSecrets(addedText);
|
|
103
|
-
if (hit) deny(`Blocked apply_patch — hardcoded credential detected (${hit.label}). Remove before patching.`);
|
|
104
|
-
|
|
105
|
-
// --- File length check ---
|
|
106
|
-
const targetPath = extractTargetPath(patch);
|
|
107
|
-
const estimated = estimateResultLines(patch, targetPath);
|
|
108
|
-
if (estimated !== null && estimated > maxLines) {
|
|
109
|
-
deny(`Blocked apply_patch — resulting file would be ~${estimated} lines (limit ${maxLines}). Break it up first.`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
process.exit(0);
|
|
113
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* warden-apply-patch-hook.js — Codex PreToolUse hook
|
|
4
|
+
*
|
|
5
|
+
* Fires on apply_patch tool calls. Scans added lines for hardcoded credentials
|
|
6
|
+
* and estimates resulting file size where the target path can be extracted.
|
|
7
|
+
*
|
|
8
|
+
* Codex hook payload (stdin, JSON):
|
|
9
|
+
* { tool: "apply_patch", toolInput: { patch: "<patch text>" } }
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 — proceed
|
|
13
|
+
* 2 — block (writes JSON deny to stdout)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { scanForSecrets } = require('../../lib/secret-patterns');
|
|
21
|
+
const { countLines } = require('../../lib/line-count');
|
|
22
|
+
const { loadConfig } = require('../../lib/config');
|
|
23
|
+
|
|
24
|
+
const { maxFileLength: maxLines } = loadConfig();
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function deny(reason) {
|
|
28
|
+
process.stdout.write(JSON.stringify({ deny: true, message: `[CodeWarden] ${reason}` }) + '\n');
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Patch parsing helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract lines added by the patch (lines starting with '+', not '+++').
|
|
38
|
+
*/
|
|
39
|
+
function extractAddedLines(patch) {
|
|
40
|
+
return patch.split('\n')
|
|
41
|
+
.filter(l => l.startsWith('+') && !l.startsWith('+++'))
|
|
42
|
+
.map(l => l.slice(1));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extract target file path from patch header (*** path or +++ path or --- path).
|
|
47
|
+
* Returns null if not detectable.
|
|
48
|
+
*/
|
|
49
|
+
function extractTargetPath(patch) {
|
|
50
|
+
for (const line of patch.split('\n')) {
|
|
51
|
+
const m = line.match(/^\+{3}\s+(.+?)(\s+\d{4}-\d{2}-\d{2}.*)?$/) ||
|
|
52
|
+
line.match(/^\*{3}\s+(.+?)(\s+\d{4}-\d{2}-\d{2}.*)?$/);
|
|
53
|
+
if (m) {
|
|
54
|
+
const p = m[1].trim();
|
|
55
|
+
if (p !== '/dev/null') return p;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Estimate resulting line count: base file lines + added lines - removed lines.
|
|
63
|
+
* Returns null if file not readable.
|
|
64
|
+
*/
|
|
65
|
+
function estimateResultLines(patch, targetPath) {
|
|
66
|
+
let baseLines = 0;
|
|
67
|
+
if (targetPath && fs.existsSync(targetPath)) {
|
|
68
|
+
try {
|
|
69
|
+
baseLines = countLines(fs.readFileSync(targetPath, 'utf8'));
|
|
70
|
+
} catch { return null; }
|
|
71
|
+
} else if (!targetPath) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Count added and removed lines
|
|
75
|
+
let added = 0;
|
|
76
|
+
let removed = 0;
|
|
77
|
+
for (const line of patch.split('\n')) {
|
|
78
|
+
if (line.startsWith('+') && !line.startsWith('+++')) added++;
|
|
79
|
+
else if (line.startsWith('-') && !line.startsWith('---')) removed++;
|
|
80
|
+
}
|
|
81
|
+
return baseLines + added - removed;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Main
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
let raw = '';
|
|
89
|
+
process.stdin.setEncoding('utf8');
|
|
90
|
+
process.stdin.on('data', chunk => { raw += chunk; });
|
|
91
|
+
process.stdin.on('end', () => {
|
|
92
|
+
let payload;
|
|
93
|
+
try { payload = JSON.parse(raw); } catch { process.exit(0); }
|
|
94
|
+
|
|
95
|
+
const input = payload.toolInput || payload.tool_input || {};
|
|
96
|
+
const patch = String(input.patch || '');
|
|
97
|
+
if (!patch) process.exit(0);
|
|
98
|
+
|
|
99
|
+
// --- Secrets check on added lines ---
|
|
100
|
+
const added = extractAddedLines(patch);
|
|
101
|
+
const addedText = added.join('\n');
|
|
102
|
+
const hit = scanForSecrets(addedText);
|
|
103
|
+
if (hit) deny(`Blocked apply_patch — hardcoded credential detected (${hit.label}). Remove before patching.`);
|
|
104
|
+
|
|
105
|
+
// --- File length check ---
|
|
106
|
+
const targetPath = extractTargetPath(patch);
|
|
107
|
+
const estimated = estimateResultLines(patch, targetPath);
|
|
108
|
+
if (estimated !== null && estimated > maxLines) {
|
|
109
|
+
deny(`Blocked apply_patch — resulting file would be ~${estimated} lines (limit ${maxLines}). Break it up first.`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* warden-bash-hook.js — Codex PreToolUse hook
|
|
4
|
-
*
|
|
5
|
-
* Fires on Bash tool calls. Scans the command string for patterns that
|
|
6
|
-
* would embed hardcoded credentials into files (e.g. echo/printf/cat with
|
|
7
|
-
* secret values, curl with Authorization headers, env assignments, etc.).
|
|
8
|
-
*
|
|
9
|
-
* This is a best-effort surface: Bash is intentionally wide. The hook catches
|
|
10
|
-
* the most common accidental secret exposure patterns; it does not attempt to
|
|
11
|
-
* sandbox arbitrary shell execution.
|
|
12
|
-
*
|
|
13
|
-
* Codex hook payload (stdin, JSON):
|
|
14
|
-
* { tool: "Bash", toolInput: { command: "<shell command>" } }
|
|
15
|
-
*
|
|
16
|
-
* Exit codes:
|
|
17
|
-
* 0 — proceed
|
|
18
|
-
* 2 — block (writes JSON deny to stdout)
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
'use strict';
|
|
22
|
-
|
|
23
|
-
const { scanForSecrets } = require('../../lib/secret-patterns');
|
|
24
|
-
|
|
25
|
-
function deny(reason) {
|
|
26
|
-
process.stdout.write(JSON.stringify({ deny: true, message: `[CodeWarden] ${reason}` }) + '\n');
|
|
27
|
-
process.exit(2);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Main
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
let raw = '';
|
|
35
|
-
process.stdin.setEncoding('utf8');
|
|
36
|
-
process.stdin.on('data', chunk => { raw += chunk; });
|
|
37
|
-
process.stdin.on('end', () => {
|
|
38
|
-
let payload;
|
|
39
|
-
try { payload = JSON.parse(raw); } catch { process.exit(0); }
|
|
40
|
-
|
|
41
|
-
const input = payload.toolInput || payload.tool_input || {};
|
|
42
|
-
const command = String(input.command || '');
|
|
43
|
-
if (!command) process.exit(0);
|
|
44
|
-
|
|
45
|
-
const hit = scanForSecrets(command);
|
|
46
|
-
if (hit) {
|
|
47
|
-
deny(`Blocked Bash command — hardcoded credential detected (${hit.label}). Use environment variables or a secrets manager instead.`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
process.exit(0);
|
|
51
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* warden-bash-hook.js — Codex PreToolUse hook
|
|
4
|
+
*
|
|
5
|
+
* Fires on Bash tool calls. Scans the command string for patterns that
|
|
6
|
+
* would embed hardcoded credentials into files (e.g. echo/printf/cat with
|
|
7
|
+
* secret values, curl with Authorization headers, env assignments, etc.).
|
|
8
|
+
*
|
|
9
|
+
* This is a best-effort surface: Bash is intentionally wide. The hook catches
|
|
10
|
+
* the most common accidental secret exposure patterns; it does not attempt to
|
|
11
|
+
* sandbox arbitrary shell execution.
|
|
12
|
+
*
|
|
13
|
+
* Codex hook payload (stdin, JSON):
|
|
14
|
+
* { tool: "Bash", toolInput: { command: "<shell command>" } }
|
|
15
|
+
*
|
|
16
|
+
* Exit codes:
|
|
17
|
+
* 0 — proceed
|
|
18
|
+
* 2 — block (writes JSON deny to stdout)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const { scanForSecrets } = require('../../lib/secret-patterns');
|
|
24
|
+
|
|
25
|
+
function deny(reason) {
|
|
26
|
+
process.stdout.write(JSON.stringify({ deny: true, message: `[CodeWarden] ${reason}` }) + '\n');
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Main
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
let raw = '';
|
|
35
|
+
process.stdin.setEncoding('utf8');
|
|
36
|
+
process.stdin.on('data', chunk => { raw += chunk; });
|
|
37
|
+
process.stdin.on('end', () => {
|
|
38
|
+
let payload;
|
|
39
|
+
try { payload = JSON.parse(raw); } catch { process.exit(0); }
|
|
40
|
+
|
|
41
|
+
const input = payload.toolInput || payload.tool_input || {};
|
|
42
|
+
const command = String(input.command || '');
|
|
43
|
+
if (!command) process.exit(0);
|
|
44
|
+
|
|
45
|
+
const hit = scanForSecrets(command);
|
|
46
|
+
if (hit) {
|
|
47
|
+
deny(`Blocked Bash command — hardcoded credential detected (${hit.label}). Use environment variables or a secrets manager instead.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.exit(0);
|
|
51
|
+
});
|
package/tools/lib/config.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* config.js
|
|
6
|
-
* Shared codewarden.json loader.
|
|
7
|
-
*
|
|
8
|
-
* Previously warden-lint.js, warden-lint-hook.js, and warden-apply-patch-hook.js
|
|
9
|
-
* each parsed codewarden.json independently with slightly different fallback paths.
|
|
10
|
-
* This module centralises config loading with a single documented precedence.
|
|
11
|
-
*
|
|
12
|
-
* Resolution order for config file:
|
|
13
|
-
* 1. Explicit path passed to loadConfig(configPath)
|
|
14
|
-
* 2. <__dirname>/../../codewarden.json (relative to tools/lib/ → skill root)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
|
|
20
|
-
const DEFAULT_CONFIG_PATH = path.join(__dirname, '..', '..', 'codewarden.json');
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Load and parse codewarden.json, returning a merged config object.
|
|
24
|
-
* Falls back to defaults silently if the file is missing or unparseable.
|
|
25
|
-
*
|
|
26
|
-
* @param {string} [configPath] - Override the config file location
|
|
27
|
-
* @returns {{ maxFileLength: number }}
|
|
28
|
-
*/
|
|
29
|
-
function loadConfig(configPath) {
|
|
30
|
-
const target = configPath || DEFAULT_CONFIG_PATH;
|
|
31
|
-
let maxFileLength = 400;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const raw = fs.readFileSync(target, 'utf8');
|
|
35
|
-
const cfg = JSON.parse(raw);
|
|
36
|
-
const configured =
|
|
37
|
-
cfg?.thresholds?.max_file_length ??
|
|
38
|
-
cfg?.max_file_length;
|
|
39
|
-
if (typeof configured === 'number' && configured > 0) {
|
|
40
|
-
maxFileLength = configured;
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
// Missing or invalid config — use defaults
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return { maxFileLength };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
module.exports = { loadConfig, DEFAULT_CONFIG_PATH };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* config.js
|
|
6
|
+
* Shared codewarden.json loader.
|
|
7
|
+
*
|
|
8
|
+
* Previously warden-lint.js, warden-lint-hook.js, and warden-apply-patch-hook.js
|
|
9
|
+
* each parsed codewarden.json independently with slightly different fallback paths.
|
|
10
|
+
* This module centralises config loading with a single documented precedence.
|
|
11
|
+
*
|
|
12
|
+
* Resolution order for config file:
|
|
13
|
+
* 1. Explicit path passed to loadConfig(configPath)
|
|
14
|
+
* 2. <__dirname>/../../codewarden.json (relative to tools/lib/ → skill root)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const DEFAULT_CONFIG_PATH = path.join(__dirname, '..', '..', 'codewarden.json');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load and parse codewarden.json, returning a merged config object.
|
|
24
|
+
* Falls back to defaults silently if the file is missing or unparseable.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} [configPath] - Override the config file location
|
|
27
|
+
* @returns {{ maxFileLength: number }}
|
|
28
|
+
*/
|
|
29
|
+
function loadConfig(configPath) {
|
|
30
|
+
const target = configPath || DEFAULT_CONFIG_PATH;
|
|
31
|
+
let maxFileLength = 400;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const raw = fs.readFileSync(target, 'utf8');
|
|
35
|
+
const cfg = JSON.parse(raw);
|
|
36
|
+
const configured =
|
|
37
|
+
cfg?.thresholds?.max_file_length ??
|
|
38
|
+
cfg?.max_file_length;
|
|
39
|
+
if (typeof configured === 'number' && configured > 0) {
|
|
40
|
+
maxFileLength = configured;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Missing or invalid config — use defaults
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { maxFileLength };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { loadConfig, DEFAULT_CONFIG_PATH };
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* file-collection.js
|
|
6
|
-
* Shared file traversal helpers for warden-lint and verify-secrets CLI tools.
|
|
7
|
-
*
|
|
8
|
-
* Previously each CLI tool duplicated identical SKIP_DIRS, SKIP_EXTS,
|
|
9
|
-
* collectFiles, and expandPaths logic — any change had to be made twice.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', 'target', 'dist', '.next']);
|
|
16
|
-
|
|
17
|
-
const SKIP_EXTS = new Set([
|
|
18
|
-
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp',
|
|
19
|
-
'.zip', '.tar', '.gz', '.7z', '.rar',
|
|
20
|
-
'.dll', '.exe', '.bin', '.so', '.dylib',
|
|
21
|
-
'.pdf', '.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
22
|
-
'.mp4', '.mp3', '.wav', '.ogg', '.avi', '.mov',
|
|
23
|
-
'.map', '.lock',
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Recursively collect all scannable files under a directory.
|
|
28
|
-
*
|
|
29
|
-
* @param {string} dir
|
|
30
|
-
* @param {string[]} results - accumulator (mutated)
|
|
31
|
-
*/
|
|
32
|
-
function collectFiles(dir, results) {
|
|
33
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
34
|
-
if (SKIP_DIRS.has(entry)) continue;
|
|
35
|
-
const full = path.join(dir, entry);
|
|
36
|
-
const stat = fs.statSync(full);
|
|
37
|
-
if (stat.isDirectory()) {
|
|
38
|
-
collectFiles(full, results);
|
|
39
|
-
} else if (!SKIP_EXTS.has(path.extname(entry).toLowerCase())) {
|
|
40
|
-
results.push(full);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Expand a list of CLI path arguments into a flat array of file paths.
|
|
47
|
-
* Directories are walked recursively; individual files are included as-is.
|
|
48
|
-
* Exits with usage error if no args provided or a path is not found.
|
|
49
|
-
*
|
|
50
|
-
* @param {string[]} args - process.argv slice
|
|
51
|
-
* @param {string} toolName - used in the usage message
|
|
52
|
-
* @returns {string[]}
|
|
53
|
-
*/
|
|
54
|
-
function expandPaths(args, toolName) {
|
|
55
|
-
if (args.length === 0) {
|
|
56
|
-
console.log(`Usage: ${toolName} <file|dir> [file|dir] ...`);
|
|
57
|
-
console.log(` node tools/${toolName} . # scan entire project`);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
const files = [];
|
|
61
|
-
for (const arg of args) {
|
|
62
|
-
if (!fs.existsSync(arg)) {
|
|
63
|
-
console.error(`Error: path not found: ${arg}`);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (fs.statSync(arg).isDirectory()) collectFiles(arg, files);
|
|
67
|
-
else files.push(arg);
|
|
68
|
-
}
|
|
69
|
-
return files;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports = { collectFiles, expandPaths, SKIP_DIRS, SKIP_EXTS };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* file-collection.js
|
|
6
|
+
* Shared file traversal helpers for warden-lint and verify-secrets CLI tools.
|
|
7
|
+
*
|
|
8
|
+
* Previously each CLI tool duplicated identical SKIP_DIRS, SKIP_EXTS,
|
|
9
|
+
* collectFiles, and expandPaths logic — any change had to be made twice.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'target', 'dist', '.next']);
|
|
16
|
+
|
|
17
|
+
const SKIP_EXTS = new Set([
|
|
18
|
+
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp',
|
|
19
|
+
'.zip', '.tar', '.gz', '.7z', '.rar',
|
|
20
|
+
'.dll', '.exe', '.bin', '.so', '.dylib',
|
|
21
|
+
'.pdf', '.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
22
|
+
'.mp4', '.mp3', '.wav', '.ogg', '.avi', '.mov',
|
|
23
|
+
'.map', '.lock',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Recursively collect all scannable files under a directory.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} dir
|
|
30
|
+
* @param {string[]} results - accumulator (mutated)
|
|
31
|
+
*/
|
|
32
|
+
function collectFiles(dir, results) {
|
|
33
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
34
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
35
|
+
const full = path.join(dir, entry);
|
|
36
|
+
const stat = fs.statSync(full);
|
|
37
|
+
if (stat.isDirectory()) {
|
|
38
|
+
collectFiles(full, results);
|
|
39
|
+
} else if (!SKIP_EXTS.has(path.extname(entry).toLowerCase())) {
|
|
40
|
+
results.push(full);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Expand a list of CLI path arguments into a flat array of file paths.
|
|
47
|
+
* Directories are walked recursively; individual files are included as-is.
|
|
48
|
+
* Exits with usage error if no args provided or a path is not found.
|
|
49
|
+
*
|
|
50
|
+
* @param {string[]} args - process.argv slice
|
|
51
|
+
* @param {string} toolName - used in the usage message
|
|
52
|
+
* @returns {string[]}
|
|
53
|
+
*/
|
|
54
|
+
function expandPaths(args, toolName) {
|
|
55
|
+
if (args.length === 0) {
|
|
56
|
+
console.log(`Usage: ${toolName} <file|dir> [file|dir] ...`);
|
|
57
|
+
console.log(` node tools/${toolName} . # scan entire project`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const files = [];
|
|
61
|
+
for (const arg of args) {
|
|
62
|
+
if (!fs.existsSync(arg)) {
|
|
63
|
+
console.error(`Error: path not found: ${arg}`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (fs.statSync(arg).isDirectory()) collectFiles(arg, files);
|
|
67
|
+
else files.push(arg);
|
|
68
|
+
}
|
|
69
|
+
return files;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { collectFiles, expandPaths, SKIP_DIRS, SKIP_EXTS };
|
package/tools/lib/line-count.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* line-count.js
|
|
6
|
-
* Shared line-counting helper for warden-lint and hook consumers.
|
|
7
|
-
*
|
|
8
|
-
* Problem with the naive split('\n').length:
|
|
9
|
-
* "a\nb\n".split('\n') === ['a', 'b', ''] → length 3, not 2
|
|
10
|
-
* A trailing newline (standard in well-formed files) would push a file
|
|
11
|
-
* that is exactly at the limit over it, producing false positives.
|
|
12
|
-
*
|
|
13
|
-
* This helper normalises CRLF, strips the trailing newline before
|
|
14
|
-
* splitting, and treats an empty string as 0 lines.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Count logical lines in a file's content string.
|
|
19
|
-
*
|
|
20
|
-
* @param {string} content - Raw file content (may include CRLF or trailing newline)
|
|
21
|
-
* @returns {number} Line count (0 for empty content)
|
|
22
|
-
*/
|
|
23
|
-
function countLines(content) {
|
|
24
|
-
if (typeof content !== 'string' || content.length === 0) return 0;
|
|
25
|
-
return content.replace(/\r\n/g, '\n').trimEnd().split('\n').length;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
module.exports = { countLines };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* line-count.js
|
|
6
|
+
* Shared line-counting helper for warden-lint and hook consumers.
|
|
7
|
+
*
|
|
8
|
+
* Problem with the naive split('\n').length:
|
|
9
|
+
* "a\nb\n".split('\n') === ['a', 'b', ''] → length 3, not 2
|
|
10
|
+
* A trailing newline (standard in well-formed files) would push a file
|
|
11
|
+
* that is exactly at the limit over it, producing false positives.
|
|
12
|
+
*
|
|
13
|
+
* This helper normalises CRLF, strips the trailing newline before
|
|
14
|
+
* splitting, and treats an empty string as 0 lines.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Count logical lines in a file's content string.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} content - Raw file content (may include CRLF or trailing newline)
|
|
21
|
+
* @returns {number} Line count (0 for empty content)
|
|
22
|
+
*/
|
|
23
|
+
function countLines(content) {
|
|
24
|
+
if (typeof content !== 'string' || content.length === 0) return 0;
|
|
25
|
+
return content.replace(/\r\n/g, '\n').trimEnd().split('\n').length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { countLines };
|