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.
- package/README.md +6 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/core/commands/glm-command.js +9 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/providers/glm/glm-bench.js +4 -4
- package/dist/core/providers/glm/glm-latency-trace.js +1 -1
- package/dist/core/providers/glm/glm-request-cache.js +10 -2
- package/dist/core/providers/glm/naruto/glm-naruto-artifacts.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-bench.js +68 -0
- package/dist/core/providers/glm/naruto/glm-naruto-budget.js +45 -0
- package/dist/core/providers/glm/naruto/glm-naruto-command.js +97 -0
- package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +74 -0
- package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +99 -0
- package/dist/core/providers/glm/naruto/glm-naruto-file-lease.js +23 -0
- package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +22 -0
- package/dist/core/providers/glm/naruto/glm-naruto-judge.js +84 -0
- package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +57 -0
- package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +224 -0
- package/dist/core/providers/glm/naruto/glm-naruto-patch-envelope.js +55 -0
- package/dist/core/providers/glm/naruto/glm-naruto-quorum.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-rate-limiter.js +18 -0
- package/dist/core/providers/glm/naruto/glm-naruto-repair-wave.js +21 -0
- package/dist/core/providers/glm/naruto/glm-naruto-shard-planner.js +32 -0
- package/dist/core/providers/glm/naruto/glm-naruto-trace.js +51 -0
- package/dist/core/providers/glm/naruto/glm-naruto-types.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-work-graph.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +79 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +196 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worktree.js +48 -0
- package/dist/core/providers/openrouter/openrouter-provider-health.js +46 -0
- package/dist/core/providers/openrouter/openrouter-secret-store.js +33 -0
- package/dist/core/providers/openrouter/openrouter-stream.js +73 -5
- package/dist/core/version.js +1 -1
- 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.
|
|
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.
|
|
@@ -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.
|
|
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
|
@@ -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, '--
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
72
|
+
version: '4.0.8',
|
|
73
73
|
generated_at: nowIso(),
|
|
74
74
|
status: 'dry_run',
|
|
75
75
|
dry_run: true,
|
|
@@ -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,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
|