sneakoscope 6.4.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +26 -34
  2. package/config/codex-releases/{rust-v0.144.1.json → rust-v0.144.5.json} +5 -5
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/bin/install.js +1 -1
  6. package/dist/cli/command-manifest-lite.js +2 -3
  7. package/dist/cli/command-registry.js +6 -4
  8. package/dist/cli/help-fast.js +0 -1
  9. package/dist/cli/router.js +24 -3
  10. package/dist/commands/zellij.js +18 -29
  11. package/dist/config/skills-manifest.json +62 -62
  12. package/dist/core/agent-bridge/agent-manifest.js +22 -1
  13. package/dist/core/agents/agent-codex-cockpit.js +1 -81
  14. package/dist/core/agents/agent-effort-policy.js +13 -7
  15. package/dist/core/agents/agent-orchestrator.js +0 -6
  16. package/dist/core/agents/agent-output-validator.js +2 -1
  17. package/dist/core/agents/agent-patch-queue-store.js +0 -8
  18. package/dist/core/agents/agent-proof-evidence.js +0 -6
  19. package/dist/core/agents/agent-worker-pipeline.js +12 -6
  20. package/dist/core/agents/native-cli-worker-runtime.js +1 -16
  21. package/dist/core/agents/native-worker-backend-router.js +1 -1
  22. package/dist/core/bench.js +1 -1
  23. package/dist/core/codex/official-goal-mode.js +4 -2
  24. package/dist/core/codex-app/codex-app-fast-ui-repair.js +11 -17
  25. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +34 -6
  26. package/dist/core/codex-app/menubar/constants.js +0 -1
  27. package/dist/core/codex-app.js +1 -1
  28. package/dist/core/codex-compat/codex-release-manifest.js +5 -5
  29. package/dist/core/codex-control/codex-0139-image-path-real-probe.js +1 -1
  30. package/dist/core/codex-control/codex-0139-web-search-probe.js +1 -1
  31. package/dist/core/codex-control/codex-control-proof.js +1 -0
  32. package/dist/core/codex-control/codex-fake-sdk-adapter.js +1 -1
  33. package/dist/core/codex-control/codex-lb-launch-recovery.js +7 -3
  34. package/dist/core/codex-control/codex-sdk-adapter.js +8 -7
  35. package/dist/core/codex-control/codex-sdk-config-policy.js +4 -24
  36. package/dist/core/codex-control/codex-sdk-env-policy.js +396 -11
  37. package/dist/core/codex-control/codex-task-runner.js +112 -57
  38. package/dist/core/codex-control/gpt-final-arbiter.js +1 -2
  39. package/dist/core/codex-control/python-codex-sdk-adapter.js +38 -15
  40. package/dist/core/codex-lb/codex-lb-tool-catalog.js +3 -3
  41. package/dist/core/codex-native/core-skill-manifest.js +4 -7
  42. package/dist/core/codex-runtime/resolve-codex-runtime.js +199 -0
  43. package/dist/core/commands/agent-bridge-command.js +46 -15
  44. package/dist/core/commands/basic-cli.js +1 -1
  45. package/dist/core/commands/command-utils.js +2 -2
  46. package/dist/core/commands/eval-command.js +19 -2
  47. package/dist/core/commands/goal-command.js +37 -86
  48. package/dist/core/commands/mad-sks-command.js +0 -2
  49. package/dist/core/commands/naruto-command.js +236 -176
  50. package/dist/core/doctor/current-project-guidance.js +1 -1
  51. package/dist/core/doctor/doctor-dirty-planner.js +1 -1
  52. package/dist/core/doctor/retired-managed-residue-missions.js +26 -11
  53. package/dist/core/doctor/retired-managed-residue-private.js +1 -1
  54. package/dist/core/doctor/retired-managed-residue-runtime.js +12 -2
  55. package/dist/core/doctor/retired-managed-residue-state.js +7 -7
  56. package/dist/core/evaluation.js +83 -21
  57. package/dist/core/feature-fixtures.js +4 -7
  58. package/dist/core/feature-registry.js +0 -1
  59. package/dist/core/fsx.js +169 -14
  60. package/dist/core/goal-workflow.js +71 -140
  61. package/dist/core/hooks-runtime/hook-io.js +2 -2
  62. package/dist/core/hooks-runtime.js +228 -50
  63. package/dist/core/init/skills.js +7 -6
  64. package/dist/core/init.js +52 -33
  65. package/dist/core/install/installed-package-smoke.js +1 -1
  66. package/dist/core/lean-engineering-policy.js +25 -23
  67. package/dist/core/locks/file-lock.js +74 -21
  68. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  69. package/dist/core/mcp-config/health-check.js +7 -2
  70. package/dist/core/mission.js +99 -3
  71. package/dist/core/ops/upgrade-migration-fixtures.js +30 -2
  72. package/dist/core/pipeline-internals/runtime-core.js +67 -95
  73. package/dist/core/pipeline-internals/runtime-gates.js +25 -6
  74. package/dist/core/proof/fake-real-proof-policy.js +32 -9
  75. package/dist/core/proof/route-proof-gate.js +49 -1
  76. package/dist/core/provider/model-router.js +2 -0
  77. package/dist/core/questions.js +31 -5
  78. package/dist/core/recallpulse.js +2 -2
  79. package/dist/core/release/canonical-test-proof.js +135 -0
  80. package/dist/core/release/release-closure-contract.js +1 -0
  81. package/dist/core/release/release-closure-validation.js +25 -12
  82. package/dist/core/release/release-closure.js +34 -5
  83. package/dist/core/release/release-gate-contract.js +4 -3
  84. package/dist/core/release/release-gate-dag.js +4 -2
  85. package/dist/core/release/release-pack-content-scanner.js +7 -6
  86. package/dist/core/release/release-pack-receipt.js +33 -12
  87. package/dist/core/release/release-real-contract.js +41 -2
  88. package/dist/core/retention.js +95 -16
  89. package/dist/core/routes/constants.js +1 -1
  90. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  91. package/dist/core/routes.js +18 -20
  92. package/dist/core/safety/command-contract/contracts.js +33 -0
  93. package/dist/core/safety/command-contract/types.js +1 -1
  94. package/dist/core/safety/ssot-guard.js +1 -54
  95. package/dist/core/stop-gate/stop-gate-check.js +0 -14
  96. package/dist/core/stop-gate/stop-gate-writer.js +0 -4
  97. package/dist/core/strategy/adhd-orchestrating-gate.js +2 -2
  98. package/dist/core/subagents/agent-catalog.js +28 -5
  99. package/dist/core/subagents/model-policy.js +52 -11
  100. package/dist/core/subagents/naruto-help-contract.js +2 -1
  101. package/dist/core/subagents/naruto-proof-projection.js +449 -0
  102. package/dist/core/subagents/official-subagent-preparation.js +641 -51
  103. package/dist/core/subagents/official-subagent-prompt.js +16 -6
  104. package/dist/core/subagents/official-subagent-runner.js +132 -12
  105. package/dist/core/subagents/subagent-evidence.js +44 -8
  106. package/dist/core/subagents/wave-lifecycle.js +238 -0
  107. package/dist/core/update-check.js +13 -4
  108. package/dist/core/verification/machine-feedback.js +39 -19
  109. package/dist/core/version.js +1 -1
  110. package/dist/core/zellij/zellij-lane-renderer.js +19 -19
  111. package/dist/core/zellij/zellij-official-subagent-activity.js +2 -0
  112. package/dist/core/zellij/zellij-right-column-manager.js +6 -55
  113. package/dist/core/zellij/zellij-slot-column-anchor.js +4 -5
  114. package/dist/core/zellij/zellij-slot-pane-renderer.js +1 -1
  115. package/dist/core/zellij/zellij-ui-mode.js +0 -7
  116. package/dist/core/zellij/zellij-worker-pane-manager.js +3 -11
  117. package/dist/native/sks-menubar/Sources/AppDelegate.swift +0 -1
  118. package/dist/native/sks-menubar/Sources/NotificationCoordinator.swift +1 -3
  119. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +194 -24
  120. package/dist/native/sks-menubar/Sources/ProcessClient.swift +49 -4
  121. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +38 -10
  122. package/dist/native/sks-menubar/Sources/StatusItemController.swift +73 -11
  123. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +27 -2
  124. package/dist/scripts/architecture-guard-check.js +1 -9
  125. package/dist/scripts/canonical-test-runner.js +51 -8
  126. package/dist/scripts/check-architecture.js +11 -1
  127. package/dist/scripts/cli-output-consistency-check.js +0 -2
  128. package/dist/scripts/codex-0144-app-server-v2-check.js +4 -4
  129. package/dist/scripts/codex-0144-binary-identity-check.js +10 -4
  130. package/dist/scripts/codex-0144-capability-check.js +5 -5
  131. package/dist/scripts/codex-0144-policy-check.js +4 -0
  132. package/dist/scripts/codex-control-all-pipelines-check.js +2 -1
  133. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +3 -3
  134. package/dist/scripts/codex-sdk-all-pipelines-check.js +2 -1
  135. package/dist/scripts/codex-sdk-real-smoke-check.js +20 -2
  136. package/dist/scripts/current-command-surface-check.js +5 -3
  137. package/dist/scripts/current-surface-update-e2e-check.js +2 -2
  138. package/dist/scripts/docs-truthfulness-check.js +8 -6
  139. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +3 -2
  140. package/dist/scripts/gate-policy-audit-check.js +7 -1
  141. package/dist/scripts/harness-benchmark-check.js +4 -4
  142. package/dist/scripts/hook-latency-quantum-check.js +2 -2
  143. package/dist/scripts/legacy-gate-purge-check.js +4 -2
  144. package/dist/scripts/loop-directive-check-lib.js +14 -13
  145. package/dist/scripts/naruto-codex-e2e-check.js +2 -2
  146. package/dist/scripts/naruto-ssot-gate-aliases-check.js +9 -0
  147. package/dist/scripts/naruto-ssot-pipeline-default-check.js +10 -0
  148. package/dist/scripts/naruto-ssot-route-normalization-check.js +8 -0
  149. package/dist/scripts/naruto-ssot-routing-check.js +12 -0
  150. package/dist/scripts/product-design-plugin-routing-check.js +4 -9
  151. package/dist/scripts/release-affected-selector-check.js +7 -1
  152. package/dist/scripts/release-check-stamp.js +151 -18
  153. package/dist/scripts/release-dynamic-presets-check.js +5 -2
  154. package/dist/scripts/release-metadata-1-19-check.js +4 -0
  155. package/dist/scripts/release-real-check.js +21 -3
  156. package/dist/scripts/sks-menubar-install-check.js +5 -5
  157. package/dist/scripts/upgrade-migration-matrix-check.js +278 -5
  158. package/dist/scripts/zellij-developer-controls-check.js +3 -5
  159. package/dist/scripts/zellij-initial-main-only-blackbox.js +3 -8
  160. package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +1 -2
  161. package/dist/scripts/zellij-right-column-headless-overflow-check.js +1 -1
  162. package/dist/scripts/zellij-right-column-manager-check.js +3 -4
  163. package/dist/scripts/zellij-slot-only-ui-check.js +2 -2
  164. package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +1 -2
  165. package/docs/demo.tape +1 -1
  166. package/infra-harness-gates.json +0 -27
  167. package/package.json +11 -6
  168. package/release-gates.v2.json +150 -22
  169. package/schemas/codex/agent-result.schema.json +4 -0
  170. package/schemas/codex/app-server-0.144/codex_app_server_protocol.v2.schemas.json +1 -18812
  171. package/schemas/codex/codex-0139-real-probe-result.schema.json +2 -2
  172. package/schemas/zellij/zellij-right-column-state.schema.json +0 -1
  173. package/dist/core/commands/ui-command.js +0 -198
  174. package/dist/core/loops/goal-to-loop-compat.js +0 -22
  175. package/dist/core/naruto/naruto-allocation-policy.js +0 -98
  176. package/dist/core/naruto/naruto-finalizer.js +0 -22
  177. package/dist/core/naruto/naruto-gpt-final-pack.js +0 -50
  178. package/dist/core/naruto/naruto-real-worker-child.js +0 -209
  179. package/dist/core/naruto/naruto-real-worker-runtime.js +0 -178
  180. package/dist/core/naruto/naruto-rebalance-policy.js +0 -47
  181. package/dist/core/naruto/naruto-verification-pool.js +0 -17
  182. package/dist/core/ui/dashboard-html.js +0 -110
  183. package/dist/core/zellij/zellij-dashboard-pane.js +0 -70
  184. package/dist/core/zellij/zellij-dashboard-renderer.js +0 -59
  185. package/dist/core/zellij/zellij-naruto-dashboard.js +0 -44
  186. package/dist/scripts/zellij-dashboard-pane-check.js +0 -69
  187. package/dist/scripts/zellij-dashboard-watch.js +0 -40
@@ -29,15 +29,25 @@ export async function reconcileRetiredGitPolicyMode(root, fix, quarantineRoot, c
29
29
  const value = await readJson(file, null).catch(() => null);
30
30
  if (value?.schema !== 'sks.git-policy.v1')
31
31
  return;
32
- if (['solo', 'work', 'strict-work', 'ci'].includes(String(value.mode || '')))
32
+ const mode = String(value.mode || '');
33
+ if (['solo', 'work', 'strict-work', 'ci'].includes(mode))
33
34
  return;
35
+ const replacement = mode === 'team'
36
+ ? 'work'
37
+ : mode === 'strict-team'
38
+ ? 'strict-work'
39
+ : null;
40
+ if (!replacement) {
41
+ await reconcileKnownRetiredPath(root, file, false, fix, quarantineRoot, counters);
42
+ return;
43
+ }
34
44
  counters.detected += 1;
35
45
  if (!fix) {
36
46
  counters.remaining += 1;
37
47
  return;
38
48
  }
39
49
  try {
40
- await writeJsonAtomic(file, { ...value, mode: 'work' });
50
+ await writeJsonAtomic(file, { ...value, mode: replacement });
41
51
  counters.removed += 1;
42
52
  counters.rewrittenState += 1;
43
53
  }
@@ -45,7 +45,7 @@ async function reconcileStateFiles(root, fix, quarantineRoot, counters) {
45
45
  const value = await readJson(file, null).catch(() => null);
46
46
  if (!value || typeof value !== 'object')
47
47
  continue;
48
- const stripped = stripRetiredPublicState(value);
48
+ const stripped = stripRetiredPublicState(value, true);
49
49
  if (!stripped.changed)
50
50
  continue;
51
51
  counters.detected += 1;
@@ -100,12 +100,12 @@ async function reconcileUserStateCollision(root, file, fix, quarantineRoot, coun
100
100
  counters.remaining += 1;
101
101
  }
102
102
  }
103
- function stripRetiredPublicState(value) {
103
+ function stripRetiredPublicState(value, routeStateRoot) {
104
104
  if (Array.isArray(value)) {
105
105
  let changed = false;
106
106
  const next = [];
107
107
  for (const entry of value) {
108
- const result = stripRetiredPublicState(entry);
108
+ const result = stripRetiredPublicState(entry, false);
109
109
  changed ||= result.changed;
110
110
  next.push(result.value);
111
111
  }
@@ -115,16 +115,16 @@ function stripRetiredPublicState(value) {
115
115
  return { value, changed: false };
116
116
  const next = {};
117
117
  let changed = false;
118
- const retiredRoute = isRetiredStateObject(value);
118
+ const retiredRoute = routeStateRoot && isRetiredStateObject(value);
119
119
  for (const [key, entry] of Object.entries(value)) {
120
120
  if (RETIRED_DB_STATE_KEY_RE.test(key)) {
121
121
  changed = true;
122
122
  continue;
123
123
  }
124
- if (key === 'preempted_missions' && Array.isArray(entry)) {
124
+ if (routeStateRoot && key === 'preempted_missions' && Array.isArray(entry)) {
125
125
  const filtered = entry.filter((row) => !row || typeof row !== 'object' || !isRetiredStateObject(row));
126
126
  changed ||= filtered.length !== entry.length;
127
- const nested = stripRetiredPublicState(filtered);
127
+ const nested = stripRetiredPublicState(filtered, false);
128
128
  changed ||= nested.changed;
129
129
  next[key] = nested.value;
130
130
  continue;
@@ -137,7 +137,7 @@ function stripRetiredPublicState(value) {
137
137
  changed = true;
138
138
  continue;
139
139
  }
140
- const nested = stripRetiredPublicState(entry);
140
+ const nested = stripRetiredPublicState(entry, false);
141
141
  changed ||= nested.changed;
142
142
  next[key] = nested.value;
143
143
  }
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { nowIso, sha256, writeJsonAtomic } from './fsx.js';
3
+ import { DEFAULT_OFFICIAL_SUBAGENT_JOB_MAX_RUNTIME_SECONDS, DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH, DEFAULT_OFFICIAL_SUBAGENT_MAX_THREADS } from './subagents/official-subagent-config.js';
3
4
  import { contextCapsule } from './triwiki-attention.js';
4
5
  import { validateWikiCoordinateIndex } from './wiki-coordinate.js';
5
6
  export const DEFAULT_EVAL_THRESHOLDS = Object.freeze({
@@ -67,9 +68,9 @@ export const PERMISSION_PROFILES = Object.freeze({
67
68
  dangerous_full_access: { filesystem: 'full-access', network: 'controlled', purpose: 'Never default. Requires explicit reason and review.' }
68
69
  });
69
70
  export const DEFAULT_MULTIAGENT_V2 = Object.freeze({
70
- max_threads: 4,
71
- max_depth: 1,
72
- job_max_runtime_seconds: 1800,
71
+ max_threads: DEFAULT_OFFICIAL_SUBAGENT_MAX_THREADS,
72
+ max_depth: DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH,
73
+ job_max_runtime_seconds: DEFAULT_OFFICIAL_SUBAGENT_JOB_MAX_RUNTIME_SECONDS,
73
74
  wait_control: 'bounded_wait_then_structured_summary',
74
75
  subagent_output: 'structured_summary_only'
75
76
  });
@@ -88,9 +89,9 @@ export const ZELLIJ_COCKPIT_VIEWS = Object.freeze([
88
89
  'Code Structure View',
89
90
  'Statusline / Terminal Title Preview'
90
91
  ]);
91
- export function estimateTokens(value) {
92
+ export function serializedSizeBytes(value) {
92
93
  const text = typeof value === 'string' ? value : JSON.stringify(value);
93
- return Math.max(1, Math.ceil(String(text || '').length / 4));
94
+ return Buffer.byteLength(String(text ?? ''), 'utf8');
94
95
  }
95
96
  export function classifyToolError(input = {}) {
96
97
  const text = `${input.code || ''} ${input.name || ''} ${input.message || ''} ${input.stderr || ''}`.toLowerCase();
@@ -472,7 +473,19 @@ function scoreSelection(allClaims, selectedIds) {
472
473
  accuracy_proxy: Number(accuracy.toFixed(4))
473
474
  };
474
475
  }
475
- function metricBlock({ label, context, scenario, msPerRun }) {
476
+ function tokenMeasurementFromEvidence(evidence, label) {
477
+ const rawCount = evidence?.[`${label}_tokens`];
478
+ const source = String(evidence?.source || '').trim();
479
+ if (rawCount === null || rawCount === undefined || !source) {
480
+ return { token_count: null, token_evidence: null };
481
+ }
482
+ const count = Number(rawCount);
483
+ if (!Number.isInteger(count) || count < 0) {
484
+ return { token_count: null, token_evidence: null };
485
+ }
486
+ return { token_count: count, token_evidence: { source } };
487
+ }
488
+ function metricBlock({ label, context, scenario, msPerRun, tokenMeasurement }) {
476
489
  const selectedIds = (context.claims || []).map((claim) => claim.id);
477
490
  const wikiValidation = context.wiki ? validateWikiCoordinateIndex(context.wiki) : { ok: false };
478
491
  const voxel = context.wiki?.vx || context.wiki?.voxel_overlay || null;
@@ -480,7 +493,14 @@ function metricBlock({ label, context, scenario, msPerRun }) {
480
493
  return {
481
494
  label,
482
495
  context_hash: sha256(JSON.stringify(context)),
483
- estimated_tokens: estimateTokens(context),
496
+ serialized_size_bytes: serializedSizeBytes(context),
497
+ size_metric: {
498
+ name: 'serialized_json_bytes',
499
+ unit: 'bytes',
500
+ proxy_for_tokens: false
501
+ },
502
+ token_count: tokenMeasurement?.token_count ?? null,
503
+ token_evidence: tokenMeasurement?.token_evidence ?? null,
484
504
  context_build_ms_per_run: Number(msPerRun.toFixed(4)),
485
505
  wiki: context.wiki ? {
486
506
  schema: context.wiki.schema,
@@ -493,16 +513,40 @@ function metricBlock({ label, context, scenario, msPerRun }) {
493
513
  quality: scoreSelection(scenario.claims, selectedIds)
494
514
  };
495
515
  }
516
+ function measuredTokenEvidence(metric) {
517
+ const count = metric?.token_count;
518
+ const identity = String(metric?.token_evidence?.source || '').trim();
519
+ return Number.isInteger(count) && count >= 0 && identity ? { count, identity } : null;
520
+ }
521
+ function serializedSize(metric) {
522
+ const size = metric?.serialized_size_bytes;
523
+ return Number.isFinite(size) && size >= 0 ? Number(size) : null;
524
+ }
496
525
  export function compareMetricBlocks(baseline, candidate, thresholds = DEFAULT_EVAL_THRESHOLDS) {
497
- const tokenSavingsPct = baseline.estimated_tokens
498
- ? (baseline.estimated_tokens - candidate.estimated_tokens) / baseline.estimated_tokens
499
- : 0;
526
+ const baselineTokenEvidence = measuredTokenEvidence(baseline);
527
+ const candidateTokenEvidence = measuredTokenEvidence(candidate);
528
+ const comparableTokenEvidence = baselineTokenEvidence !== null
529
+ && baselineTokenEvidence.count > 0
530
+ && candidateTokenEvidence !== null
531
+ && baselineTokenEvidence.identity === candidateTokenEvidence.identity
532
+ ? { baseline: baselineTokenEvidence.count, candidate: candidateTokenEvidence.count, identity: baselineTokenEvidence.identity }
533
+ : null;
534
+ const tokenEvidenceAvailable = comparableTokenEvidence !== null;
535
+ const tokenSavingsPct = comparableTokenEvidence
536
+ ? (comparableTokenEvidence.baseline - comparableTokenEvidence.candidate) / comparableTokenEvidence.baseline
537
+ : null;
538
+ const baselineSize = serializedSize(baseline);
539
+ const candidateSize = serializedSize(candidate);
540
+ const serializedSizeSavingsPct = baselineSize !== null && baselineSize > 0 && candidateSize !== null
541
+ ? (baselineSize - candidateSize) / baselineSize
542
+ : null;
500
543
  const accuracyDelta = candidate.quality.accuracy_proxy - baseline.quality.accuracy_proxy;
501
544
  const precisionDelta = candidate.quality.relevance_precision - baseline.quality.relevance_precision;
502
545
  const supportDelta = candidate.quality.support_ratio - baseline.quality.support_ratio;
503
546
  const buildRuntimeDeltaMs = candidate.context_build_ms_per_run - baseline.context_build_ms_per_run;
504
547
  const checks = {
505
- token_savings: tokenSavingsPct >= thresholds.min_token_savings_pct,
548
+ token_evidence: tokenEvidenceAvailable,
549
+ token_savings: tokenSavingsPct === null ? null : tokenSavingsPct >= thresholds.min_token_savings_pct,
506
550
  accuracy_delta: accuracyDelta >= thresholds.min_accuracy_delta,
507
551
  required_recall: candidate.quality.required_recall >= thresholds.min_required_recall,
508
552
  unsupported_critical: candidate.quality.unsupported_critical_selected <= thresholds.max_unsupported_critical_selected,
@@ -511,13 +555,23 @@ export function compareMetricBlocks(baseline, candidate, thresholds = DEFAULT_EV
511
555
  if (candidate.wiki)
512
556
  checks.wiki_index = candidate.wiki.valid === true;
513
557
  return {
514
- token_savings_pct: Number(tokenSavingsPct.toFixed(4)),
558
+ token_savings_pct: tokenSavingsPct === null ? null : Number(tokenSavingsPct.toFixed(4)),
559
+ serialized_size_savings_pct: serializedSizeSavingsPct === null ? null : Number(serializedSizeSavingsPct.toFixed(4)),
560
+ token_measurement: {
561
+ available: tokenEvidenceAvailable,
562
+ evidence_identity: comparableTokenEvidence?.identity ?? null,
563
+ reason: tokenEvidenceAvailable
564
+ ? null
565
+ : baselineTokenEvidence && candidateTokenEvidence && baselineTokenEvidence.identity !== candidateTokenEvidence.identity
566
+ ? 'matching_token_evidence_identity_required'
567
+ : 'actual_token_counts_with_evidence_required'
568
+ },
515
569
  accuracy_delta: Number(accuracyDelta.toFixed(4)),
516
570
  precision_delta: Number(precisionDelta.toFixed(4)),
517
571
  support_ratio_delta: Number(supportDelta.toFixed(4)),
518
572
  build_runtime_delta_ms: Number(buildRuntimeDeltaMs.toFixed(4)),
519
573
  checks,
520
- meaningful_improvement: Object.values(checks).every(Boolean)
574
+ meaningful_improvement: Object.values(checks).every((value) => value === true)
521
575
  };
522
576
  }
523
577
  export function runEvaluationBenchmark(opts = {}) {
@@ -536,17 +590,19 @@ export function runEvaluationBenchmark(opts = {}) {
536
590
  label: 'uncompressed-all-claims',
537
591
  context: baselineTiming.result,
538
592
  scenario,
539
- msPerRun: baselineTiming.ms_per_run
593
+ msPerRun: baselineTiming.ms_per_run,
594
+ tokenMeasurement: tokenMeasurementFromEvidence(opts.tokenEvidence, 'baseline')
540
595
  });
541
596
  const candidate = metricBlock({
542
597
  label: 'triwiki-compressed-capsule',
543
598
  context: candidateTiming.result,
544
599
  scenario,
545
- msPerRun: candidateTiming.ms_per_run
600
+ msPerRun: candidateTiming.ms_per_run,
601
+ tokenMeasurement: tokenMeasurementFromEvidence(opts.tokenEvidence, 'candidate')
546
602
  });
547
603
  const comparison = compareMetricBlocks(baseline, candidate, opts.thresholds || DEFAULT_EVAL_THRESHOLDS);
548
604
  return {
549
- schema_version: 1,
605
+ schema_version: 2,
550
606
  generated_at: nowIso(),
551
607
  scenario: {
552
608
  id: scenario.id,
@@ -560,25 +616,31 @@ export function runEvaluationBenchmark(opts = {}) {
560
616
  candidate,
561
617
  comparison,
562
618
  notes: [
563
- 'estimated_tokens uses a deterministic chars/4 approximation for local regression tracking.',
619
+ 'serialized_size_bytes is a deterministic UTF-8 JSON byte-size metric, not a token estimate.',
620
+ 'Token savings are unavailable unless actual baseline and candidate token counts share the same nonempty token_evidence.source identity.',
564
621
  'accuracy_proxy scores evidence-weighted context selection quality; it is not a live model task accuracy measurement.'
565
622
  ]
566
623
  };
567
624
  }
568
625
  function reportMetric(report) {
569
- if (report?.candidate?.quality && report?.candidate?.estimated_tokens)
626
+ if (report?.candidate?.quality && hasSizeOrTokenMetric(report.candidate))
570
627
  return report.candidate;
571
- if (report?.metrics?.quality && report?.metrics?.estimated_tokens)
628
+ if (report?.metrics?.quality && hasSizeOrTokenMetric(report.metrics))
572
629
  return report.metrics;
573
- if (report?.quality && report?.estimated_tokens)
630
+ if (report?.quality && hasSizeOrTokenMetric(report))
574
631
  return report;
575
632
  throw new Error('Unsupported eval report shape.');
576
633
  }
634
+ function hasSizeOrTokenMetric(metric) {
635
+ return metric?.serialized_size_bytes !== undefined
636
+ || metric?.token_count !== undefined
637
+ || metric?.estimated_tokens !== undefined;
638
+ }
577
639
  export function compareEvaluationReports(baselineReport, candidateReport, thresholds = DEFAULT_EVAL_THRESHOLDS) {
578
640
  const baseline = reportMetric(baselineReport);
579
641
  const candidate = reportMetric(candidateReport);
580
642
  return {
581
- schema_version: 1,
643
+ schema_version: 2,
582
644
  generated_at: nowIso(),
583
645
  baseline_label: baseline.label || baselineReport?.scenario?.id || 'baseline',
584
646
  candidate_label: candidate.label || candidateReport?.scenario?.id || 'candidate',
@@ -40,10 +40,6 @@ const FIXTURES = Object.freeze({
40
40
  'cli-check': fixture('execute', 'sks check --tier confidence --sla 5m --plan --json', [], 'pass'),
41
41
  'cli-plan': fixture('execute', 'sks plan "fixture" --json', [], 'pass'),
42
42
  'cli-review': fixture('execute', 'sks review --diff HEAD --json', [], 'pass'),
43
- 'cli-ui': fixture('static', 'sks ui [--port 4477] [--mission latest] [--once] [--json]', [], 'pass', {
44
- quality: 'static_contract',
45
- reason: 'UI command opens a localhost dashboard; release fixture tracks the CLI contract without launching a server.'
46
- }),
47
43
  'cli-run': fixture('execute_and_validate_artifacts', 'sks run "fixture" --mock --json', ['run-classification.json', 'completion-proof.json', 'evidence-index.json', 'route-completion-contract.json', 'trust-report.json', 'work-order-ledger.json'], 'blocked', { reason: 'finalizeMockRun() in run-command.ts intentionally hardcodes gate.passed=false for every --mock invocation so a mock run can never claim a real completion; it does write all declared artifacts, including a work-order-ledger honestly closed to blocked (18차).' }),
48
44
  'cli-status': fixture('execute', 'sks status --json', [], 'pass'),
49
45
  'cli-usage': fixture('execute', 'sks usage overview', [], 'pass'),
@@ -66,7 +62,7 @@ const FIXTURES = Object.freeze({
66
62
  'cli-selftest': fixture('execute', 'sks selftest --mock', [], 'pass'),
67
63
  'cli-git': fixture('execute', 'sks git policy --json', [], 'pass'),
68
64
  'cli-uninstall': fixture('execute', 'sks uninstall --dry-run --json', [], 'pass'),
69
- 'cli-goal': fixture('execute_and_validate_artifacts', 'sks goal create "Fixture smoke: create a minimal Node.js CLI health-check script" --json', ['goal-workflow.json', 'work-order-ledger.json'], 'blocked', { timeout_ms: 300000, reason: 'goal create drives a real loop-graph -> $Naruto multi-agent orchestration pass that requires live agent infrastructure (GPT dispatch, zellij dashboard, verification DAG) unavailable in a fixture/CI sandbox, so loop_result/gate legitimately reports blocked (naruto_*_missing, loop-graph-proof.json_missing) and the command exits 1 by design; goal-workflow.json and a work-order-ledger honestly closed to blocked (18차) are still written.' }),
65
+ 'cli-goal': fixture('execute', 'sks goal create "Fixture smoke: create a minimal Node.js CLI health-check script" --json', [], 'pass', { reason: 'The compatibility command is stateless: it prints a detailed Codex native /goal request and writes no SKS mission, artifact, loop, or fallback state.' }),
70
66
  'cli-seo-geo-optimizer': fixture('execute_and_validate_artifacts', 'sks seo-geo-optimizer fixture --mode seo --json', ['search-visibility/site-inventory.json', 'search-visibility/seo-findings.json', 'search-visibility/verification-report.json', 'seo-gate.json', 'completion-proof.json'], 'pass'),
71
67
  'cli-research': fixture('execute_and_validate_artifacts', 'sks research run latest --mock --json', ['research-gate.json', 'completion-proof.json'], 'blocked', { timeout_ms: 180000, reason: '"run" (not "status") is the command that actually writes research-gate.json/completion-proof.json, but research is a two-step prepare-then-run workflow gated by an active-route-not-closed check between steps that a single fixture command cannot express; on a hermetic run "latest" will not be a properly prepared+closed research mission.' }),
72
68
  'cli-qa-loop': fixture('execute_and_validate_artifacts', 'sks qa-loop run latest --mock --json', ['qa-gate.json', 'completion-proof.json'], 'pass', { timeout_ms: 180000 }),
@@ -152,8 +148,9 @@ const FIXTURES = Object.freeze({
152
148
  quality: 'wiring_only',
153
149
  reason: 'Policy-only route (answer-only conversational mode with no writes); nothing executable to run beyond static contract text.'
154
150
  }),
155
- 'route-goal': fixture('mock', '$Goal bridge route', ['goal-workflow.json', 'completion-proof.json'], 'pass', {
156
- reason: 'sks goal create "<prompt>" --json never calls maybeFinalizeRoute, so it does not write completion-proof.json even on success; live-testing it also surfaced a real defect (loop-worker-runtime.ts omitted an explicit `agents` value, so buildAgentRoster() fell back to DEFAULT_AGENT_COUNT=5 while maxAgentCount was capped to the loop\'s smaller worker budget, throwing "Agent count 5 exceeds max N" for any ordinary fixture prompt) which has been fixed in this change, but goal-workflow.json/completion-proof.json parity still requires either wiring maybeFinalizeRoute into goalCreate or relaxing this fixture\'s expected_artifacts, both out of scope here; left as documented mock.'
151
+ 'route-goal': fixture('static', '$Goal Codex-native control route', [], 'pass', {
152
+ quality: 'wiring_only',
153
+ reason: 'Goal lifecycle is owned entirely by Codex native /goal. The SKS surface only builds a detailed native objective and command; it creates no mission, proof artifact, compatibility loop, or fallback goal state.'
157
154
  }),
158
155
  'route-super-search': fixture('execute', 'sks run "$Super-Search doctor" --execute --json', [], 'pass'),
159
156
  'route-seo-geo-optimizer': fixture('execute_and_validate_artifacts', 'sks seo-geo-optimizer fixture --mode geo --json', ['search-visibility/site-inventory.json', 'search-visibility/geo-findings.json', 'search-visibility/verification-report.json', 'geo-gate.json', 'completion-proof.json'], 'pass'),
@@ -13,7 +13,6 @@ export const FEATURE_INVENTORY_SCHEMA = 'sks.feature-inventory.v1';
13
13
  export const ALL_FEATURES_SELFTEST_SCHEMA = 'sks.all-features-selftest.v1';
14
14
  export const ALL_FEATURE_COMPLETION_SCHEMA = 'sks.all-feature-completion.v1';
15
15
  const HANDLER_ALIAS_TO_COMMAND = Object.freeze({
16
- ui: 'wizard',
17
16
  auth: 'codex-lb',
18
17
  hook: 'hooks',
19
18
  memory: 'gc',
package/dist/core/fsx.js CHANGED
@@ -446,6 +446,88 @@ export class TailBuffer {
446
446
  return this.totalBytes;
447
447
  }
448
448
  }
449
+ const activeDetachedProcessGroups = new Map();
450
+ let processGroupLifecycleHandlersInstalled = false;
451
+ let relayingParentSignal = false;
452
+ export function registerDetachedProcessGroup(child) {
453
+ const pid = child.pid;
454
+ if (process.platform === 'win32' || !pid)
455
+ return () => { };
456
+ const group = { pid, child };
457
+ activeDetachedProcessGroups.set(pid, group);
458
+ installProcessGroupLifecycleHandlers();
459
+ return () => {
460
+ if (activeDetachedProcessGroups.get(pid) !== group)
461
+ return;
462
+ activeDetachedProcessGroups.delete(pid);
463
+ if (activeDetachedProcessGroups.size === 0)
464
+ uninstallProcessGroupLifecycleHandlers();
465
+ };
466
+ }
467
+ function installProcessGroupLifecycleHandlers() {
468
+ if (processGroupLifecycleHandlersInstalled || process.platform === 'win32')
469
+ return;
470
+ processGroupLifecycleHandlersInstalled = true;
471
+ process.on('exit', terminateRegisteredProcessGroups);
472
+ // Run before existing once listeners remove themselves so rawListeners still
473
+ // reflects whether Node's default signal termination was already suppressed.
474
+ process.prependListener('SIGHUP', handleParentSighup);
475
+ process.prependListener('SIGINT', handleParentSigint);
476
+ process.prependListener('SIGTERM', handleParentSigterm);
477
+ }
478
+ function uninstallProcessGroupLifecycleHandlers() {
479
+ if (!processGroupLifecycleHandlersInstalled)
480
+ return;
481
+ processGroupLifecycleHandlersInstalled = false;
482
+ process.off('exit', terminateRegisteredProcessGroups);
483
+ process.off('SIGHUP', handleParentSighup);
484
+ process.off('SIGINT', handleParentSigint);
485
+ process.off('SIGTERM', handleParentSigterm);
486
+ }
487
+ function handleParentSighup() {
488
+ relayParentSignal('SIGHUP', handleParentSighup);
489
+ }
490
+ function handleParentSigint() {
491
+ relayParentSignal('SIGINT', handleParentSigint);
492
+ }
493
+ function handleParentSigterm() {
494
+ relayParentSignal('SIGTERM', handleParentSigterm);
495
+ }
496
+ function relayParentSignal(signal, ownHandler) {
497
+ if (relayingParentSignal)
498
+ return;
499
+ terminateRegisteredProcessGroups();
500
+ if (process.rawListeners(signal).some((listener) => listener !== ownHandler))
501
+ return;
502
+ relayingParentSignal = true;
503
+ uninstallProcessGroupLifecycleHandlers();
504
+ try {
505
+ process.kill(process.pid, signal);
506
+ }
507
+ catch {
508
+ process.exit(signal === 'SIGHUP' ? 129 : signal === 'SIGINT' ? 130 : 143);
509
+ }
510
+ setImmediate(() => {
511
+ relayingParentSignal = false;
512
+ if (activeDetachedProcessGroups.size > 0)
513
+ installProcessGroupLifecycleHandlers();
514
+ });
515
+ }
516
+ function terminateRegisteredProcessGroups() {
517
+ for (const group of [...activeDetachedProcessGroups.values()]) {
518
+ if (activeDetachedProcessGroups.get(group.pid) !== group)
519
+ continue;
520
+ try {
521
+ process.kill(-group.pid, 'SIGKILL');
522
+ continue;
523
+ }
524
+ catch { }
525
+ try {
526
+ group.child.kill('SIGKILL');
527
+ }
528
+ catch { }
529
+ }
530
+ }
449
531
  export function runProcess(command, args, options = {}) {
450
532
  return new Promise((resolve) => {
451
533
  const tailBytes = options.maxOutputBytes ?? DEFAULT_PROCESS_TAIL_BYTES;
@@ -453,26 +535,36 @@ export function runProcess(command, args, options = {}) {
453
535
  const stderrTail = new TailBuffer(tailBytes);
454
536
  const timeoutMs = options.timeoutMs ?? DEFAULT_PROCESS_TIMEOUT_MS;
455
537
  let killedByTimeout = false;
538
+ let spawnRegistrationFailed = false;
456
539
  let settled = false;
540
+ let processTreeCleanup = null;
457
541
  let stdoutStream = null;
458
542
  let stderrStream = null;
459
543
  const spawnOptions = {
460
544
  cwd: options.cwd || process.cwd(),
461
545
  shell: false,
462
546
  stdio: [options.input !== undefined ? 'pipe' : 'ignore', 'pipe', 'pipe'],
547
+ detached: process.platform !== 'win32',
463
548
  };
464
- if (options.env) {
549
+ if (options.envMode === 'replace') {
550
+ spawnOptions.env = { ...(options.env || {}) };
551
+ }
552
+ else if (options.env) {
465
553
  spawnOptions.env = { ...process.env, ...options.env };
466
554
  }
467
555
  const child = spawn(command, args, spawnOptions);
468
- if (options.input !== undefined && child.stdin) {
469
- child.stdin.end(options.input);
470
- }
556
+ const unregisterDetachedProcessGroup = registerDetachedProcessGroup(child);
557
+ const pausedForSpawnRegistration = Boolean(options.onSpawn
558
+ && child.pid
559
+ && process.platform !== 'win32'
560
+ && child.kill('SIGSTOP'));
471
561
  const finish = async (result) => {
472
562
  if (settled)
473
563
  return;
474
564
  settled = true;
475
565
  clearTimeout(timer);
566
+ await processTreeCleanup;
567
+ unregisterDetachedProcessGroup();
476
568
  try {
477
569
  await stdoutStream?.end?.();
478
570
  }
@@ -493,16 +585,7 @@ export function runProcess(command, args, options = {}) {
493
585
  }
494
586
  const timer = setTimeout(() => {
495
587
  killedByTimeout = true;
496
- try {
497
- child.kill('SIGTERM');
498
- }
499
- catch { }
500
- setTimeout(() => {
501
- try {
502
- child.kill('SIGKILL');
503
- }
504
- catch { }
505
- }, 1500).unref?.();
588
+ processTreeCleanup = terminateProcessTree(child.pid, child);
506
589
  }, timeoutMs);
507
590
  timer.unref?.();
508
591
  child.stdout?.on('data', (d) => {
@@ -524,6 +607,7 @@ export function runProcess(command, args, options = {}) {
524
607
  stderrBytes: stderrTail.totalBytesCounted,
525
608
  truncated: stdoutTail.truncated || stderrTail.truncated,
526
609
  timedOut: killedByTimeout,
610
+ ...(spawnRegistrationFailed ? { spawnRegistrationFailed: true } : {}),
527
611
  }));
528
612
  child.on('close', (code) => void finish({
529
613
  code: killedByTimeout ? 124 : code,
@@ -534,9 +618,80 @@ export function runProcess(command, args, options = {}) {
534
618
  stderrBytes: stderrTail.totalBytesCounted,
535
619
  truncated: stdoutTail.truncated || stderrTail.truncated,
536
620
  timedOut: killedByTimeout,
621
+ ...(spawnRegistrationFailed ? { spawnRegistrationFailed: true } : {}),
537
622
  }));
623
+ void (async () => {
624
+ if (options.onSpawn) {
625
+ const pid = child.pid;
626
+ if (!pid) {
627
+ spawnRegistrationFailed = true;
628
+ child.kill('SIGKILL');
629
+ return;
630
+ }
631
+ try {
632
+ await options.onSpawn(pid);
633
+ }
634
+ catch {
635
+ spawnRegistrationFailed = true;
636
+ processTreeCleanup = terminateProcessTree(pid, child);
637
+ await processTreeCleanup;
638
+ return;
639
+ }
640
+ if (pausedForSpawnRegistration)
641
+ child.kill('SIGCONT');
642
+ }
643
+ if (options.input !== undefined && child.stdin)
644
+ child.stdin.end(options.input);
645
+ })();
538
646
  });
539
647
  }
648
+ async function terminateProcessTree(pid, child) {
649
+ signalProcessTree(pid, 'SIGTERM', child);
650
+ if (await waitForProcessTreeExit(pid, 1_000))
651
+ return;
652
+ signalProcessTree(pid, 'SIGKILL', child);
653
+ await waitForProcessTreeExit(pid, 1_500);
654
+ }
655
+ function signalProcessTree(pid, signal, child) {
656
+ if (pid && process.platform !== 'win32') {
657
+ try {
658
+ process.kill(-pid, signal);
659
+ return;
660
+ }
661
+ catch { }
662
+ }
663
+ try {
664
+ child.kill(signal);
665
+ }
666
+ catch { }
667
+ }
668
+ async function waitForProcessTreeExit(pid, timeoutMs) {
669
+ if (!pid)
670
+ return true;
671
+ const deadline = Date.now() + timeoutMs;
672
+ while (Date.now() < deadline) {
673
+ if (!processTreeIsAlive(pid))
674
+ return true;
675
+ await new Promise((resolve) => setTimeout(resolve, 25));
676
+ }
677
+ return !processTreeIsAlive(pid);
678
+ }
679
+ function processTreeIsAlive(pid) {
680
+ if (process.platform !== 'win32') {
681
+ try {
682
+ process.kill(-pid, 0);
683
+ return true;
684
+ }
685
+ catch { }
686
+ }
687
+ try {
688
+ process.kill(pid, 0);
689
+ return true;
690
+ }
691
+ catch {
692
+ return false;
693
+ }
694
+ }
540
695
  export async function readStdin() {
541
696
  let data = '';
542
697
  process.stdin.setEncoding('utf8');