-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathharness-lint.js
More file actions
118 lines (107 loc) Β· 3.69 KB
/
Copy pathharness-lint.js
File metadata and controls
118 lines (107 loc) Β· 3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env node
/**
* harness-lint.js β Mechanical enforcement example for Harness Engineering
*
* Run: node templates/lint/harness-lint.js
* Add to CI: run this script and fail the pipeline if exit code != 0
*
* Customize the RULES array for your project's invariants.
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// βββ Configuration ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const RULES = [
{
id: 'agents-md-exists',
description: 'AGENTS.md must exist at repo root',
check: () => fs.existsSync('AGENTS.md'),
fix: 'Copy templates/AGENTS.md.template to AGENTS.md and fill it in.',
},
{
id: 'agents-md-size',
description: 'AGENTS.md must be under 150 lines (map, not manual)',
check: () => {
if (!fs.existsSync('AGENTS.md')) return true; // caught by previous rule
const lines = fs.readFileSync('AGENTS.md', 'utf8').split('\n').length;
return lines <= 150;
},
fix: 'AGENTS.md is too long. Move detailed content to referenced docs and keep the entry point concise.',
},
{
id: 'no-todos-in-source',
description: 'No TODO comments in source files (open issues instead)',
check: () => {
try {
const result = execSync(
'git ls-files | grep -E "\\.(js|ts|py|go|rs|java)$" | xargs grep -l "TODO" 2>/dev/null || true',
{ encoding: 'utf8' }
).trim();
return result === '';
} catch {
return true;
}
},
fix: 'Replace TODO comments with GitHub issues. Reference the issue number in a comment instead.',
},
{
id: 'no-secrets-in-source',
description: 'No hardcoded secrets or API keys in source',
check: () => {
try {
const patterns = [
'sk-[a-zA-Z0-9]{20,}',
'AKIA[0-9A-Z]{16}',
'ghp_[a-zA-Z0-9]{36}',
'xox[baprs]-[a-zA-Z0-9-]+',
];
const grepPattern = patterns.join('|');
const result = execSync(
`git ls-files | xargs grep -lE '${grepPattern}' 2>/dev/null || true`,
{ encoding: 'utf8' }
).trim();
return result === '';
} catch {
return true;
}
},
fix: 'Move secrets to environment variables. Add .env to .gitignore. Rotate any exposed keys immediately.',
},
{
id: 'planning-md-exists',
description: 'PLANNING.md must exist (current task state)',
check: () => fs.existsSync('PLANNING.md'),
fix: 'Copy templates/PLANNING.md.template to PLANNING.md and fill in current milestone.',
},
];
// βββ Runner βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const RED = '\x1b[31m';
const GREEN = '\x1b[32m';
const YELLOW = '\x1b[33m';
const RESET = '\x1b[0m';
let failures = 0;
console.log('\nHarness Lint\n' + 'β'.repeat(40));
for (const rule of RULES) {
let passed;
try {
passed = rule.check();
} catch (e) {
passed = false;
}
if (passed) {
console.log(`${GREEN}β${RESET} ${rule.id}`);
} else {
failures++;
console.log(`${RED}β${RESET} ${rule.id}`);
console.log(` ${YELLOW}Description:${RESET} ${rule.description}`);
console.log(` ${YELLOW}Fix:${RESET} ${rule.fix}`);
}
}
console.log('β'.repeat(40));
if (failures === 0) {
console.log(`${GREEN}All rules passed.${RESET}\n`);
process.exit(0);
} else {
console.log(`${RED}${failures} rule(s) failed.${RESET} Fix the issues above and re-run.\n`);
process.exit(1);
}