sneakoscope 4.0.9 → 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 (43) 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/pipeline-internals/runtime-gates.js +5 -2
  7. package/dist/core/providers/glm/glm-request-cache.js +9 -4
  8. package/dist/core/providers/glm/naruto/glm-naruto-apply-transaction.js +90 -0
  9. package/dist/core/providers/glm/naruto/glm-naruto-bench.js +65 -12
  10. package/dist/core/providers/glm/naruto/glm-naruto-combined-patch.js +49 -0
  11. package/dist/core/providers/glm/naruto/glm-naruto-command.js +9 -22
  12. package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +10 -9
  13. package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +7 -1
  14. package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +54 -4
  15. package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +1 -0
  16. package/dist/core/providers/glm/naruto/glm-naruto-hunk-conflict.js +16 -0
  17. package/dist/core/providers/glm/naruto/glm-naruto-hunk-parser.js +36 -0
  18. package/dist/core/providers/glm/naruto/glm-naruto-isolation-policy.js +38 -0
  19. package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +9 -4
  20. package/dist/core/providers/glm/naruto/glm-naruto-metrics.js +34 -0
  21. package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +116 -28
  22. package/dist/core/providers/glm/naruto/glm-naruto-patch-candidate-gate.js +42 -0
  23. package/dist/core/providers/glm/naruto/glm-naruto-patch-candidate-parser.js +62 -0
  24. package/dist/core/providers/glm/naruto/glm-naruto-scoreboard.js +75 -0
  25. package/dist/core/providers/glm/naruto/glm-naruto-secret-audit.js +91 -0
  26. package/dist/core/providers/glm/naruto/glm-naruto-session-id.js +10 -0
  27. package/dist/core/providers/glm/naruto/glm-naruto-terminal.js +44 -0
  28. package/dist/core/providers/glm/naruto/glm-naruto-trace.js +47 -18
  29. package/dist/core/providers/glm/naruto/glm-naruto-usage-extractor.js +31 -0
  30. package/dist/core/providers/glm/naruto/glm-naruto-verifier-output.js +26 -0
  31. package/dist/core/providers/glm/naruto/glm-naruto-worker-artifacts.js +56 -0
  32. package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +81 -12
  33. package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +217 -19
  34. package/dist/core/providers/glm/naruto/glm-naruto-worktree-cleanup.js +20 -0
  35. package/dist/core/providers/glm/naruto/glm-naruto-worktree-manager.js +57 -0
  36. package/dist/core/providers/glm/naruto/glm-naruto-worktree-worker.js +44 -0
  37. package/dist/core/providers/openrouter/openrouter-client.js +1 -1
  38. package/dist/core/providers/openrouter/openrouter-provider-health.js +3 -2
  39. package/dist/core/providers/openrouter/openrouter-stream.js +31 -14
  40. package/dist/core/stop-gate/stop-gate-check.js +14 -2
  41. package/dist/core/stop-gate/stop-gate-writer.js +61 -11
  42. package/dist/core/version.js +1 -1
  43. package/package.json +1 -1
@@ -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
@@ -2,20 +2,24 @@ import path from 'node:path';
2
2
  import { nowIso, writeJsonAtomic } from '../../../fsx.js';
3
3
  import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
4
4
  import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
5
- import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
6
5
  import { decomposeTask, validateWorkGraph } from './glm-naruto-decomposer.js';
7
6
  import { planShardCandidates, computeInitialLaneMix } from './glm-naruto-shard-planner.js';
8
7
  import { runPatchWorkerPool } from './glm-naruto-worker-pool.js';
9
8
  import { runVerifierWorker } from './glm-naruto-worker-runtime.js';
10
9
  import { buildConflictGraph } from './glm-naruto-conflict-graph.js';
11
- import { planMerge } from './glm-naruto-merge-planner.js';
12
10
  import { finalizeMergePlan } from './glm-naruto-finalizer.js';
13
11
  import { planRepairWave } from './glm-naruto-repair-wave.js';
14
- import { createBudget, checkBudget, recordRequest } from './glm-naruto-budget.js';
12
+ import { createBudget, checkBudget } from './glm-naruto-budget.js';
15
13
  import { createProviderHealthTracker } from '../../openrouter/openrouter-provider-health.js';
16
14
  import { createMissionTrace, recordWorkerTrace, writeMissionArtifacts, buildMissionSummary } from './glm-naruto-trace.js';
17
15
  import { runGlmJudge } from './glm-naruto-judge.js';
18
16
  import { writeFinalStopGate } from '../../../stop-gate/stop-gate-writer.js';
17
+ import { auditGlmNarutoArtifactsForSecrets } from './glm-naruto-secret-audit.js';
18
+ import { resolveGlmNarutoIsolationPolicy } from './glm-naruto-isolation-policy.js';
19
+ import { getGitHead, getGitRoot } from './glm-naruto-worktree-manager.js';
20
+ import { buildGlmNarutoCandidateScoreboard } from './glm-naruto-scoreboard.js';
21
+ import { runGlmNarutoApplyTransaction } from './glm-naruto-apply-transaction.js';
22
+ import { finalizeGlmNarutoTerminal } from './glm-naruto-terminal.js';
19
23
  import { GLM_NARUTO_LIMITS } from './glm-naruto-types.js';
20
24
  export async function runGlmNarutoMission(input) {
21
25
  const missionId = input.missionId || `glm-naruto-${nowIso().replace(/[:.]/g, '-')}`;
@@ -23,10 +27,29 @@ export async function runGlmNarutoMission(input) {
23
27
  const startedMs = Date.now();
24
28
  const key = await resolveOpenRouterApiKey({ env: process.env });
25
29
  if (!key.key) {
26
- return missionResult(missionId, input.task, 'blocked', 'glm_missing_openrouter_key', 0, startedMs, [], [], ['glm_missing_openrouter_key'], []);
30
+ return finalizeGlmNarutoTerminal({
31
+ root: cwd,
32
+ missionId,
33
+ result: missionResult(missionId, input.task, 'blocked', 'glm_missing_openrouter_key', 0, startedMs, [], [], ['glm_missing_openrouter_key'], [])
34
+ });
27
35
  }
28
36
  const mentionedPaths = extractMentionedPaths(input.task);
29
37
  const gitStatus = await readGitStatus(cwd);
38
+ const gitRoot = await getGitRoot(cwd);
39
+ const baseCommit = gitRoot ? await getGitHead(cwd) : null;
40
+ const isolationPolicy = resolveGlmNarutoIsolationPolicy({
41
+ ...(input.useWorktree !== undefined ? { useWorktree: input.useWorktree } : {}),
42
+ ...(input.patchEnvelopeOnly !== undefined ? { patchEnvelopeOnly: input.patchEnvelopeOnly } : {}),
43
+ ...(input.allowPatchEnvelopeFallback !== undefined ? { fallbackAllowed: input.allowPatchEnvelopeFallback } : {}),
44
+ gitAvailable: Boolean(gitRoot && baseCommit)
45
+ });
46
+ if (isolationPolicy.selected === 'blocked') {
47
+ return finalizeGlmNarutoTerminal({
48
+ root: cwd,
49
+ missionId,
50
+ result: missionResult(missionId, input.task, 'blocked', isolationPolicy.reason, 0, startedMs, [], [], isolationPolicy.blockers, [])
51
+ });
52
+ }
30
53
  const graph = decomposeTask({
31
54
  missionId,
32
55
  task: input.task,
@@ -36,12 +59,20 @@ export async function runGlmNarutoMission(input) {
36
59
  const isVerifyOnly = input.task.trim().toLowerCase().startsWith('verify');
37
60
  const validation = validateWorkGraph(graph, isVerifyOnly);
38
61
  if (!validation.ok) {
39
- return missionResult(missionId, input.task, 'blocked', validation.reason || 'invalid_work_graph', 0, startedMs, [], [], [validation.reason || 'invalid_work_graph'], []);
62
+ return finalizeGlmNarutoTerminal({
63
+ root: cwd,
64
+ missionId,
65
+ result: missionResult(missionId, input.task, 'blocked', validation.reason || 'invalid_work_graph', 0, startedMs, [], [], [validation.reason || 'invalid_work_graph'], [])
66
+ });
40
67
  }
41
68
  const budget = createBudget(missionId, input.deep || false);
42
69
  const budgetCheck = checkBudget(budget);
43
70
  if (!budgetCheck.ok) {
44
- return missionResult(missionId, input.task, 'budget_exhausted', budgetCheck.reason, 0, startedMs, [], [], [budgetCheck.reason], []);
71
+ return finalizeGlmNarutoTerminal({
72
+ root: cwd,
73
+ missionId,
74
+ result: missionResult(missionId, input.task, 'budget_exhausted', budgetCheck.reason, 0, startedMs, [], [], [budgetCheck.reason], [])
75
+ });
45
76
  }
46
77
  const laneMix = computeInitialLaneMix(graph);
47
78
  const strategies = planShardCandidates(graph);
@@ -60,10 +91,22 @@ export async function runGlmNarutoMission(input) {
60
91
  contextSummary: JSON.stringify({ task: input.task, git_status: gitStatus || '' }),
61
92
  maxWorkers: input.maxWorkers || laneMix.patch_workers,
62
93
  workerTimeoutMs: GLM_NARUTO_LIMITS.max_worker_runtime_ms,
63
- strategies: strategyMap
94
+ strategies: strategyMap,
95
+ isolationMode: isolationPolicy.selected,
96
+ cleanupWorktrees: input.cleanupWorktrees ?? !input.keepWorktrees,
97
+ baseCommit
64
98
  });
65
99
  for (const trace of poolResult.traces) {
66
100
  traceState = recordWorkerTrace(traceState, trace);
101
+ if (trace.ttft_ms !== null) {
102
+ healthTracker.record({
103
+ provider_slug: trace.provider_slug || 'openrouter',
104
+ model: trace.model,
105
+ p50_ttft_ms: trace.ttft_ms,
106
+ last_success: trace.status === 'completed' || trace.status === 'verification_passed' ? nowIso() : null,
107
+ last_failure: trace.status === 'failed' ? nowIso() : null
108
+ });
109
+ }
67
110
  }
68
111
  healthTracker.record({ provider_slug: 'openrouter', model: GLM_52_OPENROUTER_MODEL, count_429: 0, count_5xx: 0 });
69
112
  let envelopes = poolResult.envelopes;
@@ -86,7 +129,10 @@ export async function runGlmNarutoMission(input) {
86
129
  contextSummary: JSON.stringify({ task: input.task, repair: true }),
87
130
  maxWorkers: input.maxWorkers || 3,
88
131
  workerTimeoutMs: GLM_NARUTO_LIMITS.max_worker_runtime_ms,
89
- strategies: new Map(repairPlan.shardsToRepair.map((s) => [s.id, [s.strategy]]))
132
+ strategies: new Map(repairPlan.shardsToRepair.map((s) => [s.id, [s.strategy]])),
133
+ isolationMode: isolationPolicy.selected,
134
+ cleanupWorktrees: input.cleanupWorktrees ?? !input.keepWorktrees,
135
+ baseCommit
90
136
  });
91
137
  envelopes = [...envelopes, ...repairPool.envelopes];
92
138
  for (const trace of repairPool.traces) {
@@ -97,7 +143,9 @@ export async function runGlmNarutoMission(input) {
97
143
  }
98
144
  // 4.0.9: Verifier wave — run parallel verifier workers over gate-passed candidates.
99
145
  let passedEnvelopes = envelopes.filter((e) => e.status === 'gate_passed');
100
- if (passedEnvelopes.length > 0 && !input.noApply) {
146
+ let verifierWaveRun = false;
147
+ if (passedEnvelopes.length > 0 && !input.skipVerifier) {
148
+ verifierWaveRun = true;
101
149
  const verifyApiKey = key.key;
102
150
  const verifyResults = await Promise.allSettled(passedEnvelopes.map((env) => runVerifierWorker({
103
151
  apiKey: verifyApiKey,
@@ -118,6 +166,15 @@ export async function runGlmNarutoMission(input) {
118
166
  }
119
167
  if (res.status === 'fulfilled') {
120
168
  traceState = recordWorkerTrace(traceState, res.value.trace);
169
+ if (res.value.trace.ttft_ms !== null) {
170
+ healthTracker.record({
171
+ provider_slug: res.value.trace.provider_slug || 'openrouter',
172
+ model: res.value.trace.model,
173
+ p50_ttft_ms: res.value.trace.ttft_ms,
174
+ last_success: res.value.ok ? nowIso() : null,
175
+ last_failure: res.value.ok ? null : nowIso()
176
+ });
177
+ }
121
178
  }
122
179
  }
123
180
  envelopes = envelopes.map((e) => {
@@ -126,6 +183,7 @@ export async function runGlmNarutoMission(input) {
126
183
  });
127
184
  passedEnvelopes = envelopes.filter((e) => e.status === 'gate_passed');
128
185
  }
186
+ const warnings = input.skipVerifier && passedEnvelopes.length > 0 ? ['verifier_skipped_by_flag'] : [];
129
187
  // Build conflict graph and merge plan
130
188
  const nodes = passedEnvelopes.map((env) => ({
131
189
  patch_id: env.worker_id,
@@ -136,6 +194,13 @@ export async function runGlmNarutoMission(input) {
136
194
  patch_sha256: env.patch_sha256
137
195
  }));
138
196
  const conflictGraph = buildConflictGraph(passedEnvelopes, nodes);
197
+ const candidateScoreboard = buildGlmNarutoCandidateScoreboard({
198
+ missionId,
199
+ envelopes,
200
+ traces: traceState.workerTraces,
201
+ graph: conflictGraph,
202
+ requestedPaths: mentionedPaths
203
+ });
139
204
  let judgeResult = null;
140
205
  if (input.useJudge && passedEnvelopes.length > 1) {
141
206
  judgeResult = await runGlmJudge({
@@ -149,26 +214,33 @@ export async function runGlmNarutoMission(input) {
149
214
  missionId,
150
215
  envelopes: passedEnvelopes,
151
216
  ...(judgeResult ? { judgeResult } : {}),
217
+ scoreboard: candidateScoreboard,
152
218
  useJudge: input.useJudge || false,
153
219
  xhighFinalizer: input.xhighFinalizer || false
154
220
  });
155
221
  // Apply winning merge plan
156
222
  let appliedPatches = 0;
157
223
  let applyResult = null;
224
+ let applyTransaction = null;
225
+ const artifactDir = path.join(cwd, '.sneakoscope', 'glm-naruto', missionId);
158
226
  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 };
227
+ const transactionResult = await runGlmNarutoApplyTransaction({
228
+ cwd,
229
+ missionId,
230
+ envelopes,
231
+ selectedPatchIds: mergePlan.selected_patches,
232
+ artifactDir
233
+ });
234
+ applyTransaction = transactionResult.transaction;
235
+ appliedPatches = transactionResult.ok ? transactionResult.applied.length : 0;
236
+ applyResult = { ok: transactionResult.ok, applied: transactionResult.applied, ...(transactionResult.transaction.blockers[0] ? { blocker: transactionResult.transaction.blockers[0] } : {}) };
169
237
  }
170
238
  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';
239
+ const terminationReason = appliedPatches > 0
240
+ ? 'completed_merge_applied'
241
+ : applyResult && !applyResult.ok
242
+ ? 'apply_transaction_failed'
243
+ : passedEnvelopes.length > 0 ? 'partial_no_apply' : 'no_gate_passed_candidates';
172
244
  const summary = buildMissionSummary({
173
245
  missionId,
174
246
  startedMs,
@@ -198,10 +270,10 @@ export async function runGlmNarutoMission(input) {
198
270
  failed_shards: summary.failed_shards,
199
271
  repair_waves: summary.repair_waves,
200
272
  budget_used_ms: summary.budget_used_ms,
201
- blockers: terminalState === 'blocked' ? ['no_gate_passed_candidates'] : [],
202
- warnings: []
273
+ blockers: terminalState === 'blocked' ? ['no_gate_passed_candidates'] : (applyResult && !applyResult.ok ? [applyResult.blocker || 'apply_transaction_failed'] : []),
274
+ warnings
203
275
  };
204
- const artifactDir = await writeMissionArtifacts({
276
+ const writtenArtifactDir = await writeMissionArtifacts({
205
277
  root: cwd,
206
278
  missionId,
207
279
  workGraph: graph,
@@ -210,19 +282,31 @@ export async function runGlmNarutoMission(input) {
210
282
  ...(judgeResult ? { judgeResult } : {}),
211
283
  workerTraces: traceState.workerTraces,
212
284
  providerHealth: healthTracker.snapshot(),
285
+ concurrencyDecisions: poolResult.concurrencyDecisions,
286
+ isolationPolicy,
287
+ candidateScoreboard,
213
288
  termination: { schema: 'sks.glm-naruto-termination.v1', mission_id: missionId, terminal_state: terminalState, reason: terminationReason, wall_clock_ms: summary.wall_clock_ms },
214
289
  ...(applyResult ? { applyResult: { ...applyResult, schema: 'sks.glm-naruto-apply-result.v1' } } : {}),
215
- verificationSummary: { schema: 'sks.glm-naruto-verification.v1', verified: passedEnvelopes.length, total: envelopes.length },
290
+ ...(applyTransaction ? { applyTransaction } : {}),
291
+ verificationSummary: { schema: 'sks.glm-naruto-verification.v1', verified: passedEnvelopes.length, total: envelopes.length, verifier_wave_run: verifierWaveRun, skip_verifier: input.skipVerifier === true },
216
292
  missionResult: result,
217
293
  envelopes
218
294
  });
295
+ const secretAudit = await auditGlmNarutoArtifactsForSecrets(path.join(cwd, '.sneakoscope', 'glm-naruto', missionId)).catch((err) => ({
296
+ schema: 'sks.glm-naruto-secret-audit.v1',
297
+ ok: false,
298
+ root: path.join(cwd, '.sneakoscope', 'glm-naruto', missionId),
299
+ scanned_files: 0,
300
+ findings: [`audit_failed:${err instanceof Error ? err.message : String(err)}`]
301
+ }));
302
+ await writeJsonAtomic(path.join(writtenArtifactDir, 'secret-audit.json'), secretAudit).catch(() => undefined);
219
303
  // 4.0.9: Write canonical stop-gate artifacts for hook resolution.
220
304
  await writeFinalStopGate({
221
305
  root: cwd,
222
306
  missionId,
223
307
  route: 'GLM_NARUTO',
224
308
  routeCommand: '$Naruto',
225
- status: result.ok ? 'passed' : (terminalState === 'blocked' ? 'blocked' : 'failed'),
309
+ status: result.ok && secretAudit.ok ? 'passed' : (terminalState === 'blocked' || !secretAudit.ok ? 'blocked' : 'failed'),
226
310
  terminal: terminalState === 'completed' || terminalState === 'blocked',
227
311
  terminalState,
228
312
  evidence: {
@@ -230,13 +314,17 @@ export async function runGlmNarutoMission(input) {
230
314
  tests_passed: result.ok,
231
315
  route_evidence_passed: result.ok,
232
316
  per_worker_artifacts: true,
233
- verifier_wave_run: true,
317
+ verifier_wave_run: verifierWaveRun,
234
318
  model_guard_enforced: true,
319
+ proof_required: false,
320
+ proof_passed: true,
321
+ reflection_required: false,
322
+ reflection_passed: 'not_required',
235
323
  },
236
- blockers: result.blockers || [],
324
+ blockers: secretAudit.ok ? (result.blockers || []) : ['glm_naruto_secret_leak_detected'],
237
325
  nativeGateFile: 'termination.json',
238
326
  }).catch(() => null);
239
- return { ...result, artifact_dir: artifactDir };
327
+ return { ...result, artifact_dir: writtenArtifactDir };
240
328
  }
241
329
  function missionResult(missionId, task, status, reason, patchCandidates, startedMs, envelopes, traces, blockers, warnings) {
242
330
  return {
@@ -0,0 +1,42 @@
1
+ import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
2
+ import { parseGlmNarutoPatchCandidate } from './glm-naruto-patch-candidate-parser.js';
3
+ const PROTECTED_PATH = /(^|\/)(\.github|dist|node_modules)(\/|$)/;
4
+ const SECRET_PATTERN = /\b(?:Bearer\s+[A-Za-z0-9._~+/-]+|sk-(?:or-)?[A-Za-z0-9_-]{12,}|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)\b/;
5
+ export async function evaluateGlmNarutoPatchCandidateGate(input) {
6
+ const checks = [];
7
+ const parsed = parseGlmNarutoPatchCandidate(input.envelope.patch);
8
+ checks.push(check('candidate_parse', parsed.ok, parsed.blockers.join(',') || undefined));
9
+ checks.push(check('patch_section', Boolean(parsed.patch), parsed.patch ? undefined : 'missing_patch_section'));
10
+ checks.push(check('unified_diff', /^diff --git /m.test(parsed.patch), parsed.patch ? undefined : 'no_diff_git'));
11
+ const blockedPath = parsed.touched_paths.find((file) => PROTECTED_PATH.test(file));
12
+ checks.push(check('protected_path_guard', !blockedPath, blockedPath));
13
+ const secretLeak = SECRET_PATTERN.test(parsed.patch);
14
+ checks.push(check('secret_leakage_guard', !secretLeak, secretLeak ? 'secret_like_content' : undefined));
15
+ if (parsed.ok && !blockedPath && !secretLeak) {
16
+ const applyCheck = await checkAndApplyGlmPatch({
17
+ cwd: input.cwd,
18
+ patch: parsed.patch,
19
+ apply: input.apply === true
20
+ });
21
+ checks.push(check('git_apply_check', applyCheck.ok, applyCheck.ok ? undefined : applyCheck.error.code));
22
+ }
23
+ else {
24
+ checks.push({ id: 'git_apply_check', ok: false, reason: 'skipped_due_to_prior_blocker', ms: 0 });
25
+ }
26
+ const blockers = checks.filter((row) => !row.ok).map((row) => row.reason || row.id);
27
+ return {
28
+ schema: 'sks.glm-naruto-patch-candidate-gate.v1',
29
+ ok: blockers.length === 0,
30
+ worker_id: input.envelope.worker_id,
31
+ shard_id: input.envelope.shard_id,
32
+ patch_id: input.envelope.patch_sha256,
33
+ extracted_patch: parsed.patch,
34
+ touched_paths: parsed.touched_paths,
35
+ checks,
36
+ blockers
37
+ };
38
+ }
39
+ function check(id, ok, reason) {
40
+ return { id, ok, ...(reason ? { reason } : {}), ms: 0 };
41
+ }
42
+ //# sourceMappingURL=glm-naruto-patch-candidate-gate.js.map
@@ -0,0 +1,62 @@
1
+ import { parseUnifiedDiffPatch } from '../glm-patch-parser.js';
2
+ const CANDIDATE_OPEN = '<sks_patch_candidate>';
3
+ const CANDIDATE_CLOSE = '</sks_patch_candidate>';
4
+ const LEGACY_PATCH_OPEN = '<sks_patch>';
5
+ export function parseGlmNarutoPatchCandidate(text) {
6
+ const blockers = [];
7
+ if (text.includes(LEGACY_PATCH_OPEN) && !text.includes(CANDIDATE_OPEN)) {
8
+ return failed(text, ['legacy_sks_patch_envelope_rejected']);
9
+ }
10
+ const body = unwrapCandidateBody(text);
11
+ if (!body.ok)
12
+ return failed(text, body.blockers);
13
+ const extracted = extractPatchSection(body.body);
14
+ if (!extracted.ok)
15
+ return failed(body.body, extracted.blockers);
16
+ const parsed = parseUnifiedDiffPatch(extracted.patch);
17
+ if (parsed.empty)
18
+ blockers.push('patch_missing_unified_diff');
19
+ return {
20
+ ok: blockers.length === 0,
21
+ body: body.body,
22
+ patch: extracted.patch,
23
+ touched_paths: parsed.touchedPaths,
24
+ blockers
25
+ };
26
+ }
27
+ function unwrapCandidateBody(text) {
28
+ const start = text.indexOf(CANDIDATE_OPEN);
29
+ const end = text.indexOf(CANDIDATE_CLOSE);
30
+ if (start >= 0 || end >= 0) {
31
+ if (start < 0 || end <= start)
32
+ return { ok: false, blockers: ['malformed_patch_candidate_envelope'] };
33
+ return { ok: true, body: text.slice(start + CANDIDATE_OPEN.length, end).trim() };
34
+ }
35
+ if (/^\s*patch\s*:/im.test(text))
36
+ return { ok: true, body: text.trim() };
37
+ return { ok: false, blockers: ['missing_patch_candidate_envelope'] };
38
+ }
39
+ function extractPatchSection(body) {
40
+ const lines = body.split(/\r?\n/);
41
+ const patchLine = lines.findIndex((line) => /^\s*patch\s*:/i.test(line));
42
+ if (patchLine < 0)
43
+ return { ok: false, blockers: ['missing_patch_section'] };
44
+ const firstLine = lines[patchLine] || '';
45
+ const inline = firstLine.replace(/^\s*patch\s*:\s*/i, '');
46
+ const tail = inline.trim() ? [inline, ...lines.slice(patchLine + 1)] : lines.slice(patchLine + 1);
47
+ const raw = tail.join('\n').replace(/^\s*```(?:diff|patch)?\s*/i, '').replace(/\s*```\s*$/i, '').trim();
48
+ const diffIndex = raw.search(/^diff --git /m);
49
+ if (diffIndex < 0)
50
+ return { ok: false, blockers: ['patch_missing_diff_git'] };
51
+ return { ok: true, patch: raw.slice(diffIndex).trimEnd() + '\n' };
52
+ }
53
+ function failed(body, blockers) {
54
+ return {
55
+ ok: false,
56
+ body,
57
+ patch: '',
58
+ touched_paths: [],
59
+ blockers
60
+ };
61
+ }
62
+ //# sourceMappingURL=glm-naruto-patch-candidate-parser.js.map
@@ -0,0 +1,75 @@
1
+ const SECRET_PATTERN = /\b(?:Bearer\s+[A-Za-z0-9._~+/-]+|sk-(?:or-)?[A-Za-z0-9_-]{12,}|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)\b/;
2
+ export function buildGlmNarutoCandidateScoreboard(input) {
3
+ const strategyCounts = new Map();
4
+ for (const envelope of input.envelopes) {
5
+ const set = strategyCounts.get(envelope.shard_id) ?? new Set();
6
+ set.add(envelope.strategy);
7
+ strategyCounts.set(envelope.shard_id, set);
8
+ }
9
+ return {
10
+ schema: 'sks.glm-naruto-candidate-scoreboard.v1',
11
+ mission_id: input.missionId,
12
+ scores: input.envelopes.map((envelope) => {
13
+ const trace = input.traces.find((item) => item.worker_id === envelope.worker_id || item.patch_digest === envelope.patch_sha256);
14
+ return scoreEnvelope({
15
+ envelope,
16
+ ...(trace ? { trace } : {}),
17
+ graph: input.graph,
18
+ requestedPaths: input.requestedPaths,
19
+ strategyDiversity: strategyCounts.get(envelope.shard_id)?.size ?? 1
20
+ });
21
+ })
22
+ };
23
+ }
24
+ function scoreEnvelope(input) {
25
+ const disqualification_reasons = [];
26
+ const secretLeak = SECRET_PATTERN.test(input.envelope.patch) || input.envelope.blockers.includes('secret_like_content');
27
+ const verifierFailed = input.envelope.status === 'verification_failed' || input.envelope.verification_passed === false;
28
+ const gatePassed = input.envelope.status === 'gate_passed' || input.envelope.status === 'selected' || input.envelope.verification_passed === true;
29
+ if (!gatePassed)
30
+ disqualification_reasons.push('deterministic_gate_failed');
31
+ if (verifierFailed)
32
+ disqualification_reasons.push('verifier_failed');
33
+ if (secretLeak)
34
+ disqualification_reasons.push('secret_leak');
35
+ if (input.envelope.blockers.some((blocker) => blocker.includes('protected')))
36
+ disqualification_reasons.push('protected_path');
37
+ const conflictCount = input.graph.edges.filter((edge) => edge.left_patch_id === input.envelope.worker_id || edge.right_patch_id === input.envelope.worker_id).length;
38
+ const requested = new Set(input.requestedPaths);
39
+ const targetAligned = requested.size === 0 || input.envelope.target_paths.some((target) => requested.has(target));
40
+ const risk = clamp(input.trace?.verifier_risk_score ?? (verifierFailed ? 1 : 0));
41
+ const confidence = clamp(input.trace?.verifier_confidence ?? (input.envelope.verification_passed ? 1 : 0));
42
+ const latency = input.trace?.ttft_ms ?? input.trace?.total_ms ?? 0;
43
+ const cacheTokens = input.trace?.cached_tokens ?? 0;
44
+ const patchSizePenalty = Math.min(50, Math.ceil(input.envelope.patch.length / 400));
45
+ const components = {
46
+ deterministic_gate: gatePassed ? 100 : 0,
47
+ verifier: input.envelope.verification_passed === false ? -100 : input.envelope.verification_passed === true ? 50 : 0,
48
+ verifier_confidence: Math.round(confidence * 20),
49
+ verifier_risk_penalty: -Math.round(risk * 50),
50
+ patch_size_penalty: -patchSizePenalty,
51
+ touched_path_penalty: -Math.min(30, input.envelope.target_paths.length * 5),
52
+ target_alignment: targetAligned ? 20 : -30,
53
+ hunk_conflict_penalty: -Math.min(60, conflictCount * 20),
54
+ latency_penalty: -Math.min(25, Math.floor(latency / 2_000)),
55
+ cache_bonus: Math.min(20, Math.floor(cacheTokens / 1_000)),
56
+ strategy_diversity_bonus: input.strategyDiversity > 1 ? 10 : 0,
57
+ secret_safety: secretLeak ? -1_000 : 25
58
+ };
59
+ const total = Object.values(components).reduce((sum, value) => sum + value, 0);
60
+ return {
61
+ schema: 'sks.glm-naruto-candidate-score.v1',
62
+ patch_id: input.envelope.worker_id,
63
+ shard_id: input.envelope.shard_id,
64
+ total_score: disqualification_reasons.length ? -1_000 : total,
65
+ components,
66
+ disqualified: disqualification_reasons.length > 0,
67
+ disqualification_reasons
68
+ };
69
+ }
70
+ function clamp(value) {
71
+ if (!Number.isFinite(value))
72
+ return 0;
73
+ return Math.max(0, Math.min(1, value));
74
+ }
75
+ //# sourceMappingURL=glm-naruto-scoreboard.js.map
@@ -0,0 +1,91 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const SECRET_PATTERN = /\b(?:Bearer\s+[A-Za-z0-9._~+/-]+|sk-(?:or-)?[A-Za-z0-9_-]{12,}|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)\b/;
4
+ const SECRET_KEY_PATTERN = /^(authorization|api_key|apiKey|access_token|token|secret|password|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)$/i;
5
+ const REDACTED_MARKERS = new Set(['[REDACTED]', '<redacted>', 'sk-or-[REDACTED]', 'Bearer [REDACTED]']);
6
+ export async function auditGlmNarutoArtifactsForSecrets(root) {
7
+ const findings = [];
8
+ let scanned = 0;
9
+ await scan(root, async (file) => {
10
+ if (!/\.(json|jsonl|md|txt)$/i.test(file))
11
+ return;
12
+ scanned++;
13
+ const content = await fsp.readFile(file, 'utf8').catch(() => '');
14
+ const fileFindings = auditContentForSecrets(content);
15
+ if (fileFindings.length)
16
+ findings.push(`${path.relative(root, file)}:${fileFindings.join(',')}`);
17
+ });
18
+ return {
19
+ schema: 'sks.glm-naruto-secret-audit.v1',
20
+ ok: findings.length === 0,
21
+ root,
22
+ scanned_files: scanned,
23
+ findings
24
+ };
25
+ }
26
+ export function auditContentForSecrets(content) {
27
+ const findings = [];
28
+ if (SECRET_PATTERN.test(content))
29
+ findings.push('secret_like_content');
30
+ for (const parsed of parseJsonLike(content)) {
31
+ collectJsonSecretFindings(parsed, findings);
32
+ }
33
+ return [...new Set(findings)];
34
+ }
35
+ function parseJsonLike(content) {
36
+ const parsed = [];
37
+ try {
38
+ parsed.push(JSON.parse(content));
39
+ return parsed;
40
+ }
41
+ catch { }
42
+ for (const line of content.split(/\r?\n/)) {
43
+ const trimmed = line.trim();
44
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('['))
45
+ continue;
46
+ try {
47
+ parsed.push(JSON.parse(trimmed));
48
+ }
49
+ catch { }
50
+ }
51
+ return parsed;
52
+ }
53
+ function collectJsonSecretFindings(value, findings, key = '') {
54
+ if (!value || typeof value !== 'object') {
55
+ if (SECRET_KEY_PATTERN.test(key) && typeof value === 'string' && value.trim() && !REDACTED_MARKERS.has(value.trim())) {
56
+ findings.push(`secret_key:${key}`);
57
+ }
58
+ return;
59
+ }
60
+ if (Array.isArray(value)) {
61
+ for (const item of value)
62
+ collectJsonSecretFindings(item, findings, key);
63
+ return;
64
+ }
65
+ for (const [entryKey, entryValue] of Object.entries(value)) {
66
+ if (SECRET_KEY_PATTERN.test(entryKey)) {
67
+ if (typeof entryValue === 'string' && entryValue.trim() && !REDACTED_MARKERS.has(entryValue.trim()))
68
+ findings.push(`secret_key:${entryKey}`);
69
+ else if (entryValue && typeof entryValue === 'object')
70
+ findings.push(`secret_key:${entryKey}`);
71
+ }
72
+ collectJsonSecretFindings(entryValue, findings, entryKey);
73
+ }
74
+ }
75
+ async function scan(dir, visit) {
76
+ let entries;
77
+ try {
78
+ entries = await fsp.readdir(dir, { withFileTypes: true });
79
+ }
80
+ catch {
81
+ return;
82
+ }
83
+ for (const entry of entries) {
84
+ const p = path.join(dir, String(entry.name));
85
+ if (entry.isDirectory())
86
+ await scan(p, visit);
87
+ else if (entry.isFile())
88
+ await visit(p);
89
+ }
90
+ }
91
+ //# sourceMappingURL=glm-naruto-secret-audit.js.map
@@ -0,0 +1,10 @@
1
+ import crypto from 'node:crypto';
2
+ const SAFE_SESSION_ID = /^[A-Za-z0-9._:-]+$/;
3
+ export function normalizeGlmNarutoSessionId(raw) {
4
+ const sanitized = raw.replace(/[^A-Za-z0-9._:-]/g, '-');
5
+ if (sanitized.length <= 256 && SAFE_SESSION_ID.test(sanitized))
6
+ return sanitized;
7
+ const digest = crypto.createHash('sha256').update(sanitized).digest('hex').slice(0, 24);
8
+ return `${sanitized.slice(0, 231)}-${digest}`.slice(0, 256);
9
+ }
10
+ //# sourceMappingURL=glm-naruto-session-id.js.map