@boyingliu01/xp-gate 0.20.2 → 0.22.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.
Files changed (44) hide show
  1. package/adapters/gate-9.sh +142 -60
  2. package/adapters/python.sh +73 -6
  3. package/bin/xp-gate.js +2 -2
  4. package/gate-10.sh +107 -64
  5. package/gate-9.sh +142 -59
  6. package/hooks/gate-10.sh +107 -64
  7. package/hooks/gate-9.sh +142 -59
  8. package/hooks/lib/coverage-total.sh +72 -0
  9. package/hooks/pre-commit +128 -44
  10. package/hooks/pre-push +32 -0
  11. package/lib/__tests__/arch.test.js +403 -0
  12. package/lib/__tests__/doctor-469-hook-env.test.js +200 -0
  13. package/lib/arch.js +336 -49
  14. package/lib/doctor.js +319 -13
  15. package/lib/mattpocock-license-verdict.json +1 -1
  16. package/mock-policy/AGENTS.md +4 -4
  17. package/mutation/AGENTS.md +4 -4
  18. package/package.json +1 -1
  19. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  20. package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
  21. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
  22. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  23. package/plugins/dsh/package.json +1 -1
  24. package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
  25. package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
  26. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
  27. package/plugins/dsh/src/command.test.ts +14 -2
  28. package/plugins/dsh/src/command.ts +8 -1
  29. package/plugins/dsh/src/index.test.ts +43 -26
  30. package/plugins/dsh/src/index.ts +164 -138
  31. package/plugins/opencode/README.md +5 -2
  32. package/plugins/opencode/index.ts +215 -164
  33. package/plugins/opencode/package.json +1 -1
  34. package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
  35. package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
  36. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  37. package/plugins/qoder/plugin.json +1 -1
  38. package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
  39. package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
  40. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  41. package/principles/AGENTS.md +4 -4
  42. package/skills/delphi-review/AGENTS.md +4 -4
  43. package/skills/sprint-flow/AGENTS.md +4 -4
  44. package/skills/test-specification-alignment/AGENTS.md +4 -4
package/lib/arch.js CHANGED
@@ -4,78 +4,365 @@ const { spawnSync } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
- function isPythonProject(dir) {
8
- const markers = ['setup.py', 'setup.cfg', 'pyproject.toml', 'requirements.txt'];
9
- return markers.some((m) => fs.existsSync(path.join(dir, m)));
7
+ // #447: `xp-gate arch` used to shell out to
8
+ // npx -y @archlinter/cli scan . --config architecture.yaml
9
+ // which failed twice over: `-y` pulls whatever the registry serves (version
10
+ // drift — REQ-4: "本机能跑、别人不能跑"), and this repo's pre-0.16
11
+ // `architecture.yaml` `rules:` block is rejected by archlint 0.16's own
12
+ // validator. The default path now runs the PINNED local @archlinter/cli with
13
+ // its built-in rules — the same rule source Gate 6 (pre-commit) uses — with
14
+ // baseline-ratchet verdicts when `.architecture-baseline.json` exists. The old
15
+ // config-driven path survives as the explicit opt-in `--use-config <file>`;
16
+ // `--config` remains as a deprecated alias.
17
+
18
+ const ARCHLINT_PACKAGE = '@archlinter/cli';
19
+ const BASELINE_FILE = '.architecture-baseline.json';
20
+ // Blocking threshold, aligned with Gate 6's pre-commit ratchet
21
+ // (`archlint diff .architecture-baseline.json --fail-on high`).
22
+ const FAIL_ON = 'high';
23
+ const BLOCKING_SEVERITIES = new Set(['high', 'critical']);
24
+ const MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
25
+ const BLOCKING_PREVIEW_LIMIT = 10;
26
+
27
+ /**
28
+ * Windows extended-length prefixes (`\\?\C:\...`, `\\?\UNC\server\share\...`) —
29
+ * strip for readable output. UNC first: its prefix also starts with `\\?\`.
30
+ */
31
+ function stripExtendedPrefix(filePath) {
32
+ return String(filePath)
33
+ .replace(/^\\\\\?\\UNC\\/, '\\\\')
34
+ .replace(/^\\\\\?\\/, '');
10
35
  }
11
36
 
12
- function findArchConfig(startDir) {
37
+ /**
38
+ * Walk up from startDir to the git root. Falls back to startDir when no .git
39
+ * marker exists (Gate 6 uses the same PROJECT_ROOT notion).
40
+ */
41
+ function findProjectRoot(startDir) {
13
42
  let dir = path.resolve(startDir);
14
- const root = path.parse(dir).root;
15
- while (dir !== root) {
16
- const candidate = path.join(dir, 'architecture.yaml');
17
- if (fs.existsSync(candidate)) return candidate;
18
- if (fs.existsSync(path.join(dir, '.git'))) return null;
43
+ const fsRoot = path.parse(dir).root;
44
+ while (dir !== fsRoot) {
45
+ if (fs.existsSync(path.join(dir, '.git'))) return dir;
19
46
  dir = path.dirname(dir);
20
47
  }
21
- return null;
48
+ return path.resolve(startDir);
22
49
  }
23
50
 
24
- function runArchlinter(config) {
25
- const result = spawnSync(
26
- 'npx',
27
- ['-y', '@archlinter/cli', 'scan', '.', '--config', config],
28
- { stdio: 'inherit', shell: process.platform === 'win32' },
29
- );
30
- if (result.error && result.error.code === 'ENOENT') {
31
- console.error('[xp-gate arch] ERROR: npx not found in PATH. Install Node.js >=18.');
32
- return 1;
51
+ /**
52
+ * Absolute path to the JS bin entry of an @archlinter/cli installation, or
53
+ * null when the directory is not a usable installation. The bin field is
54
+ * either a string or a map; the entry must exist on disk.
55
+ */
56
+ function binEntryFromPackageDir(pkgDir) {
57
+ const manifestPath = path.join(pkgDir, 'package.json');
58
+ if (!fs.existsSync(manifestPath)) return null;
59
+ let manifest;
60
+ try {
61
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
62
+ } catch {
63
+ return null;
33
64
  }
34
- return result.status === null ? 1 : result.status;
65
+ const bin = typeof manifest.bin === 'string' ? { [manifest.name]: manifest.bin } : manifest.bin;
66
+ if (!bin || typeof bin !== 'object') return null;
67
+ const entry = bin.archlint || Object.values(bin)[0];
68
+ if (!entry || typeof entry !== 'string') return null;
69
+ const entryPath = path.resolve(pkgDir, entry);
70
+ return fs.existsSync(entryPath) ? entryPath : null;
35
71
  }
36
72
 
37
- function runArchy(config) {
38
- const result = spawnSync('archy', ['check', '.', '--config', config], {
39
- stdio: 'inherit',
40
- shell: process.platform === 'win32',
73
+ /** xp-gate's own pinned dependency: walk up from this file to its node_modules. */
74
+ function ownDependencyEntry() {
75
+ let dir = __dirname;
76
+ for (;;) {
77
+ const entry = binEntryFromPackageDir(path.join(dir, 'node_modules', ARCHLINT_PACKAGE));
78
+ if (entry) return entry;
79
+ const parent = path.dirname(dir);
80
+ if (parent === dir) return null;
81
+ dir = parent;
82
+ }
83
+ }
84
+
85
+ /** First match of `name` on PATH, or null. */
86
+ function findOnPath(name) {
87
+ const lookup = spawnSync(process.platform === 'win32' ? 'where' : 'which', [name], {
88
+ encoding: 'utf8',
41
89
  });
42
- if (result.error && result.error.code === 'ENOENT') {
43
- console.warn('[xp-gate arch] archy not found on PATH; trying npx fallback.');
44
- const npxResult = spawnSync('npx', ['-y', 'archy', 'check', '.', '--config', config], {
45
- stdio: 'inherit',
46
- shell: process.platform === 'win32',
47
- });
48
- if (npxResult.error && npxResult.error.code === 'ENOENT') {
49
- console.error('[xp-gate arch] ERROR: neither archy nor npx found in PATH.');
90
+ if (lookup.status !== 0 || !lookup.stdout) return null;
91
+ const first = lookup.stdout.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
92
+ return first && fs.existsSync(first) ? first : null;
93
+ }
94
+
95
+ /**
96
+ * Resolve which archlint to run, in pin-priority order (REQ-4):
97
+ * 1. xp-gate's own pinned dependency (version fixed by xp-gate's package.json)
98
+ * 2. the target project's local node_modules install
99
+ * 3. an `archlint` on PATH
100
+ * 4. `npx --no-install @archlinter/cli` (local-only; NEVER `npx -y`)
101
+ * `includeOwnDependency: false` skips step 1 (test seam for the lower tiers).
102
+ */
103
+ function resolveArchlint(projectRoot, options = {}) {
104
+ const includeOwnDependency = options.includeOwnDependency !== false;
105
+ if (includeOwnDependency) {
106
+ const own = ownDependencyEntry();
107
+ if (own) return { kind: 'node-script', script: own, source: 'xp-gate dependency (pinned)' };
108
+ }
109
+ const local = binEntryFromPackageDir(path.join(projectRoot, 'node_modules', ARCHLINT_PACKAGE));
110
+ if (local) return { kind: 'node-script', script: local, source: 'project node_modules' };
111
+ const onPath = findOnPath('archlint');
112
+ if (onPath) return { kind: 'binary', bin: onPath, source: 'PATH' };
113
+ return { kind: 'npx', source: 'npx --no-install (local only)' };
114
+ }
115
+
116
+ /**
117
+ * Quote an argv element for cmd.exe when shell mode is unavoidable.
118
+ * Embedded double quotes are doubled (cmd's in-quote escape) and anything
119
+ * carrying whitespace OR a quote is wrapped, so `a"b.yaml` cannot terminate
120
+ * the quoting early and hand the rest to the shell. NOTE: this exists only
121
+ * for the binary/npx fallback tiers on win32 — the default pinned path
122
+ * spawns node directly against the JS entry with no shell at all.
123
+ */
124
+ function quoteForShell(arg) {
125
+ const s = String(arg);
126
+ if (/[\s"]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
127
+ return s;
128
+ }
129
+
130
+ /**
131
+ * Run the resolved archlint. `capture` pipes stdout/stderr for parsing;
132
+ * otherwise output is inherited so the user sees archlint's own report.
133
+ * Returns { ok, status, stdout, stderr }; ok:false means the tool never ran.
134
+ */
135
+ function runArchlint(resolved, args, { cwd, capture = false } = {}) {
136
+ let command;
137
+ let commandArgs;
138
+ let useShell = false;
139
+ if (resolved.kind === 'node-script') {
140
+ // Spawn node against the JS entry: cross-platform, no .cmd/.ps1 shims.
141
+ command = process.execPath;
142
+ commandArgs = [resolved.script, ...args];
143
+ } else if (resolved.kind === 'binary') {
144
+ command = resolved.bin;
145
+ commandArgs = args;
146
+ useShell = process.platform === 'win32';
147
+ } else {
148
+ command = 'npx';
149
+ commandArgs = ['--no-install', ARCHLINT_PACKAGE, ...args];
150
+ useShell = process.platform === 'win32';
151
+ }
152
+ if (useShell) {
153
+ command = quoteForShell(command);
154
+ commandArgs = commandArgs.map(quoteForShell);
155
+ }
156
+ const result = spawnSync(command, commandArgs, {
157
+ cwd,
158
+ encoding: 'utf8',
159
+ stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
160
+ shell: useShell,
161
+ maxBuffer: MAX_OUTPUT_BYTES,
162
+ });
163
+ if (result.error) {
164
+ if (result.error.code === 'ENOENT') {
165
+ console.error(`[xp-gate arch] ERROR: archlint not found (${resolved.source}).`);
166
+ console.error(` Fix: npm install --save-dev ${ARCHLINT_PACKAGE} in your project,`);
167
+ console.error(' or reinstall xp-gate when its own pinned copy is the missing one.');
168
+ } else {
169
+ console.error(`[xp-gate arch] ERROR: failed to run archlint (${resolved.source}): ${result.error.message}`);
170
+ }
171
+ return { ok: false, status: 1, stdout: '', stderr: '' };
172
+ }
173
+ return {
174
+ ok: true,
175
+ status: result.status === null ? 1 : result.status,
176
+ stdout: result.stdout || '',
177
+ stderr: result.stderr || '',
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Parse `archlint scan --format json` output into a verdict.
183
+ * Fail-closed: unparseable output is reported as an error, never as "clean".
184
+ */
185
+ function verdictFromScanJson(jsonText) {
186
+ let parsed;
187
+ try {
188
+ parsed = JSON.parse(jsonText);
189
+ } catch {
190
+ return { parseError: 'archlint JSON output could not be parsed', total: 0, blocking: 0, blockingDetails: [] };
191
+ }
192
+ // A payload that parses but carries no `smells` array is schema drift, not a
193
+ // clean tree: archlint 0.16.0 always emits `smells` on a successful JSON scan
194
+ // (an empty run reports []). Reading the absence as "zero findings" would
195
+ // silently PASS a renamed or restructured report — the exact no-op failure
196
+ // class this rewrite exists to kill. Fail closed instead.
197
+ if (!parsed || !Array.isArray(parsed.smells)) {
198
+ return { parseError: 'archlint JSON output has no smells array (schema drift?)', total: 0, blocking: 0, blockingDetails: [] };
199
+ }
200
+ const smells = parsed.smells;
201
+ let blocking = 0;
202
+ const blockingDetails = [];
203
+ for (const smell of smells) {
204
+ const severity = String((smell && smell.severity) || '').toLowerCase();
205
+ if (BLOCKING_SEVERITIES.has(severity)) {
206
+ blocking += 1;
207
+ if (blockingDetails.length < BLOCKING_PREVIEW_LIMIT) blockingDetails.push(smell);
208
+ }
209
+ }
210
+ return {
211
+ parseError: null,
212
+ total: smells.length,
213
+ blocking,
214
+ blockingDetails,
215
+ summary: (parsed && parsed.summary) || null,
216
+ };
217
+ }
218
+
219
+ function printScanSummary(verdict) {
220
+ const grade = verdict.summary && verdict.summary.architecture_grade;
221
+ if (grade && grade.score !== undefined) {
222
+ console.log(`[xp-gate arch] Architecture quality: ${grade.score}/10 (${grade.level})`);
223
+ }
224
+ console.log(`[xp-gate arch] Findings: ${verdict.total} total, ${verdict.blocking} high/critical`);
225
+ for (const smell of verdict.blockingDetails) {
226
+ const files = Array.isArray(smell.files) ? smell.files.map(stripExtendedPrefix) : [];
227
+ console.error(` - [${smell.severity}] ${smell.type}${files.length ? ` @ ${files.join(', ')}` : ''}`);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Default path: built-in archlint rules, no config.
233
+ * - `.architecture-baseline.json` present → baseline-ratchet mode, the exact
234
+ * verdict semantics of Gate 6's pre-commit path (`diff --fail-on high`):
235
+ * only NEW high/critical findings block, so an adopted legacy tree stays green.
236
+ * - no baseline → zero-tolerance mode: any high/critical finding blocks.
237
+ * Tool failures (spawn error, non-zero scan exit, unparseable JSON) always
238
+ * exit non-zero — a broken scan must never read as a clean one.
239
+ */
240
+ function runBuiltinScan(projectRoot, resolved) {
241
+ const baselinePath = path.join(projectRoot, BASELINE_FILE);
242
+ if (fs.existsSync(baselinePath)) {
243
+ console.log(`[xp-gate arch] Baseline ratchet mode — ${BASELINE_FILE} present; blocking new ${FAIL_ON}+ findings only`);
244
+ const diff = runArchlint(resolved, ['diff', baselinePath, '--fail-on', FAIL_ON], { cwd: projectRoot });
245
+ if (!diff.ok) return 1;
246
+ // `--fail-on high` makes the exit code itself the verdict contract: 0 = no
247
+ // new high+ findings, 1 = at least one. This is the exact mechanism Gate
248
+ // 6's pre-commit has used since before #447 (issue evidence: "Gate 6
249
+ // actually uses: archlint diff .architecture-baseline.json --fail-on high
250
+ // ✅ normal"), so parity here is the point, not a shortcut. Anything other
251
+ // than 0/1 is a tool failure and blocks.
252
+ if (diff.status === 0) {
253
+ console.log(`✅ arch: no new ${FAIL_ON}+ architecture findings (baseline ratchet)`);
254
+ return 0;
255
+ }
256
+ if (diff.status === 1) {
257
+ console.error(`❌ BLOCKED - new ${FAIL_ON}+ architecture findings (baseline ratchet mode)`);
258
+ console.error(' Fix the findings above, or refresh the baseline with:');
259
+ console.error(` npx ${ARCHLINT_PACKAGE} snapshot -o ${BASELINE_FILE}`);
50
260
  return 1;
51
261
  }
52
- return npxResult.status === null ? 1 : npxResult.status;
262
+ console.error(`❌ ERROR - archlint diff exited ${diff.status}; tool failure is treated as blocking (never a silent pass)`);
263
+ return 1;
264
+ }
265
+
266
+ console.log('[xp-gate arch] Zero-tolerance mode — no .architecture-baseline.json; blocking any high/critical finding');
267
+ const scan = runArchlint(resolved, ['scan', '.', '--format', 'json', '--quiet'], {
268
+ cwd: projectRoot,
269
+ capture: true,
270
+ });
271
+ if (!scan.ok) return 1;
272
+ // Empirically verified against @archlinter/cli 0.16.0: the JSON scan exits 1
273
+ // exactly when high/critical findings exist (0 findings -> 0, medium-only ->
274
+ // 0, any high -> 1); the table format always exits 0. Any other non-zero
275
+ // status is a tool failure, never a verdict. The observation is bounded to
276
+ // the PINNED release (step 1 of resolveArchlint, REQ-4), and
277
+ // __tests__/arch.test.js re-runs that real binary, so a semantic change
278
+ // fails tests instead of silently misreading the gate.
279
+ if (scan.status !== 0 && scan.status !== 1) {
280
+ console.error(`❌ ERROR - archlint scan exited ${scan.status}; tool failure is treated as blocking (never a silent pass)`);
281
+ if (scan.stderr) process.stderr.write(scan.stderr);
282
+ return 1;
283
+ }
284
+ const verdict = verdictFromScanJson(scan.stdout);
285
+ if (verdict.parseError) {
286
+ console.error(`❌ ERROR - ${verdict.parseError}; tool failure is treated as blocking (never a silent pass)`);
287
+ return 1;
288
+ }
289
+ printScanSummary(verdict);
290
+ if (verdict.blocking > 0) {
291
+ console.error(`❌ BLOCKED - ${verdict.blocking} high/critical architecture finding(s) (zero-tolerance mode)`);
292
+ return 1;
53
293
  }
54
- return result.status === null ? 1 : result.status;
294
+ if (scan.status === 1) {
295
+ // archlint signalled failure but the parsed report shows no high/critical
296
+ // finding — never silently pass on a verdict we cannot reproduce.
297
+ console.error('❌ ERROR - archlint exited 1 but no high/critical finding was parsed; treating as blocking (fail-closed)');
298
+ return 1;
299
+ }
300
+ console.log(`✅ arch: no high/critical architecture findings (zero-tolerance mode; ${verdict.total} finding(s) reported)`);
301
+ return 0;
55
302
  }
56
303
 
57
- function arch(args) {
58
- const configIdx = args.indexOf('--config');
59
- const explicitConfig = configIdx >= 0 ? args[configIdx + 1] : null;
60
- const config = explicitConfig || findArchConfig(process.cwd());
304
+ /**
305
+ * Opt-in legacy path (`--use-config <file>`): the file is passed straight to
306
+ * archlint as `--config`. archlint validates its own schema, so pre-0.16 files
307
+ * (this repo's architecture.yaml `rules:` block) are rejected by archlint
308
+ * itself — schema-version responsibility sits with the user who opts in.
309
+ */
310
+ function runWithConfig(configPath, projectRoot, resolved) {
311
+ console.warn(`[xp-gate arch] NOTE: opt-in config path — passing ${configPath} to archlint --config.`);
312
+ console.warn(' archlint validates its own config schema; pre-0.16 files (e.g. an architecture.yaml');
313
+ console.warn(' with a `rules:` block) are rejected by archlint itself. The default built-in-rules');
314
+ console.warn(' path needs no config at all.');
315
+ const scan = runArchlint(resolved, ['scan', '.', '--config', configPath], { cwd: projectRoot });
316
+ if (!scan.ok) return 1;
317
+ return scan.status;
318
+ }
61
319
 
62
- if (!config) {
63
- console.error('[xp-gate arch] ERROR: architecture.yaml not found in CWD or git root');
64
- console.error(' Create one or pass --config <path>');
320
+ function arch(args) {
321
+ const useConfigIdx = args.indexOf('--use-config');
322
+ const legacyIdx = args.indexOf('--config');
323
+ if (useConfigIdx >= 0 && legacyIdx >= 0) {
324
+ console.error('[xp-gate arch] ERROR: pass either --use-config or --config, not both');
65
325
  return Promise.resolve(1);
66
326
  }
67
-
68
- if (!fs.existsSync(config)) {
69
- console.error(`[xp-gate arch] ERROR: config file not found: ${config}`);
327
+ if (legacyIdx >= 0) {
328
+ console.warn('[xp-gate arch] NOTE: --config is deprecated; use --use-config (same behavior, clearer intent)');
329
+ }
330
+ const configIdx = useConfigIdx >= 0 ? useConfigIdx : legacyIdx;
331
+ const config = configIdx >= 0 ? args[configIdx + 1] : null;
332
+ if (configIdx >= 0 && (!config || config.startsWith('--'))) {
333
+ console.error(`[xp-gate arch] ERROR: ${useConfigIdx >= 0 ? '--use-config' : '--config'} requires a file path`);
70
334
  return Promise.resolve(1);
71
335
  }
72
336
 
73
- if (isPythonProject(process.cwd())) {
74
- console.log('[xp-gate arch] Detected Python project — using archy');
75
- return Promise.resolve(runArchy(config));
337
+ const projectRoot = findProjectRoot(process.cwd());
338
+ const resolved = resolveArchlint(projectRoot);
339
+
340
+ if (config) {
341
+ const configPath = path.resolve(process.cwd(), config);
342
+ if (!fs.existsSync(configPath)) {
343
+ console.error(`[xp-gate arch] ERROR: config file not found: ${config}`);
344
+ return Promise.resolve(1);
345
+ }
346
+ return Promise.resolve(runWithConfig(configPath, projectRoot, resolved));
76
347
  }
77
348
 
78
- return Promise.resolve(runArchlinter(config));
349
+ console.log(`[xp-gate arch] Project root: ${projectRoot}`);
350
+ console.log(`[xp-gate arch] archlint: ${resolved.source}${resolved.script ? ` (${resolved.script})` : ''}`);
351
+ return Promise.resolve(runBuiltinScan(projectRoot, resolved));
79
352
  }
80
353
 
81
- module.exports = { arch };
354
+ module.exports = {
355
+ arch,
356
+ // Test seams (anti-noop verification needs to reach the resolution and
357
+ // verdict layers without shelling out; see __tests__/arch.test.js).
358
+ __internals: {
359
+ findProjectRoot,
360
+ binEntryFromPackageDir,
361
+ resolveArchlint,
362
+ verdictFromScanJson,
363
+ quoteForShell,
364
+ stripExtendedPrefix,
365
+ BLOCKING_SEVERITIES,
366
+ BASELINE_FILE,
367
+ },
368
+ };