sneakoscope 4.0.7 → 4.0.9

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 (49) hide show
  1. package/README.md +2 -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/cli/command-registry.js +1 -0
  7. package/dist/core/commands/glm-command.js +8 -1
  8. package/dist/core/commands/naruto-command.js +25 -0
  9. package/dist/core/commands/stop-gate-command.js +63 -0
  10. package/dist/core/fsx.js +1 -1
  11. package/dist/core/pipeline-internals/runtime-gates.js +28 -4
  12. package/dist/core/providers/glm/glm-bench.js +4 -4
  13. package/dist/core/providers/glm/glm-direct-run.js +1 -1
  14. package/dist/core/providers/glm/glm-latency-trace.js +1 -1
  15. package/dist/core/providers/glm/glm-request-cache.js +10 -2
  16. package/dist/core/providers/glm/naruto/glm-naruto-artifacts.js +2 -0
  17. package/dist/core/providers/glm/naruto/glm-naruto-bench.js +68 -0
  18. package/dist/core/providers/glm/naruto/glm-naruto-budget.js +45 -0
  19. package/dist/core/providers/glm/naruto/glm-naruto-command.js +97 -0
  20. package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +37 -0
  21. package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +74 -0
  22. package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +99 -0
  23. package/dist/core/providers/glm/naruto/glm-naruto-file-lease.js +23 -0
  24. package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +22 -0
  25. package/dist/core/providers/glm/naruto/glm-naruto-judge.js +84 -0
  26. package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +57 -0
  27. package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +277 -0
  28. package/dist/core/providers/glm/naruto/glm-naruto-patch-envelope.js +55 -0
  29. package/dist/core/providers/glm/naruto/glm-naruto-quorum.js +37 -0
  30. package/dist/core/providers/glm/naruto/glm-naruto-rate-limiter.js +18 -0
  31. package/dist/core/providers/glm/naruto/glm-naruto-repair-wave.js +21 -0
  32. package/dist/core/providers/glm/naruto/glm-naruto-shard-planner.js +32 -0
  33. package/dist/core/providers/glm/naruto/glm-naruto-trace.js +91 -0
  34. package/dist/core/providers/glm/naruto/glm-naruto-types.js +37 -0
  35. package/dist/core/providers/glm/naruto/glm-naruto-work-graph.js +2 -0
  36. package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +79 -0
  37. package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +198 -0
  38. package/dist/core/providers/glm/naruto/glm-naruto-worker.js +2 -0
  39. package/dist/core/providers/glm/naruto/glm-naruto-worktree.js +48 -0
  40. package/dist/core/providers/openrouter/openrouter-provider-health.js +46 -0
  41. package/dist/core/providers/openrouter/openrouter-secret-store.js +33 -0
  42. package/dist/core/providers/openrouter/openrouter-stream.js +101 -8
  43. package/dist/core/stop-gate/stop-gate-check.js +208 -0
  44. package/dist/core/stop-gate/stop-gate-diagnostics.js +4 -0
  45. package/dist/core/stop-gate/stop-gate-resolver.js +122 -0
  46. package/dist/core/stop-gate/stop-gate-types.js +2 -0
  47. package/dist/core/stop-gate/stop-gate-writer.js +76 -0
  48. package/dist/core/version.js +1 -1
  49. package/package.json +1 -1
@@ -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
@@ -0,0 +1,84 @@
1
+ import { buildGlm52Request } from '../glm-52-request.js';
2
+ import { sendOpenRouterChatCompletionStream } from '../../openrouter/openrouter-stream.js';
3
+ import { assertGlm52ActualModel } from '../glm-52-response-guard.js';
4
+ import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
5
+ export async function runGlmJudge(input) {
6
+ const validEnvelopes = input.envelopes.filter((e) => e.status === 'gate_passed');
7
+ if (validEnvelopes.length === 0) {
8
+ return {
9
+ schema: 'sks.glm-naruto-judge.v1',
10
+ ranked_patch_ids: [],
11
+ reject_patch_ids: [],
12
+ mergeable_sets: [],
13
+ risks: ['no_gate_passed_candidates'],
14
+ requires_repair_wave: false
15
+ };
16
+ }
17
+ const systemPrompt = `You are a GLM Naruto judge. Rank patch candidates by quality. Output strict JSON with schema: {"ranked_patch_ids":["id1","id2"],"reject_patch_ids":["id3"],"mergeable_sets":[["id1","id2"]],"risks":[],"requires_repair_wave":false}. Model: ${GLM_52_OPENROUTER_MODEL}. No GPT fallback.`;
18
+ const candidateDescriptions = validEnvelopes.map((e) => ({
19
+ patch_id: e.worker_id,
20
+ shard_id: e.shard_id,
21
+ target_paths: e.target_paths,
22
+ patch_sha256: e.patch_sha256.slice(0, 12),
23
+ strategy: e.strategy,
24
+ patch_size: e.patch.length
25
+ }));
26
+ const userContent = JSON.stringify({
27
+ mission_id: input.missionId,
28
+ candidates: candidateDescriptions,
29
+ instruction: 'Rank by: gate pass, minimal diff, correct target paths, no protected paths. Return mergeable non-conflicting sets.'
30
+ });
31
+ const messages = [
32
+ { role: 'system', content: systemPrompt },
33
+ { role: 'user', content: userContent }
34
+ ];
35
+ const request = buildGlm52Request({
36
+ profile: 'deep',
37
+ messages,
38
+ maxTokens: 8192,
39
+ reasoningEffort: 'high',
40
+ toolChoice: 'none',
41
+ parallelToolCalls: false
42
+ });
43
+ const response = await sendOpenRouterChatCompletionStream({
44
+ apiKey: input.apiKey,
45
+ request: { ...request, session_id: `sks-glm-naruto-judge-${input.missionId}` },
46
+ timeoutMs: input.timeoutMs || 120_000
47
+ });
48
+ if (!response.ok) {
49
+ return fallbackJudgeResult(validEnvelopes, [`judge_request_failed:${response.error.code}`]);
50
+ }
51
+ const modelGuard = assertGlm52ActualModel(response.value.model);
52
+ if (!modelGuard.ok) {
53
+ return fallbackJudgeResult(validEnvelopes, [`judge_model_guard:${modelGuard.code}`]);
54
+ }
55
+ try {
56
+ const jsonMatch = response.value.content.match(/\{[\s\S]*\}/);
57
+ if (!jsonMatch)
58
+ return fallbackJudgeResult(validEnvelopes, ['judge_no_json_output']);
59
+ const parsed = JSON.parse(jsonMatch[0]);
60
+ return {
61
+ schema: 'sks.glm-naruto-judge.v1',
62
+ ranked_patch_ids: Array.isArray(parsed.ranked_patch_ids) ? parsed.ranked_patch_ids : [],
63
+ reject_patch_ids: Array.isArray(parsed.reject_patch_ids) ? parsed.reject_patch_ids : [],
64
+ mergeable_sets: Array.isArray(parsed.mergeable_sets) ? parsed.mergeable_sets : [],
65
+ risks: Array.isArray(parsed.risks) ? parsed.risks : [],
66
+ requires_repair_wave: Boolean(parsed.requires_repair_wave)
67
+ };
68
+ }
69
+ catch {
70
+ return fallbackJudgeResult(validEnvelopes, ['judge_json_parse_failed']);
71
+ }
72
+ }
73
+ function fallbackJudgeResult(envelopes, risks) {
74
+ const sorted = [...envelopes].sort((a, b) => a.patch.length - b.patch.length);
75
+ return {
76
+ schema: 'sks.glm-naruto-judge.v1',
77
+ ranked_patch_ids: sorted.map((e) => e.worker_id),
78
+ reject_patch_ids: [],
79
+ mergeable_sets: sorted.length > 0 ? [[sorted[0].worker_id]] : [],
80
+ risks,
81
+ requires_repair_wave: false
82
+ };
83
+ }
84
+ //# sourceMappingURL=glm-naruto-judge.js.map
@@ -0,0 +1,57 @@
1
+ import { getNonConflictingSets } from './glm-naruto-conflict-graph.js';
2
+ export function planMerge(input) {
3
+ const passedNodes = input.graph.nodes.filter((n) => n.gate_passed);
4
+ const nonConflictingSets = getNonConflictingSets(input.graph);
5
+ const candidates = nonConflictingSets.map((patchIds) => {
6
+ const nodes = passedNodes.filter((n) => patchIds.includes(n.patch_id));
7
+ const totalScore = nodes.reduce((sum, n) => sum + n.score, 0);
8
+ return { patch_ids: patchIds, total_score: totalScore, conflict_free: true };
9
+ });
10
+ candidates.sort((a, b) => b.total_score - a.total_score);
11
+ let selected = [];
12
+ let rationale = '';
13
+ if (input.strategy === 'judge' && input.judgeRanking && input.judgeRanking.length > 0) {
14
+ const ranked = input.judgeRanking.filter((id) => passedNodes.some((n) => n.patch_id === id));
15
+ const bestSet = candidates.find((set) => set.patch_ids.every((id) => ranked.includes(id))) || candidates[0];
16
+ selected = bestSet ? bestSet.patch_ids : [];
17
+ rationale = 'judge_ranked_deterministic_gated';
18
+ }
19
+ else if (input.strategy === 'quorum') {
20
+ const quorumMap = new Map();
21
+ for (const node of passedNodes) {
22
+ const key = node.shard_id;
23
+ quorumMap.set(key, (quorumMap.get(key) || 0) + 1);
24
+ }
25
+ const bestSet = candidates[0];
26
+ selected = bestSet ? bestSet.patch_ids : [];
27
+ rationale = 'quorum_consensus_deterministic_gated';
28
+ }
29
+ else {
30
+ const bestSet = candidates[0];
31
+ selected = bestSet ? bestSet.patch_ids : [];
32
+ rationale = 'highest_score_non_conflicting';
33
+ }
34
+ return {
35
+ schema: 'sks.glm-naruto-merge-plan.v1',
36
+ mission_id: input.missionId,
37
+ strategy: input.strategy,
38
+ selected_patches: selected,
39
+ candidates,
40
+ rationale
41
+ };
42
+ }
43
+ export function scoreCandidate(input) {
44
+ let score = 0;
45
+ if (input.node.gate_passed)
46
+ score += 100;
47
+ score -= input.patchSize / 100;
48
+ score -= input.touchedPathsCount * 5;
49
+ if (input.protectedPath)
50
+ score -= 200;
51
+ if (input.testFailure)
52
+ score -= 50;
53
+ if (input.judgeRank !== null && input.judgeRank !== undefined)
54
+ score += Math.max(0, 50 - input.judgeRank * 10);
55
+ return Math.round(score);
56
+ }
57
+ //# sourceMappingURL=glm-naruto-merge-planner.js.map
@@ -0,0 +1,277 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../../../fsx.js';
3
+ import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
4
+ import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
5
+ import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
6
+ import { decomposeTask, validateWorkGraph } from './glm-naruto-decomposer.js';
7
+ import { planShardCandidates, computeInitialLaneMix } from './glm-naruto-shard-planner.js';
8
+ import { runPatchWorkerPool } from './glm-naruto-worker-pool.js';
9
+ import { runVerifierWorker } from './glm-naruto-worker-runtime.js';
10
+ import { buildConflictGraph } from './glm-naruto-conflict-graph.js';
11
+ import { planMerge } from './glm-naruto-merge-planner.js';
12
+ import { finalizeMergePlan } from './glm-naruto-finalizer.js';
13
+ import { planRepairWave } from './glm-naruto-repair-wave.js';
14
+ import { createBudget, checkBudget, recordRequest } from './glm-naruto-budget.js';
15
+ import { createProviderHealthTracker } from '../../openrouter/openrouter-provider-health.js';
16
+ import { createMissionTrace, recordWorkerTrace, writeMissionArtifacts, buildMissionSummary } from './glm-naruto-trace.js';
17
+ import { runGlmJudge } from './glm-naruto-judge.js';
18
+ import { writeFinalStopGate } from '../../../stop-gate/stop-gate-writer.js';
19
+ import { GLM_NARUTO_LIMITS } from './glm-naruto-types.js';
20
+ export async function runGlmNarutoMission(input) {
21
+ const missionId = input.missionId || `glm-naruto-${nowIso().replace(/[:.]/g, '-')}`;
22
+ const cwd = input.cwd;
23
+ const startedMs = Date.now();
24
+ const key = await resolveOpenRouterApiKey({ env: process.env });
25
+ if (!key.key) {
26
+ return missionResult(missionId, input.task, 'blocked', 'glm_missing_openrouter_key', 0, startedMs, [], [], ['glm_missing_openrouter_key'], []);
27
+ }
28
+ const mentionedPaths = extractMentionedPaths(input.task);
29
+ const gitStatus = await readGitStatus(cwd);
30
+ const graph = decomposeTask({
31
+ missionId,
32
+ task: input.task,
33
+ gitStatus,
34
+ mentionedPaths
35
+ });
36
+ const isVerifyOnly = input.task.trim().toLowerCase().startsWith('verify');
37
+ const validation = validateWorkGraph(graph, isVerifyOnly);
38
+ if (!validation.ok) {
39
+ return missionResult(missionId, input.task, 'blocked', validation.reason || 'invalid_work_graph', 0, startedMs, [], [], [validation.reason || 'invalid_work_graph'], []);
40
+ }
41
+ const budget = createBudget(missionId, input.deep || false);
42
+ const budgetCheck = checkBudget(budget);
43
+ if (!budgetCheck.ok) {
44
+ return missionResult(missionId, input.task, 'budget_exhausted', budgetCheck.reason, 0, startedMs, [], [], [budgetCheck.reason], []);
45
+ }
46
+ const laneMix = computeInitialLaneMix(graph);
47
+ const strategies = planShardCandidates(graph);
48
+ const strategyMap = new Map();
49
+ for (const entry of strategies) {
50
+ strategyMap.set(entry.shard.id, entry.strategies);
51
+ }
52
+ const healthTracker = createProviderHealthTracker();
53
+ let traceState = createMissionTrace(missionId);
54
+ // Wave 1: parallel patch candidate generation
55
+ const poolResult = await runPatchWorkerPool({
56
+ apiKey: key.key,
57
+ missionId,
58
+ cwd,
59
+ shards: graph.shards,
60
+ contextSummary: JSON.stringify({ task: input.task, git_status: gitStatus || '' }),
61
+ maxWorkers: input.maxWorkers || laneMix.patch_workers,
62
+ workerTimeoutMs: GLM_NARUTO_LIMITS.max_worker_runtime_ms,
63
+ strategies: strategyMap
64
+ });
65
+ for (const trace of poolResult.traces) {
66
+ traceState = recordWorkerTrace(traceState, trace);
67
+ }
68
+ healthTracker.record({ provider_slug: 'openrouter', model: GLM_52_OPENROUTER_MODEL, count_429: 0, count_5xx: 0 });
69
+ let envelopes = poolResult.envelopes;
70
+ let failedShardIds = poolResult.failedShardIds;
71
+ let repairWaves = 0;
72
+ // Repair wave if needed
73
+ if (failedShardIds.length > 0 && repairWaves < GLM_NARUTO_LIMITS.max_repair_waves) {
74
+ const repairPlan = planRepairWave({
75
+ failedEnvelopes: envelopes.filter((e) => e.status === 'gate_failed'),
76
+ shards: graph.shards,
77
+ repairWaveCount: repairWaves
78
+ });
79
+ if (repairPlan.canRepair && repairPlan.shardsToRepair.length > 0) {
80
+ repairWaves++;
81
+ const repairPool = await runPatchWorkerPool({
82
+ apiKey: key.key,
83
+ missionId,
84
+ cwd,
85
+ shards: repairPlan.shardsToRepair,
86
+ contextSummary: JSON.stringify({ task: input.task, repair: true }),
87
+ maxWorkers: input.maxWorkers || 3,
88
+ workerTimeoutMs: GLM_NARUTO_LIMITS.max_worker_runtime_ms,
89
+ strategies: new Map(repairPlan.shardsToRepair.map((s) => [s.id, [s.strategy]]))
90
+ });
91
+ envelopes = [...envelopes, ...repairPool.envelopes];
92
+ for (const trace of repairPool.traces) {
93
+ traceState = recordWorkerTrace(traceState, trace);
94
+ }
95
+ failedShardIds = [...failedShardIds, ...repairPool.failedShardIds];
96
+ }
97
+ }
98
+ // 4.0.9: Verifier wave — run parallel verifier workers over gate-passed candidates.
99
+ let passedEnvelopes = envelopes.filter((e) => e.status === 'gate_passed');
100
+ if (passedEnvelopes.length > 0 && !input.noApply) {
101
+ const verifyApiKey = key.key;
102
+ const verifyResults = await Promise.allSettled(passedEnvelopes.map((env) => runVerifierWorker({
103
+ apiKey: verifyApiKey,
104
+ missionId,
105
+ workerId: env.worker_id,
106
+ envelope: env,
107
+ timeoutMs: 120_000,
108
+ })));
109
+ const verifiedEnvelopes = [];
110
+ for (let vi = 0; vi < passedEnvelopes.length; vi++) {
111
+ const env = passedEnvelopes[vi];
112
+ const res = verifyResults[vi];
113
+ if (res.status === 'fulfilled' && res.value.ok) {
114
+ verifiedEnvelopes.push({ ...env, verification_passed: true, status: 'gate_passed' });
115
+ }
116
+ else {
117
+ verifiedEnvelopes.push({ ...env, verification_passed: false, status: 'verification_failed' });
118
+ }
119
+ if (res.status === 'fulfilled') {
120
+ traceState = recordWorkerTrace(traceState, res.value.trace);
121
+ }
122
+ }
123
+ envelopes = envelopes.map((e) => {
124
+ const verified = verifiedEnvelopes.find((v) => v.worker_id === e.worker_id);
125
+ return verified ?? e;
126
+ });
127
+ passedEnvelopes = envelopes.filter((e) => e.status === 'gate_passed');
128
+ }
129
+ // Build conflict graph and merge plan
130
+ const nodes = passedEnvelopes.map((env) => ({
131
+ patch_id: env.worker_id,
132
+ shard_id: env.shard_id,
133
+ target_paths: env.target_paths,
134
+ score: Math.max(0, 100 - Math.floor(env.patch.length / 100)),
135
+ gate_passed: true,
136
+ patch_sha256: env.patch_sha256
137
+ }));
138
+ const conflictGraph = buildConflictGraph(passedEnvelopes, nodes);
139
+ let judgeResult = null;
140
+ if (input.useJudge && passedEnvelopes.length > 1) {
141
+ judgeResult = await runGlmJudge({
142
+ apiKey: key.key,
143
+ missionId,
144
+ envelopes: passedEnvelopes,
145
+ timeoutMs: 120_000
146
+ });
147
+ }
148
+ const mergePlan = finalizeMergePlan({
149
+ missionId,
150
+ envelopes: passedEnvelopes,
151
+ ...(judgeResult ? { judgeResult } : {}),
152
+ useJudge: input.useJudge || false,
153
+ xhighFinalizer: input.xhighFinalizer || false
154
+ });
155
+ // Apply winning merge plan
156
+ let appliedPatches = 0;
157
+ let applyResult = null;
158
+ if (!input.noApply && mergePlan.selected_patches.length > 0) {
159
+ for (const patchId of mergePlan.selected_patches) {
160
+ const envelope = envelopes.find((e) => e.worker_id === patchId);
161
+ if (!envelope)
162
+ continue;
163
+ const applied = await checkAndApplyGlmPatch({ cwd, patch: envelope.patch, apply: true });
164
+ if (applied.ok) {
165
+ appliedPatches++;
166
+ }
167
+ }
168
+ applyResult = { ok: appliedPatches > 0, applied: mergePlan.selected_patches };
169
+ }
170
+ const terminalState = appliedPatches > 0 ? 'completed' : passedEnvelopes.length > 0 ? 'partial_candidates' : 'blocked';
171
+ const terminationReason = appliedPatches > 0 ? 'completed_merge_applied' : passedEnvelopes.length > 0 ? 'partial_no_apply' : 'no_gate_passed_candidates';
172
+ const summary = buildMissionSummary({
173
+ missionId,
174
+ startedMs,
175
+ workerTraces: traceState.workerTraces,
176
+ patchCandidates: envelopes.length,
177
+ gatePassed: passedEnvelopes.length,
178
+ mergeable: mergePlan.candidates.length,
179
+ appliedPatches,
180
+ failedShards: failedShardIds.length,
181
+ repairWaves
182
+ });
183
+ const result = {
184
+ schema: 'sks.glm-naruto-mission-result.v1',
185
+ ok: terminalState === 'completed',
186
+ status: terminalState,
187
+ mission_id: missionId,
188
+ task: input.task,
189
+ model: GLM_52_OPENROUTER_MODEL,
190
+ gpt_fallback_allowed: false,
191
+ termination_reason: terminationReason,
192
+ workers_started: summary.workers_started,
193
+ workers_completed: summary.workers_completed,
194
+ patch_candidates: summary.patch_candidates,
195
+ gate_passed_candidates: summary.gate_passed_candidates,
196
+ mergeable_candidates: summary.mergeable_candidates,
197
+ applied_patches: summary.applied_patches,
198
+ failed_shards: summary.failed_shards,
199
+ repair_waves: summary.repair_waves,
200
+ budget_used_ms: summary.budget_used_ms,
201
+ blockers: terminalState === 'blocked' ? ['no_gate_passed_candidates'] : [],
202
+ warnings: []
203
+ };
204
+ const artifactDir = await writeMissionArtifacts({
205
+ root: cwd,
206
+ missionId,
207
+ workGraph: graph,
208
+ conflictGraph,
209
+ mergePlan,
210
+ ...(judgeResult ? { judgeResult } : {}),
211
+ workerTraces: traceState.workerTraces,
212
+ providerHealth: healthTracker.snapshot(),
213
+ termination: { schema: 'sks.glm-naruto-termination.v1', mission_id: missionId, terminal_state: terminalState, reason: terminationReason, wall_clock_ms: summary.wall_clock_ms },
214
+ ...(applyResult ? { applyResult: { ...applyResult, schema: 'sks.glm-naruto-apply-result.v1' } } : {}),
215
+ verificationSummary: { schema: 'sks.glm-naruto-verification.v1', verified: passedEnvelopes.length, total: envelopes.length },
216
+ missionResult: result,
217
+ envelopes
218
+ });
219
+ // 4.0.9: Write canonical stop-gate artifacts for hook resolution.
220
+ await writeFinalStopGate({
221
+ root: cwd,
222
+ missionId,
223
+ route: 'GLM_NARUTO',
224
+ routeCommand: '$Naruto',
225
+ status: result.ok ? 'passed' : (terminalState === 'blocked' ? 'blocked' : 'failed'),
226
+ terminal: terminalState === 'completed' || terminalState === 'blocked',
227
+ terminalState,
228
+ evidence: {
229
+ build_passed: result.ok,
230
+ tests_passed: result.ok,
231
+ route_evidence_passed: result.ok,
232
+ per_worker_artifacts: true,
233
+ verifier_wave_run: true,
234
+ model_guard_enforced: true,
235
+ },
236
+ blockers: result.blockers || [],
237
+ nativeGateFile: 'termination.json',
238
+ }).catch(() => null);
239
+ return { ...result, artifact_dir: artifactDir };
240
+ }
241
+ function missionResult(missionId, task, status, reason, patchCandidates, startedMs, envelopes, traces, blockers, warnings) {
242
+ return {
243
+ schema: 'sks.glm-naruto-mission-result.v1',
244
+ ok: status === 'completed',
245
+ status,
246
+ mission_id: missionId,
247
+ task,
248
+ model: GLM_52_OPENROUTER_MODEL,
249
+ gpt_fallback_allowed: false,
250
+ termination_reason: reason,
251
+ workers_started: traces.length,
252
+ workers_completed: traces.filter((t) => t.status === 'completed').length,
253
+ patch_candidates: envelopes.length,
254
+ gate_passed_candidates: envelopes.filter((e) => e.status === 'gate_passed').length,
255
+ mergeable_candidates: 0,
256
+ applied_patches: 0,
257
+ failed_shards: 0,
258
+ repair_waves: 0,
259
+ budget_used_ms: Date.now() - startedMs,
260
+ blockers,
261
+ warnings
262
+ };
263
+ }
264
+ async function readGitStatus(cwd) {
265
+ const { spawn } = await import('node:child_process');
266
+ return new Promise((resolve) => {
267
+ const child = spawn('git', ['status', '--short'], { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
268
+ let stdout = '';
269
+ child.stdout.on('data', (chunk) => { stdout += String(chunk); });
270
+ child.on('close', () => resolve(stdout.trim() || undefined));
271
+ });
272
+ }
273
+ function extractMentionedPaths(task) {
274
+ const matches = task.match(/(?:^|\s|[`"'])([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+\.[A-Za-z0-9]+)(?:\s|[`"']|$)/g) || [];
275
+ return [...new Set(matches.map((value) => value.trim().replace(/^[`"']|[`"']$/g, '')))];
276
+ }
277
+ //# sourceMappingURL=glm-naruto-orchestrator.js.map