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.
- package/README.md +28 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -0
- package/dist/core/agents/agent-runner-ollama.js +2 -0
- package/dist/core/agents/native-worker-backend-router.js +3 -0
- package/dist/core/bench.js +115 -0
- package/dist/core/code-structure.js +399 -11
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +67 -9
- package/dist/core/codex-control/gpt-final-arbiter.js +4 -1
- package/dist/core/codex-control/gpt-final-review-schema.js +58 -0
- package/dist/core/codex-native/core-skill-manifest.js +23 -0
- package/dist/core/commands/bench-command.js +11 -2
- package/dist/core/commands/code-structure-command.js +34 -2
- package/dist/core/commands/run-command.js +92 -2
- package/dist/core/commands/seo-command.js +130 -0
- package/dist/core/feature-fixtures.js +6 -0
- package/dist/core/feature-registry.js +3 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +8 -0
- package/dist/core/init.js +8 -6
- package/dist/core/lean-engineering-policy.js +159 -0
- package/dist/core/pipeline-internals/runtime-core.js +15 -5
- package/dist/core/proof/auto-finalize.js +3 -2
- package/dist/core/proof/proof-schema.js +2 -1
- package/dist/core/proof/proof-writer.js +1 -0
- package/dist/core/proof/route-adapter.js +4 -2
- package/dist/core/proof/route-finalizer.js +35 -3
- package/dist/core/routes.js +75 -9
- package/dist/core/search-visibility/adapter-registry.js +26 -0
- package/dist/core/search-visibility/adapters/next-app.js +6 -0
- package/dist/core/search-visibility/adapters/next-pages.js +6 -0
- package/dist/core/search-visibility/adapters/static-site.js +6 -0
- package/dist/core/search-visibility/analyzers.js +377 -0
- package/dist/core/search-visibility/artifacts.js +183 -0
- package/dist/core/search-visibility/discovery.js +347 -0
- package/dist/core/search-visibility/index.js +199 -0
- package/dist/core/search-visibility/mission.js +67 -0
- package/dist/core/search-visibility/mutation.js +314 -0
- package/dist/core/search-visibility/types.js +2 -0
- package/dist/core/search-visibility/verifier.js +60 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/check-architecture.js +40 -7
- package/dist/scripts/check-command-module-budget.js +43 -5
- package/dist/scripts/check-pipeline-budget.js +17 -30
- package/dist/scripts/check-publish-tag.js +33 -6
- package/dist/scripts/check-route-modularity.js +25 -33
- package/dist/scripts/check-runtime-schemas.js +22 -0
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +2 -2
- package/dist/scripts/core-skill-immutable-sync-check.js +3 -2
- package/dist/scripts/core-skill-integrity-blackbox.js +3 -2
- package/dist/scripts/core-skill-manifest-check.js +7 -2
- package/dist/scripts/geo-claim-evidence-check.js +18 -0
- package/dist/scripts/geo-cli-blackbox-check.js +18 -0
- package/dist/scripts/geo-crawler-policy-check.js +16 -0
- package/dist/scripts/geo-llms-txt-optional-check.js +19 -0
- package/dist/scripts/gpt-final-arbiter-check.js +4 -1
- package/dist/scripts/release-parallel-check.js +15 -0
- package/dist/scripts/release-registry-check.js +33 -14
- package/dist/scripts/search-visibility-gate-lib.js +124 -0
- package/dist/scripts/seo-audit-fixture-check.js +16 -0
- package/dist/scripts/seo-canonical-locale-check.js +19 -0
- package/dist/scripts/seo-cli-blackbox-check.js +18 -0
- package/dist/scripts/seo-geo-feature-fixture-quality-check.js +18 -0
- package/dist/scripts/seo-geo-geo-disambiguation-check.js +12 -0
- package/dist/scripts/seo-geo-no-unsupported-ranking-claims-check.js +18 -0
- package/dist/scripts/seo-geo-route-identity-check.js +12 -0
- package/dist/scripts/seo-geo-skill-rich-content-check.js +22 -0
- package/dist/scripts/seo-mutation-rollback-check.js +23 -0
- package/dist/scripts/seo-no-mutation-by-default-check.js +17 -0
- package/dist/scripts/seo-structured-data-visible-content-check.js +19 -0
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
- package/package.json +19 -1
- package/schemas/search-visibility/finding-ledger.schema.json +36 -0
- package/schemas/search-visibility/gate.schema.json +22 -0
- package/schemas/search-visibility/mutation-plan.schema.json +27 -0
- package/schemas/search-visibility/site-inventory.schema.json +21 -0
- 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', '.
|
|
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
|
|
13
|
-
const
|
|
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 =
|
|
17
|
-
const
|
|
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
|
-
|
|
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) :
|
|
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
|
|
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
|
|
42
|
+
status,
|
|
34
43
|
summary: unsafe
|
|
35
44
|
? 'Fake Codex SDK GPT final arbiter rejected an unsafe candidate for hermetic verification.'
|
|
36
|
-
:
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
41
|
-
required_followup_work:
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
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,
|