-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautoresearch.checks.sh
More file actions
executable file
·81 lines (74 loc) · 5.19 KB
/
autoresearch.checks.sh
File metadata and controls
executable file
·81 lines (74 loc) · 5.19 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
#!/usr/bin/env bash
set -euo pipefail
branch_name="$(git branch --show-current 2>/dev/null || echo unknown)"
if [[ ! "$branch_name" =~ ^autoresearch/ && ! "$branch_name" =~ ^(feat|fix|perf|refactor|test|docs|chore|build|ci)/autoresearch- ]]; then
echo "Refusing autoresearch checks on non-dedicated branch: $branch_name" >&2
exit 1
fi
if grep -Eq 'assert\.ok\(true\)|METRIC\s+kapi_architecture_score=100|echo\s+.*METRIC|exit 0\s*(#.*)?$|\|\|\s*true|--passWithNoTests' package.json scripts/* 2>/dev/null; then
echo "Refusing autoresearch checks because anti-gaming patterns were detected." >&2
exit 1
fi
if node <<'NODE'
const fs = require('node:fs');
const path = require('node:path');
const obsoleteCommands = ['/kapi-clarify','/kapi-plan','/kapi-execute','/kapi-review','/kapi-tdd','/kapi-ultrawork','/kapi-autopilot','/kapi-ralplan','/kapi-autoresearch-plan','/kapi-autoresearch-loop','/kapi-resume','/kapi-validate','/kapi-artifact','/kapi-evidence','/kapi-complete','/kapi-fail','/kapi-tmux'];
function walk(dir, files = []) { if (!fs.existsSync(dir)) return files; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (entry.name === '.ilchul' || entry.name === '.kapi') continue; const full = path.join(dir, entry.name); if (entry.isDirectory()) walk(full, files); else files.push(full); } return files; }
const text = [...walk('src'), ...walk('test')].filter((file) => /\.(ts|js|json|md)$/.test(file)).map((file) => fs.readFileSync(file, 'utf8')).join('\n');
process.exit(obsoleteCommands.some((command) => text.includes(command)) ? 1 : 0);
NODE
then
:
else
echo "Refusing autoresearch checks because obsolete Kapi commands remain in source/tests." >&2
exit 1
fi
if grep -REiq '(legacy|compat(ibility)?|removed|obsolete|shadow|hidden)[[:space:]]+(alias|redirect|fallback|shim|path|command)|(alias|redirect|fallback|shim)[[:space:]]+for[[:space:]]+(legacy|compat(ibility)?|removed|obsolete)' src 2>/dev/null; then
echo "Refusing autoresearch checks because legacy/fallback command behavior was detected." >&2
exit 1
fi
baseline="$(git merge-base HEAD dev 2>/dev/null || echo HEAD)"
changed_files="$({ git diff --name-only "$baseline...HEAD" 2>/dev/null; git status --porcelain | awk '{print substr($0,4)}'; } | sort -u)"
if [[ -n "$changed_files" ]] && ! grep -Eq '^(src|test)/|^(index|eslint\.config|tsconfig|vite\.config)\.(ts|js|mjs|json)$|^package(-lock)?\.json$' <<<"$changed_files"; then
echo "Refusing docs-only autoresearch change; implementation or test changes are required for keep-worthy checks." >&2
exit 1
fi
if node <<'NODE'
const fs = require('node:fs');
const { execFileSync, execSync } = require('node:child_process');
function read(file) { try { return fs.readFileSync(file, 'utf8'); } catch { return ''; } }
function baselineRef() { try { return execFileSync('git', ['merge-base', 'HEAD', 'dev'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || 'HEAD'; } catch { return 'HEAD'; } }
const baseline = baselineRef();
function head(file) { try { return execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch { return ''; } }
function directMetricHardcode(file) {
return read(file).split('\n').some((line) => {
if (/antiGaming|grep -Eq|METRIC\\s|countRegex/.test(line)) return false;
return /METRIC\s+kapi_architecture_score\s*=\s*(?:100|[0-9]+)/.test(line) || /echo\s+.*METRIC/.test(line);
});
}
const current = JSON.parse(read('package.json') || '{}');
const before = JSON.parse(head('package.json') || '{}');
const protectedScripts = ['verify', 'test', 'check', 'quality', 'quality:budgets', 'quality:strict'];
for (const name of protectedScripts) {
const oldScript = before.scripts?.[name];
const newScript = current.scripts?.[name];
if (!oldScript && !newScript) continue;
if (!newScript || (/\|\|\s*true|true\s*$|--passWithNoTests|--runInBand=false|echo\s+|exit\s+0/.test(newScript)) || (oldScript !== newScript && newScript.length < oldScript.length * 0.7)) process.exit(1);
}
if (directMetricHardcode('autoresearch.sh') || directMetricHardcode('autoresearch.checks.sh')) process.exit(1);
const status = execSync('git status --porcelain', { encoding: 'utf8' });
const diffStatus = execFileSync('git', ['diff', '--name-status', `${baseline}...HEAD`], { encoding: 'utf8' });
if (/^\s*(D|R)\s+test\//m.test(status) || /^(D|R\d*)\s+test\//m.test(diffStatus)) process.exit(1);
const testFiles = execFileSync('find', ['test', '-type', 'f', '(', '-name', '*.ts', '-o', '-name', '*.js', ')'], { encoding: 'utf8' }).trim().split('\n').filter(Boolean);
const currentSkipCount = testFiles.reduce((sum, file) => sum + ((read(file).match(/\.(skip|only)\s*\(/g) || []).length), 0);
const headSkipCount = testFiles.reduce((sum, file) => sum + ((head(file).match(/\.(skip|only)\s*\(/g) || []).length), 0);
const weakAssertionCount = testFiles.reduce((sum, file) => sum + ((read(file).match(/assert\.(?:ok|strictEqual|deepStrictEqual)\s*\([^\n]*(?:true\s*\)|,\s*true\s*\)|,\s*[^,)]+\s*,\s*[^)]*TODO)/g) || []).length), 0);
if (currentSkipCount > headSkipCount || weakAssertionCount > 0) process.exit(1);
NODE
then
:
else
echo "Refusing autoresearch checks because verification weakening was detected." >&2
exit 1
fi
npm run verify --silent