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.
- package/README.md +1 -1
- 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/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -0
- package/dist/cli/context7-command.js +29 -5
- package/dist/cli/install-helpers.js +15 -7
- package/dist/core/agents/runtime-proof-summary.js +4 -0
- package/dist/core/commands/goal-command.js +19 -1
- package/dist/core/commands/loop-command.js +135 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +6 -1
- package/dist/core/loops/goal-to-loop-compat.js +23 -0
- package/dist/core/loops/loop-artifacts.js +41 -0
- package/dist/core/loops/loop-decomposer.js +56 -0
- package/dist/core/loops/loop-finalizer.js +28 -0
- package/dist/core/loops/loop-gate-ladder.js +16 -0
- package/dist/core/loops/loop-gate-runner.js +29 -0
- package/dist/core/loops/loop-gate-selector.js +52 -0
- package/dist/core/loops/loop-iteration-runner.js +2 -0
- package/dist/core/loops/loop-lease.js +76 -0
- package/dist/core/loops/loop-observability.js +19 -0
- package/dist/core/loops/loop-owner-inference.js +57 -0
- package/dist/core/loops/loop-owner-ledger.js +2 -0
- package/dist/core/loops/loop-planner.js +139 -0
- package/dist/core/loops/loop-proof-summary.js +10 -0
- package/dist/core/loops/loop-proof.js +2 -0
- package/dist/core/loops/loop-risk-classifier.js +42 -0
- package/dist/core/loops/loop-runtime.js +159 -0
- package/dist/core/loops/loop-scheduler.js +60 -0
- package/dist/core/loops/loop-schema.js +63 -0
- package/dist/core/loops/loop-state.js +61 -0
- package/dist/core/naruto/naruto-loop-mesh.js +33 -0
- package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
- package/dist/core/pipeline-internals/runtime-core.js +82 -2
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-column-anchor.js +5 -2
- package/dist/core/zellij/zellij-slot-pane-renderer.js +2 -0
- package/dist/scripts/loop-directive-check-lib.js +165 -0
- package/package.json +34 -2
- package/schemas/loops/loop-node.schema.json +21 -0
- package/schemas/loops/loop-plan.schema.json +21 -0
- package/schemas/loops/loop-proof.schema.json +20 -0
- 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
|
|
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)
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.0
|
|
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
|
|
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
|
+
"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
|
+
}
|