@miller-tech/uap 2.1.6 → 2.11.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/config/capacity-policy.json +32 -0
- package/config/classify-thresholds.json +9 -0
- package/config/supervise-policy.json +13 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +87 -2
- package/dist/bin/cli.js.map +1 -1
- package/dist/capacity/policy.d.ts +67 -0
- package/dist/capacity/policy.d.ts.map +1 -0
- package/dist/capacity/policy.js +133 -0
- package/dist/capacity/policy.js.map +1 -0
- package/dist/capacity/probe.d.ts +23 -0
- package/dist/capacity/probe.d.ts.map +1 -0
- package/dist/capacity/probe.js +93 -0
- package/dist/capacity/probe.js.map +1 -0
- package/dist/classify/baseline.d.ts +24 -0
- package/dist/classify/baseline.d.ts.map +1 -0
- package/dist/classify/baseline.js +140 -0
- package/dist/classify/baseline.js.map +1 -0
- package/dist/classify/index.d.ts +30 -0
- package/dist/classify/index.d.ts.map +1 -0
- package/dist/classify/index.js +84 -0
- package/dist/classify/index.js.map +1 -0
- package/dist/classify/shadow.d.ts +34 -0
- package/dist/classify/shadow.d.ts.map +1 -0
- package/dist/classify/shadow.js +33 -0
- package/dist/classify/shadow.js.map +1 -0
- package/dist/classify/types.d.ts +64 -0
- package/dist/classify/types.d.ts.map +1 -0
- package/dist/classify/types.js +37 -0
- package/dist/classify/types.js.map +1 -0
- package/dist/cli/classify.d.ts +24 -0
- package/dist/cli/classify.d.ts.map +1 -0
- package/dist/cli/classify.js +94 -0
- package/dist/cli/classify.js.map +1 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +26 -0
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +49 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/review.d.ts +12 -0
- package/dist/cli/review.d.ts.map +1 -0
- package/dist/cli/review.js +118 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/supervise.d.ts +8 -0
- package/dist/cli/supervise.d.ts.map +1 -0
- package/dist/cli/supervise.js +76 -0
- package/dist/cli/supervise.js.map +1 -0
- package/dist/delivery/convergence-loop.d.ts +8 -0
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +1 -0
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/delivery-result.d.ts.map +1 -1
- package/dist/delivery/delivery-result.js +5 -0
- package/dist/delivery/delivery-result.js.map +1 -1
- package/dist/delivery/gate-evidence.d.ts +143 -0
- package/dist/delivery/gate-evidence.d.ts.map +1 -0
- package/dist/delivery/gate-evidence.js +239 -0
- package/dist/delivery/gate-evidence.js.map +1 -0
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1 -0
- package/dist/delivery/index.js.map +1 -1
- package/dist/evals/routing-corpus.d.ts +35 -0
- package/dist/evals/routing-corpus.d.ts.map +1 -0
- package/dist/evals/routing-corpus.js +142 -0
- package/dist/evals/routing-corpus.js.map +1 -0
- package/dist/evals/routing-eval.d.ts +104 -0
- package/dist/evals/routing-eval.d.ts.map +1 -0
- package/dist/evals/routing-eval.js +231 -0
- package/dist/evals/routing-eval.js.map +1 -0
- package/dist/evals/tfidf.d.ts +35 -0
- package/dist/evals/tfidf.d.ts.map +1 -0
- package/dist/evals/tfidf.js +132 -0
- package/dist/evals/tfidf.js.map +1 -0
- package/dist/review/prepass.d.ts +88 -0
- package/dist/review/prepass.d.ts.map +1 -0
- package/dist/review/prepass.js +428 -0
- package/dist/review/prepass.js.map +1 -0
- package/dist/review/visual-captures.d.ts +60 -0
- package/dist/review/visual-captures.d.ts.map +1 -0
- package/dist/review/visual-captures.js +222 -0
- package/dist/review/visual-captures.js.map +1 -0
- package/dist/supervise/assess.d.ts +73 -0
- package/dist/supervise/assess.d.ts.map +1 -0
- package/dist/supervise/assess.js +215 -0
- package/dist/supervise/assess.js.map +1 -0
- package/dist/supervise/config.d.ts +13 -0
- package/dist/supervise/config.d.ts.map +1 -0
- package/dist/supervise/config.js +81 -0
- package/dist/supervise/config.js.map +1 -0
- package/dist/supervise/loop.d.ts +78 -0
- package/dist/supervise/loop.d.ts.map +1 -0
- package/dist/supervise/loop.js +311 -0
- package/dist/supervise/loop.js.map +1 -0
- package/dist/supervise/observe.d.ts +31 -0
- package/dist/supervise/observe.d.ts.map +1 -0
- package/dist/supervise/observe.js +172 -0
- package/dist/supervise/observe.js.map +1 -0
- package/dist/supervise/policy.d.ts +16 -0
- package/dist/supervise/policy.d.ts.map +1 -0
- package/dist/supervise/policy.js +147 -0
- package/dist/supervise/policy.js.map +1 -0
- package/dist/supervise/types.d.ts +109 -0
- package/dist/supervise/types.d.ts.map +1 -0
- package/dist/supervise/types.js +11 -0
- package/dist/supervise/types.js.map +1 -0
- package/docs/INDEX.md +9 -3
- package/docs/architecture/adr/0001-automode-risk-floor.md +75 -0
- package/docs/architecture/adr/0002-evidence-bound-ship.md +81 -0
- package/docs/guides/CAPACITY_POLICY.md +98 -0
- package/docs/performance/automode-risk-calibration.md +72 -0
- package/docs/performance/reliability-ladder.md +78 -0
- package/docs/plans/system1-uplift-2026-09-18.md +191 -0
- package/docs/reference/automode-risk.md +134 -0
- package/docs/reference/evidence-bound-ship.md +145 -0
- package/docs/reference/review-prepass.md +77 -0
- package/docs/reference/routing-evals.md +71 -0
- package/docs/reference/semantic-supervisor.md +140 -0
- package/docs/reference/system1-classifier.md +103 -0
- package/docs/reference/visual-captures.md +113 -0
- package/package.json +5 -3
- package/scripts/run-anthropic-proxy-continuity.sh +36 -0
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_self_protect.py +25 -2
- package/src/policies/enforcers/evidence_bound_ship.py +257 -0
- package/src/policies/enforcers/expert_review_required.py +120 -0
- package/src/policies/schemas/policies/evidence-bound-ship.md +106 -0
- package/src/policies/schemas/policies/visual-verification.md +8 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +782 -19
- package/tools/agents/scripts/project_telemetry.py +52 -0
- package/tools/agents/scripts/tool_risk.py +789 -0
- package/tools/agents/scripts/tool_risk_calibrate.py +255 -0
- package/tools/agents/tests/fixtures/tool_risk_cases.json +124 -0
- package/tools/agents/tests/test_enforcer_escape_hatches.py +13 -0
- package/tools/agents/tests/test_gate_evidence.py +56 -0
- package/tools/agents/tests/test_tool_risk.py +521 -0
- package/tools/agents/tests/test_verbatim_compact.py +629 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DETERMINISTIC REVIEW PRE-PASS (uplift 0.2)
|
|
3
|
+
*
|
|
4
|
+
* A ruleset scanner that runs BEFORE the LLM reviewers in the parallel
|
|
5
|
+
* review protocol (open-code-review precedent: deterministic pipelines first,
|
|
6
|
+
* LLM second). Reviewers receive pre-filtered, line-anchored findings so
|
|
7
|
+
* their tokens go to judgment, not pattern-matching.
|
|
8
|
+
*
|
|
9
|
+
* Design stance: precision over recall. Every rule here fires on a pattern
|
|
10
|
+
* that is almost always worth a human/reviewer glance; ambiguous heuristics
|
|
11
|
+
* belong to the LLM reviewers, not to this pass. Findings are advisory input
|
|
12
|
+
* to review — the blocking behavior stays in the expert-review enforcer,
|
|
13
|
+
* which is unchanged.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
|
|
16
|
+
import { execFileSync } from 'child_process';
|
|
17
|
+
import { join } from 'path';
|
|
18
|
+
/** git invocation without a shell — this module must pass its own scanner. */
|
|
19
|
+
function git(dir, args) {
|
|
20
|
+
try {
|
|
21
|
+
return execFileSync('git', args, { cwd: dir, encoding: 'utf-8', timeout: 10_000 }).trim();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const CODE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.js', '.mjs', '.cjs', '.jsx', '.py']);
|
|
28
|
+
/**
|
|
29
|
+
* Line-anchored rules. Keep each one boringly precise — a finding that cries
|
|
30
|
+
* wolf trains reviewers to ignore the whole pass.
|
|
31
|
+
*/
|
|
32
|
+
export const LINE_RULES = [
|
|
33
|
+
{
|
|
34
|
+
name: 'aws-access-key',
|
|
35
|
+
category: 'secrets',
|
|
36
|
+
severity: 'high',
|
|
37
|
+
pattern: /\bAKIA[0-9A-Z]{16}\b/,
|
|
38
|
+
message: 'hardcoded AWS access key id',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'github-token',
|
|
42
|
+
category: 'secrets',
|
|
43
|
+
severity: 'high',
|
|
44
|
+
pattern: /\b(?:gh[posur]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{22,})\b/,
|
|
45
|
+
message: 'hardcoded GitHub token',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'slack-token',
|
|
49
|
+
category: 'secrets',
|
|
50
|
+
severity: 'high',
|
|
51
|
+
pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
|
52
|
+
message: 'hardcoded Slack token',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'llm-api-key',
|
|
56
|
+
category: 'secrets',
|
|
57
|
+
severity: 'high',
|
|
58
|
+
pattern: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/,
|
|
59
|
+
message: 'hardcoded LLM provider API key',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'npm-token',
|
|
63
|
+
category: 'secrets',
|
|
64
|
+
severity: 'high',
|
|
65
|
+
pattern: /\bnpm_[A-Za-z0-9]{20,}\b/,
|
|
66
|
+
message: 'hardcoded npm token',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'jwt-token',
|
|
70
|
+
category: 'secrets',
|
|
71
|
+
severity: 'high',
|
|
72
|
+
pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\b/,
|
|
73
|
+
message: 'hardcoded JWT',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'private-key-block',
|
|
77
|
+
category: 'secrets',
|
|
78
|
+
severity: 'high',
|
|
79
|
+
pattern: /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY(?: BLOCK)?-----/,
|
|
80
|
+
message: 'private key material committed to source',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'hardcoded-secret',
|
|
84
|
+
category: 'secrets',
|
|
85
|
+
severity: 'high',
|
|
86
|
+
// assignment of a quoted literal (>=8 chars) to a secret-named variable
|
|
87
|
+
pattern: /\b(?:password|passwd|secret|api[_-]?key|auth[_-]?token)\b\s*[:=]\s*['"][^'"\n]{8,}['"]/i,
|
|
88
|
+
message: 'secret-looking value assigned as a string literal',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'eval-call',
|
|
92
|
+
category: 'injection',
|
|
93
|
+
severity: 'high',
|
|
94
|
+
pattern: /(?<![\w.])eval\s*\(/,
|
|
95
|
+
// Message deliberately avoids the literal trigger text so this scanner's
|
|
96
|
+
// own source survives its own dogfood scan.
|
|
97
|
+
message: 'dynamic string execution (eval-family) — injection surface',
|
|
98
|
+
codeOnly: true,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'new-function',
|
|
102
|
+
category: 'injection',
|
|
103
|
+
severity: 'high',
|
|
104
|
+
pattern: /new\s+Function\s*\(/,
|
|
105
|
+
message: 'dynamic code construction via the Function constructor',
|
|
106
|
+
codeOnly: true,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'exec-interpolated',
|
|
110
|
+
category: 'injection',
|
|
111
|
+
severity: 'high',
|
|
112
|
+
pattern: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{/,
|
|
113
|
+
message: 'child_process exec with an interpolated template string (shell injection)',
|
|
114
|
+
codeOnly: true,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'exec-concat',
|
|
118
|
+
category: 'injection',
|
|
119
|
+
severity: 'high',
|
|
120
|
+
pattern: /\bexec(?:Sync)?\s*\(\s*['"][^'"]*['"]\s*\+/,
|
|
121
|
+
message: 'child_process exec with string concatenation (shell injection)',
|
|
122
|
+
codeOnly: true,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'shell-true',
|
|
126
|
+
category: 'injection',
|
|
127
|
+
severity: 'medium',
|
|
128
|
+
pattern: /\bshell\s*[:=]\s*(?:True|true)\b/,
|
|
129
|
+
message: 'command routed through a shell (shell flag) — injection surface',
|
|
130
|
+
codeOnly: true,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'sql-template-interpolation',
|
|
134
|
+
category: 'sql',
|
|
135
|
+
severity: 'high',
|
|
136
|
+
pattern: /`\s*(?:SELECT|INSERT\s+INTO|UPDATE\s|DELETE\s+FROM)\b[^`]*\$\{/i,
|
|
137
|
+
message: 'SQL built by template interpolation — use parameterized queries',
|
|
138
|
+
codeOnly: true,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'sql-string-concat',
|
|
142
|
+
category: 'sql',
|
|
143
|
+
severity: 'medium',
|
|
144
|
+
pattern: /['"]\s*(?:SELECT|INSERT\s+INTO|UPDATE\s|DELETE\s+FROM)\b[^'"\n]*['"]\s*\+/i,
|
|
145
|
+
message: 'SQL built by string concatenation — use parameterized queries',
|
|
146
|
+
codeOnly: true,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'innerhtml-assign',
|
|
150
|
+
category: 'injection',
|
|
151
|
+
severity: 'medium',
|
|
152
|
+
pattern: /\.innerHTML\s*=(?!=)/,
|
|
153
|
+
message: 'innerHTML assignment — XSS surface unless the value is sanitized',
|
|
154
|
+
codeOnly: true,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
// \w* so this rule's own definition line does not match itself
|
|
158
|
+
name: 'dangerously-set-innerhtml',
|
|
159
|
+
category: 'injection',
|
|
160
|
+
severity: 'medium',
|
|
161
|
+
pattern: /dangerously\w*InnerHTML/,
|
|
162
|
+
message: 'React raw-HTML injection prop — XSS surface unless sanitized',
|
|
163
|
+
codeOnly: true,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'document-write',
|
|
167
|
+
category: 'injection',
|
|
168
|
+
severity: 'medium',
|
|
169
|
+
pattern: /\bdocument\.write\s*\(/,
|
|
170
|
+
message: 'document.write — XSS and parser-blocking surface',
|
|
171
|
+
codeOnly: true,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'insert-adjacent-html',
|
|
175
|
+
category: 'injection',
|
|
176
|
+
severity: 'medium',
|
|
177
|
+
pattern: /\.insertAdjacentHTML\s*\(/,
|
|
178
|
+
message: 'insertAdjacentHTML — XSS surface unless the value is sanitized',
|
|
179
|
+
codeOnly: true,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'cors-wildcard',
|
|
183
|
+
category: 'injection',
|
|
184
|
+
severity: 'medium',
|
|
185
|
+
pattern: /Access-Control-Allow-Origin['"]?\s*[:,]\s*['"]?\*/,
|
|
186
|
+
message: 'CORS wildcard origin — verify this endpoint serves no private data',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'cors-origin-wildcard',
|
|
190
|
+
category: 'injection',
|
|
191
|
+
severity: 'medium',
|
|
192
|
+
pattern: /\borigin\s*:\s*['"]\*['"]/,
|
|
193
|
+
// Message avoids the literal trigger text (dogfood-clean, like the others)
|
|
194
|
+
message: 'CORS wildcard via middleware options — verify no private data is served',
|
|
195
|
+
codeOnly: true,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
const MAX_SNIPPET_LEN = 120;
|
|
199
|
+
/**
|
|
200
|
+
* Redact secret material from a finding snippet. The artifact's purpose is to
|
|
201
|
+
* be pasted into LLM reviewer prompts — a secrets scanner must not multiply
|
|
202
|
+
* copies of what it detects. The file:line anchor is enough for a reviewer
|
|
203
|
+
* to inspect the secret in place.
|
|
204
|
+
*/
|
|
205
|
+
export function redactSecrets(snippet) {
|
|
206
|
+
return snippet
|
|
207
|
+
.replace(/(['"])[^'"\n]{4,}\1/g, '$1***redacted***$1')
|
|
208
|
+
.replace(/\b(?:AKIA[0-9A-Z]{4}[0-9A-Z]{12}|gh[posur]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|xox[baprs]-[A-Za-z0-9-]{4,}|sk-(?:ant-)?[A-Za-z0-9_-]{8,}|npm_[A-Za-z0-9]{8,}|eyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{3,})\b/g, '***redacted***');
|
|
209
|
+
}
|
|
210
|
+
function fileExtension(file) {
|
|
211
|
+
const base = file.split('/').pop() ?? '';
|
|
212
|
+
const dot = base.lastIndexOf('.');
|
|
213
|
+
return dot >= 0 ? base.slice(dot) : '';
|
|
214
|
+
}
|
|
215
|
+
/** Scan one file's content. `file` is the repo-relative path used in findings. */
|
|
216
|
+
export function scanFileContent(file, content) {
|
|
217
|
+
const ext = fileExtension(file);
|
|
218
|
+
const isCode = CODE_EXTENSIONS.has(ext);
|
|
219
|
+
const findings = [];
|
|
220
|
+
const lines = content.split('\n');
|
|
221
|
+
for (let i = 0; i < lines.length; i++) {
|
|
222
|
+
const text = lines[i];
|
|
223
|
+
for (const rule of LINE_RULES) {
|
|
224
|
+
if (rule.codeOnly && !isCode)
|
|
225
|
+
continue;
|
|
226
|
+
if (rule.pattern.test(text)) {
|
|
227
|
+
const snippet = text.trim().slice(0, MAX_SNIPPET_LEN);
|
|
228
|
+
findings.push({
|
|
229
|
+
rule: rule.name,
|
|
230
|
+
category: rule.category,
|
|
231
|
+
severity: rule.severity,
|
|
232
|
+
file,
|
|
233
|
+
line: i + 1,
|
|
234
|
+
snippet: rule.category === 'secrets' ? redactSecrets(snippet) : snippet,
|
|
235
|
+
message: rule.message,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
findings.push(...scanEmptyCatches(file, content, isCode));
|
|
241
|
+
findings.push(...scanPythonErrorSwallowing(file, lines, isCode));
|
|
242
|
+
findings.push(...scanConcurrencySmells(file, content, isCode));
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Whole-content pass for empty catch blocks — a per-line rule can never see
|
|
247
|
+
* the idiomatic multi-line form (`} catch (e) {` newline `}`), which is also
|
|
248
|
+
* the easiest way to deliberately evade the gate.
|
|
249
|
+
*/
|
|
250
|
+
function scanEmptyCatches(file, content, isCode) {
|
|
251
|
+
if (!isCode || file.endsWith('.py'))
|
|
252
|
+
return [];
|
|
253
|
+
const findings = [];
|
|
254
|
+
const pattern = /\bcatch\s*(?:\([^)]*\))?\s*\{\s*\}/g;
|
|
255
|
+
for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) {
|
|
256
|
+
const line = content.slice(0, match.index).split('\n').length;
|
|
257
|
+
findings.push({
|
|
258
|
+
rule: 'empty-catch',
|
|
259
|
+
category: 'error-handling',
|
|
260
|
+
severity: 'medium',
|
|
261
|
+
file,
|
|
262
|
+
line,
|
|
263
|
+
snippet: match[0].replace(/\s+/g, ' ').slice(0, MAX_SNIPPET_LEN),
|
|
264
|
+
message: 'empty catch swallows the error with no trace',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return findings;
|
|
268
|
+
}
|
|
269
|
+
/** Python `except: ... pass` pairs — a two-line pattern the line rules can't see. */
|
|
270
|
+
function scanPythonErrorSwallowing(file, lines, isCode) {
|
|
271
|
+
if (!isCode || !file.endsWith('.py'))
|
|
272
|
+
return [];
|
|
273
|
+
const findings = [];
|
|
274
|
+
for (let i = 0; i < lines.length; i++) {
|
|
275
|
+
// single-line form: except ...: pass
|
|
276
|
+
if (/^\s*except\b[^:\n]*:\s*(?:pass|\.\.\.)\s*(?:#.*)?$/.test(lines[i])) {
|
|
277
|
+
findings.push({
|
|
278
|
+
rule: 'except-pass',
|
|
279
|
+
category: 'error-handling',
|
|
280
|
+
severity: 'medium',
|
|
281
|
+
file,
|
|
282
|
+
line: i + 1,
|
|
283
|
+
snippet: lines[i].trim().slice(0, MAX_SNIPPET_LEN),
|
|
284
|
+
message: 'except block whose entire body is pass — error swallowed silently',
|
|
285
|
+
});
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (i >= lines.length - 1)
|
|
289
|
+
break;
|
|
290
|
+
if (!/^\s*except\b[^:]*:\s*(?:#.*)?$/.test(lines[i]))
|
|
291
|
+
continue;
|
|
292
|
+
const next = lines[i + 1].trim();
|
|
293
|
+
if (next === 'pass' || next === '...') {
|
|
294
|
+
findings.push({
|
|
295
|
+
rule: 'except-pass',
|
|
296
|
+
category: 'error-handling',
|
|
297
|
+
severity: 'medium',
|
|
298
|
+
file,
|
|
299
|
+
line: i + 1,
|
|
300
|
+
snippet: lines[i].trim().slice(0, MAX_SNIPPET_LEN),
|
|
301
|
+
message: 'except block whose entire body is pass — error swallowed silently',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return findings;
|
|
306
|
+
}
|
|
307
|
+
/** File-level: Python threads without any lock in the same file. */
|
|
308
|
+
function scanConcurrencySmells(file, content, isCode) {
|
|
309
|
+
if (!isCode || !file.endsWith('.py'))
|
|
310
|
+
return [];
|
|
311
|
+
if (!/\bthreading\.Thread\s*\(/.test(content))
|
|
312
|
+
return [];
|
|
313
|
+
if (/\b(?:threading\.)?Lock\s*\(/.test(content))
|
|
314
|
+
return [];
|
|
315
|
+
return [
|
|
316
|
+
{
|
|
317
|
+
rule: 'threading-no-lock',
|
|
318
|
+
category: 'concurrency',
|
|
319
|
+
severity: 'medium',
|
|
320
|
+
file,
|
|
321
|
+
line: 1,
|
|
322
|
+
snippet: 'threading.Thread without any Lock in file',
|
|
323
|
+
message: 'threads spawned but no Lock in this file — check shared-state access',
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
}
|
|
327
|
+
/** Resolve the target file list for a pre-pass run. */
|
|
328
|
+
export function collectPrePassTargets(options) {
|
|
329
|
+
const dir = options.projectDir ?? process.cwd();
|
|
330
|
+
if (options.files && options.files.length > 0)
|
|
331
|
+
return options.files;
|
|
332
|
+
// Default: everything the branch touched, plus uncommitted/untracked work —
|
|
333
|
+
// the same surface the reviewers are about to read.
|
|
334
|
+
const found = new Set();
|
|
335
|
+
const collect = (args) => {
|
|
336
|
+
const out = git(dir, args);
|
|
337
|
+
if (out === null)
|
|
338
|
+
return;
|
|
339
|
+
for (const line of out.split('\n')) {
|
|
340
|
+
const f = line.trim();
|
|
341
|
+
if (f && !f.includes('node_modules/'))
|
|
342
|
+
found.add(f);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
let baseResolved = false;
|
|
346
|
+
for (const base of ['origin/master', 'origin/main', 'master', 'main']) {
|
|
347
|
+
if (git(dir, ['rev-parse', '--verify', base]) !== null) {
|
|
348
|
+
collect(['diff', '--name-only', `${base}...HEAD`]);
|
|
349
|
+
baseResolved = true;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (!baseResolved)
|
|
354
|
+
collect(['diff', '--name-only', 'HEAD~1..HEAD']);
|
|
355
|
+
collect(['diff', '--name-only', '--cached']);
|
|
356
|
+
collect(['diff', '--name-only']);
|
|
357
|
+
collect(['ls-files', '--others', '--exclude-standard']);
|
|
358
|
+
return [...found].filter((f) => existsSync(join(dir, f)));
|
|
359
|
+
}
|
|
360
|
+
/** Scan a resolved target list. */
|
|
361
|
+
export function runPrePass(options) {
|
|
362
|
+
const dir = options.projectDir ?? process.cwd();
|
|
363
|
+
const targets = collectPrePassTargets(options);
|
|
364
|
+
const findings = [];
|
|
365
|
+
const filesScanned = [];
|
|
366
|
+
for (const file of targets) {
|
|
367
|
+
let content;
|
|
368
|
+
try {
|
|
369
|
+
content = readFileSync(join(dir, file), 'utf-8');
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
continue; // binary or unreadable — not this pass's job
|
|
373
|
+
}
|
|
374
|
+
filesScanned.push(file);
|
|
375
|
+
findings.push(...scanFileContent(file, content));
|
|
376
|
+
}
|
|
377
|
+
return { findings, filesScanned };
|
|
378
|
+
}
|
|
379
|
+
/** Percent-encode branch to a filename slug — mirrors slug_for() in the
|
|
380
|
+
* expert_review_required enforcer exactly (the artifact must land where the
|
|
381
|
+
* enforcer looks). */
|
|
382
|
+
export function branchSlug(branch) {
|
|
383
|
+
return branch.replace(/%/g, '%25').replace(/\//g, '%2F');
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Write the pre-pass artifact to .uap/reviews/<branch-slug>.pre-pass.json.
|
|
387
|
+
*
|
|
388
|
+
* Deliberately a SIBLING of the review artifact (<slug>.json), not a block
|
|
389
|
+
* inside it: the expert-review enforcer treats the existence of <slug>.json
|
|
390
|
+
* as "a review happened", and every substantive check in it is conditional on
|
|
391
|
+
* keys the pre-pass never writes. A pre-pass-only <slug>.json would satisfy
|
|
392
|
+
* the blocking gate with zero reviewers — so this advisory pass must never
|
|
393
|
+
* create that file. Consolidation reads this sibling and merges its findings
|
|
394
|
+
* into the real artifact along with the verdict.
|
|
395
|
+
*/
|
|
396
|
+
export function writePrePassArtifact(projectDir, findings, filesScanned) {
|
|
397
|
+
const branch = git(projectDir, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
398
|
+
if (!branch || branch === 'HEAD')
|
|
399
|
+
return null; // detached/non-git: nowhere to write
|
|
400
|
+
const head = git(projectDir, ['rev-parse', 'HEAD']);
|
|
401
|
+
const dir = join(projectDir, '.uap', 'reviews');
|
|
402
|
+
mkdirSync(dir, { recursive: true });
|
|
403
|
+
const path = join(dir, `${branchSlug(branch)}.pre-pass.json`);
|
|
404
|
+
let existing = {};
|
|
405
|
+
if (existsSync(path)) {
|
|
406
|
+
try {
|
|
407
|
+
existing = JSON.parse(readFileSync(path, 'utf-8'));
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
existing = {}; // corrupt artifact: overwrite, don't crash the review
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const artifact = {
|
|
414
|
+
ran_at: new Date().toISOString(),
|
|
415
|
+
head,
|
|
416
|
+
files_scanned: filesScanned,
|
|
417
|
+
finding_count: findings.length,
|
|
418
|
+
findings,
|
|
419
|
+
};
|
|
420
|
+
const merged = { ...existing, pre_pass: artifact };
|
|
421
|
+
// Atomic write: a crash mid-write must not destroy reviewer-written keys —
|
|
422
|
+
// a torn artifact would be reset to {} on next read, erasing a review.
|
|
423
|
+
const tmpPath = `${path}.tmp`;
|
|
424
|
+
writeFileSync(tmpPath, JSON.stringify(merged, null, 2) + '\n');
|
|
425
|
+
renameSync(tmpPath, path);
|
|
426
|
+
return { path, artifact };
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=prepass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepass.js","sourceRoot":"","sources":["../../src/review/prepass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** UI surface definition — mirrored (with matching case handling) by
|
|
2
|
+
* UI_EXT/UI_DIR_PREFIXES in src/policies/enforcers/expert_review_required.py
|
|
3
|
+
* and visual_verification.py. Keep all three in sync; the parity test in
|
|
4
|
+
* test/visual-captures.test.ts pins the intended surface. */
|
|
5
|
+
export declare const UI_EXT: Set<string>;
|
|
6
|
+
export declare const UI_DIR_PREFIXES: string[];
|
|
7
|
+
export interface CaptureEntry {
|
|
8
|
+
/** Capture tool: agent-browser | tuistory | pty-capture | playwright | ... */
|
|
9
|
+
tool: string;
|
|
10
|
+
/** Pre-change capture path (stored project-relative). */
|
|
11
|
+
before: string;
|
|
12
|
+
/** Post-change capture path (stored project-relative). */
|
|
13
|
+
after: string;
|
|
14
|
+
note?: string;
|
|
15
|
+
/** ISO timestamp when the pair was registered. */
|
|
16
|
+
at: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CapturesArtifact {
|
|
19
|
+
branch: string;
|
|
20
|
+
head: string | null;
|
|
21
|
+
ui_files: string[];
|
|
22
|
+
captures: CaptureEntry[];
|
|
23
|
+
at: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CapturesValidation {
|
|
26
|
+
/** True when the diff vs base touches UI files and captures are mandatory. */
|
|
27
|
+
required: boolean;
|
|
28
|
+
ok: boolean;
|
|
29
|
+
reasons: string[];
|
|
30
|
+
uiFiles: string[];
|
|
31
|
+
artifactPath: string;
|
|
32
|
+
captures: CaptureEntry[];
|
|
33
|
+
}
|
|
34
|
+
export declare function isUiFile(file: string): boolean;
|
|
35
|
+
/** Files changed vs the upstream base (origin/master, origin/main, master,
|
|
36
|
+
* main — first that resolves), or null when no base exists (fail-open). */
|
|
37
|
+
export declare function changedFilesVsBase(projectDir: string): string[] | null;
|
|
38
|
+
export declare function capturesArtifactPath(projectDir: string, branch: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Register one before/after capture pair for the current branch.
|
|
41
|
+
* Returns the artifact path, or an error string when the pair is unusable.
|
|
42
|
+
*/
|
|
43
|
+
export declare function recordCapture(projectDir: string, entry: {
|
|
44
|
+
tool: string;
|
|
45
|
+
before: string;
|
|
46
|
+
after: string;
|
|
47
|
+
note?: string;
|
|
48
|
+
}): {
|
|
49
|
+
path: string;
|
|
50
|
+
artifact: CapturesArtifact;
|
|
51
|
+
} | {
|
|
52
|
+
error: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Validate that a UI diff is covered by complete, fresh capture pairs.
|
|
56
|
+
* Fail-open only when no base is resolvable (non-git/detached), matching the
|
|
57
|
+
* expert-review enforcer's posture.
|
|
58
|
+
*/
|
|
59
|
+
export declare function validateCaptures(projectDir: string): CapturesValidation;
|
|
60
|
+
//# sourceMappingURL=visual-captures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-captures.d.ts","sourceRoot":"","sources":["../../src/review/visual-captures.ts"],"names":[],"mappings":"AA+BA;;;6DAG6D;AAC7D,eAAO,MAAM,MAAM,aAGjB,CAAC;AACH,eAAO,MAAM,eAAe,UAAwC,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,CAAC;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAeD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK9C;AAED;2EAC2E;AAC3E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAMtE;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/E;AA6BD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CA0ClE;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAqFvE"}
|