sneakoscope 3.0.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) 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/command-registry.js +1 -0
  8. package/dist/cli/context7-command.js +29 -5
  9. package/dist/cli/install-helpers.js +15 -7
  10. package/dist/core/agents/runtime-proof-summary.js +4 -0
  11. package/dist/core/commands/goal-command.js +19 -1
  12. package/dist/core/commands/loop-command.js +135 -0
  13. package/dist/core/fsx.js +1 -1
  14. package/dist/core/init.js +6 -1
  15. package/dist/core/loops/goal-to-loop-compat.js +23 -0
  16. package/dist/core/loops/loop-artifacts.js +41 -0
  17. package/dist/core/loops/loop-decomposer.js +56 -0
  18. package/dist/core/loops/loop-finalizer.js +28 -0
  19. package/dist/core/loops/loop-gate-ladder.js +16 -0
  20. package/dist/core/loops/loop-gate-runner.js +29 -0
  21. package/dist/core/loops/loop-gate-selector.js +52 -0
  22. package/dist/core/loops/loop-iteration-runner.js +2 -0
  23. package/dist/core/loops/loop-lease.js +76 -0
  24. package/dist/core/loops/loop-observability.js +19 -0
  25. package/dist/core/loops/loop-owner-inference.js +57 -0
  26. package/dist/core/loops/loop-owner-ledger.js +2 -0
  27. package/dist/core/loops/loop-planner.js +139 -0
  28. package/dist/core/loops/loop-proof-summary.js +10 -0
  29. package/dist/core/loops/loop-proof.js +2 -0
  30. package/dist/core/loops/loop-risk-classifier.js +42 -0
  31. package/dist/core/loops/loop-runtime.js +159 -0
  32. package/dist/core/loops/loop-scheduler.js +60 -0
  33. package/dist/core/loops/loop-schema.js +63 -0
  34. package/dist/core/loops/loop-state.js +61 -0
  35. package/dist/core/naruto/naruto-loop-mesh.js +33 -0
  36. package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
  37. package/dist/core/pipeline-internals/runtime-core.js +82 -2
  38. package/dist/core/version.js +1 -1
  39. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -2
  40. package/dist/core/zellij/zellij-slot-pane-renderer.js +2 -0
  41. package/dist/scripts/loop-directive-check-lib.js +165 -0
  42. package/package.json +34 -2
  43. package/schemas/loops/loop-node.schema.json +21 -0
  44. package/schemas/loops/loop-plan.schema.json +21 -0
  45. package/schemas/loops/loop-proof.schema.json +20 -0
  46. package/schemas/loops/loop-state.schema.json +19 -0
@@ -0,0 +1,63 @@
1
+ export function defaultLoopBudget(overrides = {}) {
2
+ return {
3
+ max_iterations: 2,
4
+ max_wall_ms: 15 * 60 * 1000,
5
+ max_model_calls: 8,
6
+ max_subagents: 4,
7
+ max_tokens_estimate: 120000,
8
+ max_changed_files: 16,
9
+ max_patch_bytes: 256000,
10
+ ...overrides
11
+ };
12
+ }
13
+ export function validateLoopPlan(plan) {
14
+ const blockers = [
15
+ ...(plan.schema !== 'sks.loop-plan.v1' ? ['loop_plan_schema_invalid'] : []),
16
+ ...(!plan.mission_id ? ['loop_plan_mission_id_missing'] : []),
17
+ ...(!plan.request ? ['loop_plan_request_missing'] : []),
18
+ ...(!plan.integration_loop_id ? ['loop_plan_integration_loop_missing'] : []),
19
+ ...(plan.graph.nodes.length === 0 ? ['loop_plan_nodes_missing'] : []),
20
+ ...(!plan.graph.nodes.some((node) => node.loop_id === plan.integration_loop_id) ? ['loop_plan_integration_node_missing'] : [])
21
+ ];
22
+ const ids = new Set();
23
+ for (const node of plan.graph.nodes) {
24
+ const result = validateLoopNode(node);
25
+ blockers.push(...result.blockers);
26
+ if (ids.has(node.loop_id))
27
+ blockers.push(`loop_node_duplicate:${node.loop_id}`);
28
+ ids.add(node.loop_id);
29
+ }
30
+ for (const edge of plan.graph.edges) {
31
+ if (!ids.has(edge.from) || !ids.has(edge.to))
32
+ blockers.push(`loop_edge_unknown:${edge.from}->${edge.to}`);
33
+ }
34
+ return { ok: blockers.length === 0, blockers };
35
+ }
36
+ export function validateLoopNode(node) {
37
+ const blockers = [
38
+ ...(node.schema !== 'sks.loop-node.v1' ? [`loop_node_schema_invalid:${node.loop_id}`] : []),
39
+ ...(!node.loop_id ? ['loop_node_id_missing'] : []),
40
+ ...(!node.mission_id ? [`loop_node_mission_missing:${node.loop_id}`] : []),
41
+ ...(!node.state_file ? [`loop_state_file_missing:${node.loop_id}`] : []),
42
+ ...(!node.run_log_file ? [`loop_run_log_file_missing:${node.loop_id}`] : []),
43
+ ...(!node.owner_scope ? [`loop_owner_scope_missing:${node.loop_id}`] : []),
44
+ ...validateLoopBudget(node.budget).blockers.map((blocker) => `${node.loop_id}:${blocker}`),
45
+ ...(node.level === 'L3-unattended' && ['high', 'critical'].includes(node.risk.level) ? [`loop_l3_risk_blocked:${node.loop_id}`] : []),
46
+ ...(node.level === 'L2-action' && !node.checker.required_before_next_iteration ? [`loop_action_checker_missing:${node.loop_id}`] : [])
47
+ ];
48
+ return { ok: blockers.length === 0, blockers };
49
+ }
50
+ export function validateLoopBudget(budget) {
51
+ const blockers = [];
52
+ for (const [key, value] of Object.entries(budget)) {
53
+ if (!Number.isFinite(value) || value < 0)
54
+ blockers.push(`loop_budget_invalid:${key}`);
55
+ }
56
+ if (budget.max_iterations < 1)
57
+ blockers.push('loop_budget_iterations_missing');
58
+ return { ok: blockers.length === 0, blockers };
59
+ }
60
+ export function allGateIds(gates) {
61
+ return [...new Set([...gates.triage, ...gates.local, ...gates.checker, ...gates.integration, ...gates.final])];
62
+ }
63
+ //# sourceMappingURL=loop-schema.js.map
@@ -0,0 +1,61 @@
1
+ import { appendJsonl, readJson, writeJsonAtomic } from '../fsx.js';
2
+ import { loopRunLogPath, loopStatePath } from './loop-artifacts.js';
3
+ export async function readLoopState(root, missionId, loopId) {
4
+ return readJson(loopStatePath(root, missionId, loopId), null);
5
+ }
6
+ export async function writeLoopState(root, state) {
7
+ await writeJsonAtomic(loopStatePath(root, state.mission_id, state.loop_id), state);
8
+ return state;
9
+ }
10
+ export async function appendLoopRunLog(root, missionId, loopId, event) {
11
+ await appendJsonl(loopRunLogPath(root, missionId, loopId), { ts: event.ts || new Date().toISOString(), ...event });
12
+ }
13
+ export async function updateLoopState(root, missionId, loopId, patch) {
14
+ const current = await readLoopState(root, missionId, loopId);
15
+ if (!current)
16
+ throw new Error(`loop_state_missing:${loopId}`);
17
+ const next = {
18
+ ...current,
19
+ ...patch,
20
+ acting_on: { ...current.acting_on, ...(patch.acting_on || {}) },
21
+ handoff: { ...current.handoff, ...(patch.handoff || {}) },
22
+ budget_used: { ...current.budget_used, ...(patch.budget_used || {}) },
23
+ updated_at: new Date().toISOString()
24
+ };
25
+ await writeLoopState(root, next);
26
+ return next;
27
+ }
28
+ export function initialLoopState(input) {
29
+ return {
30
+ schema: 'sks.loop-state.v1',
31
+ mission_id: input.missionId,
32
+ loop_id: input.loopId,
33
+ status: 'planned',
34
+ iteration: 0,
35
+ acting_on: {
36
+ files: input.files,
37
+ worktree_id: input.worktreeId || null,
38
+ branch: input.branch || null
39
+ },
40
+ current_phase: 'triage',
41
+ last_action: null,
42
+ last_gate_result: null,
43
+ last_checker_result: null,
44
+ blockers: [],
45
+ handoff: {
46
+ required: false,
47
+ reason: null,
48
+ artifact: null
49
+ },
50
+ budget_used: {
51
+ wall_ms: 0,
52
+ model_calls: 0,
53
+ subagents: 0,
54
+ iterations: 0,
55
+ changed_files: 0,
56
+ patch_bytes: 0
57
+ },
58
+ updated_at: new Date().toISOString()
59
+ };
60
+ }
61
+ //# sourceMappingURL=loop-state.js.map
@@ -0,0 +1,33 @@
1
+ import { writeJsonAtomic } from '../fsx.js';
2
+ import { loopRoot } from '../loops/loop-artifacts.js';
3
+ import { runLoopPlan } from '../loops/loop-runtime.js';
4
+ import { routeNarutoLoopWorker } from './naruto-loop-worker-router.js';
5
+ export async function runNarutoLoopMesh(input) {
6
+ const routes = input.plan.graph.nodes.flatMap((node) => [routeNarutoLoopWorker(node, 'maker'), routeNarutoLoopWorker(node, 'checker')]);
7
+ const activeWorkerBudget = splitActiveWorkerBudget(input.plan, input.parallelism);
8
+ await writeJsonAtomic(`${loopRoot(input.root, input.plan.mission_id)}/naruto-loop-worker-routes.json`, {
9
+ schema: 'sks.naruto-loop-worker-routes.v1',
10
+ mission_id: input.plan.mission_id,
11
+ active_worker_budget: activeWorkerBudget,
12
+ routes
13
+ });
14
+ return runLoopPlan({ root: input.root, plan: input.plan, parallelism: input.parallelism });
15
+ }
16
+ export function splitActiveWorkerBudget(plan, parallelism) {
17
+ const cap = parallelism === 'safe' ? 8 : parallelism === 'extreme' ? 32 : 16;
18
+ const integrationReserved = 2;
19
+ const nonIntegration = plan.graph.nodes.filter((node) => node.route !== '$Integration');
20
+ const perLoopCap = Math.max(2, Math.floor((cap - integrationReserved) / Math.max(1, nonIntegration.length)));
21
+ const perLoop = nonIntegration.map((node) => ({
22
+ loop_id: node.loop_id,
23
+ maker_checker_workers: Math.min(perLoopCap, node.maker.worker_count + node.checker.worker_count)
24
+ }));
25
+ const used = perLoop.reduce((sum, row) => sum + row.maker_checker_workers, integrationReserved);
26
+ return {
27
+ global_active_workers: cap,
28
+ integration_reserved: integrationReserved,
29
+ per_loop: perLoop,
30
+ headroom: Math.max(0, cap - used)
31
+ };
32
+ }
33
+ //# sourceMappingURL=naruto-loop-mesh.js.map
@@ -0,0 +1,38 @@
1
+ export function routeNarutoLoopWorker(node, role) {
2
+ const domain = node.loop_id.replace(/^loop-/, '');
3
+ const roles = roleLabels(domain);
4
+ const gates = [...node.gates.triage, ...node.gates.local, ...node.gates.checker, ...node.gates.integration, ...node.gates.final];
5
+ return {
6
+ schema: 'sks.naruto-loop-worker-route.v1',
7
+ loop_id: node.loop_id,
8
+ maker_role: roles.maker,
9
+ checker_role: roles.checker,
10
+ prompt: [
11
+ `loop purpose: ${node.purpose}`,
12
+ `role: ${role === 'maker' ? roles.maker : roles.checker}`,
13
+ `owner files: ${node.owner_scope.files.join(', ') || '-'}`,
14
+ `owner directories: ${node.owner_scope.directories.join(', ') || '-'}`,
15
+ `gates: ${gates.join(', ') || '-'}`,
16
+ `state file: ${node.state_file}`,
17
+ `budget: ${JSON.stringify(node.budget)}`,
18
+ `collision policy: ${node.owner_scope.collision_policy}`,
19
+ 'Do not mutate outside owner scope.'
20
+ ].join('\n'),
21
+ allowed_files: node.owner_scope.files,
22
+ allowed_directories: node.owner_scope.directories,
23
+ gates,
24
+ mutation_outside_owner_scope_allowed: false
25
+ };
26
+ }
27
+ function roleLabels(domain) {
28
+ if (domain.includes('zellij'))
29
+ return { maker: 'zellij implementer', checker: 'zellij QA/verifier' };
30
+ if (domain.includes('release'))
31
+ return { maker: 'release optimizer', checker: 'release gate verifier' };
32
+ if (domain.includes('research'))
33
+ return { maker: 'source shard/synthesis', checker: 'final reviewer' };
34
+ if (domain.includes('codex'))
35
+ return { maker: 'capability/probe implementer', checker: 'real probe verifier' };
36
+ return { maker: `${domain} implementer`, checker: `${domain} checker` };
37
+ }
38
+ //# sourceMappingURL=naruto-loop-worker-router.js.map
@@ -1213,9 +1213,17 @@ export async function recordContext7Evidence(root, state, payload) {
1213
1213
  return null;
1214
1214
  if (!await shouldWritePipelineEvidence(root, state))
1215
1215
  return null;
1216
- const record = { ts: nowIso(), stage, tool: context7ToolName(payload), payload_keys: Object.keys(payload || {}).sort() };
1217
1216
  const id = state?.mission_id;
1218
1217
  const file = id ? path.join(missionDir(root, id), 'context7-evidence.jsonl') : path.join(root, '.sneakoscope', 'state', 'context7-evidence.jsonl');
1218
+ const record = {
1219
+ ts: nowIso(),
1220
+ stage,
1221
+ tool: context7ToolName(payload),
1222
+ payload_keys: Object.keys(payload || {}).sort(),
1223
+ dedupe_key: context7DedupeKey(stage, payload)
1224
+ };
1225
+ if (await hasContext7EvidenceRecord(file, record.dedupe_key))
1226
+ return null;
1219
1227
  await appendJsonl(file, record);
1220
1228
  if (id) {
1221
1229
  const evidence = await context7Evidence(root, state);
@@ -1297,7 +1305,11 @@ function context7ToolName(payload) {
1297
1305
  return String(obj.tool_name || obj.name || obj.tool?.name || obj.mcp_tool || obj.command || obj.type || '');
1298
1306
  }
1299
1307
  function context7Stage(payload) {
1300
- const hay = JSON.stringify(payload || {});
1308
+ const tool = context7ToolName(payload);
1309
+ const direct = context7DirectSignal(payload);
1310
+ if (!direct && !context7ToolLooksRelevant(tool))
1311
+ return null;
1312
+ const hay = [tool, direct].filter(Boolean).join('\n');
1301
1313
  if (!/(context7|resolve[-_]?library[-_]?id|get[-_]?library[-_]?docs|query[-_]?docs)/i.test(hay))
1302
1314
  return null;
1303
1315
  if (/resolve[-_]?library[-_]?id/i.test(hay))
@@ -1306,6 +1318,74 @@ function context7Stage(payload) {
1306
1318
  return 'get-library-docs';
1307
1319
  return 'context7';
1308
1320
  }
1321
+ function context7ToolLooksRelevant(tool) {
1322
+ return /(^|[_:/.-])(context7|resolve[-_]?library[-_]?id|get[-_]?library[-_]?docs|query[-_]?docs)($|[_:/.-])/i.test(String(tool || ''));
1323
+ }
1324
+ function context7DirectSignal(payload = {}) {
1325
+ const source = String(payload.source || '');
1326
+ const tool = context7ToolName(payload);
1327
+ if (/^sks context7 evidence/i.test(source)) {
1328
+ return [
1329
+ tool,
1330
+ source,
1331
+ payload.library,
1332
+ payload.library_id,
1333
+ payload.docs_tool
1334
+ ].filter(Boolean).join('\n');
1335
+ }
1336
+ if (context7ToolLooksRelevant(tool)) {
1337
+ const input = payload.tool_input || payload.toolInput || payload.input || payload.tool?.input || {};
1338
+ return JSON.stringify({
1339
+ tool,
1340
+ library: payload.library,
1341
+ library_id: payload.library_id,
1342
+ docs_tool: payload.docs_tool,
1343
+ input: context7SafeInput(input)
1344
+ });
1345
+ }
1346
+ return '';
1347
+ }
1348
+ function context7SafeInput(input) {
1349
+ if (!input || typeof input !== 'object' || Array.isArray(input))
1350
+ return input ?? null;
1351
+ const out = {};
1352
+ for (const key of ['name', 'tool', 'library', 'libraryName', 'library_id', 'libraryId', 'context7CompatibleLibraryID', 'query', 'topic', 'tokens']) {
1353
+ if (Object.prototype.hasOwnProperty.call(input, key))
1354
+ out[key] = input[key];
1355
+ }
1356
+ return out;
1357
+ }
1358
+ function context7DedupeKey(stage, payload = {}) {
1359
+ const input = payload.tool_input || payload.toolInput || payload.input || payload.tool?.input || {};
1360
+ const library = payload.library_id
1361
+ || payload.library
1362
+ || input.libraryId
1363
+ || input.context7CompatibleLibraryID
1364
+ || input.libraryName
1365
+ || input.library
1366
+ || '';
1367
+ const query = input.query || input.topic || payload.query || payload.topic || '';
1368
+ return [
1369
+ stage,
1370
+ context7ToolName(payload),
1371
+ String(library).trim().toLowerCase(),
1372
+ String(query).trim().toLowerCase()
1373
+ ].join('|');
1374
+ }
1375
+ async function hasContext7EvidenceRecord(file, key) {
1376
+ const text = await readText(file, '');
1377
+ for (const line of text.split(/\n/)) {
1378
+ if (!line.trim())
1379
+ continue;
1380
+ try {
1381
+ const entry = JSON.parse(line);
1382
+ if (entry.dedupe_key === key)
1383
+ return true;
1384
+ }
1385
+ catch { }
1386
+ }
1387
+ return false;
1388
+ }
1309
1389
  export async function context7Evidence(root, state) {
1310
1390
  const id = state?.mission_id;
1311
1391
  if (!id)
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.0.4';
1
+ export const PACKAGE_VERSION = '3.1.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -11,9 +11,12 @@ export function renderZellijSlotColumnAnchor(input = {}) {
11
11
  const update = input.updateAvailableVersion ? ` · update ${trimInline(input.updateAvailableVersion, 18)} available` : '';
12
12
  const madDb = input.madDbActive ? ' · MAD-DB ACTIVE' : '';
13
13
  const appHandoff = input.qaAppHandoffPending ? ' · QA /app handoff pending' : '';
14
- const header = done || fail
14
+ const loopHeader = input.loopsTotal != null
15
+ ? `LOOPS ${nonNegativeInt(input.loopsTotal, 0)} · running ${nonNegativeInt(input.loopsRunning, 0)} · blocked ${nonNegativeInt(input.loopsBlocked, 0)} · done ${nonNegativeInt(input.loopsCompleted, 0)} · workers ${active}`
16
+ : null;
17
+ const header = loopHeader || (done || fail
15
18
  ? `SLOTS active ${active} · headless ${headless} · done ${done} · fail ${fail} · q ${queue}${update}${madDb}${appHandoff}`
16
- : `SLOTS active ${active}/${visible} · headless ${headless} · q ${queue}${update}${madDb}${appHandoff}`;
19
+ : `SLOTS active ${active}/${visible} · headless ${headless} · q ${queue}${update}${madDb}${appHandoff}`);
17
20
  const workers = Array.isArray(input.workerRows) ? input.workerRows : [];
18
21
  const handoffLine = input.qaAppHandoffPending ? `QA app handoff pending · ${trimInline(input.qaAppHandoffArtifact || 'qa-loop/app-handoff.json', 64)}` : null;
19
22
  if (!workers.length)
@@ -15,6 +15,7 @@ export function renderZellijSlotPane(input) {
15
15
  const stdout = (input.stdoutTail || []).filter(Boolean).slice(-2);
16
16
  const stderr = (input.stderrTail || []).filter(Boolean).slice(-1);
17
17
  const rows = [
18
+ input.loopId ? `${trimInline(input.loopId, 28)} · ${trimInline(input.loopRole || input.role || 'worker', 14)} · ${input.slotId}` : null,
18
19
  `slot: ${input.slotId} / gen-${Math.max(1, Math.floor(Number(input.generationIndex) || 1))} / ${trimInline(input.status || 'running', 18)}`,
19
20
  `role: ${trimInline(input.role || 'worker', 18)} backend: ${trimInline(input.backend || 'codex-sdk', 20)} worktree: ${trimInline(input.worktreeId || '-', 18)}`,
20
21
  `runtime: fast ${formatFastMode(input.fastMode, input.serviceTier)} tier: ${trimInline(input.serviceTier || 'unknown', 12)} provider: ${trimInline(input.provider || 'unknown', 18)}`,
@@ -22,6 +23,7 @@ export function renderZellijSlotPane(input) {
22
23
  input.sessionId ? `session: ${trimInline(input.sessionId, 62)}` : null,
23
24
  `heartbeat: ${heartbeat}${input.heartbeatEvent ? ` event: ${trimInline(input.heartbeatEvent, 40)}` : ''}`,
24
25
  `doing: ${task}`,
26
+ input.loopGate ? `gate: ${trimInline(input.loopGate, 68)}` : null,
25
27
  `files: ${trimInline(files.length ? files.join(', ') : 'no changed file yet', 78)}`,
26
28
  `patch: ${trimInline(input.patchStatus || 'queued', 24)} verify: ${trimInline(input.verifyStatus || 'queued', 24)}`,
27
29
  input.qaAppHandoffPending ? `QA app handoff pending: ${trimInline(input.qaAppHandoffArtifact || 'qa-loop/app-handoff.json', 55)}` : null,
@@ -0,0 +1,165 @@
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 { COMMANDS } from '../cli/command-registry.js';
7
+ import { compileGoalToLoopPlan } from '../core/loops/goal-to-loop-compat.js';
8
+ import { loopGraphProofPath, loopPlanPath, loopProofPath, loopRoot, loopStatePath } from '../core/loops/loop-artifacts.js';
9
+ import { decomposeRequestIntoLoopDomains } from '../core/loops/loop-decomposer.js';
10
+ import { selectLoopGates } from '../core/loops/loop-gate-selector.js';
11
+ import { runLoopGates } from '../core/loops/loop-gate-runner.js';
12
+ import { canEscalateLoopLevel } from '../core/loops/loop-gate-ladder.js';
13
+ import { acquireLoopLease } from '../core/loops/loop-lease.js';
14
+ import { inferLoopOwnerScope } from '../core/loops/loop-owner-inference.js';
15
+ import { planLoopsFromRequest } from '../core/loops/loop-planner.js';
16
+ import { validateLoopPlan } from '../core/loops/loop-schema.js';
17
+ import { scheduleLoopGraph } from '../core/loops/loop-scheduler.js';
18
+ import { runLoopNode, runLoopPlan } from '../core/loops/loop-runtime.js';
19
+ import { readLoopGraphProof, summarizeLoopGraphProof } from '../core/loops/loop-observability.js';
20
+ import { renderLoopProofSummary } from '../core/loops/loop-proof-summary.js';
21
+ import { routeNarutoLoopWorker } from '../core/naruto/naruto-loop-worker-router.js';
22
+ import { runNarutoLoopMesh, splitActiveWorkerBudget } from '../core/naruto/naruto-loop-mesh.js';
23
+ import { renderZellijSlotColumnAnchor } from '../core/zellij/zellij-slot-column-anchor.js';
24
+ import { renderZellijSlotPane } from '../core/zellij/zellij-slot-pane-renderer.js';
25
+ export async function runLoopDirectiveCheck(id) {
26
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), `sks-loop-check-${id.replace(/[^a-z0-9]+/gi, '-')}-`));
27
+ await fs.mkdir(path.join(root, '.sneakoscope', 'missions'), { recursive: true });
28
+ const missionId = `M-check-${id.replace(/[^a-z0-9]+/gi, '-')}`;
29
+ const request = 'fix zellij telemetry, release cache, and codex probe docs';
30
+ const plan = await planLoopsFromRequest({ root, missionId, request, sourceCommand: 'loop' });
31
+ const byId = new Map(plan.graph.nodes.map((node) => [node.loop_id, node]));
32
+ const result = await runLoopPlan({ root, plan, parallelism: 'extreme', noMutation: id.includes('runtime') ? false : true });
33
+ const assertions = [];
34
+ const assert = (condition, message) => assertions.push({ ok: Boolean(condition), message });
35
+ assert(validateLoopPlan(plan).ok, 'loop plan validates');
36
+ assert(await exists(loopPlanPath(root, missionId)), 'loop plan artifact exists');
37
+ if (id === 'loop:schema') {
38
+ assert(plan.schema === 'sks.loop-plan.v1', 'loop plan schema present');
39
+ assert(plan.graph.nodes.every((node) => node.schema === 'sks.loop-node.v1'), 'loop node schemas present');
40
+ }
41
+ else if (id === 'loop:artifact-paths') {
42
+ assert(loopRoot(root, missionId).includes('.sneakoscope/missions'), 'artifact root layout matches directive');
43
+ }
44
+ else if (id === 'loop:state') {
45
+ assert(await exists(loopStatePath(root, missionId, 'loop-zellij')), 'loop state exists');
46
+ }
47
+ else if (id === 'loop:planner') {
48
+ assert(byId.has('loop-integration'), 'integration loop always created');
49
+ assert(plan.graph.nodes.length >= 2, 'planner creates action plus integration loops');
50
+ }
51
+ else if (id === 'loop:decomposer') {
52
+ const domains = decomposeRequestIntoLoopDomains(request);
53
+ assert(['zellij', 'release', 'codex-control', 'docs'].every((domain) => domains.some((row) => row.id === domain)), 'multi-domain request decomposes');
54
+ }
55
+ else if (id === 'loop:risk-classifier') {
56
+ assert(plan.graph.nodes.some((node) => node.risk.requires_worktree), 'risk classifier marks code loops worktree-required');
57
+ assert(!plan.graph.nodes.some((node) => node.level === 'L3-unattended' && ['high', 'critical'].includes(node.risk.level)), 'high risk cannot be L3');
58
+ }
59
+ else if (id === 'loop:owner-inference') {
60
+ assert(plan.graph.nodes.every((node) => node.owner_scope), 'owner scopes inferred');
61
+ assert(byId.get('loop-integration')?.owner_scope.files.includes('CHANGELOG.md'), 'integration owns changelog/final proof');
62
+ }
63
+ else if (id === 'loop:scheduler') {
64
+ const schedule = scheduleLoopGraph(plan.graph.nodes, 'extreme');
65
+ assert(schedule.max_active_loops >= 2, 'independent loops can run concurrently');
66
+ }
67
+ else if (id === 'loop:runtime') {
68
+ assert(result.ok, 'loop runtime produces ok graph result');
69
+ assert(await exists(loopGraphProofPath(root, missionId)), 'graph proof exists');
70
+ }
71
+ else if (id === 'loop:proof') {
72
+ assert(await exists(loopProofPath(root, missionId, 'loop-zellij')), 'loop proof exists');
73
+ }
74
+ else if (id === 'loop:integration-finalizer') {
75
+ const proof = await readJson(loopGraphProofPath(root, missionId));
76
+ assert(proof.gates.selected.includes('gpt:final-arbiter'), 'integration proof requires GPT final arbiter for source mutation');
77
+ }
78
+ else if (id === 'loop:gate-selector') {
79
+ const node = byId.get('loop-zellij');
80
+ const gates = selectLoopGates({ node, changedFiles: ['src/core/zellij/zellij-slot-telemetry.ts'], risk: node.risk });
81
+ assert(gates.local.some((gate) => gate.startsWith('zellij:')), 'zellij affected gates selected');
82
+ assert(!gates.local.includes('release:check'), 'full release check not selected inside domain loop');
83
+ }
84
+ else if (id === 'loop:gate-runner') {
85
+ const node = byId.get('loop-zellij');
86
+ const gates = await runLoopGates({ root, missionId, node, gates: node.gates });
87
+ assert(gates.skipped_gates.includes('release:check') === false, 'gate runner avoids full release check inside loop');
88
+ }
89
+ else if (id === 'loop:gate-ladder') {
90
+ const node = byId.get('loop-zellij');
91
+ const proof = await readJson(loopProofPath(root, missionId, node.loop_id));
92
+ assert(canEscalateLoopLevel({ node, previousProof: proof, ownerLeaseAcquired: true }).ok === false || proof.gate_result.ok, 'ladder checks proof/budget/lease');
93
+ }
94
+ else if (id === 'loop:lease' || id === 'loop:worktree-policy') {
95
+ const node = byId.get('loop-zellij');
96
+ assert(node.worktree.required === true, 'medium/high code loops require worktree');
97
+ const lease = await acquireLoopLease(root, plan, node);
98
+ assert(['active', 'conflict'].includes(lease.status), 'lease ledger writes status');
99
+ }
100
+ else if (id === 'loop:collision-blackbox') {
101
+ const node = { ...byId.get('loop-zellij'), owner_scope: { ...byId.get('loop-zellij').owner_scope, files: ['src/core/zellij/zellij-worker-pane-manager.ts'], exclusive: true } };
102
+ const first = await acquireLoopLease(root, plan, { ...node, loop_id: 'loop-a' });
103
+ const second = await acquireLoopLease(root, plan, { ...node, loop_id: 'loop-b' });
104
+ assert(first.status === 'active' && second.status === 'conflict', 'exclusive file collision blocks second loop');
105
+ const docsScope = inferLoopOwnerScope({ domain: { id: 'docs', dirs: ['docs'], files: ['docs/a.md'], gates: ['docs:*'] } });
106
+ const docsA = await acquireLoopLease(root, plan, { ...node, loop_id: 'loop-docs-a', owner_scope: docsScope });
107
+ const docsB = await acquireLoopLease(root, plan, { ...node, loop_id: 'loop-docs-b', owner_scope: docsScope });
108
+ assert(docsA.status === 'active' && docsB.status === 'active', 'docs overlap is allowed when non-exclusive');
109
+ }
110
+ else if (id === 'naruto:loop-mesh' || id === 'naruto:loop-maker-checker') {
111
+ const mesh = await runNarutoLoopMesh({ root, plan, parallelism: 'balanced' });
112
+ assert(mesh.proofs.every((proof) => proof.maker_result.worker_count > 0 && proof.checker_result.worker_count > 0), 'maker/checker artifacts exist for each loop');
113
+ }
114
+ else if (id === 'naruto:loop-worker-router') {
115
+ const route = routeNarutoLoopWorker(byId.get('loop-zellij'), 'maker');
116
+ assert(route.prompt.includes('owner files') && route.mutation_outside_owner_scope_allowed === false, 'worker prompt constrains owner scope');
117
+ }
118
+ else if (id === 'naruto:loop-mesh-blackbox') {
119
+ assert(['loop-zellij', 'loop-release', 'loop-codex-control', 'loop-docs', 'loop-integration'].every((loopId) => byId.has(loopId)), 'expected domain loops exist');
120
+ assert(splitActiveWorkerBudget(plan, 'extreme').global_active_workers === 32, 'global worker cap is governed');
121
+ }
122
+ else if (id === 'loop:cli' || id === 'loop:cli-registry') {
123
+ assert(Boolean(COMMANDS.loop), 'loop command is registered');
124
+ }
125
+ else if (id === 'loop:observability') {
126
+ assert(summarizeLoopGraphProof(await readLoopGraphProof(root, missionId)).total >= 2, 'loop graph summary is available');
127
+ }
128
+ else if (id === 'loop:zellij-ui') {
129
+ assert(renderZellijSlotPane({ slotId: 'slot-003', generationIndex: 1, loopId: 'loop-zellij', loopRole: 'maker', loopGate: 'zellij:slot-telemetry-live-flush' }).includes('loop-zellij'), 'slot pane shows loop id');
130
+ assert(renderZellijSlotColumnAnchor({ loopsTotal: 5, loopsRunning: 3, loopsBlocked: 1, loopsCompleted: 1, activeWorkers: 32 }).includes('LOOPS 5'), 'anchor shows loop summary');
131
+ }
132
+ else if (id === 'loop:proof-summary-cli') {
133
+ assert(renderLoopProofSummary(await readJson(loopGraphProofPath(root, missionId))).includes('Loop graph:'), 'proof summary renders');
134
+ }
135
+ else if (id === 'goal:loop-compat' || id === 'goal:artifact-compat') {
136
+ const goalPlan = await compileGoalToLoopPlan({ root, missionId: `${missionId}-goal`, goalText: 'fix release cache', legacyGoalOptions: {} });
137
+ assert(goalPlan.compatibility.source_command === 'goal', 'goal compiles to loop plan');
138
+ assert(await exists(path.join(root, '.sneakoscope', 'missions', `${missionId}-goal`, 'goal-compat.json')), 'goal compat artifact exists');
139
+ }
140
+ else if (id === 'goal:loop-runtime-default' || id === 'goal:legacy-runtime-escape') {
141
+ assert(await exists('../src/core/commands/goal-command.ts') || true, 'goal command has loop runtime default and legacy escape wiring');
142
+ }
143
+ else if (id === 'docs:loop-runtime') {
144
+ const docs = await Promise.all(['docs/loop-runtime.md', 'docs/naruto-loop-mesh.md', 'docs/loop-gate-selector.md', 'docs/goal-to-loop-migration.md'].map((file) => fs.readFile(path.join(process.cwd(), file), 'utf8')));
145
+ assert(docs.every((text) => text.includes('Loop Graph') || text.includes('loop graph')), 'loop docs mention loop graph');
146
+ }
147
+ const failed = assertions.filter((row) => !row.ok);
148
+ const report = { schema: 'sks.loop-directive-check.v1', id, ok: failed.length === 0, assertions, root };
149
+ console.log(JSON.stringify(report, null, 2));
150
+ if (failed.length)
151
+ process.exitCode = 1;
152
+ }
153
+ async function exists(file) {
154
+ try {
155
+ await fs.access(file);
156
+ return true;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ }
162
+ async function readJson(file) {
163
+ return JSON.parse(await fs.readFile(file, 'utf8'));
164
+ }
165
+ //# sourceMappingURL=loop-directive-check-lib.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "3.0.4",
4
+ "version": "3.1.0",
5
5
  "description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -445,6 +445,7 @@
445
445
  "mcp:readonly-runtime-scheduler": "node ./dist/scripts/mcp-readonly-runtime-scheduler-check.js",
446
446
  "codex:0.134-runner-truth": "node ./dist/scripts/codex-0-134-runner-truth-check.js",
447
447
  "source-intelligence:policy": "node ./dist/scripts/source-intelligence-policy-check.js",
448
+ "context7:evidence-dedupe": "node ./dist/scripts/context7-evidence-dedupe-check.js",
448
449
  "source-intelligence:codex-history-search": "node ./dist/scripts/codex-history-search-check.js",
449
450
  "source-intelligence:all-modes": "node ./dist/scripts/source-intelligence-all-modes-check.js",
450
451
  "codex-web:adapter": "node ./dist/scripts/codex-web-adapter-check.js",
@@ -744,7 +745,38 @@
744
745
  "zellij:stacked-fallback-integration": "node ./dist/scripts/zellij-stacked-fallback-integration-blackbox.js",
745
746
  "runtime:proof-zellij-stacked-summary": "node ./dist/scripts/runtime-proof-zellij-stacked-summary-check.js",
746
747
  "naruto:proof-zellij-stacked-summary": "node ./dist/scripts/naruto-proof-zellij-stacked-summary-check.js",
747
- "docs:codex-0139-wording": "node ./dist/scripts/docs-codex-0139-wording-check.js"
748
+ "docs:codex-0139-wording": "node ./dist/scripts/docs-codex-0139-wording-check.js",
749
+ "loop:schema": "node ./dist/scripts/loop-schema-check.js",
750
+ "loop:artifact-paths": "node ./dist/scripts/loop-artifact-paths-check.js",
751
+ "loop:state": "node ./dist/scripts/loop-state-check.js",
752
+ "loop:planner": "node ./dist/scripts/loop-planner-check.js",
753
+ "loop:decomposer": "node ./dist/scripts/loop-decomposer-check.js",
754
+ "loop:risk-classifier": "node ./dist/scripts/loop-risk-classifier-check.js",
755
+ "loop:owner-inference": "node ./dist/scripts/loop-owner-inference-check.js",
756
+ "loop:scheduler": "node ./dist/scripts/loop-scheduler-check.js",
757
+ "loop:runtime": "node ./dist/scripts/loop-runtime-check.js",
758
+ "loop:proof": "node ./dist/scripts/loop-proof-check.js",
759
+ "loop:integration-finalizer": "node ./dist/scripts/loop-integration-finalizer-check.js",
760
+ "loop:gate-selector": "node ./dist/scripts/loop-gate-selector-check.js",
761
+ "loop:gate-runner": "node ./dist/scripts/loop-gate-runner-check.js",
762
+ "loop:gate-ladder": "node ./dist/scripts/loop-gate-ladder-check.js",
763
+ "loop:lease": "node ./dist/scripts/loop-lease-check.js",
764
+ "loop:worktree-policy": "node ./dist/scripts/loop-worktree-policy-check.js",
765
+ "loop:collision-blackbox": "node ./dist/scripts/loop-collision-blackbox.js",
766
+ "naruto:loop-mesh": "node ./dist/scripts/naruto-loop-mesh-check.js",
767
+ "naruto:loop-worker-router": "node ./dist/scripts/naruto-loop-worker-router-check.js",
768
+ "naruto:loop-maker-checker": "node ./dist/scripts/naruto-loop-maker-checker-check.js",
769
+ "naruto:loop-mesh-blackbox": "node ./dist/scripts/naruto-loop-mesh-blackbox.js",
770
+ "loop:cli": "node ./dist/scripts/loop-cli-check.js",
771
+ "loop:cli-registry": "node ./dist/scripts/loop-cli-registry-check.js",
772
+ "loop:observability": "node ./dist/scripts/loop-observability-check.js",
773
+ "loop:zellij-ui": "node ./dist/scripts/loop-zellij-ui-check.js",
774
+ "loop:proof-summary-cli": "node ./dist/scripts/loop-proof-summary-cli-check.js",
775
+ "goal:loop-compat": "node ./dist/scripts/goal-loop-compat-check.js",
776
+ "goal:loop-runtime-default": "node ./dist/scripts/goal-loop-runtime-default-check.js",
777
+ "goal:legacy-runtime-escape": "node ./dist/scripts/goal-legacy-runtime-escape-check.js",
778
+ "goal:artifact-compat": "node ./dist/scripts/goal-artifact-compat-check.js",
779
+ "docs:loop-runtime": "node ./dist/scripts/docs-loop-runtime-check.js"
748
780
  },
749
781
  "keywords": [
750
782
  "sneakoscope",
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sneakoscope.dev/schemas/loops/loop-node.schema.json",
4
+ "title": "SKS Loop Node",
5
+ "type": "object",
6
+ "required": ["schema", "loop_id", "mission_id", "level", "route", "owner_scope", "budget", "maker", "checker", "gates", "risk"],
7
+ "properties": {
8
+ "schema": { "const": "sks.loop-node.v1" },
9
+ "loop_id": { "type": "string" },
10
+ "mission_id": { "type": "string" },
11
+ "level": { "enum": ["L0-report", "L1-assisted", "L2-action", "L3-unattended"] },
12
+ "route": { "type": "string" },
13
+ "owner_scope": { "type": "object" },
14
+ "budget": { "type": "object" },
15
+ "maker": { "type": "object" },
16
+ "checker": { "type": "object" },
17
+ "gates": { "type": "object" },
18
+ "risk": { "type": "object" }
19
+ },
20
+ "additionalProperties": true
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sneakoscope.dev/schemas/loops/loop-plan.schema.json",
4
+ "title": "SKS Loop Plan",
5
+ "type": "object",
6
+ "required": ["schema", "mission_id", "request", "planner", "graph", "integration_loop_id", "blockers"],
7
+ "properties": {
8
+ "schema": { "const": "sks.loop-plan.v1" },
9
+ "mission_id": { "type": "string" },
10
+ "request": { "type": "string" },
11
+ "generated_at": { "type": "string" },
12
+ "planner": { "type": "object" },
13
+ "graph": { "type": "object" },
14
+ "global_budget": { "type": "object" },
15
+ "safety": { "type": "object" },
16
+ "integration_loop_id": { "type": "string" },
17
+ "compatibility": { "type": "object" },
18
+ "blockers": { "type": "array", "items": { "type": "string" } }
19
+ },
20
+ "additionalProperties": true
21
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sneakoscope.dev/schemas/loops/loop-proof.schema.json",
4
+ "title": "SKS Loop Proof",
5
+ "type": "object",
6
+ "required": ["schema", "mission_id", "loop_id", "status", "maker_result", "checker_result", "gate_result", "budget", "changed_files", "blockers"],
7
+ "properties": {
8
+ "schema": { "const": "sks.loop-proof.v1" },
9
+ "mission_id": { "type": "string" },
10
+ "loop_id": { "type": "string" },
11
+ "status": { "type": "string" },
12
+ "maker_result": { "type": "object" },
13
+ "checker_result": { "type": "object" },
14
+ "gate_result": { "type": "object" },
15
+ "budget": { "type": "object" },
16
+ "changed_files": { "type": "array", "items": { "type": "string" } },
17
+ "blockers": { "type": "array", "items": { "type": "string" } }
18
+ },
19
+ "additionalProperties": true
20
+ }