sneakoscope 3.1.0 → 3.1.2

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 (84) hide show
  1. package/README.md +1 -1
  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/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/install-helpers.js +6 -7
  8. package/dist/commands/zellij-slot-column-anchor.js +3 -1
  9. package/dist/commands/zellij-slot-pane.js +19 -2
  10. package/dist/core/agents/agent-janitor.js +10 -1
  11. package/dist/core/agents/agent-orchestrator.js +8 -2
  12. package/dist/core/agents/agent-proof-evidence.js +20 -0
  13. package/dist/core/agents/agent-runner-ollama.js +11 -4
  14. package/dist/core/agents/fast-mode-policy.js +7 -5
  15. package/dist/core/agents/intelligent-work-graph.js +93 -14
  16. package/dist/core/agents/native-cli-session-swarm.js +115 -9
  17. package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
  18. package/dist/core/agents/official-subagent-helper-policy.js +62 -0
  19. package/dist/core/codex-app.js +0 -2
  20. package/dist/core/codex-control/codex-task-runner.js +9 -0
  21. package/dist/core/commands/fast-mode-command.js +1 -1
  22. package/dist/core/commands/loop-command.js +86 -13
  23. package/dist/core/commands/naruto-command.js +34 -21
  24. package/dist/core/commands/team-command.js +1 -0
  25. package/dist/core/commands/wiki-command.js +35 -1
  26. package/dist/core/fsx.js +1 -1
  27. package/dist/core/init.js +1 -2
  28. package/dist/core/locks/file-lock.js +88 -0
  29. package/dist/core/loops/loop-artifacts.js +54 -2
  30. package/dist/core/loops/loop-checkpoint.js +22 -0
  31. package/dist/core/loops/loop-concurrency-budget.js +55 -0
  32. package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
  33. package/dist/core/loops/loop-finalizer.js +55 -7
  34. package/dist/core/loops/loop-fixture-policy.js +58 -0
  35. package/dist/core/loops/loop-gate-registry.js +96 -0
  36. package/dist/core/loops/loop-gate-runner.js +206 -17
  37. package/dist/core/loops/loop-gpt-final-arbiter.js +81 -0
  38. package/dist/core/loops/loop-integration-merge.js +80 -0
  39. package/dist/core/loops/loop-interrupt-registry.js +118 -0
  40. package/dist/core/loops/loop-lease.js +35 -20
  41. package/dist/core/loops/loop-merge-strategy.js +105 -0
  42. package/dist/core/loops/loop-mutation-ledger.js +103 -0
  43. package/dist/core/loops/loop-planner.js +36 -5
  44. package/dist/core/loops/loop-runtime-control.js +27 -0
  45. package/dist/core/loops/loop-runtime.js +254 -96
  46. package/dist/core/loops/loop-scheduler.js +14 -5
  47. package/dist/core/loops/loop-side-effect-scanner.js +91 -0
  48. package/dist/core/loops/loop-worker-prompts.js +43 -0
  49. package/dist/core/loops/loop-worker-runtime.js +281 -0
  50. package/dist/core/loops/loop-worktree-runtime.js +92 -0
  51. package/dist/core/naruto/naruto-finalizer.js +7 -2
  52. package/dist/core/naruto/naruto-loop-mesh.js +10 -1
  53. package/dist/core/proof/auto-finalize.js +3 -2
  54. package/dist/core/proof/proof-schema.js +6 -0
  55. package/dist/core/proof/proof-writer.js +5 -2
  56. package/dist/core/proof/root-cause-policy.js +70 -0
  57. package/dist/core/proof/route-adapter.js +18 -1
  58. package/dist/core/proof/route-finalizer.js +71 -6
  59. package/dist/core/proof/route-proof-gate.js +4 -0
  60. package/dist/core/release/release-gate-batch-runner.js +56 -10
  61. package/dist/core/release/release-gate-cache-v2.js +18 -3
  62. package/dist/core/release/release-gate-dag.js +121 -18
  63. package/dist/core/release/release-gate-node.js +2 -1
  64. package/dist/core/release/release-gate-resource-governor.js +27 -6
  65. package/dist/core/skills/core-skill-meta-update.js +24 -0
  66. package/dist/core/skills/core-skill-reflection.js +94 -0
  67. package/dist/core/skills/core-skill-trainer.js +103 -0
  68. package/dist/core/trust-kernel/completion-contract.js +4 -0
  69. package/dist/core/trust-kernel/route-contract.js +4 -1
  70. package/dist/core/version.js +1 -1
  71. package/dist/core/zellij/zellij-right-column-manager.js +13 -2
  72. package/dist/core/zellij/zellij-slot-column-anchor.js +40 -3
  73. package/dist/core/zellij/zellij-slot-pane-renderer.js +36 -11
  74. package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
  75. package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
  76. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
  77. package/dist/scripts/loop-directive-check-lib.js +225 -2
  78. package/dist/scripts/loop-hardening-check-lib.js +289 -0
  79. package/dist/scripts/loop-worker-fixture-child.js +53 -0
  80. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
  81. package/dist/scripts/prepublish-release-check-or-fast.js +38 -10
  82. package/dist/scripts/release-check-stamp.js +29 -4
  83. package/dist/scripts/release-gate-existence-audit.js +1 -0
  84. package/package.json +32 -2
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { runProcess } from '../core/fsx.js';
8
+ import { decideLoopFixturePolicy } from '../core/loops/loop-fixture-policy.js';
9
+ import { writeLoopFinalArbiterGateContract } from '../core/loops/loop-final-arbiter-contract.js';
10
+ import { runLoopGates } from '../core/loops/loop-gate-runner.js';
11
+ import { runLoopMakerWorkers } from '../core/loops/loop-worker-runtime.js';
12
+ import { runLoopGptFinalArbiter } from '../core/loops/loop-gpt-final-arbiter.js';
13
+ import { mergeLoopWorktrees } from '../core/loops/loop-integration-merge.js';
14
+ import { mergeSingleLoopWorktree } from '../core/loops/loop-merge-strategy.js';
15
+ import { appendLoopMutationEvent, mutationLedgerFromLoopProofs, readLoopMutationLedger } from '../core/loops/loop-mutation-ledger.js';
16
+ import { buildLoopSideEffectReport } from '../core/loops/loop-side-effect-scanner.js';
17
+ import { interruptLoopWorkers, readLoopActiveWorkers, registerLoopActiveWorker } from '../core/loops/loop-interrupt-registry.js';
18
+ import { computeLoopConcurrencyBudget } from '../core/loops/loop-concurrency-budget.js';
19
+ import { defaultLoopBudget } from '../core/loops/loop-schema.js';
20
+ import { root } from './sks-1-18-gate-lib.js';
21
+ export async function runLoopHardeningCheck(id) {
22
+ const assertions = [];
23
+ const assert = (condition, message, detail = {}) => assertions.push({ ok: Boolean(condition), message, detail });
24
+ const temp = await fs.mkdtemp(path.join(os.tmpdir(), `sks-312-${safe(id)}-`));
25
+ await fs.mkdir(path.join(temp, '.sneakoscope', 'missions'), { recursive: true });
26
+ if (id === 'loop:fixture-policy') {
27
+ const allowed = decideLoopFixturePolicy({ root: temp, missionId: 'M-check-fixture-policy', mode: 'gate', requested: true, argv: ['/x/dist/scripts/loop-fixture-policy-check.js'], env: {} });
28
+ const denied = decideLoopFixturePolicy({ root, missionId: 'M-prod-fixture-policy', mode: 'gate', requested: true, argv: ['sks', 'loop', 'run'], env: { SKS_LOOP_GATE_FIXTURE: '1' } });
29
+ assert(allowed.allowed && allowed.reason.includes('release_check_script'), 'fixture policy allows check/blackbox temp runs', allowed);
30
+ assert(!denied.allowed && denied.blockers.includes('loop_gate_fixture_forbidden_in_production'), 'fixture policy blocks production command fixture', denied);
31
+ }
32
+ else if (id === 'loop:gate-fixture-guard') {
33
+ const node = sampleNode('loop-zellij', 'M-prod-gate-fixture');
34
+ const prev = process.env.SKS_LOOP_GATE_FIXTURE;
35
+ const argv = replaceArgv(['sks', 'loop', 'run']);
36
+ await fs.writeFile(path.join(temp, 'package.json'), JSON.stringify({ scripts: { 'release:version-truth': 'node -e "process.exit(0)"' } }, null, 2));
37
+ process.env.SKS_LOOP_GATE_FIXTURE = '1';
38
+ const gates = await runLoopGates({ root: temp, missionId: node.mission_id, node, gates: { triage: [], local: ['release:version-truth'], checker: [], integration: [], final: [] } });
39
+ restoreEnv('SKS_LOOP_GATE_FIXTURE', prev);
40
+ restoreArgv(argv);
41
+ assert(!gates.ok && gates.blockers.includes('loop_gate_fixture_forbidden_in_production'), 'production gate fixture cannot synthetic-pass');
42
+ }
43
+ else if (id === 'loop:worker-fixture-guard') {
44
+ const plan = samplePlan('M-prod-worker-fixture', [sampleNode('loop-zellij', 'M-prod-worker-fixture')]);
45
+ const prev = process.env.SKS_LOOP_RUNTIME_FIXTURE;
46
+ const argv = replaceArgv(['sks', 'loop', 'run']);
47
+ process.env.SKS_LOOP_RUNTIME_FIXTURE = '1';
48
+ try {
49
+ await runLoopMakerWorkers({ root: temp, plan, node: plan.graph.nodes[0], fixture: true });
50
+ assert(false, 'production worker fixture throws');
51
+ }
52
+ catch (err) {
53
+ assert(String(err).includes('loop_fixture_runtime_forbidden'), 'production worker fixture throws forbidden error', { message: String(err) });
54
+ }
55
+ restoreEnv('SKS_LOOP_RUNTIME_FIXTURE', prev);
56
+ restoreArgv(argv);
57
+ }
58
+ else if (id === 'loop:gpt-final-fixture-guard') {
59
+ const plan = samplePlan('M-prod-gpt-fixture', [sampleNode('loop-zellij', 'M-prod-gpt-fixture')]);
60
+ const argv = replaceArgv(['sks', 'loop', 'run']);
61
+ const arbiter = await runLoopGptFinalArbiter({ root: temp, plan, proofs: [sampleProof('loop-zellij', 'M-prod-gpt-fixture', ['src/core/zellij/a.ts'])], integrationMerge: sampleMerge(['src/core/zellij/a.ts']), forceVerdict: 'approve' });
62
+ restoreArgv(argv);
63
+ assert(!arbiter.ok && arbiter.blockers.includes('loop_gpt_final_fixture_forbidden_in_production'), 'production GPT final fixture cannot approve');
64
+ }
65
+ else if (id === 'loop:fixture-production-misuse-blackbox') {
66
+ const gate = decideLoopFixturePolicy({ root, missionId: 'M-normal-production', mode: 'gate', requested: true, env: { SKS_LOOP_GATE_FIXTURE: '1' }, argv: ['sks', 'loop', 'run'] });
67
+ const worker = decideLoopFixturePolicy({ root, missionId: 'M-normal-production', mode: 'worker', requested: true, env: { SKS_LOOP_RUNTIME_FIXTURE: '1' }, argv: ['sks', 'naruto'] });
68
+ const allowed = decideLoopFixturePolicy({ root: temp, missionId: 'M-check-production-misuse', mode: 'worker', requested: true, env: { SKS_TEST_RUNTIME_FIXTURE_ALLOWED: '1' }, argv: ['/x/dist/scripts/loop-fixture-production-misuse-blackbox.js'] });
69
+ assert(!gate.allowed && !worker.allowed, 'production gate/worker fixtures are denied');
70
+ assert(allowed.allowed, 'M-check temp fixture remains allowed');
71
+ }
72
+ else if (id === 'loop:final-arbiter-contract') {
73
+ const contract = await writeLoopFinalArbiterGateContract(temp, 'M-check-final-contract');
74
+ assert(contract.handled_by === 'loop-finalizer' && contract.production_fixture_allowed === false, 'final arbiter contract is finalizer-owned');
75
+ assert(await exists(path.join(temp, '.sneakoscope/missions/M-check-final-contract/loops/gpt-final-arbiter-gate-contract.json')), 'contract artifact written');
76
+ }
77
+ else if (id === 'loop:gpt-final-gate-contract') {
78
+ const node = sampleNode('loop-zellij', 'M-check-gpt-final-gate');
79
+ const result = await runLoopGates({ root: temp, missionId: node.mission_id, node, gates: { triage: [], local: [], checker: [], integration: [], final: ['gpt:final-arbiter'] } });
80
+ const artifact = await readJson(path.join(temp, '.sneakoscope/missions/M-check-gpt-final-gate/loops/loop-zellij/gates/gpt-final-arbiter.json'));
81
+ assert(result.ok && result.skipped_gates.includes('gpt:final-arbiter'), 'gpt final pseudo gate is skipped by gate runner');
82
+ assert(artifact.handled_by === 'loop-finalizer' && artifact.deferred_contract_path, 'gate artifact points to finalizer contract');
83
+ }
84
+ else if (id === 'loop:gpt-final-contract-crossref') {
85
+ const plan = samplePlan('M-check-gpt-crossref', [sampleNode('loop-zellij', 'M-check-gpt-crossref')]);
86
+ const arbiter = await runLoopGptFinalArbiter({ root: temp, plan, proofs: [sampleProof('loop-zellij', plan.mission_id, ['src/core/zellij/a.ts'])], integrationMerge: sampleMerge(['src/core/zellij/a.ts']), forceVerdict: 'approve' });
87
+ assert(arbiter.ok, 'check mission may use forced GPT final verdict');
88
+ assert(await exists(path.join(temp, '.sneakoscope/missions/M-check-gpt-crossref/loops/fixture-policy.json')), 'GPT final fixture policy artifact written');
89
+ }
90
+ else if (id === 'loop:merge-strategy' || id === 'loop:merge-strategy-blackbox') {
91
+ const fixture = await gitFixture('merge-strategy');
92
+ const proof = sampleProof('loop-zellij', 'M-check-merge-strategy', ['src/core/zellij/a.ts']);
93
+ proof.worktree.path = fixture.worktree;
94
+ proof.worktree.branch = 'loop-branch';
95
+ await fs.writeFile(path.join(fixture.worktree, 'src/core/zellij/a.ts'), 'changed\n');
96
+ const merge = await mergeSingleLoopWorktree({ root: fixture.root, proof, worktreePath: fixture.worktree, allowBranchMerge: true });
97
+ assert(merge.ok && ['apply', 'apply-3way', 'cherry-pick', 'already_applied'].includes(String(merge.selected_strategy)), 'merge strategy ladder applies simple patch', merge);
98
+ if (id === 'loop:merge-strategy-blackbox') {
99
+ const again = await mergeSingleLoopWorktree({ root: fixture.root, proof, worktreePath: fixture.worktree, allowBranchMerge: true });
100
+ assert(again.ok && again.selected_strategy === 'already_applied', 'already applied patch is handled');
101
+ }
102
+ }
103
+ else if (id === 'loop:integration-merge-strategy') {
104
+ const fixture = await gitFixture('integration-merge');
105
+ await fs.writeFile(path.join(fixture.worktree, 'src/core/zellij/a.ts'), 'integrated\n');
106
+ const proof = sampleProof('loop-zellij', 'M-check-integration-merge', ['src/core/zellij/a.ts']);
107
+ proof.worktree.path = fixture.worktree;
108
+ const plan = samplePlan('M-check-integration-merge', [
109
+ sampleNode('loop-zellij', 'M-check-integration-merge'),
110
+ sampleNode('loop-integration', 'M-check-integration-merge')
111
+ ]);
112
+ const result = await mergeLoopWorktrees({ root: fixture.root, plan, proofs: [proof] });
113
+ assert(result.ok && result.merge_attempts?.['loop-zellij'], 'integration merge records merge strategy attempts');
114
+ }
115
+ else if (id === 'loop:mutation-ledger') {
116
+ await appendLoopMutationEvent(temp, 'M-check-ledger', { loop_id: 'loop-zellij', event_type: 'file_changed', file_path: 'src/core/zellij/a.ts', source: 'git-diff', allowed_by_owner_scope: true, details: {} });
117
+ const rows = await readLoopMutationLedger(temp, 'M-check-ledger');
118
+ assert(rows.length === 1 && rows[0].event_type === 'file_changed', 'mutation ledger append/read works');
119
+ }
120
+ else if (id === 'loop:side-effect-scanner' || id === 'loop:side-effect-blackbox') {
121
+ const proofs = [sampleProof('loop-zellij', 'M-check-side-effect', ['package.json'])];
122
+ await mutationLedgerFromLoopProofs({ root: temp, missionId: 'M-check-side-effect', proofs, integrationMerge: sampleMerge(['package.json']) });
123
+ const report = await buildLoopSideEffectReport({ root: temp, missionId: 'M-check-side-effect', proofs, integrationMerge: sampleMerge(['package.json']) });
124
+ assert(!report.ok && report.unexpected_package_changes.includes('package.json'), 'side-effect scanner blocks non-integration package mutation', report);
125
+ if (id === 'loop:side-effect-blackbox')
126
+ assert(report.blockers.some((row) => row.includes('unexpected_package_change')), 'side-effect blackbox exposes blocker');
127
+ }
128
+ else if (id === 'loop:side-effect-final-arbiter') {
129
+ const plan = samplePlan('M-check-side-effect-final', [sampleNode('loop-zellij', 'M-check-side-effect-final')]);
130
+ const report = await buildLoopSideEffectReport({ root: temp, missionId: plan.mission_id, proofs: [sampleProof('loop-zellij', plan.mission_id, ['package.json'])], integrationMerge: sampleMerge(['package.json']) });
131
+ const arbiter = await runLoopGptFinalArbiter({ root: temp, plan, proofs: [sampleProof('loop-zellij', plan.mission_id, ['package.json'])], integrationMerge: sampleMerge(['package.json']), sideEffectReport: report });
132
+ assert(!arbiter.ok && arbiter.verdict === 'reject', 'side-effect block rejects before GPT can approve');
133
+ }
134
+ else if (id === 'loop:interrupt-registry' || id === 'loop:worker-handle-registration') {
135
+ await registerLoopActiveWorker(temp, { mission_id: 'M-check-interrupt', loop_id: 'loop-zellij', phase: 'maker', worker_id: 'w1', session_id: 's1', pid: null, interrupt_supported: true });
136
+ const handles = await readLoopActiveWorkers(temp, 'M-check-interrupt');
137
+ assert(handles.length === 1 && handles[0].status === 'running', 'active worker handle registers');
138
+ }
139
+ else if (id === 'loop:worker-interrupt' || id === 'loop:kill-interrupt-real-blackbox') {
140
+ const child = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000)'], { stdio: 'ignore' });
141
+ await registerLoopActiveWorker(temp, { mission_id: 'M-check-interrupt-real', loop_id: 'loop-zellij', phase: 'maker', worker_id: 'sleepy', session_id: null, pid: child.pid || null, interrupt_supported: true });
142
+ const result = await interruptLoopWorkers({ root: temp, missionId: 'M-check-interrupt-real', target: 'loop-zellij', graceMs: 50 });
143
+ assert(result.interrupted.includes('sleepy'), 'active worker receives interrupt');
144
+ child.kill('SIGKILL');
145
+ }
146
+ else if (id === 'loop:concurrency-budget' || id === 'loop:concurrency-budget-runtime' || id === 'loop:concurrency-oversubscription-blackbox') {
147
+ const plan = samplePlan('M-check-budget', Array.from({ length: 10 }, (_, i) => sampleNode(`loop-${i}`, 'M-check-budget', 8, 8)));
148
+ const budget = computeLoopConcurrencyBudget({ plan, parallelism: 'extreme', env: { SKS_LOOP_MAX_ACTIVE_WORKERS: '16', SKS_LOOP_MAX_ACTIVE_LOOPS: '4', SKS_LOOP_MAX_MODEL_CALLS: '8' } });
149
+ assert(budget.max_active_workers === 16 && budget.max_active_loops === 4, 'env concurrency budget overrides apply', budget);
150
+ assert(budget.per_loop_worker_budget.reduce((sum, row) => sum + row.maker_workers + row.checker_workers, 0) <= 16, 'per-loop worker budget does not oversubscribe');
151
+ }
152
+ else if (id === 'loop:mesh-production-e2e-blackbox') {
153
+ const fixture = await gitFixture('mesh-e2e');
154
+ const proof = sampleProof('loop-zellij', 'M-check-mesh-e2e', ['src/core/zellij/a.ts']);
155
+ proof.worktree.path = fixture.worktree;
156
+ await fs.writeFile(path.join(fixture.worktree, 'src/core/zellij/a.ts'), 'mesh\n');
157
+ const merge = await mergeSingleLoopWorktree({ root: fixture.root, proof, worktreePath: fixture.worktree, allowBranchMerge: true });
158
+ const side = await buildLoopSideEffectReport({ root: fixture.root, missionId: 'M-check-mesh-e2e', proofs: [proof], integrationMerge: sampleMerge(['src/core/zellij/a.ts']) });
159
+ const fixturePolicy = decideLoopFixturePolicy({ root: fixture.root, missionId: 'M-check-mesh-e2e', mode: 'gpt-final', requested: true, argv: ['/x/dist/scripts/loop-mesh-production-e2e-blackbox.js'], env: {} });
160
+ assert(merge.ok && side.ok && fixturePolicy.allowed, 'production e2e blackbox covers merge, side effects, and check-only final fixture');
161
+ }
162
+ else if (id === 'loop:status-proof-ux') {
163
+ const text = await fs.readFile(path.join(root, 'src/core/commands/loop-command.ts'), 'utf8');
164
+ assert(['active_worker_handles', 'side_effects', 'strategy_summary', 'Final arbiter'].every((token) => text.includes(token)), 'loop status/proof UX exposes hardening fields');
165
+ }
166
+ else if (id === 'changelog:loop-productionization') {
167
+ const text = await fs.readFile(path.join(root, 'CHANGELOG.md'), 'utf8');
168
+ assert(text.includes('## [3.1.2] - 2026-06-13'), 'changelog has 3.1.2 section');
169
+ for (const token of ['fixture misuse guard', 'Finalizer-owned GPT final arbiter', 'merge strategy ladder', 'side-effect scanner', 'kill interrupt', 'concurrency budget', 'production e2e blackbox']) {
170
+ assert(text.toLowerCase().includes(token.toLowerCase()), `changelog mentions ${token}`);
171
+ }
172
+ }
173
+ else if (id === 'docs:loop-productionization') {
174
+ const docs = await Promise.all(['docs/loop-runtime.md', 'docs/naruto-loop-mesh.md', 'docs/loop-fixture-policy.md', 'docs/loop-merge-strategy.md'].map((file) => fs.readFile(path.join(root, file), 'utf8')));
175
+ for (const token of ['fixture', 'gpt:final-arbiter', 'merge strategy', 'side-effect', 'interrupt', 'concurrency']) {
176
+ assert(docs.some((text) => text.toLowerCase().includes(token.toLowerCase())), `docs mention ${token}`);
177
+ }
178
+ }
179
+ else {
180
+ assert(false, `unknown loop hardening check id: ${id}`);
181
+ }
182
+ const failed = assertions.filter((row) => !row.ok);
183
+ const report = { schema: 'sks.loop-hardening-check.v1', id, ok: failed.length === 0, assertions, temp_root: temp };
184
+ console.log(JSON.stringify(report, null, 2));
185
+ if (failed.length)
186
+ process.exitCode = 1;
187
+ }
188
+ function sampleNode(loopId, missionId, makerWorkers = 2, checkerWorkers = 1) {
189
+ return {
190
+ schema: 'sks.loop-node.v1',
191
+ loop_id: loopId,
192
+ mission_id: missionId,
193
+ title: loopId,
194
+ purpose: 'fixture node',
195
+ level: 'L2-action',
196
+ route: loopId.includes('integration') ? '$Integration' : '$Loop',
197
+ owner_scope: { files: [], directories: ['src/core/zellij'], package_scripts: [], release_gate_ids: [], exclusive: true, collision_policy: 'handoff' },
198
+ state_file: 'state.json',
199
+ run_log_file: 'run.jsonl',
200
+ budget: defaultLoopBudget({ max_model_calls: 8, max_subagents: makerWorkers + checkerWorkers }),
201
+ maker: { route: '$Naruto', role: 'implementer', worker_count: makerWorkers, backend_preference: ['codex-sdk'], local_draft_allowed: false, gpt_final_required: false },
202
+ checker: { route: '$QA-LOOP', worker_count: checkerWorkers, fresh_session_required: true, stronger_model_required: false, required_before_next_iteration: true },
203
+ gates: { triage: [], local: [], checker: [], integration: [], final: [] },
204
+ dependencies: [],
205
+ handoff_policy: { allow_handoff: true, reasons: [], artifact: null },
206
+ worktree: { required: false, mode: 'none', branch_prefix: 'loop', cleanup: 'keep-on-failure' },
207
+ risk: { level: 'medium', reasons: [], requires_worktree: false, requires_gpt_final: true, requires_human_handoff: false }
208
+ };
209
+ }
210
+ function samplePlan(missionId, nodes) {
211
+ return {
212
+ schema: 'sks.loop-plan.v1',
213
+ mission_id: missionId,
214
+ request: 'fixture plan',
215
+ generated_at: new Date().toISOString(),
216
+ planner: { route: '$Loop', model_policy: 'deterministic', confidence: 'high' },
217
+ graph: { nodes, edges: [] },
218
+ global_budget: defaultLoopBudget({ max_model_calls: 32, max_subagents: 32 }),
219
+ safety: { no_unrequested_fallback_code: true, require_owner_lease: true, require_checker_for_action: true, require_gpt_final_for_source_mutation: true },
220
+ integration_loop_id: nodes.at(-1)?.loop_id || 'loop-integration',
221
+ compatibility: { goal_compat_artifact: null, source_command: 'loop' },
222
+ blockers: []
223
+ };
224
+ }
225
+ function sampleProof(loopId, missionId, changedFiles) {
226
+ return {
227
+ schema: 'sks.loop-proof.v1',
228
+ mission_id: missionId,
229
+ loop_id: loopId,
230
+ status: 'completed',
231
+ iterations: 1,
232
+ owner_scope: { files: [], directories: ['src/core/zellij'], package_scripts: [], release_gate_ids: [], exclusive: true, collision_policy: 'handoff' },
233
+ worktree: { id: loopId, path: null, branch: null },
234
+ maker_result: { ok: true, worker_count: 1, artifacts: [], patch_candidates: [], backend: 'deterministic-fixture', changed_files: changedFiles, runtime_proof_path: null },
235
+ checker_result: { ok: true, worker_count: 1, artifacts: [], blockers: [], backend: 'deterministic-fixture', checker_findings: [], fresh_session: true, runtime_proof_path: null },
236
+ gate_result: { ok: true, selected_gates: [], passed_gates: [], failed_gates: [], skipped_gates: [], blockers: [] },
237
+ budget: { used: { wall_ms: 1, model_calls: 1, subagents: 2, iterations: 1, changed_files: changedFiles.length, patch_bytes: 1 }, max: defaultLoopBudget() },
238
+ changed_files: changedFiles,
239
+ patch_bytes: 1,
240
+ handoff: { required: false, reason: null, artifact: null },
241
+ blockers: []
242
+ };
243
+ }
244
+ function sampleMerge(changedFiles) {
245
+ return { schema: 'sks.loop-integration-merge.v1', ok: true, applied_loops: ['loop-zellij'], conflict_loops: [], changed_files: changedFiles, blockers: [] };
246
+ }
247
+ async function gitFixture(name) {
248
+ const repo = await fs.mkdtemp(path.join(os.tmpdir(), `sks-312-git-${safe(name)}-`));
249
+ await fs.mkdir(path.join(repo, 'src/core/zellij'), { recursive: true });
250
+ await fs.writeFile(path.join(repo, 'src/core/zellij/a.ts'), 'base\n');
251
+ await runProcess('git', ['init'], { cwd: repo, maxOutputBytes: 10000 });
252
+ await runProcess('git', ['config', 'user.email', 'sks@example.invalid'], { cwd: repo, maxOutputBytes: 10000 });
253
+ await runProcess('git', ['config', 'user.name', 'SKS Check'], { cwd: repo, maxOutputBytes: 10000 });
254
+ await runProcess('git', ['add', '.'], { cwd: repo, maxOutputBytes: 10000 });
255
+ await runProcess('git', ['commit', '-m', 'base'], { cwd: repo, maxOutputBytes: 20000 });
256
+ const worktree = `${repo}-worktree`;
257
+ await runProcess('git', ['worktree', 'add', '-b', 'loop-branch', worktree], { cwd: repo, maxOutputBytes: 20000 });
258
+ return { root: repo, worktree };
259
+ }
260
+ async function exists(file) {
261
+ try {
262
+ await fs.access(file);
263
+ return true;
264
+ }
265
+ catch {
266
+ return false;
267
+ }
268
+ }
269
+ async function readJson(file) {
270
+ return JSON.parse(await fs.readFile(file, 'utf8'));
271
+ }
272
+ function restoreEnv(key, value) {
273
+ if (value === undefined)
274
+ delete process.env[key];
275
+ else
276
+ process.env[key] = value;
277
+ }
278
+ function replaceArgv(next) {
279
+ const previous = [...process.argv];
280
+ process.argv.splice(0, process.argv.length, ...next);
281
+ return previous;
282
+ }
283
+ function restoreArgv(previous) {
284
+ process.argv.splice(0, process.argv.length, ...previous);
285
+ }
286
+ function safe(value) {
287
+ return String(value).replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'check';
288
+ }
289
+ //# sourceMappingURL=loop-hardening-check-lib.js.map
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ const intakePath = process.argv[2];
6
+ if (!intakePath)
7
+ throw new Error('Usage: loop-worker-fixture-child <intake.json>');
8
+ const intake = JSON.parse(await fs.readFile(intakePath, 'utf8'));
9
+ const dir = path.dirname(intake.result_path);
10
+ await fs.mkdir(dir, { recursive: true });
11
+ const workerIds = Array.from({ length: Math.max(1, Number(intake.worker_count || 1)) }, (_, index) => `${intake.loop_id}-${intake.phase}-fixture-worker-${index + 1}`);
12
+ const sessionIds = workerIds.map((id) => `${id}-${process.pid}`);
13
+ const artifactPath = path.join(dir, intake.phase === 'maker' ? 'maker-patch-candidate.json' : 'checker-findings.json');
14
+ const changedFiles = intake.phase === 'maker' ? [] : [];
15
+ const artifact = intake.phase === 'maker'
16
+ ? {
17
+ schema: 'sks.loop-patch-candidate.v1',
18
+ loop_id: intake.loop_id,
19
+ worker_ids: workerIds,
20
+ changed_files: changedFiles,
21
+ fixture_child_pid: process.pid,
22
+ generated_at: new Date().toISOString()
23
+ }
24
+ : {
25
+ schema: 'sks.loop-checker-findings.v1',
26
+ loop_id: intake.loop_id,
27
+ fresh_session: true,
28
+ reviewed_maker_artifacts: intake.maker_artifacts || [],
29
+ side_effects_detected: [],
30
+ approved: true,
31
+ worker_ids: workerIds,
32
+ fixture_child_pid: process.pid,
33
+ generated_at: new Date().toISOString()
34
+ };
35
+ await fs.writeFile(artifactPath, `${JSON.stringify(artifact, null, 2)}\n`);
36
+ await fs.writeFile(intake.result_path, `${JSON.stringify({
37
+ schema: 'sks.loop-worker-run-result.v1',
38
+ ok: true,
39
+ mission_id: intake.mission_id,
40
+ loop_id: intake.loop_id,
41
+ phase: intake.phase,
42
+ worker_count: workerIds.length,
43
+ backend: 'deterministic-fixture',
44
+ artifacts: [artifactPath],
45
+ patch_candidates: intake.phase === 'maker' ? [artifactPath] : [],
46
+ checker_findings: intake.phase === 'checker' ? [artifactPath] : [],
47
+ changed_files: changedFiles,
48
+ blockers: [],
49
+ runtime_proof_path: intake.result_path,
50
+ worker_ids: workerIds,
51
+ session_ids: sessionIds
52
+ }, null, 2)}\n`);
53
+ //# sourceMappingURL=loop-worker-fixture-child.js.map
@@ -1,16 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
3
5
  import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
4
6
  const finalizer = await importDist('core/naruto/naruto-finalizer.js');
5
7
  const draft = finalizer.evaluateNarutoFinalizer({ localParticipated: true, gptFinalStatus: null, applyPatches: true });
6
8
  const approved = finalizer.evaluateNarutoFinalizer({ localParticipated: true, gptFinalStatus: 'approved', applyPatches: true });
7
9
  const deterministic = finalizer.evaluateNarutoFinalizer({ localParticipated: false, applyPatches: true });
10
+ const deterministicDraft = finalizer.evaluateNarutoFinalizer({ localParticipated: false, applyPatches: false });
11
+ const narutoCommandSource = await fs.readFile(path.join(process.cwd(), 'src/core/commands/naruto-command.ts'), 'utf8');
8
12
  assertGate(draft.ok === false && draft.blockers.includes('naruto_local_worker_output_needs_gpt_final_arbiter'), 'local worker patch must be blocked until GPT final arbiter', draft);
9
13
  assertGate(approved.ok === true && approved.final_patch_source === 'gpt_final_arbiter', 'GPT-approved local output must become final patch source', approved);
10
14
  assertGate(deterministic.ok === true && deterministic.gpt_final_required === false, 'no-local deterministic run must not require GPT final', deterministic);
15
+ assertGate(deterministicDraft.final_status === 'draft', 'no-apply Naruto run must remain draft even when writes were possible', deterministicDraft);
16
+ assertGate(deterministicDraft.ok === false && deterministicDraft.run_ok === true && deterministicDraft.release_proof_allowed === false, 'no-apply Naruto draft must not masquerade as an accepted finalizer', deterministicDraft);
17
+ assertGate(narutoCommandSource.includes('applyPatches: parsed.applyPatches') && !narutoCommandSource.includes('applyPatches: writeCapable'), 'Naruto command finalizer must use explicit apply-patches flag, not write capability');
18
+ assertGate(narutoCommandSource.includes('parsed.applyPatches === true ? finalizer.ok === true : finalizer.run_ok === true') && narutoCommandSource.includes('ok: summaryOk'), 'Naruto command top-level ok must separate patch finality from readonly/no-apply run success');
11
19
  emitGate('naruto:real-local-gpt-final-smoke', {
12
20
  require_real_env: process.env.SKS_REQUIRE_LOCAL_LLM === '1' || process.env.SKS_REQUIRE_GPT_FINAL === '1',
13
21
  draft_status: draft.final_status,
14
- approved_status: approved.final_status
22
+ approved_status: approved.final_status,
23
+ deterministic_no_apply_status: deterministicDraft.final_status
15
24
  });
16
25
  //# sourceMappingURL=naruto-real-local-gpt-final-smoke.js.map
@@ -4,7 +4,8 @@
4
4
  //
5
5
  // Fast path: accept a current release-check stamp.
6
6
  // Repair path: if the stamp is missing/stale, run the authoritative full
7
- // `release:check:full` once, then require the fast check to pass.
7
+ // `release:check:full` once, then require both the fast check and the
8
+ // authoritative stamp verifier to pass.
8
9
  //
9
10
  // This keeps direct `npm publish` usable without weakening the publish gate:
10
11
  // stale stamp repair is the full release gate, not a synthetic stamp write.
@@ -26,6 +27,18 @@ function runFastCheck() {
26
27
  report: parseLastJsonLine(result.stdout)
27
28
  };
28
29
  }
30
+ function runStampVerify() {
31
+ const result = spawnSync(process.execPath, ['./dist/scripts/release-check-stamp.js', 'verify'], {
32
+ cwd: process.cwd(),
33
+ encoding: 'utf8',
34
+ env: process.env
35
+ });
36
+ if (result.stdout)
37
+ process.stdout.write(result.stdout);
38
+ if (result.stderr)
39
+ process.stderr.write(result.stderr);
40
+ return result;
41
+ }
29
42
  function runReleaseCheck() {
30
43
  const override = process.env.SKS_PREPUBLISH_RELEASE_CHECK_CMD;
31
44
  if (override) {
@@ -67,27 +80,42 @@ function isStaleOrMissingStamp(report) {
67
80
  'package_json_sha256',
68
81
  'package_files_list_sha256',
69
82
  'package_files_sha256',
83
+ 'dist_build_sha256',
84
+ 'dist_file_count',
70
85
  'release_gate_sha256',
86
+ 'release_check_sha256',
87
+ 'source_digest',
88
+ 'source_file_count',
71
89
  'stamp_unreadable'
72
90
  ].includes(name));
73
91
  }
74
- function main() {
75
- const first = runFastCheck();
76
- if (first.status === 0)
77
- process.exit(0);
78
- if (!isStaleOrMissingStamp(first.report)) {
79
- process.exit(first.status || 1);
80
- }
92
+ function repairAndVerify() {
81
93
  if (process.env.SKS_PREPUBLISH_RUN_RELEASE_CHECK_ON_STALE === '0') {
82
94
  console.error('Prepublish release-check auto-repair disabled by SKS_PREPUBLISH_RUN_RELEASE_CHECK_ON_STALE=0.');
83
- process.exit(first.status || 1);
95
+ process.exit(1);
84
96
  }
85
97
  console.error('Prepublish release stamp is stale or missing; running full `npm run release:check:full` before publish.');
86
98
  const releaseCheck = runReleaseCheck();
87
99
  if (releaseCheck.status !== 0)
88
100
  process.exit(releaseCheck.status || 1);
89
101
  const second = runFastCheck();
90
- process.exit(second.status === 0 ? 0 : (second.status || 1));
102
+ if (second.status !== 0)
103
+ process.exit(second.status || 1);
104
+ const secondStamp = runStampVerify();
105
+ process.exit(secondStamp.status === 0 ? 0 : (secondStamp.status || 1));
106
+ }
107
+ function main() {
108
+ const first = runFastCheck();
109
+ if (first.status === 0) {
110
+ const stamp = runStampVerify();
111
+ if (stamp.status === 0)
112
+ process.exit(0);
113
+ repairAndVerify();
114
+ }
115
+ if (!isStaleOrMissingStamp(first.report)) {
116
+ process.exit(first.status || 1);
117
+ }
118
+ repairAndVerify();
91
119
  }
92
120
  main();
93
121
  //# sourceMappingURL=prepublish-release-check-or-fast.js.map
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
9
9
  const stampPath = process.env.SKS_RELEASE_STAMP_PATH || path.join(root, '.sneakoscope', 'reports', 'release-check-stamp.json');
10
10
  const command = process.argv[2] || 'verify';
11
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
11
12
  function fail(message, detail = '') {
12
13
  console.error(`Release check stamp failed: ${message}`);
13
14
  if (detail)
@@ -65,6 +66,24 @@ function gitCommit() {
65
66
  const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
66
67
  return result.status === 0 ? result.stdout.trim() : null;
67
68
  }
69
+ function runRefreshCommand() {
70
+ const override = process.env.SKS_RELEASE_CHECK_REFRESH_COMMAND;
71
+ if (override) {
72
+ return spawnSync(override, {
73
+ cwd: root,
74
+ encoding: 'utf8',
75
+ env: process.env,
76
+ shell: true,
77
+ stdio: 'inherit'
78
+ });
79
+ }
80
+ return spawnSync(npmCmd, ['run', 'release:check:full'], {
81
+ cwd: root,
82
+ encoding: 'utf8',
83
+ env: process.env,
84
+ stdio: 'inherit'
85
+ });
86
+ }
68
87
  function releaseGateHash(pkg) {
69
88
  const manifests = ['release-gates.v2.json', 'release-gates.json'].map((rel) => {
70
89
  const file = path.join(root, rel);
@@ -177,7 +196,7 @@ function inspectStamp() {
177
196
  return {
178
197
  ok: false,
179
198
  message: 'missing release:check stamp',
180
- detail: 'Run `npm run release:check` once, then rerun the publish command.'
199
+ detail: 'Run `npm run release:check:full` once, then rerun the publish command.'
181
200
  };
182
201
  }
183
202
  let stamp;
@@ -201,7 +220,7 @@ function inspectStamp() {
201
220
  return {
202
221
  ok: false,
203
222
  message: 'release:check stamp is stale',
204
- detail: `${mismatches.join('\n')}\nRun \`npm run release:check\` again before publishing.`,
223
+ detail: `${mismatches.join('\n')}\nRun \`npm run release:check:full\` again before publishing.`,
205
224
  current
206
225
  };
207
226
  }
@@ -220,10 +239,16 @@ function ensureStamp() {
220
239
  console.log(`Release check stamp verified: ${path.relative(root, stampPath)} (${first.current.source_file_count} files)`);
221
240
  return;
222
241
  }
223
- console.error('Release check stamp is not current; publish path will not run a full release:check refresh.');
242
+ console.error('Release check stamp is not current; running full `npm run release:check:full` refresh.');
224
243
  if (first.detail)
225
244
  console.error(first.detail.trim());
226
- fail(first.message, 'Run `npm run release:check` outside the publish path to refresh the stamp.');
245
+ const refresh = runRefreshCommand();
246
+ if (refresh.status !== 0)
247
+ process.exit(refresh.status || 1);
248
+ const second = inspectStamp();
249
+ if (!second.ok)
250
+ fail(second.message, second.detail);
251
+ console.log(`Release check stamp verified: ${path.relative(root, stampPath)} (${second.current.source_file_count} files)`);
227
252
  }
228
253
  if (command === 'write')
229
254
  writeStamp();
@@ -65,6 +65,7 @@ const required = [
65
65
  'agent:native-cli-session-swarm-10',
66
66
  'agent:native-cli-session-swarm-20',
67
67
  'agent:no-subagent-scaling',
68
+ 'agent:official-subagent-helper-policy',
68
69
  'agent:native-cli-session-proof',
69
70
  'agent:fast-mode-default',
70
71
  'agent:fast-mode-worker-propagation',