sneakoscope 4.0.10 → 4.0.11

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 (32) hide show
  1. package/README.md +9 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/bin/sks.js +1 -1
  5. package/dist/core/fsx.js +1 -1
  6. package/dist/core/providers/glm/naruto/glm-naruto-apply-transaction.js +90 -0
  7. package/dist/core/providers/glm/naruto/glm-naruto-bench.js +26 -8
  8. package/dist/core/providers/glm/naruto/glm-naruto-command.js +9 -22
  9. package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +10 -9
  10. package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +54 -4
  11. package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +1 -0
  12. package/dist/core/providers/glm/naruto/glm-naruto-isolation-policy.js +38 -0
  13. package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +9 -4
  14. package/dist/core/providers/glm/naruto/glm-naruto-metrics.js +34 -0
  15. package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +98 -21
  16. package/dist/core/providers/glm/naruto/glm-naruto-scoreboard.js +75 -0
  17. package/dist/core/providers/glm/naruto/glm-naruto-secret-audit.js +54 -2
  18. package/dist/core/providers/glm/naruto/glm-naruto-session-id.js +10 -0
  19. package/dist/core/providers/glm/naruto/glm-naruto-terminal.js +44 -0
  20. package/dist/core/providers/glm/naruto/glm-naruto-trace.js +47 -18
  21. package/dist/core/providers/glm/naruto/glm-naruto-usage-extractor.js +31 -0
  22. package/dist/core/providers/glm/naruto/glm-naruto-worker-artifacts.js +14 -0
  23. package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +52 -2
  24. package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +71 -25
  25. package/dist/core/providers/glm/naruto/glm-naruto-worktree-cleanup.js +20 -0
  26. package/dist/core/providers/glm/naruto/glm-naruto-worktree-manager.js +57 -0
  27. package/dist/core/providers/glm/naruto/glm-naruto-worktree-worker.js +44 -0
  28. package/dist/core/providers/openrouter/openrouter-client.js +1 -1
  29. package/dist/core/providers/openrouter/openrouter-provider-health.js +3 -2
  30. package/dist/core/providers/openrouter/openrouter-stream.js +5 -3
  31. package/dist/core/version.js +1 -1
  32. package/package.json +1 -1
package/README.md CHANGED
@@ -35,7 +35,15 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **4.0.8** makes the GLM 5.2 MAD path bounded by default: `sks --mad --glm` now returns readiness/status and exits when no task is supplied, while task forms use a direct GLM-only speed path with loop guards, request timeouts, and deterministic patch gates. Ordinary `sks --mad`, Naruto/Team, and non-GLM Codex paths keep their existing defaults.
38
+ SKS **4.0.11** makes GLM Naruto operationally measurable: `--worktree` is honest, live bench records real worker/verifier/cache metrics, candidates are scored before merge planning, final apply writes rollback-aware transaction evidence, and early terminal paths write canonical stop-gates. Ordinary direct GLM remains available for single-path edits, while GLM Naruto is the measured parallel runtime.
39
+
40
+ What changed in 4.0.11:
41
+
42
+ - **Honest worktree isolation.** `sks --mad --glm --naruto --worktree "<task>"` uses per-worker git worktrees when available or blocks unless `--allow-patch-envelope-fallback` is explicit.
43
+ - **Measured live bench.** `--bench --live --no-apply` still runs verifier/scoring and reports TTFT, total latency, verifier pass rate, cache tokens, reasoning tokens, and worker completion/failure counts.
44
+ - **Scoreboard-driven merge planning.** `candidate-scoreboard.json` captures gate, verifier, risk, confidence, path, conflict, latency, cache, diversity, and secret-safety components.
45
+ - **Rollback-aware apply.** Final mutation is single-threaded and records `apply-transaction.json`, selected combined patch, diff hashes, and rollback evidence.
46
+ - **Terminal evidence and artifact safety.** Missing-key, invalid-graph, budget, and no-candidate terminal paths write canonical stop-gates, and GLM Naruto artifacts use key-aware secret audit/redaction.
39
47
 
40
48
  What changed in 4.0.8:
41
49
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "4.0.9"
79
+ version = "4.0.11"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "4.0.9"
3
+ version = "4.0.11"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '4.0.10';
2
+ const FAST_PACKAGE_VERSION = '4.0.11';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
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.0.10';
8
+ export const PACKAGE_VERSION = '4.0.11';
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,90 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { sha256, writeJsonAtomic, writeTextAtomic } from '../../../fsx.js';
4
+ import { parseUnifiedDiffPatch } from '../glm-patch-parser.js';
5
+ import { combineGlmNarutoPatches } from './glm-naruto-combined-patch.js';
6
+ export async function runGlmNarutoApplyTransaction(input) {
7
+ const preStatus = await gitText(input.cwd, ['status', '--short']);
8
+ const preDiff = await gitText(input.cwd, ['diff', '--binary']);
9
+ const patch = combineGlmNarutoPatches(input.envelopes, input.selectedPatchIds);
10
+ const parsed = parseUnifiedDiffPatch(patch);
11
+ const patchPath = path.join(input.artifactDir, 'selected-combined.patch');
12
+ await writeTextAtomic(patchPath, patch);
13
+ const blockers = [];
14
+ let applyCheckPassed = false;
15
+ let applyPassed = false;
16
+ let rollbackAttempted = false;
17
+ let rollbackPassed = null;
18
+ let finalStatus = 'blocked';
19
+ if (!patch.trim()) {
20
+ blockers.push('combined_patch_empty');
21
+ }
22
+ else {
23
+ const checked = await gitApply(input.cwd, patch, ['apply', '--check', '--whitespace=nowarn', '-']);
24
+ applyCheckPassed = checked.code === 0;
25
+ if (!applyCheckPassed)
26
+ blockers.push(checked.stderr || checked.stdout || 'git_apply_check_failed');
27
+ if (applyCheckPassed) {
28
+ const applied = await gitApply(input.cwd, patch, ['apply', '--whitespace=nowarn', '-']);
29
+ applyPassed = applied.code === 0;
30
+ if (applyPassed) {
31
+ finalStatus = 'applied';
32
+ }
33
+ else {
34
+ blockers.push(applied.stderr || applied.stdout || 'git_apply_failed');
35
+ rollbackAttempted = true;
36
+ const rollback = await gitApply(input.cwd, patch, ['apply', '-R', '--whitespace=nowarn', '-']);
37
+ rollbackPassed = rollback.code === 0;
38
+ finalStatus = rollbackPassed ? 'rolled_back' : 'blocked';
39
+ if (!rollbackPassed)
40
+ blockers.push(rollback.stderr || rollback.stdout || 'rollback_reverse_patch_failed');
41
+ }
42
+ }
43
+ }
44
+ const postDiff = await gitText(input.cwd, ['diff', '--binary']);
45
+ const transaction = {
46
+ schema: 'sks.glm-naruto-apply-transaction.v1',
47
+ mission_id: input.missionId,
48
+ selected_patch_ids: input.selectedPatchIds,
49
+ touched_paths: parsed.touchedPaths,
50
+ pre_status: preStatus,
51
+ pre_diff_sha256: sha256(preDiff),
52
+ combined_patch_sha256: sha256(patch),
53
+ apply_check_passed: applyCheckPassed,
54
+ apply_passed: applyPassed,
55
+ targeted_checks_passed: null,
56
+ rollback_attempted: rollbackAttempted,
57
+ rollback_passed: rollbackPassed,
58
+ final_status: finalStatus,
59
+ blockers
60
+ };
61
+ await writeJsonAtomic(path.join(input.artifactDir, 'apply-transaction.json'), transaction);
62
+ await writeJsonAtomic(path.join(input.artifactDir, 'apply-transaction-diff-hashes.json'), {
63
+ schema: 'sks.glm-naruto-apply-transaction-diff-hashes.v1',
64
+ mission_id: input.missionId,
65
+ pre_diff_sha256: sha256(preDiff),
66
+ post_diff_sha256: sha256(postDiff),
67
+ combined_patch_sha256: sha256(patch)
68
+ });
69
+ return { ok: finalStatus === 'applied', applied: finalStatus === 'applied' ? input.selectedPatchIds : [], patch, transaction };
70
+ }
71
+ function gitText(cwd, args) {
72
+ return new Promise((resolve) => {
73
+ const child = spawn('git', [...args], { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
74
+ let stdout = '';
75
+ child.stdout.on('data', (chunk) => { stdout += String(chunk); });
76
+ child.on('close', () => resolve(stdout));
77
+ });
78
+ }
79
+ function gitApply(cwd, patch, args) {
80
+ return new Promise((resolve) => {
81
+ const child = spawn('git', [...args], { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
82
+ let stdout = '';
83
+ let stderr = '';
84
+ child.stdout.on('data', (chunk) => { stdout += String(chunk); });
85
+ child.stderr.on('data', (chunk) => { stderr += String(chunk); });
86
+ child.on('close', (code) => resolve({ code, stdout, stderr }));
87
+ child.stdin.end(patch);
88
+ });
89
+ }
90
+ //# sourceMappingURL=glm-naruto-apply-transaction.js.map
@@ -5,6 +5,7 @@ import os from 'node:os';
5
5
  import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
6
6
  import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
7
7
  import { runGlmNarutoMission } from './glm-naruto-orchestrator.js';
8
+ import { summarizeGlmNarutoWorkerMetrics } from './glm-naruto-metrics.js';
8
9
  export async function runGlmNarutoBench(root, args = []) {
9
10
  const live = args.includes('--live');
10
11
  const execute = args.includes('--execute');
@@ -15,7 +16,7 @@ export async function runGlmNarutoBench(root, args = []) {
15
16
  if (!live) {
16
17
  return {
17
18
  schema: 'sks.glm-naruto-bench.v1',
18
- version: '4.0.10',
19
+ version: '4.0.11',
19
20
  generated_at: nowIso(),
20
21
  status: 'dry_run',
21
22
  model: GLM_52_OPENROUTER_MODEL,
@@ -48,23 +49,30 @@ export async function runGlmNarutoBench(root, args = []) {
48
49
  maxWorkers: workers,
49
50
  noApply: true
50
51
  });
52
+ const traces = await readWorkerTraces(result.artifact_dir);
53
+ const metrics = summarizeGlmNarutoWorkerMetrics(traces);
51
54
  cases.push({
52
55
  name: workers === 1 ? 'direct single GLM' : `GLM Naruto ${workers} workers`,
53
56
  workers,
54
57
  wall_clock_ms: Date.now() - caseStarted,
55
- p50_ttft_ms: null,
56
- p90_ttft_ms: null,
58
+ p50_ttft_ms: metrics.p50_ttft_ms,
59
+ p90_ttft_ms: metrics.p90_ttft_ms,
60
+ p50_total_ms: metrics.p50_total_ms,
61
+ p90_total_ms: metrics.p90_total_ms,
57
62
  candidate_count: result.patch_candidates,
58
63
  gate_pass_rate: result.patch_candidates ? result.gate_passed_candidates / result.patch_candidates : 0,
59
- verifier_pass_rate: 0,
64
+ verifier_pass_rate: metrics.verifier_pass_rate,
60
65
  merge_success: result.mergeable_candidates > 0,
61
- cached_tokens: 0,
62
- cache_write_tokens: 0
66
+ cached_tokens_sum: metrics.cached_tokens_sum,
67
+ cache_write_tokens_sum: metrics.cache_write_tokens_sum,
68
+ reasoning_tokens_sum: metrics.reasoning_tokens_sum,
69
+ workers_completed: metrics.workers_completed,
70
+ workers_failed: metrics.workers_failed
63
71
  });
64
72
  }
65
73
  return {
66
74
  schema: 'sks.glm-naruto-bench.v1',
67
- version: '4.0.10',
75
+ version: '4.0.11',
68
76
  generated_at: nowIso(),
69
77
  status: 'live',
70
78
  model: GLM_52_OPENROUTER_MODEL,
@@ -84,7 +92,7 @@ export async function runGlmNarutoBench(root, args = []) {
84
92
  function blocked(root, warnings) {
85
93
  return {
86
94
  schema: 'sks.glm-naruto-bench.v1',
87
- version: '4.0.10',
95
+ version: '4.0.11',
88
96
  generated_at: nowIso(),
89
97
  status: 'blocked',
90
98
  model: GLM_52_OPENROUTER_MODEL,
@@ -100,4 +108,14 @@ function blocked(root, warnings) {
100
108
  warnings
101
109
  };
102
110
  }
111
+ async function readWorkerTraces(artifactDir) {
112
+ if (!artifactDir)
113
+ return [];
114
+ try {
115
+ return JSON.parse(await fsp.readFile(path.join(artifactDir, 'worker-traces.json'), 'utf8'));
116
+ }
117
+ catch {
118
+ return [];
119
+ }
120
+ }
103
121
  //# sourceMappingURL=glm-naruto-bench.js.map
@@ -2,7 +2,6 @@ import { flag, readOption, positionalArgs } from '../../../../cli/args.js';
2
2
  import { printJson } from '../../../../cli/output.js';
3
3
  import { runGlmNarutoMission } from './glm-naruto-orchestrator.js';
4
4
  import { runGlmNarutoBench } from './glm-naruto-bench.js';
5
- import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
6
5
  export async function glmNarutoCommand(args = []) {
7
6
  if (flag(args, '--bench')) {
8
7
  const result = await runGlmNarutoBench(process.cwd(), args);
@@ -36,34 +35,17 @@ export async function glmNarutoCommand(args = []) {
36
35
  process.exitCode = 1;
37
36
  return result;
38
37
  }
39
- const key = await resolveOpenRouterApiKey({ env: process.env });
40
- if (!key.key) {
41
- const result = {
42
- schema: 'sks.glm-naruto-result.v1',
43
- ok: false,
44
- status: 'blocked',
45
- mission_id: 'none',
46
- task,
47
- model: 'z-ai/glm-5.2',
48
- gpt_fallback_allowed: false,
49
- termination_reason: 'glm_missing_openrouter_key',
50
- blockers: ['glm_missing_openrouter_key'],
51
- warnings: ['set_OPENROUTER_API_KEY_or_run_sks_--mad_--glm_--repair']
52
- };
53
- if (flag(args, '--json'))
54
- printJson(result);
55
- else
56
- console.error('GLM Naruto blocked: missing OpenRouter API key. Run: sks --mad --glm --repair');
57
- process.exitCode = 1;
58
- return result;
59
- }
60
38
  const maxWorkers = parseInt(readOption(args, '--clones', readOption(args, '--workers', '12')), 10) || 12;
61
39
  const deep = flag(args, '--deep');
62
40
  const useJudge = flag(args, '--judge');
63
41
  const xhighFinalizer = flag(args, '--xhigh-finalizer');
64
42
  const useWorktree = flag(args, '--worktree');
65
43
  const patchEnvelopeOnly = flag(args, '--patch-envelope-only');
44
+ const keepWorktrees = flag(args, '--keep-worktrees');
45
+ const cleanupWorktrees = flag(args, '--cleanup-worktrees') || !keepWorktrees;
46
+ const allowPatchEnvelopeFallback = flag(args, '--allow-patch-envelope-fallback');
66
47
  const noApply = flag(args, '--no-apply');
48
+ const skipVerifier = flag(args, '--skip-verifier');
67
49
  const mergeStrategy = readOption(args, '--merge-strategy', 'deterministic');
68
50
  const result = await runGlmNarutoMission({
69
51
  cwd: process.cwd(),
@@ -74,7 +56,12 @@ export async function glmNarutoCommand(args = []) {
74
56
  useJudge,
75
57
  xhighFinalizer,
76
58
  useWorktree: useWorktree && !patchEnvelopeOnly,
59
+ patchEnvelopeOnly,
60
+ allowPatchEnvelopeFallback,
61
+ keepWorktrees,
62
+ cleanupWorktrees,
77
63
  noApply: noApply || flag(args, '--dry-run'),
64
+ skipVerifier,
78
65
  mergeStrategy
79
66
  });
80
67
  if (flag(args, '--json')) {
@@ -2,24 +2,25 @@ import { GLM_NARUTO_DEFAULTS } from './glm-naruto-types.js';
2
2
  export function decideConcurrency(input) {
3
3
  const maxClones = Math.min(input.operatorMax || GLM_NARUTO_DEFAULTS.max_clones, GLM_NARUTO_DEFAULTS.max_clones);
4
4
  const requested = Math.min(input.requestedClones || GLM_NARUTO_DEFAULTS.default_clones, maxClones);
5
- if (input.rateLimited429 > 0.05 || input.ttftP90Ms > 15_000) {
5
+ const active = input.activeWorkers > 0 ? input.activeWorkers : Math.min(requested, GLM_NARUTO_DEFAULTS.safe_active_start);
6
+ if (input.failureRate > 0.10) {
6
7
  return {
7
- target_active_workers: Math.max(1, Math.floor(input.activeWorkers * 0.5)),
8
+ target_active_workers: 0,
8
9
  burst_workers: 0,
9
10
  backpressure: true,
10
- reason: 'scale_down_high_latency_or_rate_limit'
11
+ reason: 'pause_high_5xx_or_failure_rate'
11
12
  };
12
13
  }
13
- if (input.failureRate > 0.3) {
14
+ if (input.rateLimited429 > 0 || input.ttftP90Ms > 15_000) {
14
15
  return {
15
- target_active_workers: Math.max(1, Math.floor(input.activeWorkers * 0.7)),
16
+ target_active_workers: Math.max(1, Math.floor(active * 0.5)),
16
17
  burst_workers: 0,
17
18
  backpressure: true,
18
- reason: 'scale_down_high_failure_rate'
19
+ reason: 'scale_down_high_latency_or_rate_limit'
19
20
  };
20
21
  }
21
- if (input.ttftP90Ms < 5_000 && input.rateLimited429 === 0 && input.activeWorkers < requested) {
22
- const target = Math.min(requested, input.activeWorkers + Math.max(1, Math.floor(requested * 0.2)));
22
+ if (input.ttftP90Ms < 5_000 && input.rateLimited429 === 0 && input.failureRate < 0.05 && active < requested) {
23
+ const target = Math.min(requested, active + 2);
23
24
  return {
24
25
  target_active_workers: target,
25
26
  burst_workers: Math.min(2, requested - target),
@@ -28,7 +29,7 @@ export function decideConcurrency(input) {
28
29
  };
29
30
  }
30
31
  return {
31
- target_active_workers: Math.min(input.activeWorkers, requested),
32
+ target_active_workers: Math.min(active, requested),
32
33
  burst_workers: 0,
33
34
  backpressure: false,
34
35
  reason: 'steady_state'
@@ -5,9 +5,32 @@ export function decomposeTask(input) {
5
5
  const dependencies = [];
6
6
  const mutableShardIds = [];
7
7
  const verificationShardIds = [];
8
- const paths = input.mentionedPaths.length > 0
9
- ? input.mentionedPaths
10
- : ['src/'];
8
+ const paths = discoverTargetPaths(input);
9
+ if (paths.length === 0) {
10
+ const scoutShard = {
11
+ id: 'shard-scout-paths',
12
+ kind: 'verification',
13
+ task: `Discover target paths before mutation for: ${input.task}`,
14
+ target_paths: [],
15
+ forbidden_paths: ['.github/', 'dist/', 'node_modules/'],
16
+ base_digest: digestBase(input),
17
+ strategy: 'minimal_patch',
18
+ patches_per_shard: 0,
19
+ max_tokens: 2048,
20
+ reasoning: 'low',
21
+ mutable: false
22
+ };
23
+ return {
24
+ schema: 'sks.glm-naruto-work-graph.v1',
25
+ mission_id: input.missionId,
26
+ task: input.task,
27
+ shards: [scoutShard],
28
+ dependencies,
29
+ parallel_groups: [],
30
+ mutable_shards: [],
31
+ verification_shards: [scoutShard.id]
32
+ };
33
+ }
11
34
  let shardIndex = 0;
12
35
  for (const targetPath of paths) {
13
36
  const shardId = `shard-${shardIndex}`;
@@ -64,6 +87,33 @@ export function decomposeTask(input) {
64
87
  verification_shards: verificationShardIds
65
88
  };
66
89
  }
90
+ function discoverTargetPaths(input) {
91
+ const candidates = new Set();
92
+ for (const mentioned of input.mentionedPaths)
93
+ candidates.add(mentioned);
94
+ for (const line of (input.gitStatus || '').split(/\r?\n/)) {
95
+ const match = line.match(/^\s*(?:[AMDRCU?!]{1,2})\s+(.+)$/);
96
+ if (!match)
97
+ continue;
98
+ const file = match[1].split(/\s+->\s+/).pop().trim();
99
+ if (isMutableCandidate(file))
100
+ candidates.add(file);
101
+ }
102
+ for (const source of [input.task, input.lastError || '']) {
103
+ for (const match of source.matchAll(/\b([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+\.(?:ts|tsx|js|mjs|cjs|json|md|yml|yaml|toml))\b/g)) {
104
+ if (isMutableCandidate(match[1]))
105
+ candidates.add(match[1]);
106
+ }
107
+ }
108
+ return [...candidates];
109
+ }
110
+ function isMutableCandidate(file) {
111
+ return Boolean(file)
112
+ && !file.startsWith('.github/')
113
+ && !file.startsWith('dist/')
114
+ && !file.startsWith('node_modules/')
115
+ && !file.endsWith('/');
116
+ }
67
117
  function classifyShardKind(path) {
68
118
  if (path.includes('test') || path.includes('__tests__') || path.includes('.test.'))
69
119
  return 'test_fix';
@@ -87,7 +137,7 @@ export function validateWorkGraph(graph, isVerifyOnly) {
87
137
  return { ok: true };
88
138
  const mutableCount = graph.mutable_shards.length;
89
139
  if (mutableCount === 0)
90
- return { ok: false, reason: 'glm_naruto_invalid_verify_only_plan' };
140
+ return { ok: false, reason: 'glm_naruto_needs_target_path_context' };
91
141
  // Check ratio of mutable shards to total shards (excluding verification shards from the denominator)
92
142
  const totalWorkShards = graph.shards.filter(s => s.mutable || s.kind !== 'verification').length;
93
143
  const ratio = totalWorkShards > 0 ? mutableCount / totalWorkShards : 0;
@@ -15,6 +15,7 @@ export function finalizeMergePlan(input) {
15
15
  return planMerge({
16
16
  missionId: input.missionId,
17
17
  graph: conflictGraph,
18
+ ...(input.scoreboard ? { scoreboard: input.scoreboard } : {}),
18
19
  strategy,
19
20
  ...(input.judgeResult ? { judgeRanking: input.judgeResult.ranked_patch_ids } : {})
20
21
  });
@@ -0,0 +1,38 @@
1
+ export function resolveGlmNarutoIsolationPolicy(input) {
2
+ const requested = input.useWorktree ? 'git-worktree' : input.patchEnvelopeOnly ? 'patch-envelope-only' : 'auto';
3
+ if (input.patchEnvelopeOnly || !input.useWorktree) {
4
+ return {
5
+ schema: 'sks.glm-naruto-isolation-policy.v1',
6
+ requested,
7
+ selected: 'patch-envelope-only',
8
+ honest: true,
9
+ reason: input.patchEnvelopeOnly ? 'patch_envelope_only_requested' : 'worktree_not_requested',
10
+ blockers: [],
11
+ fallback_allowed: Boolean(input.fallbackAllowed),
12
+ workers_write_main_workspace: false
13
+ };
14
+ }
15
+ if (input.gitAvailable) {
16
+ return {
17
+ schema: 'sks.glm-naruto-isolation-policy.v1',
18
+ requested,
19
+ selected: 'git-worktree',
20
+ honest: true,
21
+ reason: 'git_worktree_available',
22
+ blockers: [],
23
+ fallback_allowed: Boolean(input.fallbackAllowed),
24
+ workers_write_main_workspace: false
25
+ };
26
+ }
27
+ return {
28
+ schema: 'sks.glm-naruto-isolation-policy.v1',
29
+ requested,
30
+ selected: input.fallbackAllowed ? 'patch-envelope-only' : 'blocked',
31
+ honest: true,
32
+ reason: input.fallbackAllowed ? 'git_worktree_unavailable_fallback_allowed' : 'git_worktree_unavailable',
33
+ blockers: input.fallbackAllowed ? [] : ['glm_naruto_worktree_not_implemented_or_unavailable'],
34
+ fallback_allowed: Boolean(input.fallbackAllowed),
35
+ workers_write_main_workspace: false
36
+ };
37
+ }
38
+ //# sourceMappingURL=glm-naruto-isolation-policy.js.map
@@ -1,12 +1,17 @@
1
1
  import { getNonConflictingSets } from './glm-naruto-conflict-graph.js';
2
2
  export function planMerge(input) {
3
- const passedNodes = input.graph.nodes.filter((n) => n.gate_passed);
4
- const nonConflictingSets = getNonConflictingSets(input.graph);
3
+ const scoreByPatch = new Map((input.scoreboard?.scores ?? []).map((score) => [score.patch_id, score]));
4
+ const passedNodes = input.graph.nodes.filter((n) => n.gate_passed && !scoreByPatch.get(n.patch_id)?.disqualified);
5
+ const filteredGraph = {
6
+ ...input.graph,
7
+ nodes: passedNodes
8
+ };
9
+ const nonConflictingSets = getNonConflictingSets(filteredGraph);
5
10
  const candidates = nonConflictingSets.map((patchIds) => {
6
11
  const nodes = passedNodes.filter((n) => patchIds.includes(n.patch_id));
7
- const totalScore = nodes.reduce((sum, n) => sum + n.score, 0);
12
+ const totalScore = nodes.reduce((sum, n) => sum + (scoreByPatch.get(n.patch_id)?.total_score ?? n.score), 0);
8
13
  return { patch_ids: patchIds, total_score: totalScore, conflict_free: true };
9
- });
14
+ }).filter((candidate) => candidate.patch_ids.every((id) => passedNodes.some((node) => node.patch_id === id)));
10
15
  candidates.sort((a, b) => b.total_score - a.total_score);
11
16
  let selected = [];
12
17
  let rationale = '';
@@ -0,0 +1,34 @@
1
+ export function summarizeGlmNarutoWorkerMetrics(traces) {
2
+ const ttft = traces.map((trace) => trace.ttft_ms).filter(isNumber).sort((a, b) => a - b);
3
+ const totals = traces.map((trace) => trace.total_ms).filter(isNumber).sort((a, b) => a - b);
4
+ const verifier = traces.filter((trace) => trace.status === 'verification_passed' || trace.status === 'verification_failed');
5
+ const verifierPassed = verifier.filter((trace) => trace.status === 'verification_passed').length;
6
+ return {
7
+ p50_ttft_ms: percentile(ttft, 0.5),
8
+ p90_ttft_ms: percentile(ttft, 0.9),
9
+ p50_total_ms: percentile(totals, 0.5),
10
+ p90_total_ms: percentile(totals, 0.9),
11
+ cached_tokens_sum: sumNullable(traces.map((trace) => trace.cached_tokens ?? null)),
12
+ cache_write_tokens_sum: sumNullable(traces.map((trace) => trace.cache_write_tokens ?? null)),
13
+ reasoning_tokens_sum: sumNullable(traces.map((trace) => trace.reasoning_tokens ?? null)),
14
+ workers_completed: traces.filter((trace) => trace.status === 'completed').length,
15
+ workers_failed: traces.filter((trace) => trace.status === 'failed').length,
16
+ verifier_pass_rate: verifier.length ? verifierPassed / verifier.length : 0
17
+ };
18
+ }
19
+ function percentile(values, quantile) {
20
+ if (!values.length)
21
+ return null;
22
+ const index = Math.min(values.length - 1, Math.max(0, Math.ceil(values.length * quantile) - 1));
23
+ return values[index] ?? null;
24
+ }
25
+ function sumNullable(values) {
26
+ const present = values.filter(isNumber);
27
+ if (!present.length)
28
+ return null;
29
+ return present.reduce((sum, value) => sum + value, 0);
30
+ }
31
+ function isNumber(value) {
32
+ return typeof value === 'number' && Number.isFinite(value);
33
+ }
34
+ //# sourceMappingURL=glm-naruto-metrics.js.map