sneakoscope 4.0.6 → 4.0.8

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 (39) hide show
  1. package/README.md +6 -2
  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/core/commands/glm-command.js +9 -2
  7. package/dist/core/fsx.js +1 -1
  8. package/dist/core/providers/glm/glm-bench.js +4 -4
  9. package/dist/core/providers/glm/glm-latency-trace.js +1 -1
  10. package/dist/core/providers/glm/glm-request-cache.js +10 -2
  11. package/dist/core/providers/glm/naruto/glm-naruto-artifacts.js +2 -0
  12. package/dist/core/providers/glm/naruto/glm-naruto-bench.js +68 -0
  13. package/dist/core/providers/glm/naruto/glm-naruto-budget.js +45 -0
  14. package/dist/core/providers/glm/naruto/glm-naruto-command.js +97 -0
  15. package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +37 -0
  16. package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +74 -0
  17. package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +99 -0
  18. package/dist/core/providers/glm/naruto/glm-naruto-file-lease.js +23 -0
  19. package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +22 -0
  20. package/dist/core/providers/glm/naruto/glm-naruto-judge.js +84 -0
  21. package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +57 -0
  22. package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +224 -0
  23. package/dist/core/providers/glm/naruto/glm-naruto-patch-envelope.js +55 -0
  24. package/dist/core/providers/glm/naruto/glm-naruto-quorum.js +37 -0
  25. package/dist/core/providers/glm/naruto/glm-naruto-rate-limiter.js +18 -0
  26. package/dist/core/providers/glm/naruto/glm-naruto-repair-wave.js +21 -0
  27. package/dist/core/providers/glm/naruto/glm-naruto-shard-planner.js +32 -0
  28. package/dist/core/providers/glm/naruto/glm-naruto-trace.js +51 -0
  29. package/dist/core/providers/glm/naruto/glm-naruto-types.js +37 -0
  30. package/dist/core/providers/glm/naruto/glm-naruto-work-graph.js +2 -0
  31. package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +79 -0
  32. package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +196 -0
  33. package/dist/core/providers/glm/naruto/glm-naruto-worker.js +2 -0
  34. package/dist/core/providers/glm/naruto/glm-naruto-worktree.js +48 -0
  35. package/dist/core/providers/openrouter/openrouter-provider-health.js +46 -0
  36. package/dist/core/providers/openrouter/openrouter-secret-store.js +33 -0
  37. package/dist/core/providers/openrouter/openrouter-stream.js +73 -5
  38. package/dist/core/version.js +1 -1
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -35,11 +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.6** 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.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.
39
+
40
+ What changed in 4.0.8:
41
+
42
+ - **`--open` alias for interactive GLM launch.** `sks --mad --glm --open` now opens the GLM interactive Zellij runtime, equivalent to `sks --mad --glm --interactive`.
39
43
 
40
44
  What changed in 4.0.6:
41
45
 
42
- - **No default long-lived GLM launch.** Bare `sks --mad --glm` no longer falls through to MAD/Zellij; `--interactive`, `--zellij`, or `session` is required for that path.
46
+ - **No default long-lived GLM launch.** Bare `sks --mad --glm` no longer falls through to MAD/Zellij; `--interactive`, `--open`, `--zellij`, or `session` is required for that path.
43
47
  - **Fast GLM speed profile.** Speed mode keeps OpenRouter locked to `z-ai/glm-5.2`, disables GPT/model fallback, avoids high/xhigh reasoning by default, and uses `provider.require_parameters: false` with throughput-first routing.
44
48
  - **Bounded direct task runs.** `sks --mad --glm run "task"` and `sks --mad --glm "task"` use a one-shot GLM speed run with max-turn, wall-clock, request-timeout, no-progress, repeated-output, and terminal-state guards.
45
49
  - **Deterministic mutation gate.** GLM still returns patch envelopes; SKS parses the unified diff, blocks protected paths, runs `git apply --check`, and applies only after the gate passes.
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "4.0.6"
79
+ version = "4.0.8"
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.6"
3
+ version = "4.0.8"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 4.0.6"),
7
+ Some("--version") => println!("sks-rs 4.0.8"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '4.0.6';
2
+ const FAST_PACKAGE_VERSION = '4.0.8';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -4,8 +4,13 @@ import { printJson } from '../../cli/output.js';
4
4
  import { runGlmDirectSpeedRun } from '../providers/glm/glm-direct-run.js';
5
5
  import { runGlmReadinessAndExit } from '../providers/glm/glm-readiness.js';
6
6
  import { runGlmInteractiveLaunch } from '../providers/glm/glm-interactive-launch.js';
7
+ import { glmNarutoCommand } from '../providers/glm/naruto/glm-naruto-command.js';
7
8
  export async function glmCommand(args = []) {
8
- if (flag(args, '--bench')) {
9
+ if (flag(args, '--naruto') || positionalArgs(args)[0] === 'naruto') {
10
+ const narutoArgs = args.filter((a) => a !== '--naruto' && a !== 'naruto');
11
+ return glmNarutoCommand(narutoArgs);
12
+ }
13
+ if (flag(args, '--bench') && !flag(args, '--naruto')) {
9
14
  const result = await runGlmBench(process.cwd(), args);
10
15
  if (result.status === 'blocked')
11
16
  process.exitCode = 1;
@@ -18,7 +23,7 @@ export async function glmCommand(args = []) {
18
23
  return result;
19
24
  }
20
25
  const task = extractGlmTask(args);
21
- const interactive = flag(args, '--interactive') || flag(args, '--zellij') || positionalArgs(args)[0] === 'session';
26
+ const interactive = flag(args, '--interactive') || flag(args, '--open') || flag(args, '--zellij') || positionalArgs(args)[0] === 'session';
22
27
  if (interactive) {
23
28
  const readiness = await runGlmReadinessAndExit(args);
24
29
  if (!readiness.ok)
@@ -50,6 +55,8 @@ function extractGlmTask(args) {
50
55
  return positional.slice(1).join(' ').trim() || null;
51
56
  if (positional[0] === 'session')
52
57
  return null;
58
+ if (positional[0] === 'naruto')
59
+ return null;
53
60
  return positional.join(' ').trim() || null;
54
61
  }
55
62
  //# sourceMappingURL=glm-command.js.map
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.6';
8
+ export const PACKAGE_VERSION = '4.0.8';
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() {
@@ -14,7 +14,7 @@ export async function runGlmBench(root, args = []) {
14
14
  if (execute && !live) {
15
15
  const blocked = {
16
16
  schema: 'sks.glm-bench-result.v1',
17
- version: '4.0.6',
17
+ version: '4.0.8',
18
18
  generated_at: nowIso(),
19
19
  status: 'blocked',
20
20
  dry_run: true,
@@ -32,7 +32,7 @@ export async function runGlmBench(root, args = []) {
32
32
  if (live) {
33
33
  const blocked = {
34
34
  schema: 'sks.glm-bench-result.v1',
35
- version: '4.0.6',
35
+ version: '4.0.8',
36
36
  generated_at: nowIso(),
37
37
  status: 'blocked',
38
38
  dry_run: false,
@@ -50,7 +50,7 @@ export async function runGlmBench(root, args = []) {
50
50
  if (execute) {
51
51
  const blocked = {
52
52
  schema: 'sks.glm-bench-result.v1',
53
- version: '4.0.6',
53
+ version: '4.0.8',
54
54
  generated_at: nowIso(),
55
55
  status: 'blocked',
56
56
  dry_run: true,
@@ -69,7 +69,7 @@ export async function runGlmBench(root, args = []) {
69
69
  const deepTotals = SYNTHETIC_CASES.map((row) => row.deep.total_ms);
70
70
  const result = {
71
71
  schema: 'sks.glm-bench-result.v1',
72
- version: '4.0.6',
72
+ version: '4.0.8',
73
73
  generated_at: nowIso(),
74
74
  status: 'dry_run',
75
75
  dry_run: true,
@@ -3,7 +3,7 @@ import { nowIso, writeJsonAtomic } from '../../fsx.js';
3
3
  export function createEmptyGlmLatencyTrace(mode) {
4
4
  return {
5
5
  schema: 'sks.glm-latency-trace.v1',
6
- version: '4.0.6',
6
+ version: '4.0.8',
7
7
  mode,
8
8
  total_ms: 0,
9
9
  preflight_ms: 0,
@@ -6,9 +6,17 @@ export function createGlmEncodedRequestCache(maxEntries = 128) {
6
6
  export function encodeGlmRequestWithCache(request, cache = defaultEncodedRequestCache) {
7
7
  const key = digestRequestForCache(request);
8
8
  const hit = cache.get(key);
9
+ // Fix 18.2: On cache hit, return stored body without JSON.stringify
10
+ if (hit) {
11
+ if (hit.bodyStored) {
12
+ return { body: hit.body, entry: hit, cacheHit: true };
13
+ }
14
+ // Even for non-stored bodies, skip re-stringifying by computing from request
15
+ const body = JSON.stringify(request);
16
+ return { body, entry: hit, cacheHit: true };
17
+ }
18
+ // Cache miss: stringify once
9
19
  const body = JSON.stringify(request);
10
- if (hit)
11
- return { body: hit.bodyStored ? hit.body : body, entry: hit, cacheHit: true };
12
20
  if (containsSecretLikeContent(body)) {
13
21
  const entry = {
14
22
  key,
@@ -0,0 +1,2 @@
1
+ export { writeMissionArtifacts, createMissionTrace, recordWorkerTrace, buildMissionSummary } from './glm-naruto-trace.js';
2
+ //# sourceMappingURL=glm-naruto-artifacts.js.map
@@ -0,0 +1,68 @@
1
+ import { nowIso, writeJsonAtomic } from '../../../fsx.js';
2
+ import path from 'node:path';
3
+ import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
4
+ export async function runGlmNarutoBench(root, args = []) {
5
+ const live = args.includes('--live');
6
+ const execute = args.includes('--execute');
7
+ const started = Date.now();
8
+ if (execute && !live) {
9
+ return blocked(root, ['execute_requires_live_flag']);
10
+ }
11
+ if (!live) {
12
+ return {
13
+ schema: 'sks.glm-naruto-bench.v1',
14
+ version: '4.0.8',
15
+ generated_at: nowIso(),
16
+ status: 'dry_run',
17
+ model: GLM_52_OPENROUTER_MODEL,
18
+ gpt_fallback_allowed: false,
19
+ summary: {
20
+ simulated_workers: 12,
21
+ simulated_waves: 3,
22
+ simulated_patch_candidates: 24,
23
+ simulated_gate_passed: 18,
24
+ simulated_mergeable: 12,
25
+ wall_clock_ms: Date.now() - started
26
+ },
27
+ warnings: ['dry_run_no_live_api_calls']
28
+ };
29
+ }
30
+ // Live bench would require OpenRouter key and real API calls
31
+ return {
32
+ schema: 'sks.glm-naruto-bench.v1',
33
+ version: '4.0.8',
34
+ generated_at: nowIso(),
35
+ status: 'blocked',
36
+ model: GLM_52_OPENROUTER_MODEL,
37
+ gpt_fallback_allowed: false,
38
+ summary: {
39
+ simulated_workers: 0,
40
+ simulated_waves: 0,
41
+ simulated_patch_candidates: 0,
42
+ simulated_gate_passed: 0,
43
+ simulated_mergeable: 0,
44
+ wall_clock_ms: Date.now() - started
45
+ },
46
+ warnings: ['live_bench_requires_openrouter_key_and_task']
47
+ };
48
+ }
49
+ function blocked(root, warnings) {
50
+ return {
51
+ schema: 'sks.glm-naruto-bench.v1',
52
+ version: '4.0.8',
53
+ generated_at: nowIso(),
54
+ status: 'blocked',
55
+ model: GLM_52_OPENROUTER_MODEL,
56
+ gpt_fallback_allowed: false,
57
+ summary: {
58
+ simulated_workers: 0,
59
+ simulated_waves: 0,
60
+ simulated_patch_candidates: 0,
61
+ simulated_gate_passed: 0,
62
+ simulated_mergeable: 0,
63
+ wall_clock_ms: 0
64
+ },
65
+ warnings
66
+ };
67
+ }
68
+ //# sourceMappingURL=glm-naruto-bench.js.map
@@ -0,0 +1,45 @@
1
+ import { GLM_NARUTO_LIMITS } from './glm-naruto-types.js';
2
+ export function createBudget(missionId, deep) {
3
+ return {
4
+ missionId,
5
+ startedMs: Date.now(),
6
+ wavesCompleted: 0,
7
+ totalRequests: 0,
8
+ requestsPerShard: new Map(),
9
+ noProgressWaves: 0,
10
+ repairWaves: 0,
11
+ mergeAttempts: 0,
12
+ maxWaves: deep ? GLM_NARUTO_LIMITS.max_waves_deep : GLM_NARUTO_LIMITS.max_waves_speed
13
+ };
14
+ }
15
+ export function checkBudget(budget) {
16
+ const elapsed = Date.now() - budget.startedMs;
17
+ if (elapsed >= GLM_NARUTO_LIMITS.max_wall_clock_ms) {
18
+ return { ok: false, reason: 'budget_wall_clock_exceeded' };
19
+ }
20
+ if (budget.totalRequests >= GLM_NARUTO_LIMITS.max_total_requests) {
21
+ return { ok: false, reason: 'budget_total_requests_exceeded' };
22
+ }
23
+ if (budget.wavesCompleted >= budget.maxWaves) {
24
+ return { ok: false, reason: 'budget_max_waves_reached' };
25
+ }
26
+ if (budget.noProgressWaves > GLM_NARUTO_LIMITS.max_no_progress_waves) {
27
+ return { ok: false, reason: 'budget_no_progress_waves_exceeded' };
28
+ }
29
+ if (budget.repairWaves > GLM_NARUTO_LIMITS.max_repair_waves) {
30
+ return { ok: false, reason: 'budget_max_repair_waves_exceeded' };
31
+ }
32
+ if (budget.mergeAttempts > GLM_NARUTO_LIMITS.max_merge_attempts) {
33
+ return { ok: false, reason: 'budget_max_merge_attempts_exceeded' };
34
+ }
35
+ return { ok: true };
36
+ }
37
+ export function recordRequest(budget, shardId) {
38
+ const newPerShard = new Map(budget.requestsPerShard);
39
+ newPerShard.set(shardId, (newPerShard.get(shardId) || 0) + 1);
40
+ return { ...budget, totalRequests: budget.totalRequests + 1, requestsPerShard: newPerShard };
41
+ }
42
+ export function canRequestShard(budget, shardId) {
43
+ return (budget.requestsPerShard.get(shardId) || 0) < GLM_NARUTO_LIMITS.max_requests_per_shard;
44
+ }
45
+ //# sourceMappingURL=glm-naruto-budget.js.map
@@ -0,0 +1,97 @@
1
+ import { flag, readOption, positionalArgs } from '../../../../cli/args.js';
2
+ import { printJson } from '../../../../cli/output.js';
3
+ import { runGlmNarutoMission } from './glm-naruto-orchestrator.js';
4
+ import { runGlmNarutoBench } from './glm-naruto-bench.js';
5
+ import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
6
+ export async function glmNarutoCommand(args = []) {
7
+ if (flag(args, '--bench')) {
8
+ const result = await runGlmNarutoBench(process.cwd(), args);
9
+ if (flag(args, '--json'))
10
+ printJson(result);
11
+ else if (result.status === 'blocked')
12
+ console.error(`GLM Naruto bench blocked: ${result.warnings.join(', ')}`);
13
+ else
14
+ console.log(`GLM Naruto bench: ${result.status} workers=${result.summary.simulated_workers} candidates=${result.summary.simulated_patch_candidates}`);
15
+ return result;
16
+ }
17
+ const positional = positionalArgs(args).map(String);
18
+ const task = positional.join(' ').trim();
19
+ if (!task && !flag(args, '--repair') && !flag(args, '--status')) {
20
+ const result = {
21
+ schema: 'sks.glm-naruto-result.v1',
22
+ ok: false,
23
+ status: 'blocked',
24
+ mission_id: 'none',
25
+ task: '',
26
+ model: 'z-ai/glm-5.2',
27
+ gpt_fallback_allowed: false,
28
+ termination_reason: 'no_task_provided',
29
+ blockers: ['glm_naruto_no_task'],
30
+ warnings: []
31
+ };
32
+ if (flag(args, '--json'))
33
+ printJson(result);
34
+ else
35
+ console.error('GLM Naruto requires a task. Usage: sks --mad --glm --naruto "<task>"');
36
+ process.exitCode = 1;
37
+ return result;
38
+ }
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
+ const maxWorkers = parseInt(readOption(args, '--clones', readOption(args, '--workers', '12')), 10) || 12;
61
+ const deep = flag(args, '--deep');
62
+ const useJudge = flag(args, '--judge');
63
+ const xhighFinalizer = flag(args, '--xhigh-finalizer');
64
+ const useWorktree = flag(args, '--worktree');
65
+ const patchEnvelopeOnly = flag(args, '--patch-envelope-only');
66
+ const noApply = flag(args, '--no-apply');
67
+ const mergeStrategy = readOption(args, '--merge-strategy', 'deterministic');
68
+ const result = await runGlmNarutoMission({
69
+ cwd: process.cwd(),
70
+ task,
71
+ args,
72
+ maxWorkers,
73
+ deep,
74
+ useJudge,
75
+ xhighFinalizer,
76
+ useWorktree: useWorktree && !patchEnvelopeOnly,
77
+ noApply: noApply || flag(args, '--dry-run'),
78
+ mergeStrategy
79
+ });
80
+ if (flag(args, '--json')) {
81
+ printJson(result);
82
+ }
83
+ else {
84
+ const r = result;
85
+ if (r.ok) {
86
+ console.log(`GLM Naruto completed: ${r.applied_patches} patches applied, ${r.patch_candidates} candidates, ${r.gate_passed_candidates} gate-passed, ${r.repair_waves} repair waves`);
87
+ if (r.artifact_dir)
88
+ console.log(`Artifacts: ${r.artifact_dir}`);
89
+ }
90
+ else {
91
+ console.error(`GLM Naruto ${r.status}: ${r.termination_reason} — blockers: ${r.blockers.join(', ')}`);
92
+ process.exitCode = 1;
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+ //# sourceMappingURL=glm-naruto-command.js.map
@@ -0,0 +1,37 @@
1
+ import { GLM_NARUTO_DEFAULTS } from './glm-naruto-types.js';
2
+ export function decideConcurrency(input) {
3
+ const maxClones = Math.min(input.operatorMax || GLM_NARUTO_DEFAULTS.max_clones, GLM_NARUTO_DEFAULTS.max_clones);
4
+ const requested = Math.min(input.requestedClones || GLM_NARUTO_DEFAULTS.default_clones, maxClones);
5
+ if (input.rateLimited429 > 0.05 || input.ttftP90Ms > 15_000) {
6
+ return {
7
+ target_active_workers: Math.max(1, Math.floor(input.activeWorkers * 0.5)),
8
+ burst_workers: 0,
9
+ backpressure: true,
10
+ reason: 'scale_down_high_latency_or_rate_limit'
11
+ };
12
+ }
13
+ if (input.failureRate > 0.3) {
14
+ return {
15
+ target_active_workers: Math.max(1, Math.floor(input.activeWorkers * 0.7)),
16
+ burst_workers: 0,
17
+ backpressure: true,
18
+ reason: 'scale_down_high_failure_rate'
19
+ };
20
+ }
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)));
23
+ return {
24
+ target_active_workers: target,
25
+ burst_workers: Math.min(2, requested - target),
26
+ backpressure: false,
27
+ reason: 'scale_up_low_latency_no_rate_limit'
28
+ };
29
+ }
30
+ return {
31
+ target_active_workers: Math.min(input.activeWorkers, requested),
32
+ burst_workers: 0,
33
+ backpressure: false,
34
+ reason: 'steady_state'
35
+ };
36
+ }
37
+ //# sourceMappingURL=glm-naruto-concurrency-governor.js.map
@@ -0,0 +1,74 @@
1
+ export function buildConflictGraph(envelopes, nodes) {
2
+ const edges = [];
3
+ for (let i = 0; i < nodes.length; i++) {
4
+ for (let j = i + 1; j < nodes.length; j++) {
5
+ const left = nodes[i];
6
+ const right = nodes[j];
7
+ const conflict = detectConflict(left, right, envelopes);
8
+ if (conflict)
9
+ edges.push(conflict);
10
+ }
11
+ }
12
+ return {
13
+ schema: 'sks.glm-naruto-conflict-graph.v1',
14
+ nodes,
15
+ edges
16
+ };
17
+ }
18
+ function detectConflict(left, right, envelopes) {
19
+ if (left.patch_id === right.patch_id)
20
+ return null;
21
+ const leftEnv = envelopes.find((e) => e.worker_id === left.patch_id || e.patch_sha256 === left.patch_sha256);
22
+ const rightEnv = envelopes.find((e) => e.worker_id === right.patch_id || e.patch_sha256 === right.patch_sha256);
23
+ if (leftEnv && rightEnv && leftEnv.base_digest !== rightEnv.base_digest) {
24
+ return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'base_digest_mismatch' };
25
+ }
26
+ const leftPaths = new Set(left.target_paths);
27
+ const rightPaths = new Set(right.target_paths);
28
+ const sharedFiles = [...leftPaths].filter((p) => rightPaths.has(p));
29
+ if (sharedFiles.length > 0) {
30
+ if (left.shard_id === right.shard_id) {
31
+ return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'same_hunk' };
32
+ }
33
+ return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'same_file' };
34
+ }
35
+ return null;
36
+ }
37
+ export function hasConflict(graph, patchId) {
38
+ return graph.edges.some((edge) => edge.left_patch_id === patchId || edge.right_patch_id === patchId);
39
+ }
40
+ export function getNonConflictingSets(graph) {
41
+ const passed = graph.nodes.filter((n) => n.gate_passed);
42
+ if (passed.length === 0)
43
+ return [];
44
+ const conflictMap = new Map();
45
+ for (const node of passed)
46
+ conflictMap.set(node.patch_id, new Set());
47
+ for (const edge of graph.edges) {
48
+ if (conflictMap.has(edge.left_patch_id) && conflictMap.has(edge.right_patch_id)) {
49
+ conflictMap.get(edge.left_patch_id).add(edge.right_patch_id);
50
+ conflictMap.get(edge.right_patch_id).add(edge.left_patch_id);
51
+ }
52
+ }
53
+ const results = [];
54
+ const sorted = [...passed].sort((a, b) => b.score - a.score);
55
+ const used = new Set();
56
+ for (const node of sorted) {
57
+ if (used.has(node.patch_id))
58
+ continue;
59
+ const group = [node.patch_id];
60
+ used.add(node.patch_id);
61
+ for (const other of sorted) {
62
+ if (used.has(other.patch_id))
63
+ continue;
64
+ const conflicts = conflictMap.get(other.patch_id);
65
+ if (!group.some((id) => conflicts.has(id))) {
66
+ group.push(other.patch_id);
67
+ used.add(other.patch_id);
68
+ }
69
+ }
70
+ results.push(group);
71
+ }
72
+ return results;
73
+ }
74
+ //# sourceMappingURL=glm-naruto-conflict-graph.js.map
@@ -0,0 +1,99 @@
1
+ import crypto from 'node:crypto';
2
+ import { GLM_NARUTO_DEFAULTS, NARUTO_PATCH_STRATEGIES } from './glm-naruto-types.js';
3
+ export function decomposeTask(input) {
4
+ const shards = [];
5
+ const dependencies = [];
6
+ const mutableShardIds = [];
7
+ const verificationShardIds = [];
8
+ const paths = input.mentionedPaths.length > 0
9
+ ? input.mentionedPaths
10
+ : ['src/'];
11
+ let shardIndex = 0;
12
+ for (const targetPath of paths) {
13
+ const shardId = `shard-${shardIndex}`;
14
+ const strategy = NARUTO_PATCH_STRATEGIES[shardIndex % NARUTO_PATCH_STRATEGIES.length] || 'minimal_patch';
15
+ const isCritical = paths.length <= 2;
16
+ const shard = {
17
+ id: shardId,
18
+ kind: classifyShardKind(targetPath),
19
+ task: input.task,
20
+ target_paths: [targetPath],
21
+ forbidden_paths: ['.github/', 'dist/', 'node_modules/'],
22
+ base_digest: digestBase(input),
23
+ strategy,
24
+ patches_per_shard: isCritical ? GLM_NARUTO_DEFAULTS.critical_patches_per_shard : GLM_NARUTO_DEFAULTS.default_patches_per_shard,
25
+ max_tokens: GLM_NARUTO_DEFAULTS.default_max_tokens,
26
+ reasoning: 'none',
27
+ mutable: true
28
+ };
29
+ shards.push(shard);
30
+ mutableShardIds.push(shardId);
31
+ shardIndex++;
32
+ }
33
+ const verifyShard = {
34
+ id: 'shard-verify',
35
+ kind: 'verification',
36
+ task: `Verify all patches for: ${input.task}`,
37
+ target_paths: paths,
38
+ forbidden_paths: ['.github/', 'dist/', 'node_modules/'],
39
+ base_digest: digestBase(input),
40
+ strategy: 'minimal_patch',
41
+ patches_per_shard: 0,
42
+ max_tokens: 4096,
43
+ reasoning: 'low',
44
+ mutable: false
45
+ };
46
+ shards.push(verifyShard);
47
+ verificationShardIds.push(verifyShard.id);
48
+ for (const mutableId of mutableShardIds) {
49
+ dependencies.push({ from: mutableId, to: verifyShard.id, kind: 'verifies' });
50
+ }
51
+ const parallelGroup = {
52
+ id: 'parallel-patch-wave',
53
+ shard_ids: mutableShardIds,
54
+ parallel: true
55
+ };
56
+ return {
57
+ schema: 'sks.glm-naruto-work-graph.v1',
58
+ mission_id: input.missionId,
59
+ task: input.task,
60
+ shards,
61
+ dependencies,
62
+ parallel_groups: [parallelGroup],
63
+ mutable_shards: mutableShardIds,
64
+ verification_shards: verificationShardIds
65
+ };
66
+ }
67
+ function classifyShardKind(path) {
68
+ if (path.includes('test') || path.includes('__tests__') || path.includes('.test.'))
69
+ return 'test_fix';
70
+ if (path.endsWith('.md') || path.endsWith('.txt'))
71
+ return 'doc_patch';
72
+ if (path.endsWith('.json') || path.endsWith('.yaml') || path.endsWith('.yml') || path.endsWith('.toml'))
73
+ return 'config_patch';
74
+ if (path.endsWith('.ts') || path.endsWith('.js') || path.endsWith('.mjs'))
75
+ return 'file_patch';
76
+ return 'file_patch';
77
+ }
78
+ function digestBase(input) {
79
+ return crypto.createHash('sha256').update(JSON.stringify({
80
+ task: input.task,
81
+ gitStatus: input.gitStatus || '',
82
+ paths: input.mentionedPaths
83
+ })).digest('hex').slice(0, 16);
84
+ }
85
+ export function validateWorkGraph(graph, isVerifyOnly) {
86
+ if (isVerifyOnly)
87
+ return { ok: true };
88
+ const mutableCount = graph.mutable_shards.length;
89
+ if (mutableCount === 0)
90
+ return { ok: false, reason: 'glm_naruto_invalid_verify_only_plan' };
91
+ // Check ratio of mutable shards to total shards (excluding verification shards from the denominator)
92
+ const totalWorkShards = graph.shards.filter(s => s.mutable || s.kind !== 'verification').length;
93
+ const ratio = totalWorkShards > 0 ? mutableCount / totalWorkShards : 0;
94
+ if (ratio < GLM_NARUTO_DEFAULTS.patch_worker_ratio) {
95
+ return { ok: false, reason: 'glm_naruto_insufficient_patch_workers' };
96
+ }
97
+ return { ok: true };
98
+ }
99
+ //# sourceMappingURL=glm-naruto-decomposer.js.map
@@ -0,0 +1,23 @@
1
+ export function planFileLeases(shardTargetPaths) {
2
+ const pathToShards = new Map();
3
+ for (const [shardId, paths] of shardTargetPaths) {
4
+ for (const p of paths) {
5
+ const list = pathToShards.get(p) || [];
6
+ list.push(shardId);
7
+ pathToShards.set(p, list);
8
+ }
9
+ }
10
+ const leases = [];
11
+ for (const [path, shardIds] of pathToShards) {
12
+ leases.push({
13
+ path,
14
+ shardIds,
15
+ exclusive: shardIds.length === 1
16
+ });
17
+ }
18
+ return leases;
19
+ }
20
+ export function hasLeaseConflict(leases, shardId) {
21
+ return leases.some((lease) => !lease.exclusive && lease.shardIds.includes(shardId));
22
+ }
23
+ //# sourceMappingURL=glm-naruto-file-lease.js.map
@@ -0,0 +1,22 @@
1
+ import { planMerge } from './glm-naruto-merge-planner.js';
2
+ import { buildConflictGraph } from './glm-naruto-conflict-graph.js';
3
+ export function finalizeMergePlan(input) {
4
+ const passedEnvelopes = input.envelopes.filter((e) => e.status === 'gate_passed');
5
+ const nodes = passedEnvelopes.map((env) => ({
6
+ patch_id: env.worker_id,
7
+ shard_id: env.shard_id,
8
+ target_paths: env.target_paths,
9
+ score: Math.max(0, 100 - Math.floor(env.patch.length / 100)),
10
+ gate_passed: true,
11
+ patch_sha256: env.patch_sha256
12
+ }));
13
+ const conflictGraph = buildConflictGraph(passedEnvelopes, nodes);
14
+ const strategy = input.useJudge && input.judgeResult ? 'judge' : 'deterministic';
15
+ return planMerge({
16
+ missionId: input.missionId,
17
+ graph: conflictGraph,
18
+ strategy,
19
+ ...(input.judgeResult ? { judgeRanking: input.judgeResult.ranked_patch_ids } : {})
20
+ });
21
+ }
22
+ //# sourceMappingURL=glm-naruto-finalizer.js.map