sneakoscope 4.4.0 → 4.6.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 (80) hide show
  1. package/README.md +28 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -0
  7. package/dist/core/agents/agent-runner-ollama.js +2 -0
  8. package/dist/core/agents/native-worker-backend-router.js +3 -0
  9. package/dist/core/bench.js +115 -0
  10. package/dist/core/code-structure.js +399 -11
  11. package/dist/core/codex-control/codex-fake-sdk-adapter.js +67 -9
  12. package/dist/core/codex-control/gpt-final-arbiter.js +4 -1
  13. package/dist/core/codex-control/gpt-final-review-schema.js +58 -0
  14. package/dist/core/codex-native/core-skill-manifest.js +23 -0
  15. package/dist/core/commands/bench-command.js +11 -2
  16. package/dist/core/commands/code-structure-command.js +34 -2
  17. package/dist/core/commands/run-command.js +92 -2
  18. package/dist/core/commands/seo-command.js +130 -0
  19. package/dist/core/feature-fixtures.js +6 -0
  20. package/dist/core/feature-registry.js +3 -1
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/hooks-runtime.js +8 -0
  23. package/dist/core/init.js +8 -6
  24. package/dist/core/lean-engineering-policy.js +159 -0
  25. package/dist/core/pipeline-internals/runtime-core.js +15 -5
  26. package/dist/core/proof/auto-finalize.js +3 -2
  27. package/dist/core/proof/proof-schema.js +2 -1
  28. package/dist/core/proof/proof-writer.js +1 -0
  29. package/dist/core/proof/route-adapter.js +4 -2
  30. package/dist/core/proof/route-finalizer.js +35 -3
  31. package/dist/core/routes.js +75 -9
  32. package/dist/core/search-visibility/adapter-registry.js +26 -0
  33. package/dist/core/search-visibility/adapters/next-app.js +6 -0
  34. package/dist/core/search-visibility/adapters/next-pages.js +6 -0
  35. package/dist/core/search-visibility/adapters/static-site.js +6 -0
  36. package/dist/core/search-visibility/analyzers.js +377 -0
  37. package/dist/core/search-visibility/artifacts.js +183 -0
  38. package/dist/core/search-visibility/discovery.js +347 -0
  39. package/dist/core/search-visibility/index.js +199 -0
  40. package/dist/core/search-visibility/mission.js +67 -0
  41. package/dist/core/search-visibility/mutation.js +314 -0
  42. package/dist/core/search-visibility/types.js +2 -0
  43. package/dist/core/search-visibility/verifier.js +60 -0
  44. package/dist/core/version.js +1 -1
  45. package/dist/scripts/check-architecture.js +40 -7
  46. package/dist/scripts/check-command-module-budget.js +43 -5
  47. package/dist/scripts/check-pipeline-budget.js +17 -30
  48. package/dist/scripts/check-publish-tag.js +33 -6
  49. package/dist/scripts/check-route-modularity.js +25 -33
  50. package/dist/scripts/check-runtime-schemas.js +22 -0
  51. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +2 -2
  52. package/dist/scripts/core-skill-immutable-sync-check.js +3 -2
  53. package/dist/scripts/core-skill-integrity-blackbox.js +3 -2
  54. package/dist/scripts/core-skill-manifest-check.js +7 -2
  55. package/dist/scripts/geo-claim-evidence-check.js +18 -0
  56. package/dist/scripts/geo-cli-blackbox-check.js +18 -0
  57. package/dist/scripts/geo-crawler-policy-check.js +16 -0
  58. package/dist/scripts/geo-llms-txt-optional-check.js +19 -0
  59. package/dist/scripts/gpt-final-arbiter-check.js +4 -1
  60. package/dist/scripts/release-parallel-check.js +15 -0
  61. package/dist/scripts/release-registry-check.js +33 -14
  62. package/dist/scripts/search-visibility-gate-lib.js +124 -0
  63. package/dist/scripts/seo-audit-fixture-check.js +16 -0
  64. package/dist/scripts/seo-canonical-locale-check.js +19 -0
  65. package/dist/scripts/seo-cli-blackbox-check.js +18 -0
  66. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +18 -0
  67. package/dist/scripts/seo-geo-geo-disambiguation-check.js +12 -0
  68. package/dist/scripts/seo-geo-no-unsupported-ranking-claims-check.js +18 -0
  69. package/dist/scripts/seo-geo-route-identity-check.js +12 -0
  70. package/dist/scripts/seo-geo-skill-rich-content-check.js +22 -0
  71. package/dist/scripts/seo-mutation-rollback-check.js +23 -0
  72. package/dist/scripts/seo-no-mutation-by-default-check.js +17 -0
  73. package/dist/scripts/seo-structured-data-visible-content-check.js +19 -0
  74. package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
  75. package/package.json +19 -1
  76. package/schemas/search-visibility/finding-ledger.schema.json +36 -0
  77. package/schemas/search-visibility/gate.schema.json +22 -0
  78. package/schemas/search-visibility/mutation-plan.schema.json +27 -0
  79. package/schemas/search-visibility/site-inventory.schema.json +21 -0
  80. package/schemas/search-visibility/verification-report.schema.json +23 -0
@@ -1,29 +1,43 @@
1
1
  import path from 'node:path';
2
2
  import fsp from 'node:fs/promises';
3
+ import { spawnSync } from 'node:child_process';
3
4
  import { nowIso, writeJsonAtomic } from './fsx.js';
5
+ import { LEAN_CHANGE_EVIDENCE_SCHEMA, leanPolicyReference, parseLeanSimplificationMarkerLine } from './lean-engineering-policy.js';
4
6
  export const CODE_STRUCTURE_THRESHOLDS = {
5
7
  warning: 1000,
6
8
  review: 2000,
7
9
  split_required_review: 3000
8
10
  };
9
- const DEFAULT_INCLUDE = new Set(['.js', '.js', '.ts', '.tsx', '.jsx', '.cjs']);
11
+ const DEFAULT_INCLUDE = new Set(['.js', '.ts', '.tsx', '.jsx', '.cjs', '.mjs']);
10
12
  const SKIP_DIRS = new Set(['.git', 'node_modules', '.sneakoscope', 'dist', 'build', 'coverage']);
13
+ const SOURCE_DIR_RE = /^(src|test|tests|schemas|scripts|crates|docs)\//;
14
+ const SOURCE_EXT_RE = /\.(js|ts|tsx|jsx|cjs|mjs|json|md|rs|toml)$/;
15
+ const FALLBACK_RE = /\b(fallback|legacy|shim|compat|mock|catch-all|catch all|default provider)\b/i;
16
+ const CONFIG_FLAG_RE = /\b(process\.env|SKS_[A-Z0-9_]+|CODEX_[A-Z0-9_]+|[A-Z][A-Z0-9_]{5,})\b/;
17
+ const ABSTRACTION_RE = /\b(interface|abstract class|class|factory|provider|adapter|registry|orchestrator|manager)\b/;
11
18
  export async function scanCodeStructure(root, opts = {}) {
12
- const files = opts.files?.length ? opts.files.map((file) => path.resolve(root, file)) : await listSourceFiles(root);
13
- const touched = new Set((opts.touchedFiles || []).map((file) => path.relative(root, path.resolve(root, file))));
19
+ const changedScope = await collectChangedScope(root, opts);
20
+ const files = await resolveScanFiles(root, opts, changedScope);
21
+ const touched = new Set((opts.touchedFiles || []).map((file) => normalizeRel(root, file)));
22
+ const changedSet = new Set((changedScope.source_files || []).map((file) => normalizeSlashes(file)));
14
23
  const entries = [];
24
+ const intentionalSimplifications = [];
15
25
  for (const file of files) {
16
- const rel = path.relative(root, file);
17
- const lineCount = await countLines(file).catch(() => 0);
26
+ const rel = normalizeRel(root, file);
27
+ const text = await fsp.readFile(file, 'utf8').catch(() => '');
28
+ const lineCount = text ? text.split(/\n/).length : 0;
18
29
  const status = structureStatus(lineCount);
19
- if (status === 'ok' && !opts.includeOk)
30
+ const changedByDiff = changedSet.has(rel);
31
+ if (status === 'ok' && !opts.includeOk && !changedByDiff)
20
32
  continue;
33
+ const signals = analyzeTextSignals(rel, text, changedByDiff);
34
+ intentionalSimplifications.push(...signals.lean_markers);
21
35
  entries.push({
22
36
  path: rel,
23
37
  line_count: lineCount,
24
38
  status,
25
39
  generated_or_vendor: isGeneratedOrVendor(rel),
26
- touched_by_mission: touched.size ? touched.has(rel) : false,
40
+ touched_by_mission: touched.size ? touched.has(rel) : changedByDiff,
27
41
  recommended_action: recommendedAction(rel, lineCount),
28
42
  exception: lineCount >= CODE_STRUCTURE_THRESHOLDS.split_required_review && !isGeneratedOrVendor(rel)
29
43
  ? {
@@ -34,15 +48,43 @@ export async function scanCodeStructure(root, opts = {}) {
34
48
  next_split_candidate: nextSplitCandidate(rel),
35
49
  temporary_until: 'next substantial edit to this file'
36
50
  }
37
- : null
51
+ : null,
52
+ lean_signals: signals
38
53
  });
39
54
  }
55
+ const dependencyDelta = await collectDependencyDelta(root, changedScope.base);
56
+ const fallbackSites = await collectAddedFallbackSites(root, changedScope);
57
+ const runnableChecks = collectRunnableChecks(changedScope);
58
+ const semanticReview = buildSemanticReview({
59
+ entries,
60
+ changedScope,
61
+ dependencyDelta,
62
+ fallbackSites,
63
+ intentionalSimplifications,
64
+ runnableChecks
65
+ });
66
+ const leanChangeEvidence = buildLeanChangeEvidence({
67
+ changedScope,
68
+ dependencyDelta,
69
+ fallbackSites,
70
+ intentionalSimplifications,
71
+ runnableChecks,
72
+ semanticReview
73
+ });
40
74
  return {
41
75
  schema_version: 1,
42
76
  mission_id: opts.missionId || null,
43
77
  scanned_at: nowIso(),
44
78
  thresholds: CODE_STRUCTURE_THRESHOLDS,
45
79
  files: entries.sort((a, b) => b.line_count - a.line_count),
80
+ changed_scope: changedScope,
81
+ dependencies_added: dependencyDelta.added,
82
+ dependencies_removed: dependencyDelta.removed,
83
+ fallback_sites_added: fallbackSites,
84
+ intentional_simplifications: intentionalSimplifications,
85
+ runnable_checks: runnableChecks,
86
+ semantic_review: semanticReview,
87
+ lean_change_evidence: leanChangeEvidence,
46
88
  actions_taken: opts.actions_taken || [],
47
89
  remaining_risks: entries.filter((entry) => entry.exception).map((entry) => `${entry.path}: ${entry.status}`)
48
90
  };
@@ -52,6 +94,103 @@ export async function writeCodeStructureReport(root, dir, opts = {}) {
52
94
  await writeJsonAtomic(path.join(dir, 'code-structure-report.json'), report);
53
95
  return report;
54
96
  }
97
+ export function leanChangeEvidenceFromReport(report) {
98
+ if (report?.lean_change_evidence)
99
+ return report.lean_change_evidence;
100
+ return buildLeanChangeEvidence({
101
+ changedScope: report?.changed_scope || emptyChangedScope('unknown', 'HEAD'),
102
+ dependencyDelta: {
103
+ added: report?.dependencies_added || [],
104
+ removed: report?.dependencies_removed || []
105
+ },
106
+ fallbackSites: report?.fallback_sites_added || [],
107
+ intentionalSimplifications: report?.intentional_simplifications || [],
108
+ runnableChecks: report?.runnable_checks || [],
109
+ semanticReview: report?.semantic_review || { status: 'needs-review', findings: [] }
110
+ });
111
+ }
112
+ async function resolveScanFiles(root, opts, changedScope) {
113
+ if (opts.files?.length)
114
+ return opts.files.map((file) => path.resolve(root, file));
115
+ const changedSourceFiles = (changedScope.source_files || [])
116
+ .filter((file) => DEFAULT_INCLUDE.has(path.extname(file)))
117
+ .map((file) => path.resolve(root, file));
118
+ if ((opts.changed || opts.changedSince || opts.changedFiles?.length) && changedSourceFiles.length)
119
+ return changedSourceFiles;
120
+ return listSourceFiles(root);
121
+ }
122
+ async function collectChangedScope(root, opts) {
123
+ if (opts.changedFiles?.length) {
124
+ const changedFiles = Array.from(new Set(opts.changedFiles.map((file) => normalizeRel(root, file))));
125
+ return {
126
+ ...emptyChangedScope('explicit', opts.changedSince || 'HEAD'),
127
+ changed_files: changedFiles,
128
+ source_files: changedFiles.filter(isSourceLike),
129
+ entries: changedFiles.map((file) => ({ path: file, status: 'M', lines_added: 0, lines_deleted: 0 }))
130
+ };
131
+ }
132
+ const shouldCollect = opts.changed || opts.changedSince;
133
+ if (!shouldCollect)
134
+ return emptyChangedScope('full', opts.changedSince || 'HEAD');
135
+ const base = String(opts.changedSince || (typeof opts.changed === 'string' ? opts.changed : 'HEAD'));
136
+ const numstat = gitLines(root, ['diff', '--numstat', base, '--']);
137
+ const nameStatus = gitLines(root, ['diff', '--name-status', base, '--']);
138
+ const untracked = gitLines(root, ['ls-files', '--others', '--exclude-standard']);
139
+ const entriesByPath = new Map();
140
+ for (const line of numstat) {
141
+ const parts = line.split(/\t/);
142
+ if (parts.length < 3)
143
+ continue;
144
+ const linesAdded = parseNumstat(parts[0] || '0');
145
+ const linesDeleted = parseNumstat(parts[1] || '0');
146
+ const rel = normalizeSlashes(parts.slice(2).join('\t'));
147
+ entriesByPath.set(rel, {
148
+ path: rel,
149
+ status: 'M',
150
+ lines_added: linesAdded,
151
+ lines_deleted: linesDeleted
152
+ });
153
+ }
154
+ for (const line of nameStatus) {
155
+ const parts = line.split(/\t/).filter(Boolean);
156
+ if (parts.length < 2)
157
+ continue;
158
+ const status = parts[0];
159
+ const rel = normalizeSlashes(parts[parts.length - 1] || '');
160
+ if (!rel)
161
+ continue;
162
+ const existing = entriesByPath.get(rel) || { path: rel, lines_added: 0, lines_deleted: 0 };
163
+ entriesByPath.set(rel, { ...existing, status });
164
+ }
165
+ for (const rel of untracked.map(normalizeSlashes).filter(Boolean)) {
166
+ if (entriesByPath.has(rel))
167
+ continue;
168
+ const text = await fsp.readFile(path.join(root, rel), 'utf8').catch(() => '');
169
+ entriesByPath.set(rel, {
170
+ path: rel,
171
+ status: 'A',
172
+ lines_added: text ? text.split(/\n/).length : 0,
173
+ lines_deleted: 0
174
+ });
175
+ }
176
+ const entries = [...entriesByPath.values()].filter((entry) => entry.path);
177
+ const changedFiles = entries.map((entry) => entry.path);
178
+ const sourceFiles = changedFiles.filter(isSourceLike);
179
+ const linesAdded = entries.reduce((sum, entry) => sum + Number(entry.lines_added || 0), 0);
180
+ const linesDeleted = entries.reduce((sum, entry) => sum + Number(entry.lines_deleted || 0), 0);
181
+ return {
182
+ mode: 'git-diff',
183
+ base,
184
+ changed_files: changedFiles,
185
+ files_added: entries.filter((entry) => String(entry.status || '').startsWith('A')).length,
186
+ files_deleted: entries.filter((entry) => String(entry.status || '').startsWith('D')).length,
187
+ lines_added: linesAdded,
188
+ lines_deleted: linesDeleted,
189
+ net_lines: linesAdded - linesDeleted,
190
+ source_files: sourceFiles,
191
+ entries
192
+ };
193
+ }
55
194
  async function listSourceFiles(root, dir = root, out = []) {
56
195
  const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
57
196
  for (const entry of entries) {
@@ -70,6 +209,203 @@ async function listSourceFiles(root, dir = root, out = []) {
70
209
  }
71
210
  return out;
72
211
  }
212
+ async function collectDependencyDelta(root, base = 'HEAD') {
213
+ const current = await readPackageDependencyNames(path.join(root, 'package.json'));
214
+ const previousText = gitText(root, ['show', `${base}:package.json`]);
215
+ const previous = parsePackageDependencyNames(previousText || '{}');
216
+ return {
217
+ added: [...current].filter((name) => !previous.has(name)).sort(),
218
+ removed: [...previous].filter((name) => !current.has(name)).sort()
219
+ };
220
+ }
221
+ async function readPackageDependencyNames(file) {
222
+ const text = await fsp.readFile(file, 'utf8').catch(() => '{}');
223
+ return parsePackageDependencyNames(text);
224
+ }
225
+ function parsePackageDependencyNames(text) {
226
+ const value = safeJson(text);
227
+ const names = new Set();
228
+ for (const section of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
229
+ const deps = value?.[section] && typeof value[section] === 'object' ? value[section] : {};
230
+ for (const name of Object.keys(deps))
231
+ names.add(`${section}:${name}`);
232
+ }
233
+ return names;
234
+ }
235
+ async function collectAddedFallbackSites(root, changedScope) {
236
+ if (!changedScope?.source_files?.length || changedScope.mode !== 'git-diff')
237
+ return [];
238
+ const diff = gitText(root, ['diff', '--unified=0', changedScope.base || 'HEAD', '--', ...changedScope.source_files]);
239
+ const sites = [];
240
+ let currentFile = '';
241
+ let currentLine = 0;
242
+ for (const line of diff.split(/\r?\n/)) {
243
+ if (line.startsWith('+++ b/')) {
244
+ currentFile = normalizeSlashes(line.slice('+++ b/'.length));
245
+ continue;
246
+ }
247
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
248
+ if (hunk) {
249
+ currentLine = Number(hunk[1]) || 0;
250
+ continue;
251
+ }
252
+ if (line.startsWith('+') && !line.startsWith('+++')) {
253
+ if (FALLBACK_RE.test(line))
254
+ sites.push({ file: currentFile, line: currentLine, text: line.slice(1).trim().slice(0, 160) });
255
+ currentLine += 1;
256
+ continue;
257
+ }
258
+ if (!line.startsWith('-'))
259
+ currentLine += 1;
260
+ }
261
+ return sites;
262
+ }
263
+ function analyzeTextSignals(rel, text, changedByDiff) {
264
+ const lines = text.split(/\r?\n/);
265
+ const imports = lines.filter((line) => /^\s*import\s/.test(line));
266
+ const externalImports = imports
267
+ .map((line) => /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/.exec(line)?.[1] || /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/.exec(line)?.[2] || '')
268
+ .filter((specifier) => specifier && !specifier.startsWith('.') && !specifier.startsWith('node:'));
269
+ const leanMarkers = lines
270
+ .map((line, index) => parseLeanSimplificationMarkerLine(line, rel, index + 1))
271
+ .filter((marker) => Boolean(marker));
272
+ const effectiveLines = lines.map((line) => line.trim()).filter(Boolean);
273
+ const forwardingOnly = effectiveLines.length > 0
274
+ && effectiveLines.length <= 10
275
+ && effectiveLines.every((line) => line.startsWith('import ') || line.startsWith('export ') || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*'));
276
+ return {
277
+ import_count: imports.length,
278
+ external_dependency_imports: Array.from(new Set(externalImports)).sort(),
279
+ ts_nocheck: /^\s*\/\/\s*@ts-nocheck\b/m.test(text),
280
+ changed_by_diff: changedByDiff,
281
+ forwarding_only: forwardingOnly,
282
+ fallback_markers: countMatches(text, FALLBACK_RE),
283
+ config_flag_markers: countMatches(text, CONFIG_FLAG_RE),
284
+ abstraction_markers: countMatches(text, ABSTRACTION_RE),
285
+ lean_markers: leanMarkers
286
+ };
287
+ }
288
+ function collectRunnableChecks(changedScope) {
289
+ return (changedScope.source_files || [])
290
+ .filter((file) => /\b(test|tests|fixture|fixtures|spec|check|__tests__)\b/i.test(file))
291
+ .sort();
292
+ }
293
+ function buildSemanticReview(input) {
294
+ const findings = [];
295
+ for (const dep of input.dependencyDelta.added || []) {
296
+ findings.push({
297
+ tag: 'reuse',
298
+ severity: 'blocker',
299
+ summary: `new dependency requires explicit lean justification: ${dep}`
300
+ });
301
+ }
302
+ if ((input.fallbackSites || []).length) {
303
+ findings.push({
304
+ tag: 'fallback',
305
+ severity: 'review',
306
+ summary: `${input.fallbackSites.length} added fallback/compat/mock marker(s) need authority and proof`
307
+ });
308
+ }
309
+ const changedEntries = (input.entries || input.changedScope?.entries || []);
310
+ if (input.changedScope?.net_lines > 300) {
311
+ findings.push({
312
+ tag: 'shrink',
313
+ severity: 'review',
314
+ summary: `changed diff is +${input.changedScope.net_lines} net lines; confirm this is the smallest sufficient change`
315
+ });
316
+ }
317
+ if ((input.changedScope?.source_files || []).length && !(input.runnableChecks || []).length) {
318
+ findings.push({
319
+ tag: 'verify',
320
+ severity: 'review',
321
+ summary: 'changed source files were detected without a changed runnable check file'
322
+ });
323
+ }
324
+ for (const entry of input.entries || []) {
325
+ if (!entry.lean_signals?.changed_by_diff)
326
+ continue;
327
+ if (entry.lean_signals.ts_nocheck) {
328
+ findings.push({
329
+ tag: 'verify',
330
+ severity: isLeanOwnedTypeSafetyPath(entry.path) ? 'blocker' : 'review',
331
+ file: entry.path,
332
+ summary: isLeanOwnedTypeSafetyPath(entry.path)
333
+ ? 'changed Lean/architecture-owned file contains @ts-nocheck'
334
+ : 'changed auxiliary fixture/gate file contains @ts-nocheck; keep typed migration scoped'
335
+ });
336
+ }
337
+ if (entry.lean_signals.forwarding_only) {
338
+ findings.push({
339
+ tag: 'reuse',
340
+ severity: 'review',
341
+ file: entry.path,
342
+ summary: 'changed file is forwarding-only; confirm it replaces an older path instead of duplicating an SSOT'
343
+ });
344
+ }
345
+ if (entry.lean_signals.config_flag_markers > 6 || entry.lean_signals.abstraction_markers > 12) {
346
+ findings.push({
347
+ tag: 'yagni',
348
+ severity: 'review',
349
+ file: entry.path,
350
+ summary: 'changed file has dense config/abstraction markers; review for unrequested knobs or layers'
351
+ });
352
+ }
353
+ }
354
+ for (const marker of input.intentionalSimplifications || []) {
355
+ if (marker.status === 'complete')
356
+ continue;
357
+ findings.push({
358
+ tag: 'shrink',
359
+ severity: 'review',
360
+ file: marker.file,
361
+ line: marker.line,
362
+ summary: `lean simplification marker is incomplete: ${marker.status}`
363
+ });
364
+ }
365
+ const status = findings.some((finding) => finding.severity === 'blocker')
366
+ ? 'blocked'
367
+ : findings.some((finding) => finding.severity === 'review')
368
+ ? 'needs-review'
369
+ : 'pass';
370
+ return { status, findings };
371
+ }
372
+ function isLeanOwnedTypeSafetyPath(file) {
373
+ return [
374
+ 'src/core/code-structure.ts',
375
+ 'src/core/commands/code-structure-command.ts',
376
+ 'src/core/lean-engineering-policy.ts',
377
+ 'src/core/codex-control/gpt-final-arbiter.ts',
378
+ 'src/core/codex-control/gpt-final-review-schema.ts',
379
+ 'src/core/codex-control/codex-fake-sdk-adapter.ts',
380
+ 'src/core/agents/native-worker-backend-router.ts',
381
+ 'src/scripts/check-architecture.ts',
382
+ 'src/scripts/check-command-module-budget.ts',
383
+ 'src/scripts/check-pipeline-budget.ts',
384
+ 'src/scripts/check-route-modularity.ts',
385
+ 'src/scripts/check-publish-tag.ts',
386
+ 'src/scripts/gpt-final-arbiter-check.ts',
387
+ 'src/scripts/release-registry-check.ts'
388
+ ].includes(file);
389
+ }
390
+ function buildLeanChangeEvidence(input) {
391
+ const changedScope = input.changedScope || emptyChangedScope('unknown', 'HEAD');
392
+ return {
393
+ schema: LEAN_CHANGE_EVIDENCE_SCHEMA,
394
+ ...leanPolicyReference(),
395
+ changed_files: changedScope.changed_files || [],
396
+ files_added: changedScope.files_added || 0,
397
+ files_deleted: changedScope.files_deleted || 0,
398
+ lines_added: changedScope.lines_added || 0,
399
+ lines_deleted: changedScope.lines_deleted || 0,
400
+ net_lines: changedScope.net_lines || 0,
401
+ dependencies_added: input.dependencyDelta?.added || [],
402
+ dependencies_removed: input.dependencyDelta?.removed || [],
403
+ fallback_sites_added: input.fallbackSites || [],
404
+ intentional_simplifications: input.intentionalSimplifications || [],
405
+ runnable_checks: input.runnableChecks || [],
406
+ semantic_review: input.semanticReview || { status: 'needs-review', findings: [] }
407
+ };
408
+ }
73
409
  async function countLines(file) {
74
410
  const text = await fsp.readFile(file, 'utf8');
75
411
  return text ? text.split(/\n/).length : 0;
@@ -89,17 +425,69 @@ function isGeneratedOrVendor(rel) {
89
425
  function recommendedAction(rel, lines) {
90
426
  if (lines < CODE_STRUCTURE_THRESHOLDS.warning)
91
427
  return 'none';
92
- if (/src\/cli\/main\.js$/.test(rel))
428
+ if (/src\/cli\/main\.(js|ts)$/.test(rel))
93
429
  return 'extract CLI subcommand handlers into focused modules before adding substantial command logic';
94
430
  if (/routes|pipeline|init/.test(rel))
95
431
  return 'extract policy tables or route-specific execution into focused modules';
96
432
  return 'identify a cohesive module boundary and extract before adding unrelated logic';
97
433
  }
98
434
  function nextSplitCandidate(rel) {
99
- if (/src\/cli\/main\.js$/.test(rel))
435
+ if (/src\/cli\/main\.(js|ts)$/.test(rel))
100
436
  return 'goal/wiki/team/eval/db command handlers';
101
- if (/src\/core\/pipeline\.js$/.test(rel))
437
+ if (/src\/core\/pipeline\.(js|ts)$/.test(rel))
102
438
  return 'route prepare handlers and stop-gate evaluators';
103
439
  return 'largest cohesive command or policy section';
104
440
  }
441
+ function countMatches(text, pattern) {
442
+ const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
443
+ return [...text.matchAll(new RegExp(pattern.source, flags))].length;
444
+ }
445
+ function isSourceLike(file) {
446
+ const rel = normalizeSlashes(file);
447
+ return SOURCE_DIR_RE.test(rel) && SOURCE_EXT_RE.test(rel) && !isGeneratedOrVendor(rel);
448
+ }
449
+ function parseNumstat(value) {
450
+ const parsed = Number(value);
451
+ return Number.isFinite(parsed) ? parsed : 0;
452
+ }
453
+ function safeJson(text) {
454
+ try {
455
+ return JSON.parse(text);
456
+ }
457
+ catch {
458
+ return {};
459
+ }
460
+ }
461
+ function gitLines(root, args) {
462
+ const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
463
+ if (result.status !== 0)
464
+ return [];
465
+ return String(result.stdout || '').split(/\r?\n/).filter(Boolean);
466
+ }
467
+ function gitText(root, args) {
468
+ const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
469
+ if (result.status !== 0)
470
+ return '';
471
+ return String(result.stdout || '');
472
+ }
473
+ function emptyChangedScope(mode, base) {
474
+ return {
475
+ mode,
476
+ base,
477
+ changed_files: [],
478
+ files_added: 0,
479
+ files_deleted: 0,
480
+ lines_added: 0,
481
+ lines_deleted: 0,
482
+ net_lines: 0,
483
+ source_files: [],
484
+ entries: []
485
+ };
486
+ }
487
+ function normalizeRel(root, file) {
488
+ return normalizeSlashes(path.relative(root, path.resolve(root, file)));
489
+ }
490
+ function normalizeSlashes(file) {
491
+ return String(file || '').replace(/\\/g, '/');
492
+ }
105
493
  //# sourceMappingURL=code-structure.js.map
@@ -27,22 +27,44 @@ export async function runFakeCodexSdkTask(input) {
27
27
  }
28
28
  function fakeStructuredOutput(input) {
29
29
  if (input.outputSchemaId === GPT_FINAL_ARBITER_RESULT_SCHEMA_ID) {
30
- const unsafe = /\b(truncate|delete all|drop table|credential)\b/i.test(input.prompt || '');
30
+ const prompt = String(input.prompt || '');
31
+ const leanEnabled = /\b(Lean review|Lean Engineering Policy|sks\.lean-engineering-policy)\b/i.test(prompt);
32
+ const unsafe = /\b(truncate|delete all|drop table|credential|delete validation|validation removed|path traversal|sql injection|secret leak)\b/i.test(prompt);
33
+ const overbuild = leanEnabled ? classifyLeanOverbuild(prompt) : null;
34
+ const status = unsafe ? 'rejected' : overbuild ? 'needs_more_work' : 'approved';
35
+ const leanStatus = unsafe ? 'rejected' : overbuild ? 'needs_more_work' : 'pass';
36
+ const blockers = unsafe ? ['unsafe_candidate_patch'] : overbuild?.blockers || [];
37
+ const findings = unsafe
38
+ ? [{ id: 'unsafe-candidate', severity: 'high', summary: 'unsafe candidate rejected' }]
39
+ : overbuild ? overbuild.findings : [];
31
40
  return {
32
41
  schema: GPT_FINAL_ARBITER_RESULT_SCHEMA_ID,
33
- status: unsafe ? 'rejected' : 'approved',
42
+ status,
34
43
  summary: unsafe
35
44
  ? 'Fake Codex SDK GPT final arbiter rejected an unsafe candidate for hermetic verification.'
36
- : 'Fake Codex SDK GPT final arbiter approved the candidate for hermetic verification.',
37
- gpt_review_findings: unsafe ? [{ id: 'unsafe-candidate', severity: 'high', summary: 'unsafe candidate rejected' }] : [],
38
- accepted_patch_envelopes: unsafe ? [] : [],
45
+ : overbuild
46
+ ? 'Fake Codex SDK GPT final arbiter requested a leaner candidate for hermetic verification.'
47
+ : 'Fake Codex SDK GPT final arbiter approved the candidate for hermetic verification.',
48
+ gpt_review_findings: findings,
49
+ accepted_patch_envelopes: status === 'approved' ? [] : [],
39
50
  modified_patch_envelopes: [],
40
- rejected_patch_envelopes: unsafe ? [{ id: 'unsafe-candidate', summary: 'unsafe candidate', patch_envelope_json: '{}' }] : [],
41
- required_followup_work: unsafe ? [{ id: 'unsafe_candidate_patch', severity: 'high', summary: 'unsafe_candidate_patch' }] : [],
51
+ rejected_patch_envelopes: status === 'rejected' ? [{ id: blockers[0] || 'rejected-candidate', summary: blockers[0] || 'rejected candidate', patch_envelope_json: '{}' }] : [],
52
+ required_followup_work: blockers.map((blocker) => ({ id: blocker, severity: unsafe ? 'high' : 'medium', summary: blocker })),
42
53
  verification_plan: ['schema validation', 'local collaboration final gate'],
43
54
  rollback_notes: [],
44
- blockers: unsafe ? ['unsafe_candidate_patch'] : [],
45
- confidence: unsafe ? 'medium' : 'high'
55
+ blockers,
56
+ lean_review: {
57
+ status: leanStatus,
58
+ selected_rung: unsafe ? 'unknown' : overbuild?.selected_rung || 'minimal-custom',
59
+ unnecessary_files: [],
60
+ unnecessary_dependencies: overbuild?.unnecessary_dependencies || [],
61
+ unnecessary_abstractions: overbuild?.unnecessary_abstractions || [],
62
+ fallback_findings: unsafe ? ['unsafe_candidate_patch'] : overbuild?.fallback_findings || [],
63
+ root_cause_review: overbuild?.root_cause_review || [],
64
+ verification_minimum_present: !unsafe && !/\b(missing runnable check|no runnable check)\b/i.test(prompt),
65
+ net_lines: null
66
+ },
67
+ confidence: unsafe || overbuild ? 'medium' : 'high'
46
68
  };
47
69
  }
48
70
  return {
@@ -56,4 +78,40 @@ function fakeStructuredOutput(input) {
56
78
  blockers: []
57
79
  };
58
80
  }
81
+ function classifyLeanOverbuild(prompt) {
82
+ if (/\b(existing helper reimplementation|same helper reimplementation|reimplement existing helper)\b/i.test(prompt)) {
83
+ return leanFinding('reuse_existing_helper', 'reuse-existing', 'Candidate reimplements an existing helper instead of reusing the repository authority.', {
84
+ root_cause_review: ['reuse existing helper or fix the common helper once']
85
+ });
86
+ }
87
+ if (/\b(new dependency|dependency bloat|native platform instead of dependency)\b/i.test(prompt)) {
88
+ return leanFinding('unnecessary_dependency', 'stdlib', 'Candidate adds a dependency where stdlib or platform support is sufficient.', {
89
+ unnecessary_dependencies: ['unjustified dependency']
90
+ });
91
+ }
92
+ if (/\b(one implementation factory|single implementation factory|one implementation interface)\b/i.test(prompt)) {
93
+ return leanFinding('single_impl_abstraction', 'minimal-custom', 'Candidate adds an abstraction without a second implementation or real variation axis.', {
94
+ unnecessary_abstractions: ['single implementation factory/interface']
95
+ });
96
+ }
97
+ if (/\b(hidden mock fallback|silent mock fallback|fixture fallback)\b/i.test(prompt)) {
98
+ return leanFinding('hidden_fallback', 'minimal-custom', 'Candidate hides a production failure behind a mock or fixture fallback.', {
99
+ fallback_findings: ['hidden mock fallback']
100
+ });
101
+ }
102
+ if (/\b(caller duplicate guard|duplicate caller guard|symptom patch)\b/i.test(prompt)) {
103
+ return leanFinding('root_cause_missing', 'minimal-custom', 'Candidate patches a caller symptom instead of the shared root cause.', {
104
+ root_cause_review: ['move duplicated guard to the shared root-cause helper']
105
+ });
106
+ }
107
+ return null;
108
+ }
109
+ function leanFinding(id, selectedRung, summary, extra = {}) {
110
+ return {
111
+ selected_rung: selectedRung,
112
+ blockers: [id],
113
+ findings: [{ id, severity: 'medium', summary }],
114
+ ...extra
115
+ };
116
+ }
59
117
  //# sourceMappingURL=codex-fake-sdk-adapter.js.map
@@ -5,6 +5,7 @@ import { evaluateLocalCollaborationFinalGate, resolveLocalCollaborationPolicy }
5
5
  import { runCodexTask } from './codex-control-plane.js';
6
6
  import { GPT_FINAL_ARBITER_INPUT_SCHEMA, GPT_FINAL_ARBITER_RESULT_SCHEMA_ID, gptFinalArbiterResultSchema, normalizeGptFinalArbiterResult } from './gpt-final-review-schema.js';
7
7
  import { compressGptFinalContext } from './gpt-final-context-compressor.js';
8
+ import { leanEngineeringCompactText } from '../lean-engineering-policy.js';
8
9
  export const GPT_FINAL_ARBITER_RUN_SCHEMA = 'sks.gpt-final-arbiter-run.v1';
9
10
  export async function runGptFinalArbiter(input, opts = {}) {
10
11
  const started = Date.now();
@@ -145,7 +146,9 @@ function buildArbiterPrompt(input, compressed) {
145
146
  return [
146
147
  'You are the GPT Final Arbiter for an SKS local collaboration run.',
147
148
  'Local model outputs are drafts only. Review the proof pack, candidate diff, patch envelopes, verification results, side effects, mutation ledger, and rollback plan.',
148
- 'Approve or modify only when the candidate is safe and supported. Reject unsafe local patches. Return only the requested structured JSON schema.',
149
+ leanEngineeringCompactText(),
150
+ 'Lean review: check for reused helpers before reimplementation, unjustified dependencies, one-implementation factories/interfaces, hidden mock or provider fallbacks, duplicated caller guards instead of root-cause fixes, forwarding-only files, missing runnable checks for non-trivial logic, and safety/validation removal disguised as simplification.',
151
+ 'Approve or modify only when the candidate is safe, supported, and no more complex than the request requires. Reject unsafe local patches. Return only the requested structured JSON schema.',
149
152
  JSON.stringify({
150
153
  route: input.route,
151
154
  mission_id: input.mission_id,