sneakoscope 2.0.12 → 2.0.14

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 (86) 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/core/agents/agent-orchestrator.js +11 -4
  8. package/dist/core/agents/agent-output-validator.js +1 -1
  9. package/dist/core/codex-control/codex-fake-sdk-adapter.js +3 -3
  10. package/dist/core/codex-control/codex-sdk-adapter.js +10 -0
  11. package/dist/core/codex-control/codex-task-runner.js +4 -2
  12. package/dist/core/codex-control/gpt-final-review-schema.js +61 -14
  13. package/dist/core/commands/naruto-command.js +1 -0
  14. package/dist/core/commands/research-command.js +112 -19
  15. package/dist/core/fsx.js +1 -1
  16. package/dist/core/naruto/naruto-real-worker-child.js +11 -3
  17. package/dist/core/naruto/naruto-real-worker-runtime.js +4 -0
  18. package/dist/core/pipeline/final-gpt-patch-stage.js +20 -3
  19. package/dist/core/research/claim-evidence-matrix.js +160 -0
  20. package/dist/core/research/experiment-plan.js +53 -0
  21. package/dist/core/research/falsification.js +18 -0
  22. package/dist/core/research/implementation-blueprint-densifier.js +124 -0
  23. package/dist/core/research/implementation-blueprint-markdown.js +31 -0
  24. package/dist/core/research/implementation-blueprint.js +66 -0
  25. package/dist/core/research/replication-pack.js +50 -0
  26. package/dist/core/research/research-claim-builder.js +114 -0
  27. package/dist/core/research/research-cycle-runner.js +129 -0
  28. package/dist/core/research/research-final-reviewer.js +212 -0
  29. package/dist/core/research/research-handoff.js +51 -0
  30. package/dist/core/research/research-prompt-contract.js +24 -0
  31. package/dist/core/research/research-quality-contract.js +61 -0
  32. package/dist/core/research/research-report-quality.js +67 -0
  33. package/dist/core/research/research-source-ledger-merge.js +186 -0
  34. package/dist/core/research/research-source-shards.js +176 -0
  35. package/dist/core/research/research-stage-runner.js +515 -0
  36. package/dist/core/research/research-work-graph.js +166 -0
  37. package/dist/core/research/source-quality-report.js +94 -0
  38. package/dist/core/research.js +356 -44
  39. package/dist/core/version.js +1 -1
  40. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -3
  41. package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
  42. package/dist/scripts/codex-sdk-research-pipeline-check.js +44 -5
  43. package/dist/scripts/packlist-performance-check.js +1 -1
  44. package/dist/scripts/release-dag-full-coverage-check.js +14 -1
  45. package/dist/scripts/release-parallel-speed-budget-check.js +7 -2
  46. package/dist/scripts/research-blueprint-densifier-check.js +21 -0
  47. package/dist/scripts/research-claim-builder-check.js +19 -0
  48. package/dist/scripts/research-complete-package-fixture-check.js +23 -0
  49. package/dist/scripts/research-final-reviewer-blackbox.js +22 -0
  50. package/dist/scripts/research-parallel-source-shards-check.js +22 -0
  51. package/dist/scripts/research-quality-gate-check.js +111 -0
  52. package/dist/scripts/research-real-cycle-no-legacy-final-md-check.js +14 -0
  53. package/dist/scripts/research-short-report-rejection-check.js +46 -0
  54. package/dist/scripts/research-source-ledger-merge-check.js +26 -0
  55. package/dist/scripts/research-stage-cycle-runtime-blackbox.js +24 -0
  56. package/dist/scripts/zellij-slot-column-anchor-check.js +26 -5
  57. package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
  58. package/package.json +28 -1
  59. package/schemas/codex/agent-result.schema.json +1 -1
  60. package/schemas/research/claim-evidence-matrix.schema.json +37 -0
  61. package/schemas/research/experiment-plan.schema.json +17 -0
  62. package/schemas/research/implementation-blueprint.schema.json +30 -0
  63. package/schemas/research/replication-pack.schema.json +17 -0
  64. package/schemas/research/research-final-review.schema.json +16 -0
  65. package/schemas/research/research-quality-contract.schema.json +37 -0
  66. package/schemas/research/research-source-shard.schema.json +46 -0
  67. package/schemas/research/source-quality-report.schema.json +18 -0
  68. package/dist/build-manifest.json +0 -1168
  69. package/dist/scripts/release-readiness-report.js +0 -1146
  70. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/permission-request.command.input.schema.json +0 -61
  71. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/permission-request.command.output.schema.json +0 -103
  72. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-compact.command.input.schema.json +0 -52
  73. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-compact.command.output.schema.json +0 -24
  74. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-tool-use.command.input.schema.json +0 -67
  75. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-tool-use.command.output.schema.json +0 -84
  76. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-compact.command.input.schema.json +0 -52
  77. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-compact.command.output.schema.json +0 -24
  78. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-tool-use.command.input.schema.json +0 -65
  79. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-tool-use.command.output.schema.json +0 -105
  80. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/session-start.command.input.schema.json +0 -59
  81. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/session-start.command.output.schema.json +0 -63
  82. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/snapshot-metadata.json +0 -31
  83. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/stop.command.input.schema.json +0 -63
  84. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/stop.command.output.schema.json +0 -45
  85. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/user-prompt-submit.command.input.schema.json +0 -59
  86. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/user-prompt-submit.command.output.schema.json +0 -81
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '2.0.12';
1
+ export const PACKAGE_VERSION = '2.0.14';
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
  }
@@ -1,8 +1,47 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
- import { assertGate, emitGate, readText } from './lib/codex-sdk-gate-lib.js';
4
- const source = readText('src/core/commands/research-command.ts');
5
- assertGate(source.includes("backend: mock ? 'fake' : 'codex-sdk'"), 'Research pipeline must default native agents to codex-sdk');
6
- assertGate(source.includes("flag(args, '--autoresearch') ? '$AutoResearch' : '$Research'"), 'Research/AutoResearch route selection missing');
7
- emitGate('codex-sdk:research-pipeline', { route: '$Research' });
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist, readText } from './lib/codex-sdk-gate-lib.js';
7
+ const researchCommand = readText('src/core/commands/research-command.ts');
8
+ assertGate(researchCommand.includes("backend: mock ? 'fake' : 'codex-sdk'"), 'Research pipeline must default native agents to codex-sdk');
9
+ assertGate(researchCommand.includes("flag(args, '--autoresearch') ? '$AutoResearch' : '$Research'"), 'Research/AutoResearch route selection missing');
10
+ assertGate(researchCommand.includes('narutoWorkGraph: researchWorkGraph'), 'Research pipeline must pass the stage-aware Naruto work graph');
11
+ assertGate(researchCommand.includes('readonly: true'), 'Research pipeline must force read-only native orchestration');
12
+ assertGate(researchCommand.includes('quality_metrics'), 'Research pipeline JSON output must include quality metrics');
13
+ assertGate(researchCommand.includes('const cycleResult = await runResearchCycle({'), 'Research default path must use runResearchCycle');
14
+ assertGate(researchCommand.includes('--legacy-research-cycle'), 'Legacy final.md loop must be opt-in only');
15
+ const researchCore = readText('src/core/research.ts');
16
+ assertGate(researchCore.includes('readResearchQualityContract'), 'Research gate must read research-quality-contract.json');
17
+ assertGate(researchCore.includes('claim_evidence_matrix_missing'), 'Research gate must require claim-evidence-matrix.json');
18
+ assertGate(researchCore.includes('research_final_review_not_approved'), 'Research gate must require final reviewer approval');
19
+ for (const [file, tokens] of Object.entries({
20
+ 'src/core/research/research-work-graph.ts': ['buildResearchWorkGraph', 'source_shard_academic_literature'],
21
+ 'src/core/research/research-cycle-runner.ts': ['runResearchCycle', 'Promise.race'],
22
+ 'src/core/research/research-final-reviewer.ts': ['runResearchCodexFinalReviewer'],
23
+ 'src/core/research/claim-evidence-matrix.ts': ['claim-evidence-matrix'],
24
+ 'src/core/research/implementation-blueprint.ts': ['implementation-blueprint'],
25
+ 'src/core/research/research-quality-contract.ts': ['research-quality-contract']
26
+ })) {
27
+ const text = readText(file);
28
+ for (const token of tokens)
29
+ assertGate(text.includes(token), `${file} missing token ${token}`);
30
+ }
31
+ const research = await importDist('core/research.js');
32
+ const fsx = await importDist('core/fsx.js');
33
+ const dirShort = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-codex-research-short-'));
34
+ const planShort = await research.writeResearchPlan(dirShort, 'codex sdk short rejection fixture', { missionId: 'M-CODEX-SHORT' });
35
+ await fsx.writeTextAtomic(path.join(dirShort, 'research-report.md'), `# Short Report\n\n${Array.from({ length: 300 }, (_unused, index) => `word${index}`).join(' ')}\n`);
36
+ await fsx.writeJsonAtomic(path.join(dirShort, 'source-ledger.json'), { schema_version: 1, web_search_passes: 1, source_layers: [], sources: [], counterevidence_sources: [], triangulation: { cross_layer_checks: [] }, citation_coverage: { all_key_claims_cited: false }, blockers: [] });
37
+ await fsx.writeJsonAtomic(path.join(dirShort, 'implementation-blueprint.json'), { schema: 'sks.research-implementation-blueprint.v1', sections: [] });
38
+ const shortGate = await research.evaluateResearchGate(dirShort);
39
+ assertGate(shortGate.passed === false, 'codex-sdk research pipeline must reject short report fixture', shortGate);
40
+ assertGate((shortGate.reasons || []).includes('research_report_too_short'), 'short rejection must include report length reason', shortGate);
41
+ const dirComplete = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-codex-research-complete-'));
42
+ const planComplete = await research.writeResearchPlan(dirComplete, 'codex sdk complete package fixture', { missionId: 'M-CODEX-COMPLETE' });
43
+ const completeGate = await research.writeMockResearchResult(dirComplete, planComplete);
44
+ assertGate(completeGate.passed === true, 'codex-sdk research pipeline must pass complete package fixture', completeGate);
45
+ assertGate(completeGate.metrics?.final_review_approved === true, 'complete package must include approved final review', completeGate.metrics);
46
+ emitGate('codex-sdk:research-pipeline', { route: '$Research', short_dir: dirShort, complete_dir: dirComplete });
8
47
  //# 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() {
@@ -29,7 +29,20 @@ const requiredReleasePresetIds = [
29
29
  'git:worktree-cross-rebase',
30
30
  'local-collab:worktree-gpt-final-apply-policy',
31
31
  'release:cache-glob-hashing',
32
- 'release:dag-full-coverage'
32
+ 'release:dag-full-coverage',
33
+ 'research:quality-contract',
34
+ 'research:claim-matrix',
35
+ 'research:source-quality-report',
36
+ 'research:implementation-blueprint',
37
+ 'research:experiment-plan',
38
+ 'research:replication-pack',
39
+ 'research:final-reviewer',
40
+ 'research:work-graph',
41
+ 'research:short-report-rejection',
42
+ 'research:complete-package-fixture',
43
+ 'research:stage-cycle-runtime-blackbox',
44
+ 'research:final-reviewer-blackbox',
45
+ 'codex-sdk:research-pipeline'
33
46
  ];
34
47
  const missing = legacyIds.filter((id) => !gateIds.has(id) && !allowlist.has(id));
35
48
  const missingRequiredReleasePreset = requiredReleasePresetIds.filter((id) => !gateIds.has(id) || !releasePresetIds.has(id));
@@ -12,12 +12,15 @@ const summary = JSON.parse(fs.readFileSync(latest.summaryPath, 'utf8'));
12
12
  const slowest = slowestGateResults(latest.dir);
13
13
  const warnOnly = process.env.SKS_RELEASE_SPEED_BUDGET_WARN_ONLY === '1';
14
14
  const budgetMs = Number(process.env.SKS_RELEASE_SPEED_BUDGET_MS || 20 * 60 * 1000);
15
- const cachedBudgetMs = 3 * 60 * 1000;
15
+ const cachedBudgetMs = Number(process.env.SKS_RELEASE_CACHED_SPEED_BUDGET_MS || 4 * 60 * 1000);
16
16
  const cachedRatio = Number(summary.cached || 0) / Math.max(1, Number(summary.completed || summary.selected_gates || 1));
17
17
  const cachedRun = cachedRatio >= 0.5;
18
18
  const parallelismOk = cachedRun || summary.parallelism_gain >= 2;
19
19
  const wallOk = cachedRun ? summary.wall_ms <= cachedBudgetMs : summary.wall_ms <= budgetMs;
20
- const summaryOk = Number(summary.failed || 0) === 0;
20
+ const failureIds = Array.isArray(summary.failures) ? summary.failures.map((entry) => String(entry?.id || '')).filter(Boolean) : [];
21
+ const selfFailureIds = new Set(['release:parallel-speed-budget', 'release:stability-report']);
22
+ const blockingFailureIds = failureIds.filter((id) => !selfFailureIds.has(id));
23
+ const summaryOk = Number(summary.failed || 0) === 0 || (failureIds.length > 0 && blockingFailureIds.length === 0);
21
24
  const report = {
22
25
  schema: 'sks.release-speed.v1',
23
26
  ok: summaryOk && parallelismOk && (wallOk || warnOnly),
@@ -34,6 +37,8 @@ const report = {
34
37
  sum_gate_ms: summary.sum_gate_ms,
35
38
  critical_path_ms: summary.critical_path_ms,
36
39
  slowest_gates: slowest,
40
+ functional_failure_ids: blockingFailureIds,
41
+ ignored_self_failure_ids: failureIds.filter((id) => selfFailureIds.has(id)),
37
42
  target_full_wall_ms: budgetMs,
38
43
  target_cached_wall_ms: cachedBudgetMs,
39
44
  target_changed_file_wall_ms: 90 * 1000,
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ const research = await importDist('core/research.js');
8
+ const workGraph = await importDist('core/research/research-work-graph.js');
9
+ const cycleRunner = await importDist('core/research/research-cycle-runner.js');
10
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-research-blueprint-'));
11
+ const plan = await research.writeResearchPlan(dir, 'blueprint densifier blackbox', { missionId: 'M-BLUEPRINT' });
12
+ const graph = workGraph.buildResearchWorkGraph(plan);
13
+ await cycleRunner.runResearchCycle({ root: process.cwd(), dir, plan, graph, cycle: 1, backend: 'mock', timeoutMs: 120000, maxParallelStages: 4, mock: true });
14
+ const blueprint = JSON.parse(fs.readFileSync(path.join(dir, 'implementation-blueprint.json'), 'utf8'));
15
+ assertGate(blueprint.repository_aware === true, 'blueprint must be repository-aware', blueprint);
16
+ assertGate((blueprint.existing_files || []).length > 0, 'blueprint must list concrete existing files', blueprint);
17
+ assertGate((blueprint.sections || []).length >= 8, 'blueprint must keep required sections', blueprint);
18
+ assertGate((blueprint.test_commands || []).some((cmd) => String(cmd).includes('research:stage-cycle-runtime-blackbox')), 'blueprint must include concrete test commands', blueprint);
19
+ assertGate(fs.existsSync(path.join(dir, 'team-handoff-goal.md')), 'team handoff goal must exist');
20
+ emitGate('research:blueprint-densifier', { dir, sections: blueprint.sections.length, files: blueprint.existing_files.length });
21
+ //# sourceMappingURL=research-blueprint-densifier-check.js.map
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ const research = await importDist('core/research.js');
8
+ const workGraph = await importDist('core/research/research-work-graph.js');
9
+ const cycleRunner = await importDist('core/research/research-cycle-runner.js');
10
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-research-claim-builder-'));
11
+ const plan = await research.writeResearchPlan(dir, 'claim builder blackbox', { missionId: 'M-CLAIM-BUILDER' });
12
+ const graph = workGraph.buildResearchWorkGraph(plan);
13
+ await cycleRunner.runResearchCycle({ root: process.cwd(), dir, plan, graph, cycle: 1, backend: 'mock', timeoutMs: 120000, maxParallelStages: 4, mock: true });
14
+ const matrix = JSON.parse(fs.readFileSync(path.join(dir, 'claim-evidence-matrix.json'), 'utf8'));
15
+ assertGate(matrix.key_claim_ids.length >= 8, 'claim builder must produce key claims', matrix);
16
+ assertGate(matrix.triangulated_claim_count >= 6, 'claim builder must triangulate claims', matrix);
17
+ assertGate((matrix.unsupported_claims || []).length === 0, 'claim builder must not leave important claims unsupported in complete fixture', matrix);
18
+ emitGate('research:claim-builder', { dir, key_claims: matrix.key_claim_ids.length, triangulated: matrix.triangulated_claim_count });
19
+ //# sourceMappingURL=research-claim-builder-check.js.map
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ const research = await importDist('core/research.js');
8
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-research-complete-package-'));
9
+ const plan = await research.writeResearchPlan(dir, 'complete package fixture blackbox', { missionId: 'M-COMPLETE-PACKAGE' });
10
+ const gate = await research.writeMockResearchResult(dir, plan);
11
+ const metrics = gate.metrics || {};
12
+ assertGate(gate.passed === true, 'complete mock research package must pass', gate);
13
+ assertGate(metrics.report_word_count >= 2200, 'report must satisfy word floor', metrics);
14
+ assertGate(metrics.source_entries_total_with_counterevidence >= 12, 'source count must satisfy contract', metrics);
15
+ assertGate(metrics.source_layers_covered >= 5, 'source layer coverage must satisfy contract', metrics);
16
+ assertGate(metrics.key_claims >= 8, 'key claims must satisfy contract', metrics);
17
+ assertGate(metrics.counterevidence_sources >= 2, 'counterevidence count must satisfy contract', metrics);
18
+ assertGate(metrics.final_review_approved === true, 'static plus mock Codex final review must approve', metrics);
19
+ assertGate(metrics.implementation_blueprint_validation?.ok === true, 'blueprint must validate', metrics);
20
+ assertGate(metrics.experiment_plan_validation?.ok === true, 'experiment plan must validate', metrics);
21
+ assertGate(metrics.replication_pack_validation?.ok === true, 'replication pack must validate', metrics);
22
+ emitGate('research:complete-package-fixture', { dir, metrics });
23
+ //# sourceMappingURL=research-complete-package-fixture-check.js.map
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ const research = await importDist('core/research.js');
8
+ const reviewer = await importDist('core/research/research-final-reviewer.js');
9
+ const fsx = await importDist('core/fsx.js');
10
+ const badDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-research-final-review-bad-'));
11
+ await research.writeResearchPlan(badDir, 'bad final reviewer fixture', { missionId: 'M-FINAL-BAD' });
12
+ await fsx.writeTextAtomic(path.join(badDir, 'research-report.md'), '# Bad\n\nToo short.\n');
13
+ const bad = await reviewer.runResearchFinalReviewer(badDir, { codexRequired: true });
14
+ assertGate(bad.approved === false, 'static failure must not approve final review', bad);
15
+ const goodDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-research-final-review-good-'));
16
+ const plan = await research.writeResearchPlan(goodDir, 'good final reviewer fixture', { missionId: 'M-FINAL-GOOD' });
17
+ await research.writeMockResearchResult(goodDir, plan);
18
+ const good = JSON.parse(fs.readFileSync(path.join(goodDir, 'research-final-review.json'), 'utf8'));
19
+ assertGate(good.approved === true, 'complete package must approve final review with mock Codex reviewer', good);
20
+ assertGate(good.codex_review?.verdict === 'approve', 'merged final review must include Codex/mock approval', good);
21
+ emitGate('research:final-reviewer-blackbox', { bad_dir: badDir, good_dir: goodDir });
22
+ //# sourceMappingURL=research-final-reviewer-blackbox.js.map
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
4
+ const workGraph = await importDist('core/research/research-work-graph.js');
5
+ const graph = workGraph.buildResearchWorkGraph({ mission_id: 'M-PARALLEL-SHARDS' });
6
+ const shards = graph.work_items.filter((item) => item.stage_kind === 'source_shard');
7
+ const shardIds = new Set(shards.map((item) => item.id));
8
+ const claim = graph.work_items.find((item) => item.id === 'claim_matrix_build');
9
+ const merge = graph.work_items.find((item) => item.id === 'source_ledger_merge');
10
+ assertGate(shards.length >= 8, 'work graph must create source layer shards', { count: shards.length });
11
+ assertGate(shards.some((item) => item.id === 'source_shard_academic_literature'), 'academic source shard missing');
12
+ assertGate(shards.some((item) => item.id === 'source_shard_official_government_data'), 'official government source shard missing');
13
+ assertGate(shards.some((item) => item.id === 'source_shard_standards_primary_docs'), 'standards source shard missing');
14
+ assertGate(shards.some((item) => item.id === 'source_shard_news_current_events'), 'news source shard missing');
15
+ assertGate(shards.some((item) => item.id === 'source_shard_public_discourse'), 'public discourse source shard missing');
16
+ assertGate(shards.some((item) => item.id === 'source_shard_developer_practitioner'), 'developer practitioner source shard missing');
17
+ assertGate(shards.some((item) => item.id === 'source_shard_counterevidence_factcheck'), 'counterevidence source shard missing');
18
+ assertGate(shards.some((item) => item.id === 'source_shard_local_project_evidence'), 'local project evidence source shard missing');
19
+ assertGate(merge && merge.dependencies.every((id) => shardIds.has(id)), 'source merge must depend on every source shard', merge);
20
+ assertGate(claim && [...shardIds].every((id) => claim.dependencies.includes(id)), 'claim matrix must depend on source shards', claim);
21
+ emitGate('research:parallel-source-shards', { shard_count: shards.length });
22
+ //# sourceMappingURL=research-parallel-source-shards-check.js.map