sneakoscope 2.0.12 → 2.0.13

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 (43) hide show
  1. package/README.md +5 -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/build-manifest.json +24 -8
  8. package/dist/core/codex-control/codex-sdk-adapter.js +10 -0
  9. package/dist/core/codex-control/codex-task-runner.js +4 -2
  10. package/dist/core/commands/research-command.js +43 -4
  11. package/dist/core/fsx.js +1 -1
  12. package/dist/core/research/claim-evidence-matrix.js +160 -0
  13. package/dist/core/research/experiment-plan.js +53 -0
  14. package/dist/core/research/falsification.js +18 -0
  15. package/dist/core/research/implementation-blueprint-markdown.js +31 -0
  16. package/dist/core/research/implementation-blueprint.js +66 -0
  17. package/dist/core/research/replication-pack.js +50 -0
  18. package/dist/core/research/research-cycle-runner.js +25 -0
  19. package/dist/core/research/research-final-reviewer.js +58 -0
  20. package/dist/core/research/research-handoff.js +51 -0
  21. package/dist/core/research/research-prompt-contract.js +24 -0
  22. package/dist/core/research/research-quality-contract.js +61 -0
  23. package/dist/core/research/research-report-quality.js +67 -0
  24. package/dist/core/research/research-stage-runner.js +16 -0
  25. package/dist/core/research/research-work-graph.js +75 -0
  26. package/dist/core/research/source-quality-report.js +94 -0
  27. package/dist/core/research.js +344 -44
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -3
  30. package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
  31. package/dist/scripts/codex-sdk-research-pipeline-check.js +7 -0
  32. package/dist/scripts/packlist-performance-check.js +1 -1
  33. package/dist/scripts/research-quality-gate-check.js +86 -0
  34. package/dist/scripts/zellij-slot-column-anchor-check.js +26 -5
  35. package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
  36. package/package.json +13 -1
  37. package/schemas/research/claim-evidence-matrix.schema.json +37 -0
  38. package/schemas/research/experiment-plan.schema.json +17 -0
  39. package/schemas/research/implementation-blueprint.schema.json +30 -0
  40. package/schemas/research/replication-pack.schema.json +17 -0
  41. package/schemas/research/research-final-review.schema.json +16 -0
  42. package/schemas/research/research-quality-contract.schema.json +37 -0
  43. package/schemas/research/source-quality-report.schema.json +18 -0
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '2.0.12';
1
+ export const PACKAGE_VERSION = '2.0.13';
2
2
  //# sourceMappingURL=version.js.map
@@ -8,14 +8,16 @@ export function renderZellijSlotColumnAnchor(input = {}) {
8
8
  const header = `SLOTS active ${active}/${visible} · headless ${headless} · q ${queue}`;
9
9
  const workers = Array.isArray(input.workerRows) ? input.workerRows : [];
10
10
  if (!workers.length)
11
- return header;
11
+ return `${header}\nvisible slot panes stack below this anchor`;
12
12
  const maxRows = Math.max(1, nonNegativeInt(input.maxWorkerRows, input.mode === 'full-debug' ? 24 : 12));
13
- const visibleRows = workers.slice(0, maxRows);
13
+ const overflowRows = workers.filter((row) => row.placement === 'headless').slice(0, maxRows);
14
+ const visibleRows = overflowRows.length ? overflowRows : workers.filter((row) => row.placement !== 'zellij-pane').slice(0, maxRows);
14
15
  const hidden = Math.max(0, workers.length - visibleRows.length);
15
16
  return [
16
17
  header,
18
+ `visible slot panes stack below this anchor`,
17
19
  ...visibleRows.map((row, index) => renderWorkerRow(row, index + 1)),
18
- ...(hidden ? [`+${hidden} more worker${hidden === 1 ? '' : 's'}`] : [])
20
+ ...(hidden && visibleRows.length ? [`+${hidden} worker${hidden === 1 ? '' : 's'} in dedicated panes or overflow`] : [])
19
21
  ].join('\n');
20
22
  }
21
23
  export async function renderZellijSlotColumnAnchorFromArtifacts(input) {
@@ -2,40 +2,121 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  export function renderZellijSlotPane(input) {
4
4
  const mode = input.mode || 'compact-slots';
5
- const maxLines = mode === 'compact-slots' ? 5 : mode === 'dashboard-plus-slots' ? 8 : 20;
6
- const task = trimInline(input.currentFile || input.currentTask || '-', 56);
5
+ const maxLines = mode === 'compact-slots' ? 17 : mode === 'dashboard-plus-slots' ? 20 : 32;
6
+ const task = trimInline(input.currentTask || input.currentFile || 'waiting for worker intake', 78);
7
7
  const heartbeat = input.heartbeatAgeMs == null
8
8
  ? 'unknown'
9
9
  : input.heartbeatAgeMs < 1000
10
10
  ? 'now'
11
11
  : `${Math.max(1, Math.round(input.heartbeatAgeMs / 1000))}s ago`;
12
+ const files = firstNonEmptyList(input.changedFiles, input.patchFiles, input.plannedFiles, input.currentFile ? [input.currentFile] : []);
13
+ const events = (input.eventLines || []).filter(Boolean).slice(-3);
14
+ const stdout = (input.stdoutTail || []).filter(Boolean).slice(-2);
15
+ const stderr = (input.stderrTail || []).filter(Boolean).slice(-1);
12
16
  const rows = [
13
- `${input.slotId} gen-${Math.max(1, Math.floor(Number(input.generationIndex) || 1))}`,
14
- `${trimInline(input.role || 'worker', 18)} - ${trimInline(input.backend || 'codex-sdk', 18)} - ${trimInline(input.worktreeId || '-', 18)}`,
15
- `status: ${trimInline(input.status || 'running', 14)} ${task}`,
16
- `patch: ${trimInline(input.patchStatus || 'queued', 18)} verify: ${trimInline(input.verifyStatus || 'queued', 18)}`,
17
- `heartbeat: ${heartbeat}`
18
- ];
19
- return rows.slice(0, maxLines).join('\n');
17
+ `slot: ${input.slotId} / gen-${Math.max(1, Math.floor(Number(input.generationIndex) || 1))} / ${trimInline(input.status || 'running', 18)}`,
18
+ `role: ${trimInline(input.role || 'worker', 18)} backend: ${trimInline(input.backend || 'codex-sdk', 20)} worktree: ${trimInline(input.worktreeId || '-', 18)}`,
19
+ `runtime: fast ${formatFastMode(input.fastMode, input.serviceTier)} tier: ${trimInline(input.serviceTier || 'unknown', 12)} provider: ${trimInline(input.provider || 'unknown', 18)}`,
20
+ `model: ${trimInline(input.model || 'unknown', 28)} reasoning: ${trimInline(input.reasoningEffort || 'unknown', 16)}${input.authMode ? ` auth: ${trimInline(input.authMode, 14)}` : ''}`,
21
+ input.sessionId ? `session: ${trimInline(input.sessionId, 62)}` : null,
22
+ `heartbeat: ${heartbeat}${input.heartbeatEvent ? ` event: ${trimInline(input.heartbeatEvent, 40)}` : ''}`,
23
+ `doing: ${task}`,
24
+ `files: ${trimInline(files.length ? files.join(', ') : 'no changed file yet', 78)}`,
25
+ `patch: ${trimInline(input.patchStatus || 'queued', 24)} verify: ${trimInline(input.verifyStatus || 'queued', 24)}`,
26
+ ...events.map((event) => `event: ${trimInline(event, 78)}`),
27
+ ...stdout.map((line) => `out: ${trimInline(line, 79)}`),
28
+ ...stderr.map((line) => `err: ${trimInline(line, 79)}`)
29
+ ].filter((row) => Boolean(row));
30
+ return frameSlotPane(`LIVE SLOT ${input.slotId}`, rows.slice(0, Math.max(1, maxLines - 2)));
20
31
  }
21
32
  export async function renderZellijSlotPaneFromArtifacts(input) {
22
33
  const artifactDir = path.resolve(input.artifactDir);
23
34
  const result = await readJson(path.join(artifactDir, 'worker-result.json'));
35
+ const intake = await readJson(path.join(artifactDir, 'worker-intake.json'));
36
+ const backendReport = await readJson(path.join(artifactDir, 'worker-backend-router-report.json'));
37
+ const fastReport = await readJson(path.join(artifactDir, 'worker-fast-mode.json'));
38
+ const paneReport = await readJson(path.join(artifactDir, 'zellij-worker-pane.json'));
39
+ const codexProof = await readJson(path.join(artifactDir, 'codex-control-proof.json'));
40
+ const localProof = await readJson(path.join(artifactDir, 'local-llm-proof.json'));
41
+ const patch = await firstJson([
42
+ path.join(artifactDir, 'worker-patch-envelope.json'),
43
+ path.join(artifactDir, 'codex-sdk-patch-envelope.json'),
44
+ path.join(artifactDir, 'python-codex-sdk-patch-envelope.json'),
45
+ path.join(artifactDir, 'local-llm-patch-envelope.json')
46
+ ]);
24
47
  const heartbeatPath = path.join(artifactDir, 'worker-heartbeat.jsonl');
25
48
  const heartbeatMtime = await statMtimeMs(heartbeatPath);
49
+ const heartbeatRows = await readJsonlTail(heartbeatPath, 2);
50
+ const eventRows = await readJsonlTails([
51
+ path.join(artifactDir, 'codex-sdk-events.jsonl'),
52
+ path.join(artifactDir, 'python-codex-sdk-events.jsonl'),
53
+ path.join(artifactDir, 'local-llm-events.jsonl'),
54
+ path.join(artifactDir, 'zellij-worker-pane-events.jsonl')
55
+ ], 6);
56
+ const patchFiles = patchPaths(patch || result);
57
+ const changedFiles = normalizeList(result?.changed_files);
58
+ const plannedFiles = normalizeList([
59
+ ...(Array.isArray(intake?.slice?.write_paths) ? intake.slice.write_paths : []),
60
+ ...(Array.isArray(intake?.slice?.readonly_paths) ? intake.slice.readonly_paths : []),
61
+ ...(Array.isArray(intake?.input_files) ? intake.input_files : [])
62
+ ]);
26
63
  const now = Date.now();
27
64
  return renderZellijSlotPane({
28
65
  slotId: input.slotId,
29
66
  generationIndex: input.generationIndex,
30
- role: input.role || result?.persona_id || result?.agent_id || null,
31
- backend: input.backend || result?.backend || null,
32
- status: result?.status || (heartbeatMtime ? 'running' : 'launching'),
33
- currentTask: result?.summary || null,
34
- currentFile: Array.isArray(result?.changed_files) ? result.changed_files[0] : null,
35
- patchStatus: Array.isArray(result?.patch_envelopes) && result.patch_envelopes.length ? 'candidate' : 'queued',
67
+ sessionId: result?.session_id || intake?.agent?.session_id || backendReport?.session_id || null,
68
+ role: input.role || result?.persona_id || intake?.agent?.naruto_role || intake?.agent?.role || intake?.agent?.persona_id || result?.agent_id || null,
69
+ backend: input.backend || result?.backend || backendReport?.selected_backend || intake?.backend || null,
70
+ status: result?.status || statusFromEvents(eventRows) || (heartbeatMtime ? 'running' : 'launching'),
71
+ fastMode: firstDefined(fastReport?.fast_mode, backendReport?.fast_mode, result?.fast_mode, intake?.fast_mode),
72
+ serviceTier: firstText([
73
+ fastReport?.service_tier,
74
+ paneReport?.service_tier,
75
+ backendReport?.service_tier,
76
+ codexProof?.config?.service_tier,
77
+ result?.service_tier,
78
+ intake?.service_tier
79
+ ]),
80
+ provider: firstText([
81
+ paneReport?.provider_context?.provider,
82
+ paneReport?.provider,
83
+ codexProof?.config?.model_provider,
84
+ localProof?.provider
85
+ ]),
86
+ authMode: firstText([
87
+ paneReport?.provider_context?.auth_mode,
88
+ codexProof?.config?.model_provider ? 'api_key' : null
89
+ ]),
90
+ model: firstText([
91
+ codexProof?.config?.model,
92
+ localProof?.model,
93
+ intake?.ollama_model,
94
+ intake?.local_model_model
95
+ ]),
96
+ reasoningEffort: firstText([
97
+ codexProof?.config?.model_reasoning_effort,
98
+ intake?.agent?.model_reasoning_effort,
99
+ intake?.agent?.reasoning_effort
100
+ ]),
101
+ currentTask: firstText([
102
+ result?.summary,
103
+ intake?.slice?.description,
104
+ intake?.slice?.title,
105
+ intake?.slice?.id,
106
+ lastEventLine(eventRows)
107
+ ]),
108
+ currentFile: changedFiles[0] || patchFiles[0] || plannedFiles[0] || null,
109
+ changedFiles,
110
+ plannedFiles,
111
+ patchFiles,
112
+ patchStatus: patchStatus(result, patch, patchFiles),
36
113
  verifyStatus: result?.verification?.status || 'queued',
37
114
  heartbeatAgeMs: heartbeatMtime ? now - heartbeatMtime : null,
38
- worktreeId: result?.worktree?.id || null,
115
+ heartbeatEvent: heartbeatRows.length ? formatArtifactEvent(heartbeatRows[heartbeatRows.length - 1]) : null,
116
+ worktreeId: result?.worktree?.id || intake?.worktree?.id || null,
117
+ eventLines: eventRows.map(formatArtifactEvent).filter(Boolean),
118
+ stdoutTail: await readTextTailLines(path.join(artifactDir, 'worker.stdout.log'), 2),
119
+ stderrTail: await readTextTailLines(path.join(artifactDir, 'worker.stderr.log'), 1),
39
120
  mode: input.mode || 'compact-slots'
40
121
  });
41
122
  }
@@ -62,6 +143,14 @@ async function readJson(file) {
62
143
  return null;
63
144
  }
64
145
  }
146
+ async function firstJson(files) {
147
+ for (const file of files) {
148
+ const value = await readJson(file);
149
+ if (value)
150
+ return value;
151
+ }
152
+ return null;
153
+ }
65
154
  async function statMtimeMs(file) {
66
155
  try {
67
156
  return (await fs.promises.stat(file)).mtimeMs;
@@ -70,12 +159,166 @@ async function statMtimeMs(file) {
70
159
  return null;
71
160
  }
72
161
  }
162
+ async function readJsonlTails(files, max) {
163
+ const rows = [];
164
+ for (const file of files)
165
+ rows.push(...await readJsonlTail(file, max));
166
+ return rows
167
+ .sort((a, b) => timestampMs(a) - timestampMs(b))
168
+ .slice(-max);
169
+ }
170
+ async function readJsonlTail(file, max) {
171
+ try {
172
+ const lines = (await fs.promises.readFile(file, 'utf8')).split(/\r?\n/).filter((line) => line.trim());
173
+ return lines.slice(-Math.max(1, max)).map((line) => {
174
+ try {
175
+ return JSON.parse(line);
176
+ }
177
+ catch {
178
+ return { message: line };
179
+ }
180
+ });
181
+ }
182
+ catch {
183
+ return [];
184
+ }
185
+ }
186
+ async function readTextTailLines(file, max) {
187
+ try {
188
+ const lines = (await fs.promises.readFile(file, 'utf8')).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
189
+ return lines.slice(-Math.max(1, max));
190
+ }
191
+ catch {
192
+ return [];
193
+ }
194
+ }
195
+ function patchStatus(result, patch, files) {
196
+ const resultCount = Array.isArray(result?.patch_envelopes) ? result.patch_envelopes.length : 0;
197
+ const patchCount = Number(patch?.envelope_count || (Array.isArray(patch?.envelopes) ? patch.envelopes.length : 0));
198
+ const count = Math.max(resultCount, Number.isFinite(patchCount) ? patchCount : 0);
199
+ if (count > 0)
200
+ return `candidate (${count})`;
201
+ if (files.length)
202
+ return 'candidate';
203
+ return 'queued';
204
+ }
205
+ function patchPaths(value) {
206
+ const envelopes = [
207
+ ...(Array.isArray(value?.envelopes) ? value.envelopes : []),
208
+ ...(Array.isArray(value?.patch_envelopes) ? value.patch_envelopes : [])
209
+ ];
210
+ return normalizeList(envelopes.flatMap((envelope) => {
211
+ const paths = [
212
+ ...(Array.isArray(envelope?.operations) ? envelope.operations.map((operation) => operation?.path) : []),
213
+ ...(Array.isArray(envelope?.allowed_paths) ? envelope.allowed_paths : [])
214
+ ];
215
+ return paths;
216
+ }));
217
+ }
218
+ function statusFromEvents(rows) {
219
+ const last = rows[rows.length - 1];
220
+ const status = String(last?.lane_status || last?.status || '').trim();
221
+ if (status)
222
+ return status;
223
+ const type = String(last?.event_type || last?.type || '').trim();
224
+ if (/failed|blocked/i.test(type))
225
+ return 'blocked';
226
+ if (/completed|finished|closed/i.test(type))
227
+ return 'done';
228
+ if (type)
229
+ return 'running';
230
+ return null;
231
+ }
232
+ function lastEventLine(rows) {
233
+ for (const row of [...rows].reverse()) {
234
+ const text = formatArtifactEvent(row);
235
+ if (text)
236
+ return text;
237
+ }
238
+ return null;
239
+ }
240
+ function formatArtifactEvent(row) {
241
+ if (!row)
242
+ return '';
243
+ const status = trimInline(row.lane_status || row.status || row.event || row.event_type || row.type || row.sdk_event_type || 'event', 18);
244
+ const detail = firstText([
245
+ row.current_tool && row.current_file ? `tool ${row.current_tool} file ${row.current_file}` : null,
246
+ row.current_file ? `file ${row.current_file}` : null,
247
+ row.current_tool ? `tool ${row.current_tool}` : null,
248
+ row.message_tail,
249
+ row.blocker ? `blocker ${row.blocker}` : null,
250
+ row.request_id,
251
+ row.pane_id ? `pane ${row.pane_id}` : null,
252
+ row.message,
253
+ row.reason
254
+ ]);
255
+ return trimInline(detail ? `${status}: ${detail}` : status, 96);
256
+ }
257
+ function timestampMs(row) {
258
+ const raw = row?.ts || row?.generated_at || row?.updated_at || row?.created_at;
259
+ const parsed = raw ? Date.parse(String(raw)) : NaN;
260
+ return Number.isFinite(parsed) ? parsed : 0;
261
+ }
262
+ function firstText(values) {
263
+ for (const value of values) {
264
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
265
+ if (text)
266
+ return text;
267
+ }
268
+ return null;
269
+ }
270
+ function firstDefined(...values) {
271
+ for (const value of values) {
272
+ if (value === true || value === false)
273
+ return value;
274
+ if (typeof value === 'string' && value.trim())
275
+ return value;
276
+ if (typeof value === 'number' && Number.isFinite(value))
277
+ return String(value);
278
+ }
279
+ return null;
280
+ }
281
+ function firstNonEmptyList(...values) {
282
+ for (const value of values) {
283
+ const normalized = normalizeList(value || []);
284
+ if (normalized.length)
285
+ return normalized;
286
+ }
287
+ return [];
288
+ }
289
+ function normalizeList(values) {
290
+ return [...new Set((Array.isArray(values) ? values : [values]).map((value) => String(value || '').trim()).filter(Boolean))];
291
+ }
73
292
  function trimInline(value, max) {
74
293
  const text = String(value || '').replace(/\s+/g, ' ').trim();
75
294
  if (text.length <= max)
76
295
  return text;
77
296
  return text.slice(0, Math.max(1, max - 3)) + '...';
78
297
  }
298
+ function formatFastMode(value, serviceTier) {
299
+ const text = String(value ?? '').trim().toLowerCase();
300
+ if (value === true || text === 'true' || text === '1' || text === 'on' || text === 'fast')
301
+ return 'on';
302
+ if (value === false || text === 'false' || text === '0' || text === 'off' || text === 'standard')
303
+ return 'off';
304
+ const tier = String(serviceTier || '').trim().toLowerCase();
305
+ if (tier === 'fast' || tier === 'priority')
306
+ return 'on';
307
+ if (tier === 'standard' || tier === 'default')
308
+ return 'off';
309
+ return 'unknown';
310
+ }
311
+ function frameSlotPane(title, rows) {
312
+ const width = Math.min(96, Math.max(44, title.length + 6, ...rows.map((row) => row.length + 4)));
313
+ const line = '+' + '-'.repeat(width - 2) + '+';
314
+ const label = ` ${trimInline(title, width - 4)} `;
315
+ const titleLine = '|' + label.padEnd(width - 2, ' ') + '|';
316
+ const body = rows.map((row) => {
317
+ const text = ` ${trimInline(row, width - 4)} `;
318
+ return '|' + text.padEnd(width - 2, ' ') + '|';
319
+ });
320
+ return [line, titleLine, line, ...body, line].join('\n');
321
+ }
79
322
  function shellQuote(value) {
80
323
  return `'${String(value).replace(/'/g, `'\\''`)}'`;
81
324
  }
@@ -4,5 +4,12 @@ import { assertGate, emitGate, readText } from './lib/codex-sdk-gate-lib.js';
4
4
  const source = readText('src/core/commands/research-command.ts');
5
5
  assertGate(source.includes("backend: mock ? 'fake' : 'codex-sdk'"), 'Research pipeline must default native agents to codex-sdk');
6
6
  assertGate(source.includes("flag(args, '--autoresearch') ? '$AutoResearch' : '$Research'"), 'Research/AutoResearch route selection missing');
7
+ assertGate(source.includes('narutoWorkGraph: researchWorkGraph'), 'Research pipeline must pass the stage-aware Naruto work graph');
8
+ assertGate(source.includes('readonly: true'), 'Research pipeline must force read-only native orchestration');
9
+ assertGate(source.includes('quality_metrics'), 'Research pipeline JSON output must include quality metrics');
10
+ const researchCore = readText('src/core/research.ts');
11
+ assertGate(researchCore.includes('readResearchQualityContract'), 'Research gate must read research-quality-contract.json');
12
+ assertGate(researchCore.includes('claim_evidence_matrix_missing'), 'Research gate must require claim-evidence-matrix.json');
13
+ assertGate(researchCore.includes('research_final_review_not_approved'), 'Research gate must require final reviewer approval');
7
14
  emitGate('codex-sdk:research-pipeline', { route: '$Research' });
8
15
  //# sourceMappingURL=codex-sdk-research-pipeline-check.js.map
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { spawnSync } from 'node:child_process';
7
7
  import { assertGate, emitGate, root } from './sks-1-18-gate-lib.js';
8
- const MAX_FILES = Number(process.env.SKS_MAX_PACK_FILES || 1200);
8
+ const MAX_FILES = Number(process.env.SKS_MAX_PACK_FILES || 1250);
9
9
  const MAX_UNPACKED = Number(process.env.SKS_MAX_UNPACKED_BYTES || 6 * 1024 * 1024);
10
10
  const MAX_PACKED = Number(process.env.SKS_MAX_PACK_BYTES || 1536 * 1024);
11
11
  function runNpmPack() {
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import { assertGate, emitGate, readText } from './lib/codex-sdk-gate-lib.js';
4
+ const mode = process.argv[2] || 'all';
5
+ const checks = {
6
+ 'quality-contract': () => {
7
+ assertFileIncludes('src/core/research/research-quality-contract.ts', [
8
+ 'min_sources_total: 12',
9
+ 'min_source_layers_covered: 5',
10
+ 'min_counterevidence_sources: 2',
11
+ 'min_trianguled_claims: 6',
12
+ 'min_key_claims: 8',
13
+ 'min_report_words: 2200'
14
+ ]);
15
+ assertFileIncludes('src/core/research.ts', ['quality_contract', 'writeResearchQualityContract', 'research_report_too_short']);
16
+ },
17
+ 'claim-matrix': () => {
18
+ assertFileIncludes('src/core/research/claim-evidence-matrix.ts', ['CLAIM_EVIDENCE_MATRIX_ARTIFACT', 'validateClaimEvidenceMatrix', 'buildClaimEvidenceMatrixFromLedgers']);
19
+ assertFileIncludes('src/core/research.ts', ['claim_evidence_matrix_missing', 'key_claims_below_contract', 'triangulated_claims_below_contract']);
20
+ },
21
+ 'source-quality-report': () => {
22
+ assertFileIncludes('src/core/research/source-quality-report.ts', ['SOURCE_QUALITY_REPORT_ARTIFACT', 'buildSourceQualityReport', 'claim_ids']);
23
+ assertFileIncludes('src/core/research.ts', ['source_quality_report_missing', 'writeSourceQualityReport']);
24
+ },
25
+ 'implementation-blueprint': () => {
26
+ assertFileIncludes('src/core/research/implementation-blueprint.ts', ['IMPLEMENTATION_BLUEPRINT_ARTIFACT', 'validateImplementationBlueprint']);
27
+ assertFileIncludes('src/core/research.ts', ['implementation_blueprint_missing', 'renderImplementationBlueprintMarkdown']);
28
+ },
29
+ 'experiment-plan': () => {
30
+ assertFileIncludes('src/core/research/experiment-plan.ts', ['EXPERIMENT_PLAN_JSON_ARTIFACT', 'min_experiment_steps', 'validateExperimentPlan']);
31
+ assertFileIncludes('src/core/research/experiment-plan.ts', ['experiment_plan_missing', 'experiment_plan_too_thin']);
32
+ assertFileIncludes('src/core/research.ts', ['experiment_plan_missing', 'validateExperimentPlan']);
33
+ },
34
+ 'replication-pack': () => {
35
+ assertFileIncludes('src/core/research/replication-pack.ts', ['REPLICATION_PACK_ARTIFACT', 'validateReplicationPack']);
36
+ assertFileIncludes('src/core/research.ts', ['replication_pack_missing']);
37
+ },
38
+ 'final-reviewer': () => {
39
+ assertFileIncludes('src/core/research/research-final-reviewer.ts', ['RESEARCH_FINAL_REVIEW_ARTIFACT', 'approved', 'runResearchFinalReviewer']);
40
+ assertFileIncludes('src/core/research.ts', ['research_final_review_not_approved']);
41
+ },
42
+ 'work-graph': () => {
43
+ assertFileIncludes('src/core/research/research-work-graph.ts', ['RESEARCH_WORK_GRAPH_ARTIFACT', 'buildResearchWorkGraph', 'sks.naruto-work-graph.v1']);
44
+ assertFileIncludes('src/core/commands/research-command.ts', ['narutoWorkGraph: researchWorkGraph', 'readonly: true', 'runResearchCycle']);
45
+ },
46
+ 'prompt-contract': () => {
47
+ assertFileIncludes('src/core/research/research-prompt-contract.ts', ['researchPromptContractText', 'validateResearchPromptContract']);
48
+ assertFileIncludes('src/core/research.ts', ['QUALITY CONTRACT:', 'researchPromptContractText()']);
49
+ },
50
+ 'gate-thresholds': () => {
51
+ assertFileIncludes('src/core/research.ts', [
52
+ 'source_entries_below_research_quality_contract',
53
+ 'source_layer_coverage_below_contract',
54
+ 'counterevidence_below_contract',
55
+ 'required_artifact_missing'
56
+ ]);
57
+ assertFileIncludes('src/core/research/falsification.ts', ['falsification_cases_below_contract']);
58
+ },
59
+ 'schemas': () => {
60
+ for (const file of [
61
+ 'schemas/research/research-quality-contract.schema.json',
62
+ 'schemas/research/claim-evidence-matrix.schema.json',
63
+ 'schemas/research/source-quality-report.schema.json',
64
+ 'schemas/research/implementation-blueprint.schema.json',
65
+ 'schemas/research/experiment-plan.schema.json',
66
+ 'schemas/research/replication-pack.schema.json',
67
+ 'schemas/research/research-final-review.schema.json'
68
+ ])
69
+ assertGate(readText(file).includes('"$schema"'), `${file} missing JSON Schema header`);
70
+ }
71
+ };
72
+ if (mode === 'all') {
73
+ for (const check of Object.values(checks))
74
+ check();
75
+ }
76
+ else {
77
+ assertGate(Boolean(checks[mode]), `unknown research quality check: ${mode}`);
78
+ checks[mode]();
79
+ }
80
+ emitGate(`research:${mode}`, { mode });
81
+ function assertFileIncludes(file, tokens) {
82
+ const text = readText(file);
83
+ for (const token of tokens)
84
+ assertGate(text.includes(token), `${file} missing token ${token}`);
85
+ }
86
+ //# sourceMappingURL=research-quality-gate-check.js.map
@@ -26,6 +26,24 @@ const roster = renderZellijSlotColumnAnchor({
26
26
  }
27
27
  ]
28
28
  });
29
+ const overflow = renderZellijSlotColumnAnchor({
30
+ activeWorkers: 6,
31
+ visiblePaneCap: 5,
32
+ headlessWorkers: 1,
33
+ queueDepth: 0,
34
+ mode: 'compact-slots',
35
+ workerRows: [
36
+ {
37
+ slotId: 'slot-009',
38
+ generationIndex: 1,
39
+ placement: 'headless',
40
+ status: 'running',
41
+ backend: 'local-llm',
42
+ task: 'Waiting for a visible slot pane',
43
+ heartbeatAgeMs: 1200
44
+ }
45
+ ]
46
+ });
29
47
  const command = buildZellijSlotColumnAnchorCommand({
30
48
  nodePath: '/usr/bin/node',
31
49
  cliPath: '/repo/dist/bin/sks.js',
@@ -34,12 +52,15 @@ const command = buildZellijSlotColumnAnchorCommand({
34
52
  artifactRoot: '/repo/.sneakoscope/missions/M-check/agents',
35
53
  watch: true
36
54
  });
37
- const ok = rendered === 'SLOTS active 3/8 · headless 12 · q 44'
55
+ const ok = rendered.includes('SLOTS active 3/8 · headless 12 · q 44')
56
+ && rendered.includes('visible slot panes stack below this anchor')
38
57
  && roster.includes('SLOTS active 1/5 · headless 0 · q 0')
39
- && roster.includes('slot-004 g1 running codex-sdk')
40
- && roster.includes('Inspect Zellij slot UI assignment')
58
+ && roster.includes('visible slot panes stack below this anchor')
59
+ && !roster.includes('slot-004 g1 running codex-sdk')
60
+ && overflow.includes('slot-009 g1 running local-llm')
61
+ && overflow.includes('Waiting for a visible slot pane')
41
62
  && command.includes('zellij-slot-column-anchor')
42
63
  && command.includes('--watch');
43
- assertGate(ok, 'Zellij slot-column anchor must render compact SLOTS header plus live worker rows and build the CLI command', { rendered, roster, command });
44
- emitGate('zellij:slot-column-anchor', { rendered, roster, command });
64
+ assertGate(ok, 'Zellij slot-column anchor must stay an anchor while slot workers render in dedicated stacked panes', { rendered, roster, overflow, command });
65
+ emitGate('zellij:slot-column-anchor', { rendered, roster, overflow, command });
45
66
  //# sourceMappingURL=zellij-slot-column-anchor-check.js.map
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
3
6
  import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
4
7
  const renderer = await importDist('core/zellij/zellij-slot-pane-renderer.js');
5
8
  const text = renderer.renderZellijSlotPane({
@@ -8,14 +11,72 @@ const text = renderer.renderZellijSlotPane({
8
11
  role: 'implementer',
9
12
  backend: 'local-llm',
10
13
  status: 'coding',
14
+ fastMode: true,
15
+ serviceTier: 'fast',
16
+ provider: 'codex-lb',
17
+ authMode: 'codex_lb_key',
18
+ model: 'gpt-5.5',
19
+ reasoningEffort: 'medium',
11
20
  currentFile: 'src/core/foo.ts',
21
+ currentTask: 'Editing Zellij slot pane renderer',
22
+ changedFiles: ['src/core/foo.ts', 'src/core/bar.ts'],
12
23
  patchStatus: 'candidate',
13
24
  verifyStatus: 'queued',
14
25
  heartbeatAgeMs: 2000,
15
26
  worktreeId: 'WT-0007',
27
+ eventLines: ['running: tool apply_patch', 'running: file src/core/foo.ts'],
28
+ stdoutTail: ['renderer updated live pane output'],
16
29
  mode: 'compact-slots'
17
30
  });
18
31
  const lines = text.split(/\n/);
32
+ const artifactDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-slot-pane-renderer-'));
33
+ await fs.writeFile(path.join(artifactDir, 'worker-intake.json'), JSON.stringify({
34
+ agent: { session_id: 'session-slot-003', role: 'implementer' },
35
+ backend: 'codex-sdk',
36
+ fast_mode: true,
37
+ service_tier: 'fast',
38
+ slice: {
39
+ title: 'Hydrate live slot pane from artifacts',
40
+ write_paths: ['src/core/zellij/zellij-slot-pane-renderer.ts']
41
+ },
42
+ worktree: { id: 'WT-0007' }
43
+ }, null, 2));
44
+ await fs.writeFile(path.join(artifactDir, 'worker-fast-mode.json'), JSON.stringify({
45
+ fast_mode: true,
46
+ service_tier: 'fast'
47
+ }, null, 2));
48
+ await fs.writeFile(path.join(artifactDir, 'zellij-worker-pane.json'), JSON.stringify({
49
+ provider: 'codex-lb',
50
+ service_tier: 'fast',
51
+ provider_context: {
52
+ provider: 'codex-lb',
53
+ auth_mode: 'codex_lb_key'
54
+ }
55
+ }, null, 2));
56
+ await fs.writeFile(path.join(artifactDir, 'codex-control-proof.json'), JSON.stringify({
57
+ config: {
58
+ model: 'gpt-5.5',
59
+ model_provider: 'codex-lb',
60
+ service_tier: 'fast',
61
+ model_reasoning_effort: 'medium'
62
+ }
63
+ }, null, 2));
64
+ await fs.writeFile(path.join(artifactDir, 'worker-heartbeat.jsonl'), `${JSON.stringify({ event: 'started', status: 'running' })}\n`);
65
+ await fs.writeFile(path.join(artifactDir, 'codex-sdk-events.jsonl'), `${JSON.stringify({
66
+ sdk_event_type: 'item.completed',
67
+ lane_status: 'running',
68
+ current_tool: 'apply_patch',
69
+ current_file: 'src/core/zellij/zellij-slot-pane-renderer.ts',
70
+ message_tail: null
71
+ })}\n`);
72
+ await fs.writeFile(path.join(artifactDir, 'worker.stdout.log'), 'renderer stdout tail\n');
73
+ const hydrated = await renderer.renderZellijSlotPaneFromArtifacts({
74
+ artifactDir,
75
+ slotId: 'slot-003',
76
+ generationIndex: 2,
77
+ mode: 'compact-slots'
78
+ });
79
+ await fs.rm(artifactDir, { recursive: true, force: true });
19
80
  const command = renderer.buildZellijSlotPaneCommand({
20
81
  cliPath: '/repo/dist/bin/sks.js',
21
82
  missionId: 'M-test',
@@ -27,12 +88,19 @@ const command = renderer.buildZellijSlotPaneCommand({
27
88
  const report = {
28
89
  schema: 'sks.zellij-slot-pane-renderer-check.v1',
29
90
  line_count: lines.length,
30
- max_compact_lines: 5,
31
- contains_slot: /slot-003 gen-2/.test(text),
32
- contains_status: /status: coding/.test(text),
91
+ max_compact_lines: 17,
92
+ contains_slot: /LIVE SLOT slot-003/.test(text) && /slot: slot-003 \/ gen-2/.test(text),
93
+ contains_status: /coding/.test(text),
94
+ contains_runtime: /runtime: fast on/.test(text) && /model: gpt-5\.5/.test(text) && /provider: codex-lb/.test(text),
95
+ contains_files: /src\/core\/foo\.ts/.test(text) && /src\/core\/bar\.ts/.test(text),
96
+ contains_live_event: /event: running:/.test(text),
97
+ artifact_hydrates_runtime: /runtime: fast on/.test(hydrated) && /model: gpt-5\.5/.test(hydrated) && /reasoning: medium/.test(hydrated) && /auth: codex_lb_key/.test(hydrated),
98
+ artifact_hydrates_live_event: /tool apply_patch/.test(hydrated) && /renderer stdout tail/.test(hydrated),
99
+ artifact_hydrates_planned_file: /zellij-slot-pane-renderer\.ts/.test(hydrated),
33
100
  command_uses_slot_pane: command.includes('zellij-slot-pane') && command.includes('--watch'),
34
- snapshot: text
101
+ snapshot: text,
102
+ hydrated_snapshot: hydrated
35
103
  };
36
- assertGate(lines.length <= 5 && report.contains_slot && report.contains_status && report.command_uses_slot_pane, 'compact slot pane renderer must stay within five lines', report);
104
+ assertGate(lines.length <= 17 && report.contains_slot && report.contains_status && report.contains_runtime && report.contains_files && report.contains_live_event && report.artifact_hydrates_runtime && report.artifact_hydrates_live_event && report.artifact_hydrates_planned_file && report.command_uses_slot_pane, 'compact slot pane renderer must render one live work pane per slot', report);
37
105
  emitGate('zellij:compact-slot-renderer', report);
38
106
  //# sourceMappingURL=zellij-slot-pane-renderer-check.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "2.0.12",
4
+ "version": "2.0.13",
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",
@@ -394,6 +394,18 @@
394
394
  "agent:proof-graph": "node ./dist/scripts/agent-native-release-gate.js agent-proof-graph",
395
395
  "team:native-agent-backend": "node ./dist/scripts/agent-native-release-gate.js team-native-agent-backend",
396
396
  "research:native-agent-backend": "node ./dist/scripts/agent-native-release-gate.js research-native-agent-backend",
397
+ "research:quality-contract": "node ./dist/scripts/research-quality-gate-check.js quality-contract",
398
+ "research:claim-matrix": "node ./dist/scripts/research-quality-gate-check.js claim-matrix",
399
+ "research:source-quality-report": "node ./dist/scripts/research-quality-gate-check.js source-quality-report",
400
+ "research:implementation-blueprint": "node ./dist/scripts/research-quality-gate-check.js implementation-blueprint",
401
+ "research:experiment-plan": "node ./dist/scripts/research-quality-gate-check.js experiment-plan",
402
+ "research:replication-pack": "node ./dist/scripts/research-quality-gate-check.js replication-pack",
403
+ "research:final-reviewer": "node ./dist/scripts/research-quality-gate-check.js final-reviewer",
404
+ "research:work-graph": "node ./dist/scripts/research-quality-gate-check.js work-graph",
405
+ "research:prompt-contract": "node ./dist/scripts/research-quality-gate-check.js prompt-contract",
406
+ "research:gate-thresholds": "node ./dist/scripts/research-quality-gate-check.js gate-thresholds",
407
+ "research:schemas": "node ./dist/scripts/research-quality-gate-check.js schemas",
408
+ "research:quality-gates": "node ./dist/scripts/research-quality-gate-check.js all",
397
409
  "qa:native-agent-backend": "node ./dist/scripts/agent-native-release-gate.js qa-native-agent-backend",
398
410
  "agent:non-recursive-pipeline-report": "node ./dist/scripts/non-recursive-pipeline-check.js --json",
399
411
  "agent:legacy-multiagent-removed": "node ./dist/scripts/legacy-multiagent-removal-check.js",