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,302 +1,302 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
7
- const { spawnSync } = require('child_process');
8
- const { countLines } = require('./lib/line-count');
9
- const { collectFiles } = require('./lib/file-collection');
10
- const { scanForSecrets } = require('./lib/secret-patterns');
11
- const { loadConfig } = require('./lib/config');
12
-
13
- const ROOT = path.join(__dirname, '..');
14
- const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
15
- const VERSION = PKG.version;
16
-
17
- // ---------------------------------------------------------------------------
18
- // CLI
19
- // ---------------------------------------------------------------------------
20
-
21
- function parseArgs(argv) {
22
- const args = argv.slice(2);
23
- const formatArg = args.find(a => a.startsWith('--format='));
24
- const format = formatArg ? formatArg.split('=')[1] : null;
25
- const scanPath = args.find(a => !a.startsWith('--')) || '.';
26
- return { format, scanPath };
27
- }
28
-
29
- // ---------------------------------------------------------------------------
30
- // Git metadata
31
- // ---------------------------------------------------------------------------
32
-
33
- function gitInfo() {
34
- const run = (gitArgs) => {
35
- const r = spawnSync('git', gitArgs, { encoding: 'utf8', timeout: 5000 });
36
- return r.status === 0 ? r.stdout.trim() : null;
37
- };
38
- return {
39
- branch: run(['rev-parse', '--abbrev-ref', 'HEAD']),
40
- commit: run(['rev-parse', '--short', 'HEAD']),
41
- };
42
- }
43
-
44
- // ---------------------------------------------------------------------------
45
- // File length + secrets (single pass over all files)
46
- // ---------------------------------------------------------------------------
47
-
48
- function runScans(scanPath) {
49
- const { maxFileLength } = loadConfig();
50
- const resolved = path.resolve(scanPath);
51
-
52
- if (!fs.existsSync(resolved)) {
53
- console.error(`[CodeWarden] Error: scan path not found: ${scanPath}`);
54
- process.exit(1);
55
- }
56
-
57
- const files = [];
58
- if (fs.statSync(resolved).isDirectory()) {
59
- collectFiles(resolved, files);
60
- } else {
61
- files.push(resolved);
62
- }
63
-
64
- const lengthViolations = [];
65
- const secretViolations = [];
66
-
67
- for (const f of files) {
68
- let content;
69
- try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
70
-
71
- const rel = path.relative(resolved, f);
72
-
73
- const lineCount = countLines(content);
74
- if (lineCount > maxFileLength) {
75
- lengthViolations.push({ file: rel, lines: lineCount, limit: maxFileLength });
76
- }
77
-
78
- const hit = scanForSecrets(content);
79
- if (hit) {
80
- secretViolations.push({ file: rel, pattern: hit.label });
81
- }
82
- }
83
-
84
- return {
85
- fileLength: {
86
- status: lengthViolations.length === 0 ? 'pass' : 'fail',
87
- filesScanned: files.length,
88
- violations: lengthViolations.length,
89
- details: lengthViolations.length > 0 ? lengthViolations : undefined,
90
- },
91
- secrets: {
92
- status: secretViolations.length === 0 ? 'pass' : 'fail',
93
- filesScanned: files.length,
94
- violations: secretViolations.length,
95
- details: secretViolations.length > 0 ? secretViolations : undefined,
96
- },
97
- };
98
- }
99
-
100
- // ---------------------------------------------------------------------------
101
- // Behavioral tests
102
- // ---------------------------------------------------------------------------
103
-
104
- function checkTests() {
105
- const testScript = path.join(__dirname, 'tests', 'run-tests.js');
106
- if (!fs.existsSync(testScript)) {
107
- return { status: 'skip', tests: 0, failures: 0 };
108
- }
109
-
110
- const r = spawnSync(process.execPath, [testScript], {
111
- encoding: 'utf8',
112
- timeout: 30000,
113
- cwd: ROOT,
114
- });
115
-
116
- const out = (r.stdout || '') + (r.stderr || '');
117
- const passMatch = out.match(/pass\s+(\d+)/);
118
- const failMatch = out.match(/fail\s+(\d+)/);
119
-
120
- let passed, failed;
121
- if (passMatch || failMatch) {
122
- passed = parseInt(passMatch?.[1] || '0', 10);
123
- failed = parseInt(failMatch?.[1] || '0', 10);
124
- } else {
125
- passed = (out.match(/^(?:ok \d+|✔)/gm) || []).length;
126
- failed = (out.match(/^(?:not ok \d+|✖)/gm) || []).length;
127
- }
128
-
129
- return {
130
- status: r.status === 0 ? 'pass' : 'fail',
131
- tests: passed + failed,
132
- failures: failed,
133
- };
134
- }
135
-
136
- // ---------------------------------------------------------------------------
137
- // Source integrity
138
- // ---------------------------------------------------------------------------
139
-
140
- function checkInstallHealth() {
141
- const required = [
142
- 'SKILL.md',
143
- 'references',
144
- 'tools/warden-lint.js',
145
- 'tools/verify-secrets.js',
146
- 'tools/get-context.js',
147
- ];
148
- const missing = required.filter(f => !fs.existsSync(path.join(ROOT, f)));
149
- return {
150
- status: missing.length === 0 ? 'pass' : 'fail',
151
- missing: missing.length > 0 ? missing : undefined,
152
- };
153
- }
154
-
155
- // ---------------------------------------------------------------------------
156
- // Runtime hook detection
157
- // ---------------------------------------------------------------------------
158
-
159
- function checkRuntimeHooks() {
160
- const home = os.homedir();
161
- const result = {};
162
-
163
- const claudeSettings = path.join(home, '.claude', 'settings.json');
164
- if (fs.existsSync(claudeSettings)) {
165
- try {
166
- const s = JSON.parse(fs.readFileSync(claudeSettings, 'utf8'));
167
- const hooks = (s?.hooks?.PreToolUse || [])
168
- .flatMap(m => m.hooks || [])
169
- .filter(h => String(h.description || '').startsWith('code-warden:'));
170
- if (hooks.length > 0) {
171
- const valid = hooks.every(h => h.args?.[0] && fs.existsSync(h.args[0]));
172
- result.claude = valid ? 'registered' : 'registered_broken';
173
- } else {
174
- result.claude = 'not_registered';
175
- }
176
- } catch { result.claude = 'error'; }
177
- } else {
178
- result.claude = 'not_configured';
179
- }
180
-
181
- const codexHooksPath = path.join(home, '.codex', 'hooks.json');
182
- if (fs.existsSync(codexHooksPath)) {
183
- try {
184
- const h = JSON.parse(fs.readFileSync(codexHooksPath, 'utf8'));
185
- const cw = (h?.PreToolUse || [])
186
- .filter(e => String(e.description || '').startsWith('code-warden:'));
187
- if (cw.length > 0) {
188
- const valid = cw.every(e => e.args?.[0] && fs.existsSync(e.args[0]));
189
- result.codex = valid ? 'registered' : 'registered_broken';
190
- } else {
191
- result.codex = 'not_registered';
192
- }
193
- } catch { result.codex = 'error'; }
194
- } else {
195
- result.codex = 'not_configured';
196
- }
197
-
198
- return result;
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // Report assembly
203
- // ---------------------------------------------------------------------------
204
-
205
- function generateReport(scanPath) {
206
- const repo = gitInfo();
207
- const { fileLength, secrets } = runScans(scanPath);
208
- const behavioralTests = checkTests();
209
- const installHealth = checkInstallHealth();
210
- const runtimeHooks = checkRuntimeHooks();
211
-
212
- const checks = { fileLength, secrets, behavioralTests, installHealth };
213
- const result = Object.values(checks).every(c => c.status === 'pass' || c.status === 'skip')
214
- ? 'pass' : 'fail';
215
-
216
- return {
217
- tool: 'code-warden',
218
- version: VERSION,
219
- timestamp: new Date().toISOString(),
220
- repository: { branch: repo.branch, commit: repo.commit },
221
- checks,
222
- governance: {
223
- scopeGate: 'session_only',
224
- planGate: 'session_only',
225
- runtimeHooks,
226
- },
227
- result,
228
- };
229
- }
230
-
231
- // ---------------------------------------------------------------------------
232
- // Markdown formatter
233
- // ---------------------------------------------------------------------------
234
-
235
- function formatMarkdown(report) {
236
- const badge = s => s === 'pass' ? 'PASS' : s === 'skip' ? 'SKIP' : 'FAIL';
237
- const hookLabel = (id) => {
238
- const s = report.governance.runtimeHooks[id];
239
- if (s === 'registered') return 'verified';
240
- if (s === 'registered_broken') return 'broken';
241
- if (s === 'not_registered') return 'none';
242
- return 'n/a';
243
- };
244
-
245
- const healthDetail = report.checks.installHealth.missing
246
- ? 'Missing: ' + report.checks.installHealth.missing.join(', ')
247
- : 'All source files present';
248
-
249
- const lines = [
250
- '## Code-Warden Governance Report',
251
- '',
252
- '| Check | Result | Details |',
253
- '|-------|--------|---------|',
254
- `| File length | ${badge(report.checks.fileLength.status)} | ${report.checks.fileLength.filesScanned} files scanned, ${report.checks.fileLength.violations} violations |`,
255
- `| Hardcoded credentials | ${badge(report.checks.secrets.status)} | ${report.checks.secrets.filesScanned} files scanned, ${report.checks.secrets.violations} violations |`,
256
- `| Behavioral tests | ${badge(report.checks.behavioralTests.status)} | ${report.checks.behavioralTests.tests} tests, ${report.checks.behavioralTests.failures} failures |`,
257
- `| Install health | ${badge(report.checks.installHealth.status)} | ${healthDetail} |`,
258
- `| Runtime hooks | — | Claude: ${hookLabel('claude')} / Codex: ${hookLabel('codex')} |`,
259
- '',
260
- `**Result:** ${report.result === 'pass' ? 'All governed checks passed.' : 'One or more checks failed.'}`,
261
- '',
262
- `> Generated by Code-Warden v${report.version} at ${report.timestamp}`,
263
- ];
264
-
265
- return lines.join('\n');
266
- }
267
-
268
- // ---------------------------------------------------------------------------
269
- // One-line summary (default mode stdout)
270
- // ---------------------------------------------------------------------------
271
-
272
- function formatSummary(report) {
273
- const c = report.checks;
274
- const parts = [
275
- `lint:${c.fileLength.status}`,
276
- `secrets:${c.secrets.status}`,
277
- `tests:${c.behavioralTests.status}`,
278
- `health:${c.installHealth.status}`,
279
- ];
280
- return `[CodeWarden] Governance report: ${report.result.toUpperCase()} (${parts.join(', ')})`;
281
- }
282
-
283
- // ---------------------------------------------------------------------------
284
- // Main
285
- // ---------------------------------------------------------------------------
286
-
287
- const { format, scanPath } = parseArgs(process.argv);
288
- const report = generateReport(scanPath);
289
-
290
- if (format === 'md') {
291
- console.log(formatMarkdown(report));
292
- } else if (format === 'json') {
293
- console.log(JSON.stringify(report, null, 2));
294
- } else {
295
- const json = JSON.stringify(report, null, 2);
296
- const outPath = path.resolve('.code-warden-report.json');
297
- fs.writeFileSync(outPath, json, 'utf8');
298
- console.log(formatSummary(report));
299
- console.log(`[CodeWarden] Report written to ${outPath}`);
300
- }
301
-
302
- process.exit(report.result === 'pass' ? 0 : 1);
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { spawnSync } = require('child_process');
8
+ const { countLines } = require('./lib/line-count');
9
+ const { collectFiles } = require('./lib/file-collection');
10
+ const { scanForSecrets } = require('./lib/secret-patterns');
11
+ const { loadConfig } = require('./lib/config');
12
+
13
+ const ROOT = path.join(__dirname, '..');
14
+ const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
15
+ const VERSION = PKG.version;
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // CLI
19
+ // ---------------------------------------------------------------------------
20
+
21
+ function parseArgs(argv) {
22
+ const args = argv.slice(2);
23
+ const formatArg = args.find(a => a.startsWith('--format='));
24
+ const format = formatArg ? formatArg.split('=')[1] : null;
25
+ const scanPath = args.find(a => !a.startsWith('--')) || '.';
26
+ return { format, scanPath };
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Git metadata
31
+ // ---------------------------------------------------------------------------
32
+
33
+ function gitInfo() {
34
+ const run = (gitArgs) => {
35
+ const r = spawnSync('git', gitArgs, { encoding: 'utf8', timeout: 5000 });
36
+ return r.status === 0 ? r.stdout.trim() : null;
37
+ };
38
+ return {
39
+ branch: run(['rev-parse', '--abbrev-ref', 'HEAD']),
40
+ commit: run(['rev-parse', '--short', 'HEAD']),
41
+ };
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // File length + secrets (single pass over all files)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ function runScans(scanPath) {
49
+ const { maxFileLength } = loadConfig();
50
+ const resolved = path.resolve(scanPath);
51
+
52
+ if (!fs.existsSync(resolved)) {
53
+ console.error(`[CodeWarden] Error: scan path not found: ${scanPath}`);
54
+ process.exit(1);
55
+ }
56
+
57
+ const files = [];
58
+ if (fs.statSync(resolved).isDirectory()) {
59
+ collectFiles(resolved, files);
60
+ } else {
61
+ files.push(resolved);
62
+ }
63
+
64
+ const lengthViolations = [];
65
+ const secretViolations = [];
66
+
67
+ for (const f of files) {
68
+ let content;
69
+ try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
70
+
71
+ const rel = path.relative(resolved, f);
72
+
73
+ const lineCount = countLines(content);
74
+ if (lineCount > maxFileLength) {
75
+ lengthViolations.push({ file: rel, lines: lineCount, limit: maxFileLength });
76
+ }
77
+
78
+ const hit = scanForSecrets(content);
79
+ if (hit) {
80
+ secretViolations.push({ file: rel, pattern: hit.label });
81
+ }
82
+ }
83
+
84
+ return {
85
+ fileLength: {
86
+ status: lengthViolations.length === 0 ? 'pass' : 'fail',
87
+ filesScanned: files.length,
88
+ violations: lengthViolations.length,
89
+ details: lengthViolations.length > 0 ? lengthViolations : undefined,
90
+ },
91
+ secrets: {
92
+ status: secretViolations.length === 0 ? 'pass' : 'fail',
93
+ filesScanned: files.length,
94
+ violations: secretViolations.length,
95
+ details: secretViolations.length > 0 ? secretViolations : undefined,
96
+ },
97
+ };
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Behavioral tests
102
+ // ---------------------------------------------------------------------------
103
+
104
+ function checkTests() {
105
+ const testScript = path.join(__dirname, 'tests', 'run-tests.js');
106
+ if (!fs.existsSync(testScript)) {
107
+ return { status: 'skip', tests: 0, failures: 0 };
108
+ }
109
+
110
+ const r = spawnSync(process.execPath, [testScript], {
111
+ encoding: 'utf8',
112
+ timeout: 30000,
113
+ cwd: ROOT,
114
+ });
115
+
116
+ const out = (r.stdout || '') + (r.stderr || '');
117
+ const passMatch = out.match(/pass\s+(\d+)/);
118
+ const failMatch = out.match(/fail\s+(\d+)/);
119
+
120
+ let passed, failed;
121
+ if (passMatch || failMatch) {
122
+ passed = parseInt(passMatch?.[1] || '0', 10);
123
+ failed = parseInt(failMatch?.[1] || '0', 10);
124
+ } else {
125
+ passed = (out.match(/^(?:ok \d+|✔)/gm) || []).length;
126
+ failed = (out.match(/^(?:not ok \d+|✖)/gm) || []).length;
127
+ }
128
+
129
+ return {
130
+ status: r.status === 0 ? 'pass' : 'fail',
131
+ tests: passed + failed,
132
+ failures: failed,
133
+ };
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Source integrity
138
+ // ---------------------------------------------------------------------------
139
+
140
+ function checkInstallHealth() {
141
+ const required = [
142
+ 'SKILL.md',
143
+ 'references',
144
+ 'tools/warden-lint.js',
145
+ 'tools/verify-secrets.js',
146
+ 'tools/get-context.js',
147
+ ];
148
+ const missing = required.filter(f => !fs.existsSync(path.join(ROOT, f)));
149
+ return {
150
+ status: missing.length === 0 ? 'pass' : 'fail',
151
+ missing: missing.length > 0 ? missing : undefined,
152
+ };
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Runtime hook detection
157
+ // ---------------------------------------------------------------------------
158
+
159
+ function checkRuntimeHooks() {
160
+ const home = os.homedir();
161
+ const result = {};
162
+
163
+ const claudeSettings = path.join(home, '.claude', 'settings.json');
164
+ if (fs.existsSync(claudeSettings)) {
165
+ try {
166
+ const s = JSON.parse(fs.readFileSync(claudeSettings, 'utf8'));
167
+ const hooks = (s?.hooks?.PreToolUse || [])
168
+ .flatMap(m => m.hooks || [])
169
+ .filter(h => String(h.description || '').startsWith('code-warden:'));
170
+ if (hooks.length > 0) {
171
+ const valid = hooks.every(h => h.args?.[0] && fs.existsSync(h.args[0]));
172
+ result.claude = valid ? 'registered' : 'registered_broken';
173
+ } else {
174
+ result.claude = 'not_registered';
175
+ }
176
+ } catch { result.claude = 'error'; }
177
+ } else {
178
+ result.claude = 'not_configured';
179
+ }
180
+
181
+ const codexHooksPath = path.join(home, '.codex', 'hooks.json');
182
+ if (fs.existsSync(codexHooksPath)) {
183
+ try {
184
+ const h = JSON.parse(fs.readFileSync(codexHooksPath, 'utf8'));
185
+ const cw = (h?.PreToolUse || [])
186
+ .filter(e => String(e.description || '').startsWith('code-warden:'));
187
+ if (cw.length > 0) {
188
+ const valid = cw.every(e => e.args?.[0] && fs.existsSync(e.args[0]));
189
+ result.codex = valid ? 'registered' : 'registered_broken';
190
+ } else {
191
+ result.codex = 'not_registered';
192
+ }
193
+ } catch { result.codex = 'error'; }
194
+ } else {
195
+ result.codex = 'not_configured';
196
+ }
197
+
198
+ return result;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Report assembly
203
+ // ---------------------------------------------------------------------------
204
+
205
+ function generateReport(scanPath) {
206
+ const repo = gitInfo();
207
+ const { fileLength, secrets } = runScans(scanPath);
208
+ const behavioralTests = checkTests();
209
+ const installHealth = checkInstallHealth();
210
+ const runtimeHooks = checkRuntimeHooks();
211
+
212
+ const checks = { fileLength, secrets, behavioralTests, installHealth };
213
+ const result = Object.values(checks).every(c => c.status === 'pass' || c.status === 'skip')
214
+ ? 'pass' : 'fail';
215
+
216
+ return {
217
+ tool: 'code-warden',
218
+ version: VERSION,
219
+ timestamp: new Date().toISOString(),
220
+ repository: { branch: repo.branch, commit: repo.commit },
221
+ checks,
222
+ governance: {
223
+ scopeGate: 'session_only',
224
+ planGate: 'session_only',
225
+ runtimeHooks,
226
+ },
227
+ result,
228
+ };
229
+ }
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // Markdown formatter
233
+ // ---------------------------------------------------------------------------
234
+
235
+ function formatMarkdown(report) {
236
+ const badge = s => s === 'pass' ? 'PASS' : s === 'skip' ? 'SKIP' : 'FAIL';
237
+ const hookLabel = (id) => {
238
+ const s = report.governance.runtimeHooks[id];
239
+ if (s === 'registered') return 'verified';
240
+ if (s === 'registered_broken') return 'broken';
241
+ if (s === 'not_registered') return 'none';
242
+ return 'n/a';
243
+ };
244
+
245
+ const healthDetail = report.checks.installHealth.missing
246
+ ? 'Missing: ' + report.checks.installHealth.missing.join(', ')
247
+ : 'All source files present';
248
+
249
+ const lines = [
250
+ '## Code-Warden Governance Report',
251
+ '',
252
+ '| Check | Result | Details |',
253
+ '|-------|--------|---------|',
254
+ `| File length | ${badge(report.checks.fileLength.status)} | ${report.checks.fileLength.filesScanned} files scanned, ${report.checks.fileLength.violations} violations |`,
255
+ `| Hardcoded credentials | ${badge(report.checks.secrets.status)} | ${report.checks.secrets.filesScanned} files scanned, ${report.checks.secrets.violations} violations |`,
256
+ `| Behavioral tests | ${badge(report.checks.behavioralTests.status)} | ${report.checks.behavioralTests.tests} tests, ${report.checks.behavioralTests.failures} failures |`,
257
+ `| Install health | ${badge(report.checks.installHealth.status)} | ${healthDetail} |`,
258
+ `| Runtime hooks | — | Claude: ${hookLabel('claude')} / Codex: ${hookLabel('codex')} |`,
259
+ '',
260
+ `**Result:** ${report.result === 'pass' ? 'All governed checks passed.' : 'One or more checks failed.'}`,
261
+ '',
262
+ `> Generated by Code-Warden v${report.version} at ${report.timestamp}`,
263
+ ];
264
+
265
+ return lines.join('\n');
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // One-line summary (default mode stdout)
270
+ // ---------------------------------------------------------------------------
271
+
272
+ function formatSummary(report) {
273
+ const c = report.checks;
274
+ const parts = [
275
+ `lint:${c.fileLength.status}`,
276
+ `secrets:${c.secrets.status}`,
277
+ `tests:${c.behavioralTests.status}`,
278
+ `health:${c.installHealth.status}`,
279
+ ];
280
+ return `[CodeWarden] Governance report: ${report.result.toUpperCase()} (${parts.join(', ')})`;
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Main
285
+ // ---------------------------------------------------------------------------
286
+
287
+ const { format, scanPath } = parseArgs(process.argv);
288
+ const report = generateReport(scanPath);
289
+
290
+ if (format === 'md') {
291
+ console.log(formatMarkdown(report));
292
+ } else if (format === 'json') {
293
+ console.log(JSON.stringify(report, null, 2));
294
+ } else {
295
+ const json = JSON.stringify(report, null, 2);
296
+ const outPath = path.resolve('.code-warden-report.json');
297
+ fs.writeFileSync(outPath, json, 'utf8');
298
+ console.log(formatSummary(report));
299
+ console.log(`[CodeWarden] Report written to ${outPath}`);
300
+ }
301
+
302
+ process.exit(report.result === 'pass' ? 0 : 1);