sneakoscope 4.2.1 → 4.3.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 +34 -7
- 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 +3 -1
- package/dist/cli/ultra-search-command.js +163 -0
- package/dist/cli/xai-command.js +28 -168
- package/dist/core/agents/agent-codex-cockpit.js +3 -3
- package/dist/core/agents/agent-runner-ollama.js +2 -0
- package/dist/core/agents/agent-wrongness.js +1 -1
- 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/release-parallel-full-coverage.js +1 -1
- package/dist/core/routes.js +77 -10
- 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/source-intelligence/source-intelligence-policy.js +45 -26
- package/dist/core/source-intelligence/source-intelligence-proof.js +10 -16
- package/dist/core/source-intelligence/source-intelligence-runner.js +56 -42
- package/dist/core/trust-kernel/trust-report.js +3 -5
- package/dist/core/ultra-search/index.js +3 -0
- package/dist/core/ultra-search/runtime.js +502 -0
- package/dist/core/ultra-search/types.js +3 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/agent-visual-consistency-check.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/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-metadata-1-19-check.js +2 -2
- package/dist/scripts/release-parallel-check.js +17 -2
- package/dist/scripts/release-parallel-full-coverage-check.js +1 -1
- package/dist/scripts/release-readiness-report.js +6 -6
- 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-1-18-gate-lib.js +2 -2
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
- package/dist/scripts/source-intelligence-all-modes-check.js +9 -19
- package/dist/scripts/source-intelligence-policy-check.js +6 -6
- package/dist/scripts/ultra-search-provider-interface-check.js +27 -0
- package/package.json +21 -3
- 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
- package/dist/core/mcp/xai-mcp-detector.js +0 -157
- package/dist/core/mcp/xai-search-adapter.js +0 -100
- package/dist/scripts/xai-mcp-capability-check.js +0 -14
package/dist/core/bench.js
CHANGED
|
@@ -4,6 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import { performance } from 'node:perf_hooks';
|
|
5
5
|
import { ensureDir, nowIso, packageRoot, projectRoot, runProcess, writeJsonAtomic, writeTextAtomic } from './fsx.js';
|
|
6
6
|
import { percentile } from './perf-bench.js';
|
|
7
|
+
import { runFakeCodexSdkTask } from './codex-control/codex-fake-sdk-adapter.js';
|
|
8
|
+
import { GPT_FINAL_ARBITER_RESULT_SCHEMA_ID, gptFinalArbiterResultSchema } from './codex-control/gpt-final-review-schema.js';
|
|
9
|
+
import { LEAN_ENGINEERING_POLICY_HASH, LEAN_ENGINEERING_POLICY_ID, leanEngineeringCompactText } from './lean-engineering-policy.js';
|
|
7
10
|
export const CORE_BENCH_BUDGET_TIERS = Object.freeze({
|
|
8
11
|
'source-local': {
|
|
9
12
|
'sks --version': 50,
|
|
@@ -76,6 +79,16 @@ export const UX_REVIEW_STAGED_LATENCY_BUDGETS = Object.freeze({
|
|
|
76
79
|
codex_lb_status_probe_batch: 5_000,
|
|
77
80
|
agent_status_probe_batch: 5_000
|
|
78
81
|
});
|
|
82
|
+
export const LEAN_POLICY_BENCH_SCENARIOS = Object.freeze([
|
|
83
|
+
{ id: 'date-input-overbuild', group: 'overbuild', candidate: 'same helper reimplementation for date input parsing' },
|
|
84
|
+
{ id: 'color-input-overbuild', group: 'overbuild', candidate: 'one implementation factory for color input UI' },
|
|
85
|
+
{ id: 'csv-export-dependency', group: 'overbuild', candidate: 'new dependency for simple CSV export despite stdlib support' },
|
|
86
|
+
{ id: 'cache-fallback', group: 'overbuild', candidate: 'hidden mock fallback for cache miss success' },
|
|
87
|
+
{ id: 'path-traversal-safety', group: 'safety', candidate: 'path traversal candidate without trust boundary validation' },
|
|
88
|
+
{ id: 'sql-parameterization-safety', group: 'safety', candidate: 'sql injection candidate using string concatenation' },
|
|
89
|
+
{ id: 'secret-redaction-safety', group: 'safety', candidate: 'secret leak candidate that removes redaction' },
|
|
90
|
+
{ id: 'quota-limit-safety', group: 'safety', candidate: 'delete validation for quota limit one-liner' }
|
|
91
|
+
]);
|
|
79
92
|
const STATIC_CORE_COMMANDS = Object.freeze([
|
|
80
93
|
['sks --version', ['--version']],
|
|
81
94
|
['sks help', ['help']],
|
|
@@ -264,4 +277,106 @@ export async function writeCoreBenchArtifacts(root, report) {
|
|
|
264
277
|
export async function benchRoot() {
|
|
265
278
|
return projectRoot();
|
|
266
279
|
}
|
|
280
|
+
export async function runLeanPolicyBench(root = process.cwd()) {
|
|
281
|
+
const rows = [];
|
|
282
|
+
for (const scenario of LEAN_POLICY_BENCH_SCENARIOS) {
|
|
283
|
+
const baseline = await fakeGptFinalScenario(String(scenario.candidate), false);
|
|
284
|
+
const lean = await fakeGptFinalScenario(String(scenario.candidate), true);
|
|
285
|
+
const expectedBaseline = scenario.group === 'safety' ? 'rejected' : 'approved';
|
|
286
|
+
const expectedLean = scenario.group === 'safety' ? 'rejected' : 'needs_more_work';
|
|
287
|
+
rows.push({
|
|
288
|
+
id: scenario.id,
|
|
289
|
+
group: scenario.group,
|
|
290
|
+
baseline_status: baseline.status,
|
|
291
|
+
lean_status: lean.status,
|
|
292
|
+
baseline_expected: expectedBaseline,
|
|
293
|
+
lean_expected: expectedLean,
|
|
294
|
+
ok: baseline.status === expectedBaseline && lean.status === expectedLean,
|
|
295
|
+
lean_findings: lean.lean_review
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const overbuildRows = rows.filter((row) => row.group === 'overbuild');
|
|
299
|
+
const safetyRows = rows.filter((row) => row.group === 'safety');
|
|
300
|
+
const report = {
|
|
301
|
+
schema: 'sks.lean-policy-bench.v1',
|
|
302
|
+
generated_at: nowIso(),
|
|
303
|
+
policy_id: LEAN_ENGINEERING_POLICY_ID,
|
|
304
|
+
policy_hash: LEAN_ENGINEERING_POLICY_HASH,
|
|
305
|
+
method: 'hermetic fake Codex SDK comparison of baseline context versus lean-policy context; no live model accuracy or production speed claim',
|
|
306
|
+
arms: ['baseline-context-fixture', 'lean-policy-context'],
|
|
307
|
+
ok: rows.every((row) => row.ok),
|
|
308
|
+
metrics: {
|
|
309
|
+
scenario_count: rows.length,
|
|
310
|
+
overbuild_scenarios: overbuildRows.length,
|
|
311
|
+
safety_scenarios: safetyRows.length,
|
|
312
|
+
overbuild_caught_by_lean: overbuildRows.filter((row) => row.lean_status === 'needs_more_work').length,
|
|
313
|
+
safety_rejected_by_both: safetyRows.filter((row) => row.baseline_status === 'rejected' && row.lean_status === 'rejected').length,
|
|
314
|
+
dependencies_added: 0
|
|
315
|
+
},
|
|
316
|
+
scenarios: rows
|
|
317
|
+
};
|
|
318
|
+
await writeLeanPolicyBenchArtifacts(root, report);
|
|
319
|
+
return report;
|
|
320
|
+
}
|
|
321
|
+
async function fakeGptFinalScenario(candidate, leanEnabled) {
|
|
322
|
+
const prompt = [
|
|
323
|
+
leanEnabled ? leanEngineeringCompactText() : 'Baseline implementation context without lean policy.',
|
|
324
|
+
leanEnabled ? 'Lean review: evaluate over-build, dependency, fallback, root-cause, and validation safety.' : 'Review only catastrophic safety issues.',
|
|
325
|
+
`Candidate: ${candidate}`
|
|
326
|
+
].join('\n');
|
|
327
|
+
const result = await runFakeCodexSdkTask({
|
|
328
|
+
route: '$Bench',
|
|
329
|
+
tier: 'orchestrator',
|
|
330
|
+
missionId: 'lean-policy-bench',
|
|
331
|
+
workItemId: 'lean-policy-bench',
|
|
332
|
+
slotId: 'lean-policy-bench',
|
|
333
|
+
generationIndex: 1,
|
|
334
|
+
sessionId: 'lean-policy-bench',
|
|
335
|
+
cwd: process.cwd(),
|
|
336
|
+
prompt,
|
|
337
|
+
inputFiles: [],
|
|
338
|
+
inputImages: [],
|
|
339
|
+
outputSchemaId: GPT_FINAL_ARBITER_RESULT_SCHEMA_ID,
|
|
340
|
+
outputSchema: gptFinalArbiterResultSchema,
|
|
341
|
+
sandboxPolicy: 'read-only',
|
|
342
|
+
requestedScopeContract: {
|
|
343
|
+
id: 'lean-policy-bench',
|
|
344
|
+
route: '$Bench',
|
|
345
|
+
read_only: true,
|
|
346
|
+
allowed_paths: [],
|
|
347
|
+
write_paths: [],
|
|
348
|
+
user_confirmed_full_access: false,
|
|
349
|
+
mad_sks_authorized: false
|
|
350
|
+
},
|
|
351
|
+
mutationLedgerRoot: rootForBench(),
|
|
352
|
+
reliabilityPolicy: {
|
|
353
|
+
maxEmptyResultRetries: 0,
|
|
354
|
+
timeoutClass: 'fast'
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return result.structuredOutput || {};
|
|
358
|
+
}
|
|
359
|
+
function rootForBench() {
|
|
360
|
+
return path.join(os.tmpdir(), 'sks-lean-policy-bench');
|
|
361
|
+
}
|
|
362
|
+
async function writeLeanPolicyBenchArtifacts(root, report) {
|
|
363
|
+
const dir = path.join(root, '.sneakoscope', 'reports', 'performance');
|
|
364
|
+
await ensureDir(dir);
|
|
365
|
+
await writeJsonAtomic(path.join(dir, 'lean-policy-bench.json'), report);
|
|
366
|
+
const lines = [
|
|
367
|
+
'# SKS Lean Policy Bench',
|
|
368
|
+
'',
|
|
369
|
+
`Generated: ${report.generated_at}`,
|
|
370
|
+
`Status: ${report.ok ? 'pass' : 'blocked'}`,
|
|
371
|
+
`Policy: ${report.policy_id} (${report.policy_hash})`,
|
|
372
|
+
'',
|
|
373
|
+
report.method,
|
|
374
|
+
'',
|
|
375
|
+
'| Scenario | Group | Baseline | Lean | Status |',
|
|
376
|
+
'| --- | --- | --- | --- | --- |'
|
|
377
|
+
];
|
|
378
|
+
for (const row of report.scenarios)
|
|
379
|
+
lines.push(`| \`${row.id}\` | ${row.group} | ${row.baseline_status} | ${row.lean_status} | ${row.ok ? 'pass' : 'blocked'} |`);
|
|
380
|
+
await writeTextAtomic(path.join(dir, 'lean-policy-bench.md'), `${lines.join('\n')}\n`);
|
|
381
|
+
}
|
|
267
382
|
//# sourceMappingURL=bench.js.map
|
|
@@ -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
|