sneakoscope 4.8.4 → 4.8.6

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 (81) hide show
  1. package/README.md +3 -3
  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/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -0
  7. package/dist/core/agents/agent-central-ledger.js +10 -5
  8. package/dist/core/agents/agent-orchestrator.js +22 -1
  9. package/dist/core/agents/agent-runner-codex-exec.js +24 -0
  10. package/dist/core/agents/fast-mode-policy.js +3 -4
  11. package/dist/core/agents/native-cli-session-proof.js +1 -1
  12. package/dist/core/agents/native-cli-worker.js +73 -113
  13. package/dist/core/agents/worker-pane-communication-contract.js +2 -3
  14. package/dist/core/codex/codex-cli-syntax-builder.js +2 -0
  15. package/dist/core/codex-app/codex-init-deep.js +10 -3
  16. package/dist/core/codex-control/codex-reliability-shield.js +36 -17
  17. package/dist/core/codex-control/codex-sdk-adapter.js +2 -2
  18. package/dist/core/codex-control/codex-sdk-config-policy.js +4 -0
  19. package/dist/core/codex-control/codex-task-runner.js +23 -11
  20. package/dist/core/codex-native/core-skill-manifest.js +2 -1
  21. package/dist/core/commands/gates-command.js +58 -0
  22. package/dist/core/commands/release-command.js +5 -5
  23. package/dist/core/commands/stop-gate-command.js +1 -1
  24. package/dist/core/fsx.js +1 -1
  25. package/dist/core/hooks-runtime/team-digest.js +86 -0
  26. package/dist/core/hooks-runtime.js +1 -82
  27. package/dist/core/init/skills.js +395 -0
  28. package/dist/core/init.js +2 -376
  29. package/dist/core/lean-engineering-policy.js +2 -1
  30. package/dist/core/naruto/naruto-active-pool.js +36 -7
  31. package/dist/core/naruto/naruto-real-worker-child.js +67 -11
  32. package/dist/core/permission-gates.js +45 -19
  33. package/dist/core/pipeline-internals/runtime-gates.js +26 -45
  34. package/dist/core/ppt/artifacts.js +19 -0
  35. package/dist/core/ppt/html.js +96 -0
  36. package/dist/core/ppt/style-tokens.js +248 -0
  37. package/dist/core/ppt.js +6 -352
  38. package/dist/core/qa-loop/{qa-contract-v2.js → qa-contract.js} +1 -1
  39. package/dist/core/qa-loop/{qa-gate-v2.js → qa-gate.js} +1 -1
  40. package/dist/core/qa-loop/qa-runtime-artifacts.js +2 -2
  41. package/dist/core/qa-loop.js +11 -4
  42. package/dist/core/recallpulse/policy.js +226 -0
  43. package/dist/core/recallpulse.js +2 -225
  44. package/dist/core/release/release-gate-dag.js +6 -2
  45. package/dist/core/research/mock-result.js +406 -0
  46. package/dist/core/research/prompt.js +15 -0
  47. package/dist/core/research.js +2 -402
  48. package/dist/core/routes/constants.js +48 -0
  49. package/dist/core/routes/design-policy.js +37 -0
  50. package/dist/core/routes/evidence.js +77 -0
  51. package/dist/core/routes/ppt-policy.js +26 -0
  52. package/dist/core/routes.js +8 -179
  53. package/dist/core/safety/mutation-ledger.js +2 -0
  54. package/dist/core/safety/ssot-guard.js +2 -2
  55. package/dist/core/stop-gate/stop-gate-check.js +10 -17
  56. package/dist/core/version.js +1 -1
  57. package/dist/core/zellij/zellij-slot-pane-renderer.js +15 -8
  58. package/dist/core/zellij/zellij-slot-telemetry.js +28 -12
  59. package/dist/scripts/check-pipeline-budget.js +1 -7
  60. package/dist/scripts/check-pipeline-runtime.js +1 -8
  61. package/dist/scripts/codex-control-all-pipelines-check.js +5 -14
  62. package/dist/scripts/codex-sdk-all-pipelines-check.js +5 -9
  63. package/dist/scripts/fast-codex-service-tier-proof-check.js +2 -2
  64. package/dist/scripts/gate-policy-audit-check.js +70 -0
  65. package/dist/scripts/official-docs-compat-report.js +2 -2
  66. package/dist/scripts/product-design-plugin-routing-check.js +10 -5
  67. package/dist/scripts/qa-loop-surface-router-check.js +2 -2
  68. package/dist/scripts/release-check-dynamic-execute.js +32 -35
  69. package/dist/scripts/release-check-dynamic.js +19 -28
  70. package/dist/scripts/release-check-stamp.js +2 -2
  71. package/dist/scripts/release-dag-full-coverage-check.js +57 -394
  72. package/dist/scripts/release-dynamic-performance-check.js +18 -19
  73. package/dist/scripts/release-dynamic-presets-check.js +2 -2
  74. package/dist/scripts/release-gate-dag-runner.js +3 -1
  75. package/dist/scripts/release-gate-existence-audit.js +59 -91
  76. package/dist/scripts/release-gate-planner.js +43 -23
  77. package/dist/scripts/release-metadata-1-19-check.js +100 -12
  78. package/dist/scripts/worker-pane-communication-contract-check.js +1 -2
  79. package/dist/scripts/zellij-slot-pane-renderer-check.js +3 -2
  80. package/package.json +13 -965
  81. package/dist/core/pipeline-runtime.js +0 -2
@@ -45,9 +45,8 @@ async function checkRecord(root, record) {
45
45
  const pane = await readJson(panePath, null);
46
46
  const codexControlProof = await readJson(codexControlProofPath, null);
47
47
  const patchPath = record.patch_envelope_path ? path.join(root, String(record.patch_envelope_path)) : path.join(root, workerDir, 'worker-patch-envelope.json');
48
- const noPatchPath = path.join(root, workerDir, 'worker-no-patch-reason.json');
49
48
  const intakeExists = await exists(intakePath);
50
- const patchOrNoPatchExists = await exists(patchPath) || await exists(noPatchPath);
49
+ const patchOrNoPatchExists = await exists(patchPath) || Boolean(result?.no_patch_reason);
51
50
  const events = eventsText.trim().split(/\r?\n/).filter(Boolean).map((line) => {
52
51
  try {
53
52
  return JSON.parse(line);
@@ -60,7 +59,6 @@ async function checkRecord(root, record) {
60
59
  const resultObserved = eventTypes.includes('result_written');
61
60
  const paneClosedObserved = eventTypes.includes('pane_closed');
62
61
  const blockers = [
63
- ...(!intakeExists ? ['worker_pane_contract_intake_missing'] : []),
64
62
  ...(!result ? ['worker_pane_contract_result_missing'] : []),
65
63
  ...(!heartbeatText.trim() ? ['worker_pane_contract_heartbeat_missing'] : []),
66
64
  ...(!processReport ? ['worker_pane_contract_process_report_missing'] : []),
@@ -77,6 +75,7 @@ async function checkRecord(root, record) {
77
75
  slot_id: record.slot_id || null,
78
76
  generation_index: record.generation_index || null,
79
77
  worker_artifact_dir: workerDir,
78
+ intake_present: intakeExists,
80
79
  result_path: resultPath,
81
80
  heartbeat_path: heartbeatPath,
82
81
  process_report_path: processReportPath,
@@ -22,6 +22,8 @@ export function buildCodexExecArgs(opts) {
22
22
  args.push('--ephemeral');
23
23
  if (opts.skipGitRepoCheck)
24
24
  args.push('--skip-git-repo-check');
25
+ if (opts.noMcp)
26
+ args.push('--no-mcp');
25
27
  if (opts.profile)
26
28
  args.push(...buildCodexProfileArgs(opts.profile, opts.profileAlias));
27
29
  else if (opts.ignoreUserConfig)
@@ -28,7 +28,8 @@ export async function runCodexInitDeep(input = {}) {
28
28
  }
29
29
  if (existing.trim()) {
30
30
  const beforeHash = hashText(existing);
31
- const backup = `${agentsPath}.sks-backup-${Date.now()}-${beforeHash.slice(0, 12)}`;
31
+ const backup = initDeepBackupPath(root, agentsPath, beforeHash);
32
+ await ensureDir(path.dirname(backup));
32
33
  await fs.copyFile(agentsPath, backup);
33
34
  directoryLocalAgents.backup_paths.push(path.relative(root, backup));
34
35
  directoryLocalAgents.backups_created += 1;
@@ -81,8 +82,8 @@ function mergeManagedBlockPreview(current, markerName, content) {
81
82
  async function pruneBackups(root, agentsPath, keep) {
82
83
  if (keep < 1)
83
84
  return [];
84
- const dir = path.dirname(agentsPath);
85
- const base = path.basename(agentsPath);
85
+ const dir = path.join(root, '.sneakoscope', 'backups', 'agents');
86
+ const base = backupBaseName(root, agentsPath);
86
87
  const backupPattern = new RegExp(`^${escapeRegExp(base)}\\.sks-backup-\\d{13}-[0-9a-f]{8,12}$`);
87
88
  const rows = await fs.readdir(dir).catch(() => []);
88
89
  const backups = rows
@@ -100,6 +101,12 @@ async function pruneBackups(root, agentsPath, keep) {
100
101
  await guardedRm(guard, file, { force: true }).catch(() => undefined);
101
102
  return remove;
102
103
  }
104
+ function initDeepBackupPath(root, agentsPath, beforeHash) {
105
+ return path.join(root, '.sneakoscope', 'backups', 'agents', `${backupBaseName(root, agentsPath)}.sks-backup-${Date.now()}-${beforeHash.slice(0, 12)}`);
106
+ }
107
+ function backupBaseName(root, agentsPath) {
108
+ return path.relative(root, agentsPath).replace(/[\\/]+/g, '__');
109
+ }
103
110
  function escapeRegExp(value) {
104
111
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
105
112
  }
@@ -15,8 +15,9 @@ export async function runWithCodexReliabilityShield(input, runAttempt) {
15
15
  const attempts = [];
16
16
  let selected = null;
17
17
  let selectedAttempt = 0;
18
+ const controls = {};
18
19
  for (let attempt = 1; attempt <= policy.maxEmptyResultRetries + 1; attempt += 1) {
19
- const raw = await runAttempt(attempt);
20
+ const raw = await runAttempt(attempt, controls);
20
21
  const repaired = repairToolCallSequence(Array.isArray(raw.events) ? raw.events : []);
21
22
  const heartbeats = buildKeepaliveHeartbeats(repaired.events);
22
23
  const evaluation = evaluateCodexReliabilityAttempt(raw, repaired.events, policy, attempt);
@@ -30,9 +31,11 @@ export async function runWithCodexReliabilityShield(input, runAttempt) {
30
31
  ...raw,
31
32
  events: repaired.events,
32
33
  reliabilityHeartbeats: heartbeats,
33
- blockers: [...(raw.blockers || []), ...report.blockers]
34
+ blockers: report.mcp_auth_error ? report.blockers : [...(raw.blockers || []), ...report.blockers]
34
35
  };
35
36
  selectedAttempt = attempt;
37
+ if (report.retryable && report.mcp_auth_error)
38
+ controls.noMcp = true;
36
39
  if (!report.retryable || attempt > policy.maxEmptyResultRetries)
37
40
  break;
38
41
  }
@@ -65,7 +68,8 @@ export async function runWithCodexReliabilityShield(input, runAttempt) {
65
68
  export function evaluateCodexReliabilityAttempt(result, events, policy, attempt) {
66
69
  const meaningful = events.filter(isMeaningfulEvent);
67
70
  const modelCapacity = isCodexModelCapacityError(result, events);
68
- const fatal = !modelCapacity && hasFatalError(result, events);
71
+ const mcpAuth = isMcpAuthError(result, events);
72
+ const fatal = !modelCapacity && !mcpAuth && hasFatalError(result, events);
69
73
  const idle = hasIdleTimeout(events, policy.idleTimeoutMs);
70
74
  const empty = events.length === 0 || (!String(result.finalResponse || '').trim() && meaningful.length === 0);
71
75
  const partial = meaningful.length > 0 && !result.structuredOutput;
@@ -74,13 +78,19 @@ export function evaluateCodexReliabilityAttempt(result, events, policy, attempt)
74
78
  let retryReason = null;
75
79
  if (modelCapacity)
76
80
  blockers.push('codex_model_capacity_unavailable');
81
+ if (mcpAuth)
82
+ blockers.push('codex_reliability_mcp_auth_error');
77
83
  if (!modelCapacity && idle && partial)
78
84
  blockers.push('codex_reliability_idle_after_partial_output');
79
85
  if (!modelCapacity && partial && !idle)
80
86
  blockers.push('codex_reliability_partial_output_without_structured_result');
81
87
  if (fatal)
82
88
  blockers.push('codex_reliability_fatal_error_no_retry');
83
- if (!modelCapacity && !fatal && idle && meaningful.length === 0) {
89
+ if (mcpAuth) {
90
+ retryable = true;
91
+ retryReason = 'mcp_auth_error_retry_no_mcp';
92
+ }
93
+ else if (!modelCapacity && !fatal && idle && meaningful.length === 0) {
84
94
  retryable = true;
85
95
  retryReason = 'stream_idle_before_meaningful_event';
86
96
  }
@@ -98,6 +108,7 @@ export function evaluateCodexReliabilityAttempt(result, events, policy, attempt)
98
108
  retry_reason: retryReason,
99
109
  idle_timeout: idle,
100
110
  fatal_error: fatal,
111
+ mcp_auth_error: mcpAuth,
101
112
  model_capacity_error: modelCapacity,
102
113
  capacity_fallback_hint: null,
103
114
  repaired_tool_result_count: 0,
@@ -106,16 +117,7 @@ export function evaluateCodexReliabilityAttempt(result, events, policy, attempt)
106
117
  };
107
118
  }
108
119
  export function isCodexModelCapacityError(result, events) {
109
- const text = [
110
- String(result.finalResponse || ''),
111
- ...(Array.isArray(result.blockers) ? result.blockers : []),
112
- ...events.map((event) => [
113
- event?.error?.message,
114
- event?.message,
115
- event?.item?.text,
116
- event?.raw?.failed_event?.error?.message
117
- ].filter(Boolean).join('\n'))
118
- ].join('\n');
120
+ const text = collectText(result, events);
119
121
  return /selected model is at capacity|model(?:\s+[\w.-]+)?\s+is\s+at\s+capacity|try a different model|capacity(?:\s+is)?\s+exhausted|temporarily at capacity/i.test(text);
120
122
  }
121
123
  export function repairToolCallSequence(events) {
@@ -166,11 +168,28 @@ function isMeaningfulEvent(event) {
166
168
  || itemType === 'mcp_tool_call';
167
169
  }
168
170
  function hasFatalError(result, events) {
169
- const text = [
171
+ if (isMcpAuthError(result, events))
172
+ return false;
173
+ const text = collectText(result, events);
174
+ return /\b(?:4\d\d|fatal|unauthorized|forbidden|authrequired|invalid oauth|side-effect applied|partial patch applied)\b/i.test(text);
175
+ }
176
+ function isMcpAuthError(result, events) {
177
+ const text = collectText(result, events);
178
+ return /\b(?:authrequired|oauth)\b/i.test(text)
179
+ && /transport channel closed|rmcp/i.test(text)
180
+ && !/side-effect applied|partial patch applied/i.test(text);
181
+ }
182
+ function collectText(result, events) {
183
+ return [
184
+ String(result.finalResponse || ''),
170
185
  ...(Array.isArray(result.blockers) ? result.blockers : []),
171
- ...events.map((event) => event?.error?.message || event?.message || '')
186
+ ...events.map((event) => [
187
+ event?.error?.message,
188
+ event?.message,
189
+ event?.item?.text,
190
+ event?.raw?.failed_event?.error?.message
191
+ ].filter(Boolean).join('\n'))
172
192
  ].join('\n');
173
- return /\b(?:4\d\d|fatal|unauthorized|forbidden|authrequired|invalid oauth|side-effect applied|partial patch applied)\b/i.test(text);
174
193
  }
175
194
  function hasIdleTimeout(events, idleTimeoutMs) {
176
195
  const stamps = events.map(eventTimeMs).filter((value) => Number.isFinite(value));
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { appendJsonl } from '../fsx.js';
2
+ import { appendJsonlBounded } from '../fsx.js';
3
3
  import { buildCodexExecutionPolicy, buildCodexSdkConfig } from './codex-sdk-config-policy.js';
4
4
  import { buildCodexSdkEnv } from './codex-sdk-env-policy.js';
5
5
  import { translateCodexSdkEvent } from './codex-event-translator.js';
@@ -41,7 +41,7 @@ export async function runRealCodexSdkTask(input, policy) {
41
41
  for await (const event of streamed.events) {
42
42
  events.push(event);
43
43
  if (liveEventPath) {
44
- await appendJsonl(liveEventPath, translateCodexSdkEvent(event));
44
+ await appendJsonlBounded(liveEventPath, translateCodexSdkEvent(event), 5 * 1024 * 1024);
45
45
  liveEventsWritten = true;
46
46
  }
47
47
  if (event?.type === 'item.completed' && event?.item?.type === 'agent_message')
@@ -13,6 +13,10 @@ export function buildCodexSdkConfig(input) {
13
13
  generation_index: Number(input.generationIndex || 0)
14
14
  }
15
15
  };
16
+ if (input.requestedScopeContract?.no_mcp === true) {
17
+ config.mcp_servers = {};
18
+ config.sks = { ...config.sks, no_mcp: true };
19
+ }
16
20
  const codexLbApiKey = String(process.env.CODEX_LB_API_KEY || '').trim();
17
21
  const codexLbBaseUrl = normalizeCodexLbBaseUrl(process.env.CODEX_LB_BASE_URL);
18
22
  if (codexLbApiKey && codexLbBaseUrl) {
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { appendJsonl, ensureDir, nowIso, packageRoot, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ import { appendJsonlBounded, ensureDir, nowIso, packageRoot, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
4
  import { validateJsonSchemaRecursive } from '../json-schema-validator.js';
5
5
  import { resolveCodexOutputSchema } from './codex-output-schemas.js';
6
6
  import { detectCodexSdkCapability } from './codex-sdk-capability.js';
@@ -56,11 +56,14 @@ export async function runCodexTask(input) {
56
56
  generationIndex: task.generationIndex ?? null,
57
57
  sessionId: task.sessionId || null,
58
58
  backend: 'codex-sdk'
59
- }, () => runWithCodexReliabilityShield(task, async () => {
59
+ }, () => runWithCodexReliabilityShield(task, async (_attempt, controls) => {
60
60
  try {
61
+ const attemptTask = controls?.noMcp
62
+ ? { ...task, requestedScopeContract: { ...task.requestedScopeContract, no_mcp: true } }
63
+ : task;
61
64
  return fakeAllowed
62
- ? await runFakeCodexSdkTask(task)
63
- : await runRealCodexSdkTask(task, { sandboxMode: sandbox.sandboxMode, env: runtime.env.env, config: runtime.config });
65
+ ? await runFakeCodexSdkTask(attemptTask)
66
+ : await runRealCodexSdkTask(attemptTask, { sandboxMode: sandbox.sandboxMode, env: runtime.env.env, config: { ...runtime.config, mcp_servers: {} } });
64
67
  }
65
68
  catch (err) {
66
69
  return {
@@ -79,17 +82,26 @@ export async function runCodexTask(input) {
79
82
  const translatedEvents = translateCodexSdkEvents(events);
80
83
  if (adapterResult?.liveEventsWritten !== true) {
81
84
  for (const event of translatedEvents)
82
- await appendJsonl(path.join(root, 'codex-sdk-events.jsonl'), event);
85
+ await appendJsonlBounded(path.join(root, 'codex-sdk-events.jsonl'), event, 5 * 1024 * 1024);
83
86
  }
84
87
  if (adapterResult?.reliabilityShield)
85
88
  await writeJsonAtomic(path.join(root, 'codex-reliability-shield.json'), adapterResult.reliabilityShield);
86
89
  const structuredOutput = adapterResult?.structuredOutput;
87
90
  const validation = structuredOutput ? validateJsonSchemaRecursive(structuredOutput, schema) : { ok: false, issues: ['structured_output_missing'] };
88
- const finalBlockers = [
91
+ const primaryBlockers = [
89
92
  ...blockers,
90
- ...(adapterResult?.blockers || []),
91
- ...(events.length > 0 ? [] : ['codex_sdk_event_stream_missing']),
92
- ...(validation.ok ? [] : ['codex_sdk_structured_output_invalid', ...validation.issues.map((issue) => `schema:${issue}`)])
93
+ ...(adapterResult?.blockers || [])
94
+ ];
95
+ const runFailed = primaryBlockers.some((blocker) => {
96
+ const text = String(blocker || '');
97
+ return text.startsWith('codex_sdk_run_failed')
98
+ || text === 'codex_reliability_fatal_error_no_retry'
99
+ || text === 'codex_reliability_mcp_auth_error';
100
+ });
101
+ const finalBlockers = [
102
+ ...primaryBlockers,
103
+ ...(!runFailed && events.length === 0 ? ['codex_sdk_event_stream_missing'] : []),
104
+ ...(!runFailed && !validation.ok ? ['codex_sdk_structured_output_invalid', ...validation.issues.map((issue) => `schema:${issue}`)] : [])
93
105
  ];
94
106
  const workerResult = normalizeWorkerResult(structuredOutput, task, finalBlockers, validation.ok);
95
107
  const workerResultPath = path.join(root, 'codex-sdk-worker-result.json');
@@ -183,7 +195,7 @@ async function runPythonControlTask(root, task, schema, routerDecision) {
183
195
  const events = Array.isArray(adapterResult.events) ? adapterResult.events : [];
184
196
  const translatedEvents = Array.isArray(adapterResult.translatedEvents) ? adapterResult.translatedEvents : [];
185
197
  for (const event of translatedEvents)
186
- await appendJsonl(path.join(root, 'python-codex-sdk-events.jsonl'), event);
198
+ await appendJsonlBounded(path.join(root, 'python-codex-sdk-events.jsonl'), event, 5 * 1024 * 1024);
187
199
  const structuredOutput = parseStructuredOutput(adapterResult.finalResponse || '');
188
200
  const validation = structuredOutput ? validateJsonSchemaRecursive(structuredOutput, schema) : { ok: false, issues: ['structured_output_missing'] };
189
201
  const finalBlockers = [
@@ -287,7 +299,7 @@ async function runLocalControlTask(root, task, schema, routerDecision) {
287
299
  backend: 'local-llm'
288
300
  }, () => runLocalLlmTask(task, { config, outputSchema: schema }));
289
301
  for (const event of adapterResult.events || [])
290
- await appendJsonl(path.join(root, 'local-llm-events.jsonl'), event);
302
+ await appendJsonlBounded(path.join(root, 'local-llm-events.jsonl'), event, 5 * 1024 * 1024);
291
303
  const structuredOutput = adapterResult.structuredOutput;
292
304
  const validation = structuredOutput ? validateJsonSchemaRecursive(structuredOutput, schema) : { ok: false, issues: ['structured_output_missing'] };
293
305
  const finalBlockers = [
@@ -30,8 +30,9 @@ const CORE_SKILL_DEFINITIONS = [
30
30
  canonical_name: 'qa-loop',
31
31
  display_name: 'qa-loop',
32
32
  route: '$QA-LOOP',
33
- purpose: 'dogfood UI/API behavior with safety gates and QA reports.',
33
+ purpose: 'dogfood UI/API behavior with safety gates, Codex Chrome Extension-first web UI evidence, and QA reports.',
34
34
  when: 'Use when route completion needs human-proxy verification, rechecks, and QA ledgers.',
35
+ workflow: 'Infer the QA scope, dogfood real UI/API flows, and for web/browser/webapp UI evidence use the Codex Chrome Extension readiness gate first. If the extension is missing or disabled, rapidly halt and ask the user to set it up before resuming. Computer Use is reserved for native Mac/non-web surfaces and must not satisfy web UI evidence.',
35
36
  evidence: 'qa-ledger.json, dated QA report, qa-gate.json, and post-fix verification.',
36
37
  fallback: 'Mark unverified browser/native surfaces explicitly; never substitute fake visual evidence.'
37
38
  },
@@ -0,0 +1,58 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { flag } from '../../cli/args.js';
3
+ import { printJson } from '../../cli/output.js';
4
+ import { projectRoot } from '../fsx.js';
5
+ export async function gatesCommand(args = []) {
6
+ const root = await projectRoot();
7
+ const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'run';
8
+ const json = flag(args, '--json');
9
+ if (sub !== 'run') {
10
+ const result = { schema: 'sks.gates-command.v1', ok: false, error: `Unknown subcommand: ${sub}` };
11
+ if (json)
12
+ return printJson(result);
13
+ console.error('Usage: sks gates run <gate-id|preset> [--full] [--json]');
14
+ process.exitCode = 1;
15
+ return result;
16
+ }
17
+ const target = args[1] && !args[1].startsWith('-') ? args[1] : readArg(args, '--preset', 'affected');
18
+ const isPreset = ['release', 'affected', 'fast', 'harness'].includes(target) || flag(args, '--preset');
19
+ const runnerArgs = ['dist/scripts/release-gate-dag-runner.js'];
20
+ if (isPreset)
21
+ runnerArgs.push('--preset', target);
22
+ else
23
+ runnerArgs.push('--gate', target);
24
+ if (flag(args, '--full'))
25
+ runnerArgs.push('--full');
26
+ const result = spawnSync(process.execPath, runnerArgs, {
27
+ cwd: root,
28
+ encoding: 'utf8',
29
+ maxBuffer: 1024 * 1024 * 20,
30
+ env: { ...process.env, CI: process.env.CI || 'true' }
31
+ });
32
+ const report = {
33
+ schema: 'sks.gates-command.v1',
34
+ ok: result.status === 0,
35
+ target,
36
+ mode: isPreset ? 'preset' : 'gate',
37
+ status: result.status,
38
+ stdout_tail: tail(String(result.stdout || '')),
39
+ stderr_tail: tail(String(result.stderr || ''))
40
+ };
41
+ if (json)
42
+ return printJson(report);
43
+ if (result.stdout)
44
+ process.stdout.write(result.stdout);
45
+ if (result.stderr)
46
+ process.stderr.write(result.stderr);
47
+ if (!report.ok)
48
+ process.exitCode = result.status || 1;
49
+ return report;
50
+ }
51
+ function readArg(args, name, fallback) {
52
+ const index = args.indexOf(name);
53
+ return index >= 0 && args[index + 1] ? String(args[index + 1]) : fallback;
54
+ }
55
+ function tail(value, limit = 4000) {
56
+ return value.length > limit ? value.slice(-limit) : value;
57
+ }
58
+ //# sourceMappingURL=gates-command.js.map
@@ -12,7 +12,7 @@ export async function releaseCommand(args = []) {
12
12
  process.exitCode = 1;
13
13
  return null;
14
14
  }
15
- const result = spawnSync('npm', ['run', command, '--silent'], {
15
+ const result = spawnSync(process.execPath, command.args, {
16
16
  cwd: root,
17
17
  encoding: 'utf8',
18
18
  maxBuffer: 1024 * 1024 * 20,
@@ -22,7 +22,7 @@ export async function releaseCommand(args = []) {
22
22
  schema: 'sks.release-command.v1',
23
23
  ok: result.status === 0,
24
24
  subcommand: sub,
25
- script: command,
25
+ command: [process.execPath, ...command.args],
26
26
  status: result.status,
27
27
  stdout_tail: tail(String(result.stdout || '')),
28
28
  stderr_tail: tail(String(result.stderr || ''))
@@ -39,11 +39,11 @@ export async function releaseCommand(args = []) {
39
39
  }
40
40
  function commandForSubcommand(sub) {
41
41
  if (sub === 'affected')
42
- return 'release:check:affected';
42
+ return { args: ['dist/scripts/release-gate-dag-runner.js', '--preset', 'affected', '--changed-since', 'auto', '--sla', '5m'] };
43
43
  if (sub === 'full')
44
- return 'release:check:full';
44
+ return { args: ['dist/scripts/release-gate-dag-runner.js', '--preset', 'release', '--full'] };
45
45
  if (sub === 'background')
46
- return 'release:check:full';
46
+ return { args: ['dist/scripts/release-gate-dag-runner.js', '--preset', 'release', '--full'] };
47
47
  return null;
48
48
  }
49
49
  function tail(value, limit = 4000) {
@@ -49,7 +49,7 @@ export async function stopGateCommand(command, args) {
49
49
  console.log(`Selected gate: ${result.diagnostics.selected_gate_path}`);
50
50
  }
51
51
  }
52
- if (result.action === 'continue')
52
+ if (result.action !== 'allow_stop')
53
53
  process.exitCode = 1;
54
54
  return result;
55
55
  }
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '4.8.4';
8
+ export const PACKAGE_VERSION = '4.8.6';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -0,0 +1,86 @@
1
+ import path from 'node:path';
2
+ import { readJson, readText } from '../fsx.js';
3
+ import { missionDir } from '../mission.js';
4
+ const TEAM_DIGEST_MAX_EVENTS = 4;
5
+ const TEAM_DIGEST_MESSAGE_CHARS = 180;
6
+ const TEAM_DIGEST_CONTEXT_CHARS = 1600;
7
+ const TEAM_DIGEST_SYSTEM_CHARS = 260;
8
+ export async function teamLiveDigest(root, state = {}) {
9
+ if (!isTeamState(state) || !state.mission_id)
10
+ return null;
11
+ const id = String(state.mission_id);
12
+ const dir = missionDir(root, id);
13
+ const dashboard = await readJson(path.join(dir, 'team-dashboard.json'), null).catch(() => null);
14
+ const transcript = await readText(path.join(dir, 'team-transcript.jsonl'), '').catch(() => '');
15
+ let events = transcript.split(/\n/).filter(Boolean).slice(-TEAM_DIGEST_MAX_EVENTS * 3).map(parseTeamTranscriptLine).filter(Boolean);
16
+ let source = 'team-transcript.jsonl';
17
+ if (!events.length) {
18
+ const live = await readText(path.join(dir, 'team-live.md'), '').catch(() => '');
19
+ events = live.split(/\n/).filter((line) => /^- \d{4}-\d{2}-\d{2}T/.test(line.trim())).slice(-TEAM_DIGEST_MAX_EVENTS).map(parseTeamLiveLine).filter(Boolean);
20
+ source = 'team-live.md';
21
+ }
22
+ if (!events.length) {
23
+ events = dashboard?.latest_messages || [];
24
+ source = 'team-dashboard.json';
25
+ }
26
+ events = normalizeTeamEvents(events).slice(-TEAM_DIGEST_MAX_EVENTS);
27
+ if (!events.length)
28
+ return null;
29
+ const phase = oneLine(state.phase || dashboard?.phase || 'TEAM', 48);
30
+ const lines = events.map(formatTeamDigestEvent);
31
+ const context = boundText([
32
+ `SKS Team live digest: mission ${id}, phase ${phase}, source ${source}.`,
33
+ `Open Zellij multi-view with: sks team open-zellij ${id}`,
34
+ `Open live view with: sks team watch ${id}`,
35
+ 'Recent events:',
36
+ ...lines.map((line) => `- ${line}`)
37
+ ].join('\n'), TEAM_DIGEST_CONTEXT_CHARS);
38
+ const system = boundText(`SKS Team live: ${lines.at(-1) || `${id} ${phase}`}`, TEAM_DIGEST_SYSTEM_CHARS);
39
+ return { context, system };
40
+ }
41
+ function isTeamState(state = {}) {
42
+ const values = [state.mode, state.route, state.route_command, state.stop_gate].map((value) => String(value || '').toLowerCase());
43
+ return values.some((value) => value === 'team' || value === '$team' || value.includes('team-gate'));
44
+ }
45
+ function normalizeTeamEvents(events = []) {
46
+ return events.map((event) => ({
47
+ ts: String(event?.ts || ''),
48
+ agent: oneLine(event?.agent || 'unknown', 40),
49
+ phase: oneLine(event?.phase || 'general', 48),
50
+ message: oneLine(event?.message || '', TEAM_DIGEST_MESSAGE_CHARS)
51
+ })).filter((event) => event.message);
52
+ }
53
+ function parseTeamTranscriptLine(line) {
54
+ try {
55
+ return JSON.parse(line);
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function parseTeamLiveLine(line) {
62
+ const match = String(line || '').trim().match(/^-\s+(\S+)\s+\[([^\]]+)\]\s+([^:]+):\s*(.*)$/);
63
+ if (!match)
64
+ return null;
65
+ return { ts: match[1], phase: match[2], agent: match[3], message: match[4] };
66
+ }
67
+ function formatTeamDigestEvent(event) {
68
+ const ts = shortIsoTime(event.ts);
69
+ return `${ts} [${event.phase}] ${event.agent}: ${event.message}`;
70
+ }
71
+ function shortIsoTime(ts) {
72
+ return String(ts || '').replace(/^\d{4}-\d{2}-\d{2}T/, '').replace(/\.\d{3}Z$/, 'Z') || 'recent';
73
+ }
74
+ function oneLine(value, limit) {
75
+ return boundText(String(value || '').replace(/\s+/g, ' ').trim(), limit);
76
+ }
77
+ function boundText(value, limit) {
78
+ const text = String(value || '');
79
+ if (text.length <= limit)
80
+ return text;
81
+ return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
82
+ }
83
+ export function joinSystemMessages(...parts) {
84
+ return boundText(parts.filter(Boolean).join(' | '), 420);
85
+ }
86
+ //# sourceMappingURL=team-digest.js.map
@@ -17,10 +17,7 @@ import { appendMissionStatus } from './recallpulse.js';
17
17
  import { scanAgentTextForRecursion } from './agents/agent-recursion-guard.js';
18
18
  import { evaluateLoopContinuation } from './loops/loop-continuation-enforcer.js';
19
19
  import { buildCompactContinue, buildPermissionRequestAllow, buildPermissionRequestDeny, buildPostToolUseBlock, buildPostToolUseContinue, buildPreToolUseContinue, buildPreToolUseDeny, buildSessionStartContinue, buildStopBlock, buildStopContinue, buildSubagentStartContinue, buildSubagentStopBlock, buildSubagentStopContinue, buildUserPromptSubmitBlock, buildUserPromptSubmitContinue } from './codex-compat/codex-hook-output-builders.js';
20
- const TEAM_DIGEST_MAX_EVENTS = 4;
21
- const TEAM_DIGEST_MESSAGE_CHARS = 180;
22
- const TEAM_DIGEST_CONTEXT_CHARS = 1600;
23
- const TEAM_DIGEST_SYSTEM_CHARS = 260;
20
+ import { joinSystemMessages, teamLiveDigest } from './hooks-runtime/team-digest.js';
24
21
  const STOP_REPEAT_GUARD_ARTIFACT = 'stop-hook-repeat-guard.json';
25
22
  const LIGHT_ROUTE_STOP_ARTIFACT = 'light-route-stop.json';
26
23
  const CODEX_GIT_ACTION_STOP_ARTIFACT = 'codex-git-action-stop-bypass.json';
@@ -1017,84 +1014,6 @@ async function resolveHonestModeLoopback(root, state = {}) {
1017
1014
  questions_allowed: true
1018
1015
  });
1019
1016
  }
1020
- async function teamLiveDigest(root, state = {}) {
1021
- if (!isTeamState(state) || !state.mission_id)
1022
- return null;
1023
- const id = String(state.mission_id);
1024
- const dir = missionDir(root, id);
1025
- const dashboard = await readJson(path.join(dir, 'team-dashboard.json'), null).catch(() => null);
1026
- const transcript = await readText(path.join(dir, 'team-transcript.jsonl'), '').catch(() => '');
1027
- let events = transcript.split(/\n/).filter(Boolean).slice(-TEAM_DIGEST_MAX_EVENTS * 3).map(parseTeamTranscriptLine).filter(Boolean);
1028
- let source = 'team-transcript.jsonl';
1029
- if (!events.length) {
1030
- const live = await readText(path.join(dir, 'team-live.md'), '').catch(() => '');
1031
- events = live.split(/\n/).filter((line) => /^- \d{4}-\d{2}-\d{2}T/.test(line.trim())).slice(-TEAM_DIGEST_MAX_EVENTS).map(parseTeamLiveLine).filter(Boolean);
1032
- source = 'team-live.md';
1033
- }
1034
- if (!events.length) {
1035
- events = dashboard?.latest_messages || [];
1036
- source = 'team-dashboard.json';
1037
- }
1038
- events = normalizeTeamEvents(events).slice(-TEAM_DIGEST_MAX_EVENTS);
1039
- if (!events.length)
1040
- return null;
1041
- const phase = oneLine(state.phase || dashboard?.phase || 'TEAM', 48);
1042
- const lines = events.map(formatTeamDigestEvent);
1043
- const context = boundText([
1044
- `SKS Team live digest: mission ${id}, phase ${phase}, source ${source}.`,
1045
- `Open Zellij multi-view with: sks team open-zellij ${id}`,
1046
- `Open live view with: sks team watch ${id}`,
1047
- 'Recent events:',
1048
- ...lines.map((line) => `- ${line}`)
1049
- ].join('\n'), TEAM_DIGEST_CONTEXT_CHARS);
1050
- const system = boundText(`SKS Team live: ${lines.at(-1) || `${id} ${phase}`}`, TEAM_DIGEST_SYSTEM_CHARS);
1051
- return { context, system };
1052
- }
1053
- function isTeamState(state = {}) {
1054
- const values = [state.mode, state.route, state.route_command, state.stop_gate].map((value) => String(value || '').toLowerCase());
1055
- return values.some((value) => value === 'team' || value === '$team' || value.includes('team-gate'));
1056
- }
1057
- function normalizeTeamEvents(events = []) {
1058
- return events.map((event) => ({
1059
- ts: String(event?.ts || ''),
1060
- agent: oneLine(event?.agent || 'unknown', 40),
1061
- phase: oneLine(event?.phase || 'general', 48),
1062
- message: oneLine(event?.message || '', TEAM_DIGEST_MESSAGE_CHARS)
1063
- })).filter((event) => event.message);
1064
- }
1065
- function parseTeamTranscriptLine(line) {
1066
- try {
1067
- return JSON.parse(line);
1068
- }
1069
- catch {
1070
- return null;
1071
- }
1072
- }
1073
- function parseTeamLiveLine(line) {
1074
- const match = String(line || '').trim().match(/^-\s+(\S+)\s+\[([^\]]+)\]\s+([^:]+):\s*(.*)$/);
1075
- if (!match)
1076
- return null;
1077
- return { ts: match[1], phase: match[2], agent: match[3], message: match[4] };
1078
- }
1079
- function formatTeamDigestEvent(event) {
1080
- const ts = shortIsoTime(event.ts);
1081
- return `${ts} [${event.phase}] ${event.agent}: ${event.message}`;
1082
- }
1083
- function shortIsoTime(ts) {
1084
- return String(ts || '').replace(/^\d{4}-\d{2}-\d{2}T/, '').replace(/\.\d{3}Z$/, 'Z') || 'recent';
1085
- }
1086
- function oneLine(value, limit) {
1087
- return boundText(String(value || '').replace(/\s+/g, ' ').trim(), limit);
1088
- }
1089
- function boundText(value, limit) {
1090
- const text = String(value || '');
1091
- if (text.length <= limit)
1092
- return text;
1093
- return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
1094
- }
1095
- function joinSystemMessages(...parts) {
1096
- return boundText(parts.filter(Boolean).join(' | '), 420);
1097
- }
1098
1017
  export async function emitHook(name) {
1099
1018
  const result = await hookMain(name);
1100
1019
  process.stdout.write(`${JSON.stringify(normalizeHookResult(name, result))}\n`);