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.
Files changed (40) hide show
  1. package/CONFIGURE.md +39 -39
  2. package/DECISIONS.md +107 -107
  3. package/README.md +6 -0
  4. package/SKILL.md +169 -169
  5. package/bin/code-warden.js +82 -82
  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 +62 -62
  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/tools/auto-detect.js +91 -91
  21. package/tools/auto-targets.js +104 -104
  22. package/tools/auto-windsurf-adapter.js +75 -75
  23. package/tools/get-context.js +50 -50
  24. package/tools/governance-report.js +302 -302
  25. package/tools/hooks/claude/install-hooks.js +112 -112
  26. package/tools/hooks/claude/uninstall-hooks.js +75 -75
  27. package/tools/hooks/claude/warden-lint-hook.js +106 -106
  28. package/tools/hooks/claude/warden-secrets-hook.js +73 -73
  29. package/tools/hooks/codex/install-hooks.js +100 -100
  30. package/tools/hooks/codex/uninstall-hooks.js +53 -53
  31. package/tools/hooks/codex/warden-apply-patch-hook.js +113 -113
  32. package/tools/hooks/codex/warden-bash-hook.js +51 -51
  33. package/tools/lib/config.js +49 -49
  34. package/tools/lib/file-collection.js +5 -2
  35. package/tools/lib/line-count.js +28 -28
  36. package/tools/lib/secret-patterns.js +57 -57
  37. package/tools/tests/fixtures/clean.js +9 -9
  38. package/tools/tests/run-tests.js +210 -210
  39. package/tools/verify-secrets.js +26 -26
  40. package/tools/warden-lint.js +27 -27
@@ -1,210 +1,210 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * run-tests.js
6
- * Behavioral test suite for Code-Warden scanners and hooks.
7
- *
8
- * Verifies that enforcement tools actually enforce — not just that they exist.
9
- * Uses Node's built-in node:test (no external test framework required).
10
- *
11
- * Tests:
12
- * CLI tools
13
- * 1. warden-lint clean file → exit 0
14
- * 2. warden-lint oversized → exit 1
15
- * 3. verify-secrets clean file → exit 0
16
- * 4. verify-secrets secret file → exit 1
17
- *
18
- * Claude hooks (PreToolUse JSON payloads via stdin)
19
- * 5. warden-lint-hook Write oversized → exit 2
20
- * 6. warden-secrets-hook Write secret → exit 2
21
- *
22
- * Codex hooks
23
- * 7. warden-apply-patch-hook patch with secret → exit 2
24
- * 8. warden-bash-hook command with secret → exit 2
25
- *
26
- * Fixture strategy:
27
- * - clean.js is committed (no secrets, short).
28
- * - Oversized content is generated in memory / tmpdir (committed file would
29
- * trip warden-lint itself).
30
- * - Secret content is generated via makeFakeSecret() which builds the
31
- * pattern from parts so the scanner does not flag this source file.
32
- */
33
-
34
- const { test } = require('node:test');
35
- const assert = require('node:assert/strict');
36
- const { spawnSync } = require('node:child_process');
37
- const path = require('node:path');
38
- const fs = require('node:fs');
39
- const os = require('node:os');
40
-
41
- // ---------------------------------------------------------------------------
42
- // Paths
43
- // ---------------------------------------------------------------------------
44
-
45
- const ROOT = path.join(__dirname, '..', '..');
46
- const TOOLS = path.join(ROOT, 'tools');
47
- const FIXTURES = path.join(__dirname, 'fixtures');
48
- const CLEAN = path.join(FIXTURES, 'clean.js');
49
-
50
- if (!fs.existsSync(CLEAN)) throw new Error(`Missing fixture: clean at ${CLEAN}`);
51
-
52
- // ---------------------------------------------------------------------------
53
- // Fixture generators — never committed, avoids false-positive self-scan
54
- // ---------------------------------------------------------------------------
55
-
56
- /**
57
- * Build a fake OpenAI-style key from parts so this source file does not
58
- * contain a string that matches the secret scanner's pattern literally.
59
- */
60
- function makeFakeSecret() {
61
- return ['sk', '-', 'a'.repeat(48)].join('');
62
- }
63
-
64
- /** Generate content containing a hardcoded credential. */
65
- function makeSecretContent() {
66
- return [
67
- '// Generated secret fixture — not committed to repo.',
68
- "'use strict';",
69
- `const KEY = '${makeFakeSecret()}';`,
70
- 'module.exports = { KEY };',
71
- ].join('\n') + '\n';
72
- }
73
-
74
- /** Generate content that exceeds the 400-line limit. */
75
- function makeOversizedContent(limit = 400) {
76
- const lines = [
77
- '// Generated oversized fixture — not committed to repo.',
78
- "'use strict';",
79
- '',
80
- ];
81
- for (let i = 1; i <= limit + 15; i++) {
82
- lines.push(`const line${i} = ${i}; // padding`);
83
- }
84
- return lines.join('\n') + '\n';
85
- }
86
-
87
- /** Write content to a temp file; caller is responsible for cleanup. */
88
- function writeTmp(name, content) {
89
- const p = path.join(os.tmpdir(), `cw-fixture-${process.pid}-${name}`);
90
- fs.writeFileSync(p, content);
91
- return p;
92
- }
93
-
94
- // ---------------------------------------------------------------------------
95
- // Helpers
96
- // ---------------------------------------------------------------------------
97
-
98
- function runCLI(scriptPath, args = []) {
99
- const result = spawnSync(process.execPath, [scriptPath, ...args], { encoding: 'utf8' });
100
- return { code: result.status ?? result.signal, stdout: result.stdout || '', stderr: result.stderr || '' };
101
- }
102
-
103
- function runHook(scriptPath, payload) {
104
- const result = spawnSync(process.execPath, [scriptPath], { input: JSON.stringify(payload), encoding: 'utf8' });
105
- return { code: result.status ?? result.signal, stdout: result.stdout || '', stderr: result.stderr || '' };
106
- }
107
-
108
- // ---------------------------------------------------------------------------
109
- // CLI: warden-lint
110
- // ---------------------------------------------------------------------------
111
-
112
- test('warden-lint: clean file exits 0', () => {
113
- const { code } = runCLI(path.join(TOOLS, 'warden-lint.js'), [CLEAN]);
114
- assert.equal(code, 0, 'expected exit 0 for a clean, short file');
115
- });
116
-
117
- test('warden-lint: oversized file exits 1', () => {
118
- const tmp = writeTmp('oversized.js', makeOversizedContent());
119
- try {
120
- const { code, stderr } = runCLI(path.join(TOOLS, 'warden-lint.js'), [tmp]);
121
- assert.equal(code, 1, 'expected exit 1 for an oversized file');
122
- assert.ok(stderr.includes('[FAIL]'), 'expected [FAIL] in stderr');
123
- } finally {
124
- fs.rmSync(tmp, { force: true });
125
- }
126
- });
127
-
128
- // ---------------------------------------------------------------------------
129
- // CLI: verify-secrets
130
- // ---------------------------------------------------------------------------
131
-
132
- test('verify-secrets: clean file exits 0', () => {
133
- const { code } = runCLI(path.join(TOOLS, 'verify-secrets.js'), [CLEAN]);
134
- assert.equal(code, 0, 'expected exit 0 for a file with no secrets');
135
- });
136
-
137
- test('verify-secrets: secret file exits 1', () => {
138
- const tmp = writeTmp('secret.js', makeSecretContent());
139
- try {
140
- const { code, stderr } = runCLI(path.join(TOOLS, 'verify-secrets.js'), [tmp]);
141
- assert.equal(code, 1, 'expected exit 1 for a file containing a hardcoded secret');
142
- assert.ok(stderr.includes('[FAIL]'), 'expected [FAIL] in stderr');
143
- } finally {
144
- fs.rmSync(tmp, { force: true });
145
- }
146
- });
147
-
148
- // ---------------------------------------------------------------------------
149
- // Claude hook: warden-lint-hook (Write with oversized content)
150
- // ---------------------------------------------------------------------------
151
-
152
- test('Claude lint hook: Write oversized content exits 2', () => {
153
- const payload = {
154
- tool_name: 'Write',
155
- tool_input: { file_path: '/tmp/test-oversized.js', content: makeOversizedContent() },
156
- };
157
- const { code, stdout } = runHook(path.join(TOOLS, 'hooks', 'claude', 'warden-lint-hook.js'), payload);
158
- assert.equal(code, 2, 'expected exit 2 (deny) for oversized Write');
159
- const response = JSON.parse(stdout);
160
- assert.equal(response.hookSpecificOutput.permissionDecision, 'deny', 'expected deny decision');
161
- });
162
-
163
- // ---------------------------------------------------------------------------
164
- // Claude hook: warden-secrets-hook (Write with secret content)
165
- // ---------------------------------------------------------------------------
166
-
167
- test('Claude secrets hook: Write secret content exits 2', () => {
168
- const payload = {
169
- tool_name: 'Write',
170
- tool_input: { file_path: '/tmp/test-secret.js', content: makeSecretContent() },
171
- };
172
- const { code, stdout } = runHook(path.join(TOOLS, 'hooks', 'claude', 'warden-secrets-hook.js'), payload);
173
- assert.equal(code, 2, 'expected exit 2 (deny) for Write containing secret');
174
- const response = JSON.parse(stdout);
175
- assert.equal(response.hookSpecificOutput.permissionDecision, 'deny', 'expected deny decision');
176
- });
177
-
178
- // ---------------------------------------------------------------------------
179
- // Codex hook: warden-apply-patch-hook (patch adding a secret)
180
- // ---------------------------------------------------------------------------
181
-
182
- test('Codex apply_patch hook: patch with secret exits 2', () => {
183
- const patch = [
184
- '*** /dev/null',
185
- '+++ tmp/secret-patch.js',
186
- '@@ -0,0 +1,2 @@',
187
- `+const KEY = '${makeFakeSecret()}';`,
188
- '+module.exports = { KEY };',
189
- ].join('\n');
190
-
191
- const { code, stdout } = runHook(
192
- path.join(TOOLS, 'hooks', 'codex', 'warden-apply-patch-hook.js'),
193
- { tool: 'apply_patch', toolInput: { patch } }
194
- );
195
- assert.equal(code, 2, 'expected exit 2 (deny) for apply_patch with hardcoded secret');
196
- assert.ok(JSON.parse(stdout).deny === true, 'expected deny:true');
197
- });
198
-
199
- // ---------------------------------------------------------------------------
200
- // Codex hook: warden-bash-hook (command containing a secret)
201
- // ---------------------------------------------------------------------------
202
-
203
- test('Codex Bash hook: command with secret exits 2', () => {
204
- const { code, stdout } = runHook(
205
- path.join(TOOLS, 'hooks', 'codex', 'warden-bash-hook.js'),
206
- { tool: 'Bash', toolInput: { command: `echo ${makeFakeSecret()}` } }
207
- );
208
- assert.equal(code, 2, 'expected exit 2 (deny) for Bash command with hardcoded secret');
209
- assert.ok(JSON.parse(stdout).deny === true, 'expected deny:true');
210
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * run-tests.js
6
+ * Behavioral test suite for Code-Warden scanners and hooks.
7
+ *
8
+ * Verifies that enforcement tools actually enforce — not just that they exist.
9
+ * Uses Node's built-in node:test (no external test framework required).
10
+ *
11
+ * Tests:
12
+ * CLI tools
13
+ * 1. warden-lint clean file → exit 0
14
+ * 2. warden-lint oversized → exit 1
15
+ * 3. verify-secrets clean file → exit 0
16
+ * 4. verify-secrets secret file → exit 1
17
+ *
18
+ * Claude hooks (PreToolUse JSON payloads via stdin)
19
+ * 5. warden-lint-hook Write oversized → exit 2
20
+ * 6. warden-secrets-hook Write secret → exit 2
21
+ *
22
+ * Codex hooks
23
+ * 7. warden-apply-patch-hook patch with secret → exit 2
24
+ * 8. warden-bash-hook command with secret → exit 2
25
+ *
26
+ * Fixture strategy:
27
+ * - clean.js is committed (no secrets, short).
28
+ * - Oversized content is generated in memory / tmpdir (committed file would
29
+ * trip warden-lint itself).
30
+ * - Secret content is generated via makeFakeSecret() which builds the
31
+ * pattern from parts so the scanner does not flag this source file.
32
+ */
33
+
34
+ const { test } = require('node:test');
35
+ const assert = require('node:assert/strict');
36
+ const { spawnSync } = require('node:child_process');
37
+ const path = require('node:path');
38
+ const fs = require('node:fs');
39
+ const os = require('node:os');
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Paths
43
+ // ---------------------------------------------------------------------------
44
+
45
+ const ROOT = path.join(__dirname, '..', '..');
46
+ const TOOLS = path.join(ROOT, 'tools');
47
+ const FIXTURES = path.join(__dirname, 'fixtures');
48
+ const CLEAN = path.join(FIXTURES, 'clean.js');
49
+
50
+ if (!fs.existsSync(CLEAN)) throw new Error(`Missing fixture: clean at ${CLEAN}`);
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Fixture generators — never committed, avoids false-positive self-scan
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Build a fake OpenAI-style key from parts so this source file does not
58
+ * contain a string that matches the secret scanner's pattern literally.
59
+ */
60
+ function makeFakeSecret() {
61
+ return ['sk', '-', 'a'.repeat(48)].join('');
62
+ }
63
+
64
+ /** Generate content containing a hardcoded credential. */
65
+ function makeSecretContent() {
66
+ return [
67
+ '// Generated secret fixture — not committed to repo.',
68
+ "'use strict';",
69
+ `const KEY = '${makeFakeSecret()}';`,
70
+ 'module.exports = { KEY };',
71
+ ].join('\n') + '\n';
72
+ }
73
+
74
+ /** Generate content that exceeds the 400-line limit. */
75
+ function makeOversizedContent(limit = 400) {
76
+ const lines = [
77
+ '// Generated oversized fixture — not committed to repo.',
78
+ "'use strict';",
79
+ '',
80
+ ];
81
+ for (let i = 1; i <= limit + 15; i++) {
82
+ lines.push(`const line${i} = ${i}; // padding`);
83
+ }
84
+ return lines.join('\n') + '\n';
85
+ }
86
+
87
+ /** Write content to a temp file; caller is responsible for cleanup. */
88
+ function writeTmp(name, content) {
89
+ const p = path.join(os.tmpdir(), `cw-fixture-${process.pid}-${name}`);
90
+ fs.writeFileSync(p, content);
91
+ return p;
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Helpers
96
+ // ---------------------------------------------------------------------------
97
+
98
+ function runCLI(scriptPath, args = []) {
99
+ const result = spawnSync(process.execPath, [scriptPath, ...args], { encoding: 'utf8' });
100
+ return { code: result.status ?? result.signal, stdout: result.stdout || '', stderr: result.stderr || '' };
101
+ }
102
+
103
+ function runHook(scriptPath, payload) {
104
+ const result = spawnSync(process.execPath, [scriptPath], { input: JSON.stringify(payload), encoding: 'utf8' });
105
+ return { code: result.status ?? result.signal, stdout: result.stdout || '', stderr: result.stderr || '' };
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // CLI: warden-lint
110
+ // ---------------------------------------------------------------------------
111
+
112
+ test('warden-lint: clean file exits 0', () => {
113
+ const { code } = runCLI(path.join(TOOLS, 'warden-lint.js'), [CLEAN]);
114
+ assert.equal(code, 0, 'expected exit 0 for a clean, short file');
115
+ });
116
+
117
+ test('warden-lint: oversized file exits 1', () => {
118
+ const tmp = writeTmp('oversized.js', makeOversizedContent());
119
+ try {
120
+ const { code, stderr } = runCLI(path.join(TOOLS, 'warden-lint.js'), [tmp]);
121
+ assert.equal(code, 1, 'expected exit 1 for an oversized file');
122
+ assert.ok(stderr.includes('[FAIL]'), 'expected [FAIL] in stderr');
123
+ } finally {
124
+ fs.rmSync(tmp, { force: true });
125
+ }
126
+ });
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // CLI: verify-secrets
130
+ // ---------------------------------------------------------------------------
131
+
132
+ test('verify-secrets: clean file exits 0', () => {
133
+ const { code } = runCLI(path.join(TOOLS, 'verify-secrets.js'), [CLEAN]);
134
+ assert.equal(code, 0, 'expected exit 0 for a file with no secrets');
135
+ });
136
+
137
+ test('verify-secrets: secret file exits 1', () => {
138
+ const tmp = writeTmp('secret.js', makeSecretContent());
139
+ try {
140
+ const { code, stderr } = runCLI(path.join(TOOLS, 'verify-secrets.js'), [tmp]);
141
+ assert.equal(code, 1, 'expected exit 1 for a file containing a hardcoded secret');
142
+ assert.ok(stderr.includes('[FAIL]'), 'expected [FAIL] in stderr');
143
+ } finally {
144
+ fs.rmSync(tmp, { force: true });
145
+ }
146
+ });
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Claude hook: warden-lint-hook (Write with oversized content)
150
+ // ---------------------------------------------------------------------------
151
+
152
+ test('Claude lint hook: Write oversized content exits 2', () => {
153
+ const payload = {
154
+ tool_name: 'Write',
155
+ tool_input: { file_path: '/tmp/test-oversized.js', content: makeOversizedContent() },
156
+ };
157
+ const { code, stdout } = runHook(path.join(TOOLS, 'hooks', 'claude', 'warden-lint-hook.js'), payload);
158
+ assert.equal(code, 2, 'expected exit 2 (deny) for oversized Write');
159
+ const response = JSON.parse(stdout);
160
+ assert.equal(response.hookSpecificOutput.permissionDecision, 'deny', 'expected deny decision');
161
+ });
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Claude hook: warden-secrets-hook (Write with secret content)
165
+ // ---------------------------------------------------------------------------
166
+
167
+ test('Claude secrets hook: Write secret content exits 2', () => {
168
+ const payload = {
169
+ tool_name: 'Write',
170
+ tool_input: { file_path: '/tmp/test-secret.js', content: makeSecretContent() },
171
+ };
172
+ const { code, stdout } = runHook(path.join(TOOLS, 'hooks', 'claude', 'warden-secrets-hook.js'), payload);
173
+ assert.equal(code, 2, 'expected exit 2 (deny) for Write containing secret');
174
+ const response = JSON.parse(stdout);
175
+ assert.equal(response.hookSpecificOutput.permissionDecision, 'deny', 'expected deny decision');
176
+ });
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Codex hook: warden-apply-patch-hook (patch adding a secret)
180
+ // ---------------------------------------------------------------------------
181
+
182
+ test('Codex apply_patch hook: patch with secret exits 2', () => {
183
+ const patch = [
184
+ '*** /dev/null',
185
+ '+++ tmp/secret-patch.js',
186
+ '@@ -0,0 +1,2 @@',
187
+ `+const KEY = '${makeFakeSecret()}';`,
188
+ '+module.exports = { KEY };',
189
+ ].join('\n');
190
+
191
+ const { code, stdout } = runHook(
192
+ path.join(TOOLS, 'hooks', 'codex', 'warden-apply-patch-hook.js'),
193
+ { tool: 'apply_patch', toolInput: { patch } }
194
+ );
195
+ assert.equal(code, 2, 'expected exit 2 (deny) for apply_patch with hardcoded secret');
196
+ assert.ok(JSON.parse(stdout).deny === true, 'expected deny:true');
197
+ });
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Codex hook: warden-bash-hook (command containing a secret)
201
+ // ---------------------------------------------------------------------------
202
+
203
+ test('Codex Bash hook: command with secret exits 2', () => {
204
+ const { code, stdout } = runHook(
205
+ path.join(TOOLS, 'hooks', 'codex', 'warden-bash-hook.js'),
206
+ { tool: 'Bash', toolInput: { command: `echo ${makeFakeSecret()}` } }
207
+ );
208
+ assert.equal(code, 2, 'expected exit 2 (deny) for Bash command with hardcoded secret');
209
+ assert.ok(JSON.parse(stdout).deny === true, 'expected deny:true');
210
+ });
@@ -1,26 +1,26 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const { scanForSecrets } = require('./lib/secret-patterns');
6
- const { expandPaths } = require('./lib/file-collection');
7
-
8
- const filePaths = expandPaths(process.argv.slice(2), 'verify-secrets.js');
9
- let hasErrors = false;
10
-
11
- for (const filePath of filePaths) {
12
- const content = fs.readFileSync(filePath, 'utf8');
13
- const hit = scanForSecrets(content);
14
-
15
- if (hit) {
16
- console.error(`[FAIL] [CodeWarden] Hardcoded credential detected in ${filePath} - pattern: ${hit.label}`);
17
- console.error(' Rule: All secrets must be sourced from an environment variable (e.g., process.env)');
18
- hasErrors = true;
19
- } else {
20
- console.log(`[PASS] [CodeWarden] ${filePath} passed hardcoded credential scan.`);
21
- }
22
- }
23
-
24
- if (hasErrors) {
25
- process.exit(1);
26
- }
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const { scanForSecrets } = require('./lib/secret-patterns');
6
+ const { expandPaths } = require('./lib/file-collection');
7
+
8
+ const filePaths = expandPaths(process.argv.slice(2), 'verify-secrets.js');
9
+ let hasErrors = false;
10
+
11
+ for (const filePath of filePaths) {
12
+ const content = fs.readFileSync(filePath, 'utf8');
13
+ const hit = scanForSecrets(content);
14
+
15
+ if (hit) {
16
+ console.error(`[FAIL] [CodeWarden] Hardcoded credential detected in ${filePath} - pattern: ${hit.label}`);
17
+ console.error(' Rule: All secrets must be sourced from an environment variable (e.g., process.env)');
18
+ hasErrors = true;
19
+ } else {
20
+ console.log(`[PASS] [CodeWarden] ${filePath} passed hardcoded credential scan.`);
21
+ }
22
+ }
23
+
24
+ if (hasErrors) {
25
+ process.exit(1);
26
+ }
@@ -1,27 +1,27 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const { countLines } = require('./lib/line-count');
6
- const { expandPaths } = require('./lib/file-collection');
7
- const { loadConfig } = require('./lib/config');
8
-
9
- const { maxFileLength } = loadConfig();
10
- const filePaths = expandPaths(process.argv.slice(2), 'warden-lint.js');
11
- let hasErrors = false;
12
-
13
- for (const filePath of filePaths) {
14
- const content = fs.readFileSync(filePath, 'utf8');
15
- const lines = countLines(content);
16
-
17
- if (lines > maxFileLength) {
18
- console.error(`[FAIL] [CodeWarden] File ${filePath} exceeds the maximum length of ${maxFileLength} lines (${lines} lines). Please refactor into smaller modules.`);
19
- hasErrors = true;
20
- } else {
21
- console.log(`[PASS] [CodeWarden] ${filePath} (${lines} lines) complies with the length restriction.`);
22
- }
23
- }
24
-
25
- if (hasErrors) {
26
- process.exit(1);
27
- }
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const { countLines } = require('./lib/line-count');
6
+ const { expandPaths } = require('./lib/file-collection');
7
+ const { loadConfig } = require('./lib/config');
8
+
9
+ const { maxFileLength } = loadConfig();
10
+ const filePaths = expandPaths(process.argv.slice(2), 'warden-lint.js');
11
+ let hasErrors = false;
12
+
13
+ for (const filePath of filePaths) {
14
+ const content = fs.readFileSync(filePath, 'utf8');
15
+ const lines = countLines(content);
16
+
17
+ if (lines > maxFileLength) {
18
+ console.error(`[FAIL] [CodeWarden] File ${filePath} exceeds the maximum length of ${maxFileLength} lines (${lines} lines). Please refactor into smaller modules.`);
19
+ hasErrors = true;
20
+ } else {
21
+ console.log(`[PASS] [CodeWarden] ${filePath} (${lines} lines) complies with the length restriction.`);
22
+ }
23
+ }
24
+
25
+ if (hasErrors) {
26
+ process.exit(1);
27
+ }