sneakoscope 2.0.2 → 2.0.5
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 +12 -8
- 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/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +86 -8
- package/dist/commands/doctor.js +14 -0
- package/dist/core/agents/agent-orchestrator.js +70 -4
- package/dist/core/agents/agent-patch-proof.js +5 -0
- package/dist/core/agents/agent-proof-evidence.js +61 -0
- package/dist/core/agents/agent-roster.js +35 -6
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/native-worker-backend-router.js +31 -9
- package/dist/core/agents/ollama-worker-config.js +164 -15
- package/dist/core/codex/codex-0-137-compat.js +119 -0
- package/dist/core/codex-control/codex-control-proof.js +4 -1
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +20 -0
- package/dist/core/codex-control/codex-output-schemas.js +5 -1
- package/dist/core/codex-control/codex-sdk-capability.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +329 -5
- package/dist/core/codex-control/gpt-final-arbiter.js +160 -0
- package/dist/core/codex-control/gpt-final-context-compressor.js +17 -0
- package/dist/core/codex-control/gpt-final-proof-pack.js +120 -0
- package/dist/core/codex-control/gpt-final-review-schema.js +71 -0
- package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
- package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
- package/dist/core/commands/local-model-command.js +79 -18
- package/dist/core/commands/naruto-command.js +195 -12
- package/dist/core/commands/run-command.js +6 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +34 -0
- package/dist/core/feature-fixtures.js +4 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git-simple.js +143 -4
- package/dist/core/local-llm/local-collaboration-policy.js +93 -0
- package/dist/core/local-llm/local-llm-backpressure.js +20 -0
- package/dist/core/local-llm/local-llm-capability.js +29 -0
- package/dist/core/local-llm/local-llm-client.js +100 -0
- package/dist/core/local-llm/local-llm-config.js +20 -0
- package/dist/core/local-llm/local-llm-context-cache.js +21 -0
- package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
- package/dist/core/local-llm/local-llm-json-repair.js +52 -0
- package/dist/core/local-llm/local-llm-metrics.js +42 -0
- package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
- package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
- package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
- package/dist/core/local-llm/local-llm-scheduler.js +29 -0
- package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
- package/dist/core/local-llm/local-llm-smoke.js +83 -0
- package/dist/core/local-llm/local-llm-warmup.js +20 -0
- package/dist/core/local-llm/local-worker-eligibility.js +27 -0
- package/dist/core/naruto/hardware-capacity-probe.js +36 -0
- package/dist/core/naruto/naruto-active-pool.js +118 -0
- package/dist/core/naruto/naruto-backpressure.js +13 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
- package/dist/core/naruto/naruto-finalizer.js +18 -0
- package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
- package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
- package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
- package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
- package/dist/core/naruto/naruto-role-policy.js +107 -0
- package/dist/core/naruto/naruto-verification-dag.js +42 -0
- package/dist/core/naruto/naruto-verification-pool.js +18 -0
- package/dist/core/naruto/naruto-work-graph.js +198 -0
- package/dist/core/naruto/naruto-work-item.js +40 -0
- package/dist/core/naruto/naruto-work-stealing.js +11 -0
- package/dist/core/naruto/resource-pressure-monitor.js +32 -0
- package/dist/core/pipeline/final-gpt-patch-stage.js +31 -0
- package/dist/core/pipeline/final-gpt-review-stage.js +5 -0
- package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
- package/dist/core/pipeline/gpt-final-required.js +12 -0
- package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
- package/dist/core/router/capability-card.js +13 -0
- package/dist/core/router/route-cache.js +3 -0
- package/dist/core/router/ultra-router.js +2 -1
- package/dist/core/routes.js +4 -4
- package/dist/core/safety/mutation-guard.js +2 -0
- package/dist/core/update-check.js +60 -25
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-lane-runtime.js +2 -2
- package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
- package/dist/scripts/blackbox-command-import-smoke.js +10 -1
- package/dist/scripts/check-package-boundary.js +12 -3
- package/dist/scripts/codex-0-137-compat-check.js +27 -0
- package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
- package/dist/scripts/codex-plugin-list-json-check.js +8 -0
- package/dist/scripts/codex-sdk-team-naruto-agent-pipeline-check.js +2 -1
- package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
- package/dist/scripts/gpt-final-arbiter-check.js +63 -0
- package/dist/scripts/gpt-final-arbiter-performance-check.js +36 -0
- package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
- package/dist/scripts/local-collab-gpt-final-availability-check.js +58 -0
- package/dist/scripts/local-collab-no-local-only-final-check.js +27 -0
- package/dist/scripts/local-collab-policy-check.js +17 -0
- package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
- package/dist/scripts/local-llm-cache-performance-check.js +10 -0
- package/dist/scripts/local-llm-capability-check.js +14 -0
- package/dist/scripts/local-llm-smoke-check.js +23 -0
- package/dist/scripts/local-llm-structured-output-check.js +11 -0
- package/dist/scripts/local-llm-throughput-check.js +10 -0
- package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
- package/dist/scripts/local-llm-warmup-check.js +11 -0
- package/dist/scripts/naruto-active-pool-check.js +27 -0
- package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
- package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
- package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
- package/dist/scripts/naruto-role-distribution-check.js +23 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +6 -0
- package/dist/scripts/naruto-verification-pool-check.js +36 -0
- package/dist/scripts/naruto-work-graph-check.js +24 -0
- package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
- package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
- package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
- package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
- package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
- package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
- package/dist/scripts/release-parallel-check.js +1 -1
- package/dist/scripts/release-real-check.js +5 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
- package/package.json +38 -4
- package/schemas/local-llm/local-collaboration-policy.schema.json +57 -0
- package/schemas/local-llm/local-model-config.schema.json +74 -0
- package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
- package/schemas/naruto/naruto-work-graph.schema.json +22 -0
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { createMission, findLatestMission, loadMission } from '../mission.js';
|
|
3
|
-
import { readJson, sksRoot } from '../fsx.js';
|
|
3
|
+
import { readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
4
4
|
import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
|
|
5
|
+
import { classifyOllamaWorkerSlice } from '../agents/agent-runner-ollama.js';
|
|
5
6
|
import { buildNarutoCloneRoster, systemSafeNarutoConcurrency } from '../agents/agent-roster.js';
|
|
6
7
|
import { DEFAULT_NARUTO_CLONES, MAX_NARUTO_AGENT_COUNT } from '../agents/agent-schema.js';
|
|
8
|
+
import { resolveOllamaWorkerConfig } from '../agents/ollama-worker-config.js';
|
|
7
9
|
import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/zellij-launcher.js';
|
|
10
|
+
import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
|
|
11
|
+
import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
|
|
12
|
+
import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
|
|
13
|
+
import { simulateNarutoActivePool } from '../naruto/naruto-active-pool.js';
|
|
14
|
+
import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
|
|
15
|
+
import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
|
|
16
|
+
import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
|
|
17
|
+
import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
|
|
8
18
|
const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
|
|
9
19
|
const NARUTO_ROUTE = '$Naruto';
|
|
10
20
|
// $Naruto — Shadow Clone Swarm (影分身 / Kage Bunshin no Jutsu).
|
|
@@ -23,6 +33,27 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
23
33
|
}
|
|
24
34
|
async function narutoRun(parsed) {
|
|
25
35
|
const root = await sksRoot();
|
|
36
|
+
const writeCapable = parsed.readonly !== true && parsed.writeMode !== 'off';
|
|
37
|
+
const placeholderGuard = checkPromptPlaceholders({
|
|
38
|
+
prompt: parsed.prompt,
|
|
39
|
+
writeCapable,
|
|
40
|
+
targetPaths: writeCapable ? ['.sneakoscope/naruto/patch-envelopes'] : []
|
|
41
|
+
});
|
|
42
|
+
if (!placeholderGuard.ok) {
|
|
43
|
+
return emit(parsed, {
|
|
44
|
+
schema: NARUTO_RESULT_SCHEMA,
|
|
45
|
+
ok: false,
|
|
46
|
+
mode: 'NARUTO',
|
|
47
|
+
action: 'run',
|
|
48
|
+
status: 'blocked',
|
|
49
|
+
prompt_placeholder_guard: placeholderGuard,
|
|
50
|
+
blockers: placeholderGuard.blockers
|
|
51
|
+
}, () => {
|
|
52
|
+
console.log('$Naruto blocked before work graph creation: unresolved prompt placeholder or empty write target path.');
|
|
53
|
+
for (const blocker of placeholderGuard.blockers)
|
|
54
|
+
console.log('- ' + blocker);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
26
57
|
const roster = buildNarutoCloneRoster({
|
|
27
58
|
clones: parsed.clones,
|
|
28
59
|
prompt: parsed.prompt,
|
|
@@ -32,10 +63,55 @@ async function narutoRun(parsed) {
|
|
|
32
63
|
// The clone roster is the full work fan-out; live concurrency is throttled to a
|
|
33
64
|
// system-safe number so naruto never spawns the whole count at once unless an
|
|
34
65
|
// explicit operator override asks for a higher target.
|
|
35
|
-
const
|
|
36
|
-
const
|
|
66
|
+
const localWorker = await resolveNarutoLocalWorkerMode(parsed);
|
|
67
|
+
const schedulerBackend = localWorker.auto_select_eligible ? 'ollama' : parsed.backend;
|
|
68
|
+
const safe = systemSafeNarutoConcurrency({ backend: schedulerBackend });
|
|
69
|
+
const workGraph = buildNarutoWorkGraph({
|
|
70
|
+
prompt: parsed.prompt,
|
|
71
|
+
requestedClones: roster.agent_count,
|
|
72
|
+
totalWorkItems: parsed.workItems,
|
|
73
|
+
readonly: parsed.readonly,
|
|
74
|
+
writeCapable,
|
|
75
|
+
targetPaths: ['.sneakoscope/naruto/patch-envelopes'],
|
|
76
|
+
maxActiveWorkers: parsed.concurrency || safe.cap
|
|
77
|
+
});
|
|
78
|
+
const roleDistribution = buildNarutoRoleDistribution(workGraph.work_items, { readonly: parsed.readonly });
|
|
79
|
+
const governor = decideNarutoConcurrency({
|
|
80
|
+
requestedClones: roster.agent_count,
|
|
81
|
+
totalWorkItems: workGraph.total_work_items,
|
|
82
|
+
pendingWorkQueueSize: workGraph.total_work_items,
|
|
83
|
+
backend: schedulerBackend
|
|
84
|
+
});
|
|
85
|
+
const backendMinimum = schedulerBackend === 'fake' ? roster.agent_count : Math.min(roster.agent_count, 2);
|
|
86
|
+
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
|
|
87
|
+
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
88
|
+
const activePool = simulateNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
|
|
89
|
+
const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
|
|
90
|
+
const gptFinalPack = buildNarutoGptFinalPack({
|
|
91
|
+
missionId: 'pending',
|
|
92
|
+
graph: workGraph,
|
|
93
|
+
roleDistribution,
|
|
94
|
+
localLlmMetrics: localWorker
|
|
95
|
+
});
|
|
96
|
+
const zellijDashboard = planNarutoZellijDashboard({
|
|
97
|
+
targetActiveWorkers: activeSlots,
|
|
98
|
+
visiblePaneCap: governor.safe_zellij_visible_panes,
|
|
99
|
+
backpressure: governor.backpressure,
|
|
100
|
+
roles: roleDistribution.work_item_roles.map((row) => row.role),
|
|
101
|
+
backend: schedulerBackend
|
|
102
|
+
});
|
|
37
103
|
const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
|
|
38
104
|
const ledgerRoot = path.join(mission.dir, 'agents');
|
|
105
|
+
await writeNarutoArtifacts(ledgerRoot, {
|
|
106
|
+
workGraph,
|
|
107
|
+
roleDistribution,
|
|
108
|
+
governor,
|
|
109
|
+
activePool,
|
|
110
|
+
verificationDag,
|
|
111
|
+
gptFinalPack: { ...gptFinalPack, mission_id: mission.id },
|
|
112
|
+
zellijDashboard,
|
|
113
|
+
placeholderGuard
|
|
114
|
+
});
|
|
39
115
|
let liveZellij = null;
|
|
40
116
|
if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
|
|
41
117
|
liveZellij = await launchZellijLayout({
|
|
@@ -43,12 +119,12 @@ async function narutoRun(parsed) {
|
|
|
43
119
|
missionId: mission.id,
|
|
44
120
|
ledgerRoot,
|
|
45
121
|
kind: 'naruto',
|
|
46
|
-
slotCount:
|
|
122
|
+
slotCount: zellijVisiblePanes,
|
|
47
123
|
dryRun: false,
|
|
48
124
|
attach: false
|
|
49
125
|
});
|
|
50
126
|
if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
|
|
51
|
-
console.log('Zellij: prepared ' +
|
|
127
|
+
console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + liveZellij.session_name + ' with ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s). Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
|
|
52
128
|
if (parsed.attach)
|
|
53
129
|
attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
|
|
54
130
|
}
|
|
@@ -69,12 +145,17 @@ async function narutoRun(parsed) {
|
|
|
69
145
|
agents: roster.agent_count,
|
|
70
146
|
concurrency: activeSlots,
|
|
71
147
|
targetActiveSlots: activeSlots,
|
|
72
|
-
visualLaneCount:
|
|
148
|
+
visualLaneCount: zellijVisiblePanes,
|
|
73
149
|
desiredWorkItemCount: parsed.workItems,
|
|
74
150
|
maxAgentCount: MAX_NARUTO_AGENT_COUNT,
|
|
75
151
|
narutoMode: true,
|
|
76
152
|
clones: roster.agent_count,
|
|
77
153
|
backend: parsed.backend,
|
|
154
|
+
backendExplicit: parsed.backendExplicit,
|
|
155
|
+
noOllama: parsed.noOllama,
|
|
156
|
+
ollamaEnabled: parsed.ollamaEnabled,
|
|
157
|
+
ollamaModel: parsed.ollamaModel,
|
|
158
|
+
ollamaBaseUrl: parsed.ollamaBaseUrl,
|
|
78
159
|
mock: parsed.mock,
|
|
79
160
|
real: parsed.real,
|
|
80
161
|
readonly: parsed.readonly,
|
|
@@ -86,6 +167,7 @@ async function narutoRun(parsed) {
|
|
|
86
167
|
json: parsed.json
|
|
87
168
|
});
|
|
88
169
|
const clones = result.roster?.agent_count ?? roster.agent_count;
|
|
170
|
+
const localWorkerSummary = summarizeNarutoLocalWorkerResult(localWorker, result);
|
|
89
171
|
const summary = {
|
|
90
172
|
schema: NARUTO_RESULT_SCHEMA,
|
|
91
173
|
ok: result.ok === true,
|
|
@@ -99,6 +181,21 @@ async function narutoRun(parsed) {
|
|
|
99
181
|
target_active_slots: result.target_active_slots ?? activeSlots,
|
|
100
182
|
concurrency_capped: clones > (result.target_active_slots ?? activeSlots),
|
|
101
183
|
system: { cores: safe.cores, free_gb: safe.free_gb, safe_concurrency: safe.cap, heavy_backend: safe.heavy },
|
|
184
|
+
work_graph: {
|
|
185
|
+
total_work_items: workGraph.total_work_items,
|
|
186
|
+
mixed_work_kinds: workGraph.mixed_work_kinds,
|
|
187
|
+
write_allowed_count: workGraph.write_allowed_count,
|
|
188
|
+
ok: workGraph.ok
|
|
189
|
+
},
|
|
190
|
+
role_distribution: roleDistribution,
|
|
191
|
+
concurrency_governor: governor,
|
|
192
|
+
active_pool: {
|
|
193
|
+
ok: activePool.ok,
|
|
194
|
+
max_observed_active_workers: activePool.max_observed_active_workers,
|
|
195
|
+
refill_events: activePool.refill_events,
|
|
196
|
+
completed_count: activePool.completed_count
|
|
197
|
+
},
|
|
198
|
+
local_worker: localWorkerSummary,
|
|
102
199
|
proof: result.proof?.status || 'missing',
|
|
103
200
|
run: result,
|
|
104
201
|
zellij: null
|
|
@@ -109,13 +206,27 @@ async function narutoRun(parsed) {
|
|
|
109
206
|
console.log('Mission: ' + result.mission_id);
|
|
110
207
|
console.log('Clones: ' + summary.clones + ' / max ' + MAX_NARUTO_AGENT_COUNT + ', running ' + summary.target_active_slots + ' at a time' + (summary.concurrency_capped ? ` (throttled to host capacity: ${safe.cores} cores, ${safe.free_gb} GB free)` : ''));
|
|
111
208
|
console.log('Backend: ' + result.backend);
|
|
209
|
+
console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
|
|
112
210
|
console.log('Proof: ' + summary.proof);
|
|
113
211
|
if (summary.zellij?.ok && summary.zellij.capability?.status === 'ok')
|
|
114
|
-
console.log('Zellij: prepared ' +
|
|
212
|
+
console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + summary.zellij.session_name + '; dashboard tracks ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s)');
|
|
115
213
|
else if (summary.zellij?.ok)
|
|
116
214
|
console.log('Zellij: optional live panes unavailable (' + ((summary.zellij.warnings || []).join('; ') || summary.zellij.capability?.status || 'unknown') + ')');
|
|
117
215
|
});
|
|
118
216
|
}
|
|
217
|
+
function summarizeNarutoLocalWorkerResult(localWorker, result) {
|
|
218
|
+
const backendCounts = {};
|
|
219
|
+
const rows = Array.isArray(result?.results) ? result.results : [];
|
|
220
|
+
for (const row of rows) {
|
|
221
|
+
const selected = String(row?.backend_router_report?.selected_backend || row?.backend || 'unknown');
|
|
222
|
+
backendCounts[selected] = (backendCounts[selected] || 0) + 1;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
...localWorker,
|
|
226
|
+
selected_worker_count: backendCounts.ollama || 0,
|
|
227
|
+
backend_counts: backendCounts
|
|
228
|
+
};
|
|
229
|
+
}
|
|
119
230
|
async function narutoStatus(parsed) {
|
|
120
231
|
const root = await sksRoot();
|
|
121
232
|
const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
|
|
@@ -124,6 +235,9 @@ async function narutoStatus(parsed) {
|
|
|
124
235
|
const { dir } = await loadMission(root, id);
|
|
125
236
|
const proof = await readJson(path.join(dir, 'agents', 'agent-proof-evidence.json'), null);
|
|
126
237
|
const scheduler = await readJson(path.join(dir, 'agents', 'agent-scheduler-state.json'), null);
|
|
238
|
+
const roleDistribution = await readJson(path.join(dir, 'agents', 'naruto-role-distribution.json'), null);
|
|
239
|
+
const workGraph = await readJson(path.join(dir, 'agents', 'naruto-work-graph.json'), null);
|
|
240
|
+
const governor = await readJson(path.join(dir, 'agents', 'naruto-concurrency-governor.json'), null);
|
|
127
241
|
const summary = {
|
|
128
242
|
schema: NARUTO_RESULT_SCHEMA,
|
|
129
243
|
ok: proof !== null,
|
|
@@ -132,13 +246,22 @@ async function narutoStatus(parsed) {
|
|
|
132
246
|
proof: proof?.status || 'missing',
|
|
133
247
|
target_active_slots: scheduler?.target_active_slots ?? null,
|
|
134
248
|
max_active_slots: scheduler?.max_active_slots ?? null,
|
|
135
|
-
completed: scheduler?.completed_count ?? null
|
|
249
|
+
completed: scheduler?.completed_count ?? null,
|
|
250
|
+
role_distribution: roleDistribution,
|
|
251
|
+
work_graph: workGraph ? {
|
|
252
|
+
total_work_items: workGraph.total_work_items,
|
|
253
|
+
mixed_work_kinds: workGraph.mixed_work_kinds,
|
|
254
|
+
write_allowed_count: workGraph.write_allowed_count
|
|
255
|
+
} : null,
|
|
256
|
+
concurrency_governor: governor
|
|
136
257
|
};
|
|
137
258
|
return emit(parsed, summary, () => {
|
|
138
259
|
console.log('🍥 Naruto mission: ' + id);
|
|
139
260
|
console.log('Proof: ' + summary.proof);
|
|
140
261
|
if (summary.target_active_slots !== null)
|
|
141
262
|
console.log('Active clones: ' + summary.target_active_slots + ' / max ' + summary.max_active_slots);
|
|
263
|
+
if (roleDistribution?.entries)
|
|
264
|
+
console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
|
|
142
265
|
});
|
|
143
266
|
}
|
|
144
267
|
async function narutoHelp(parsed) {
|
|
@@ -149,7 +272,7 @@ async function narutoHelp(parsed) {
|
|
|
149
272
|
mode: 'NARUTO',
|
|
150
273
|
description: 'Shadow Clone Swarm: fan out up to ' + MAX_NARUTO_AGENT_COUNT + ' parallel clone sessions.',
|
|
151
274
|
usage: [
|
|
152
|
-
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake] [--work-items N] [--real] [--readonly] [--json]',
|
|
275
|
+
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
|
|
153
276
|
'sks naruto status [--mission <id>] [--json]'
|
|
154
277
|
],
|
|
155
278
|
defaults: { clones: DEFAULT_NARUTO_CLONES, max_clones: MAX_NARUTO_AGENT_COUNT, backend: 'codex-sdk' }
|
|
@@ -162,6 +285,8 @@ async function narutoHelp(parsed) {
|
|
|
162
285
|
});
|
|
163
286
|
}
|
|
164
287
|
function parseNarutoArgs(args = []) {
|
|
288
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h'))
|
|
289
|
+
args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
|
|
165
290
|
const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
|
|
166
291
|
const actions = new Set(['run', 'status', 'help']);
|
|
167
292
|
const action = (actions.has(first) ? first : 'run');
|
|
@@ -171,18 +296,33 @@ function parseNarutoArgs(args = []) {
|
|
|
171
296
|
const clones = clampClones(requestedClones);
|
|
172
297
|
const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones)), clones);
|
|
173
298
|
const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
|
|
174
|
-
const
|
|
299
|
+
const useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
|
|
300
|
+
const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
|
|
301
|
+
const backendExplicit = hasOption(args, '--backend') || useOllama || noOllama;
|
|
302
|
+
const backend = String(readOption(args, '--backend', hasFlag(args, '--mock') ? 'fake' : useOllama && !noOllama ? 'ollama' : 'codex-sdk'));
|
|
175
303
|
const mock = hasFlag(args, '--mock') || backend === 'fake';
|
|
176
304
|
const real = hasFlag(args, '--real');
|
|
177
305
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
178
306
|
const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
|
|
179
307
|
const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
|
|
180
308
|
const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', 'latest')));
|
|
309
|
+
const ollamaModel = String(readOption(args, '--ollama-model', readOption(args, '--local-model-model', '')) || '') || null;
|
|
310
|
+
const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
|
|
181
311
|
const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
|
|
182
312
|
const attach = hasFlag(args, '--attach');
|
|
183
|
-
const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id']);
|
|
313
|
+
const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url']);
|
|
184
314
|
const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
|
|
185
|
-
return { action, prompt, clones, workItems, concurrency, backend, mock, real, readonly, writeMode, json, missionId, noOpenZellij, attach };
|
|
315
|
+
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach };
|
|
316
|
+
}
|
|
317
|
+
async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
318
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-work-graph.json'), artifacts.workGraph);
|
|
319
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
|
|
320
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-concurrency-governor.json'), artifacts.governor);
|
|
321
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-active-pool.json'), artifacts.activePool);
|
|
322
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
|
|
323
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
|
|
324
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
|
|
325
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'prompt-placeholder-guard.json'), artifacts.placeholderGuard);
|
|
186
326
|
}
|
|
187
327
|
function clampClones(value) {
|
|
188
328
|
if (!Number.isFinite(value) || value < 1)
|
|
@@ -212,6 +352,9 @@ function readOption(args, name, fallback) {
|
|
|
212
352
|
const prefixed = args.find((arg) => String(arg).startsWith(name + '='));
|
|
213
353
|
return prefixed ? prefixed.slice(name.length + 1) : fallback;
|
|
214
354
|
}
|
|
355
|
+
function hasOption(args, name) {
|
|
356
|
+
return args.includes(name) || args.some((arg) => String(arg).startsWith(name + '='));
|
|
357
|
+
}
|
|
215
358
|
function positionalArgs(args, valueFlags) {
|
|
216
359
|
const out = [];
|
|
217
360
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -233,4 +376,44 @@ function emit(parsed, result, text) {
|
|
|
233
376
|
text();
|
|
234
377
|
return result;
|
|
235
378
|
}
|
|
379
|
+
async function resolveNarutoLocalWorkerMode(parsed) {
|
|
380
|
+
const configInput = {
|
|
381
|
+
ollamaEnabled: parsed.ollamaEnabled,
|
|
382
|
+
model: parsed.ollamaModel,
|
|
383
|
+
baseUrl: parsed.ollamaBaseUrl
|
|
384
|
+
};
|
|
385
|
+
if (parsed.backend === 'ollama')
|
|
386
|
+
configInput.backend = 'ollama';
|
|
387
|
+
const config = await resolveOllamaWorkerConfig(configInput).catch(() => null);
|
|
388
|
+
const policy = classifyOllamaWorkerSlice({
|
|
389
|
+
id: 'naruto-local-worker-probe',
|
|
390
|
+
role: parsed.readonly ? 'collector' : 'implementer',
|
|
391
|
+
description: parsed.prompt,
|
|
392
|
+
write_paths: parsed.readonly ? [] : ['<lease-scoped-worker-path>']
|
|
393
|
+
}, { route: NARUTO_ROUTE, agent: { role: parsed.readonly ? 'collector' : 'implementer' } });
|
|
394
|
+
const autoSelectEligible = parsed.backend === 'codex-sdk'
|
|
395
|
+
&& parsed.backendExplicit !== true
|
|
396
|
+
&& parsed.noOllama !== true
|
|
397
|
+
&& config?.ok === true
|
|
398
|
+
&& config.enabled === true
|
|
399
|
+
&& policy.ok === true;
|
|
400
|
+
return {
|
|
401
|
+
schema: 'sks.naruto-local-worker-mode.v1',
|
|
402
|
+
enabled: config?.enabled === true,
|
|
403
|
+
provider: config?.provider || 'ollama',
|
|
404
|
+
model: config?.model || null,
|
|
405
|
+
requested_backend: parsed.backend,
|
|
406
|
+
backend_explicit: parsed.backendExplicit,
|
|
407
|
+
auto_select_eligible: autoSelectEligible,
|
|
408
|
+
worker_only: true,
|
|
409
|
+
no_strategy_planning_design: true,
|
|
410
|
+
policy,
|
|
411
|
+
blockers: [
|
|
412
|
+
...(config?.blockers || (config ? [] : ['ollama_worker_config_unavailable'])),
|
|
413
|
+
...(policy.blockers || []),
|
|
414
|
+
...(parsed.backendExplicit ? ['backend_explicit'] : []),
|
|
415
|
+
...(parsed.noOllama ? ['no_ollama_requested'] : [])
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
}
|
|
236
419
|
//# sourceMappingURL=naruto-command.js.map
|
|
@@ -231,7 +231,7 @@ async function executeRouteCommand(root, route, prompt, { auto = false } = {}) {
|
|
|
231
231
|
return routeExecutionResult(route, ['sks', ...commandArgs].join(' '), result, {
|
|
232
232
|
okStatus: 'completed',
|
|
233
233
|
trustStatus: 'verified_partial',
|
|
234
|
-
executionKind: route.command === '$DB' || route.command === '$Wiki' || route.command === '$Fast-Mode' || route.command === '$with-local-llm-on' ? 'safe_deterministic' : 'mock_safe',
|
|
234
|
+
executionKind: route.command === '$DB' || route.command === '$Wiki' || route.command === '$Fast-Mode' || route.command === '$with-local-llm-on' || route.command === '$Commit' || route.command === '$Commit-And-Push' ? 'safe_deterministic' : 'mock_safe',
|
|
235
235
|
});
|
|
236
236
|
}
|
|
237
237
|
async function runAutoVerification(root, missionId) {
|
|
@@ -290,7 +290,7 @@ async function runSks(root, commandArgs) {
|
|
|
290
290
|
cwd: root,
|
|
291
291
|
timeoutMs: 180_000,
|
|
292
292
|
maxOutputBytes: 512 * 1024,
|
|
293
|
-
env: { SKS_SKIP_NPM_FRESHNESS_CHECK: '1', CI: 'true' },
|
|
293
|
+
env: { SKS_SKIP_NPM_FRESHNESS_CHECK: '1', SKS_LOCAL_LLM_TOGGLE_ONLY: '1', CI: 'true' },
|
|
294
294
|
});
|
|
295
295
|
}
|
|
296
296
|
function routeExecutionResult(route, command, result, options = {}) {
|
|
@@ -348,6 +348,10 @@ function safeRouteExecutionArgs(route, prompt, { auto = false } = {}) {
|
|
|
348
348
|
return ['fast-mode', fastModeActionFromPrompt(prompt), '--json'];
|
|
349
349
|
if (route.command === '$with-local-llm-on')
|
|
350
350
|
return ['with-local-llm', localModelActionFromPrompt(prompt), '--json'];
|
|
351
|
+
if (route.command === '$Commit')
|
|
352
|
+
return ['commit', '--json'];
|
|
353
|
+
if (route.command === '$Commit-And-Push')
|
|
354
|
+
return ['commit-and-push', '--json'];
|
|
351
355
|
return ['team', prompt, '--mock', '--json', ...(auto ? ['--no-open-zellij'] : [])];
|
|
352
356
|
}
|
|
353
357
|
function fastModeActionFromPrompt(prompt = '') {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { resolveLocalCollaborationPolicy } from '../local-llm/local-collaboration-policy.js';
|
|
3
4
|
export const DOCTOR_READINESS_MATRIX_SCHEMA = 'sks.doctor-readiness-matrix.v1';
|
|
4
5
|
export async function writeDoctorReadinessMatrix(root, input = {}) {
|
|
5
6
|
const matrix = buildDoctorReadinessMatrix(input);
|
|
@@ -53,6 +54,20 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
53
54
|
warnings.add('codex_app_fast_selector_repaired_restart_app_if_needed');
|
|
54
55
|
if (input.codex_lb?.ok === false)
|
|
55
56
|
warnings.add(`codex_lb_${input.codex_lb?.circuit?.state || 'blocked'}`);
|
|
57
|
+
const localModel = input.local_model || {};
|
|
58
|
+
const localStatus = String(localModel.status || (localModel.enabled ? 'enabled_unverified' : 'disabled'));
|
|
59
|
+
if (localModel.enabled === true && localStatus === 'enabled_unverified')
|
|
60
|
+
warnings.add('local_llm_enabled_unverified');
|
|
61
|
+
if (localModel.enabled === true && localStatus === 'degraded')
|
|
62
|
+
warnings.add('local_llm_degraded');
|
|
63
|
+
if (localModel.enabled === true && localStatus === 'blocked')
|
|
64
|
+
warnings.add('local_llm_blocked_worker_tier_disabled');
|
|
65
|
+
const localCollaborationPolicy = resolveLocalCollaborationPolicy({ mode: input.local_collaboration?.mode || null });
|
|
66
|
+
const gptFinalAvailable = input.local_collaboration?.gpt_final_arbiter_available === undefined
|
|
67
|
+
? codexBinOk
|
|
68
|
+
: input.local_collaboration.gpt_final_arbiter_available === true;
|
|
69
|
+
if (localCollaborationPolicy.gpt_final_required && !gptFinalAvailable)
|
|
70
|
+
blockers.add('gpt_final_arbiter_unavailable');
|
|
56
71
|
const codexConfigNode = nodeRead.ok !== false && codexConfig.ok !== false;
|
|
57
72
|
const codexConfigChild = childRead.ok !== false && codexConfig.ok !== false;
|
|
58
73
|
const cliReady = codexBinOk && codexConfigNode && codexConfigChild && cliConfigOk;
|
|
@@ -92,6 +107,25 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
92
107
|
hooks_ready: input.hooks_ready !== false,
|
|
93
108
|
codex_app_ready: input.codex_app?.ok === true,
|
|
94
109
|
codex_app_required_for_cli: false,
|
|
110
|
+
local_collaboration: {
|
|
111
|
+
mode: localCollaborationPolicy.mode,
|
|
112
|
+
local_backend: input.local_collaboration?.local_backend || localModel.provider || 'ollama',
|
|
113
|
+
local_model: input.local_collaboration?.local_model || localModel.model || null,
|
|
114
|
+
final_arbiter: gptFinalAvailable ? 'GPT available' : 'missing',
|
|
115
|
+
final_apply_allowed: localCollaborationPolicy.gpt_final_required ? gptFinalAvailable : localCollaborationPolicy.mode === 'disabled',
|
|
116
|
+
blockers: localCollaborationPolicy.gpt_final_required && !gptFinalAvailable ? ['gpt_final_arbiter_unavailable'] : localCollaborationPolicy.blockers
|
|
117
|
+
},
|
|
118
|
+
local_llm: {
|
|
119
|
+
enabled: localModel.enabled === true,
|
|
120
|
+
status: localStatus,
|
|
121
|
+
provider: localModel.provider || 'ollama',
|
|
122
|
+
model: localModel.model || null,
|
|
123
|
+
endpoint: localModel.endpoint || localModel.base_url || null,
|
|
124
|
+
last_smoke: localModel.last_smoke || null,
|
|
125
|
+
final_arbiter: 'GPT required',
|
|
126
|
+
worker_tier_enabled: localModel.enabled === true && localStatus === 'verified',
|
|
127
|
+
blockers: normalizeList(localModel.blockers)
|
|
128
|
+
},
|
|
95
129
|
ready: blockers.size === 0 && cliReady,
|
|
96
130
|
primary_blocker: [...blockers][0] || null,
|
|
97
131
|
blockers: [...blockers],
|
|
@@ -25,6 +25,7 @@ const FIXTURES = Object.freeze({
|
|
|
25
25
|
'cli-status': fixture('execute', 'sks status --json', [], 'pass'),
|
|
26
26
|
'cli-usage': fixture('execute', 'sks usage overview', [], 'pass'),
|
|
27
27
|
'cli-quickstart': fixture('execute', 'sks quickstart', [], 'pass'),
|
|
28
|
+
'cli-update': fixture('mock', 'sks update now --dry-run --json', [], 'pass'),
|
|
28
29
|
'cli-update-check': fixture('static', 'sks update-check --json', [], 'pass'),
|
|
29
30
|
'cli-guard': fixture('execute', 'sks guard check --json', [], 'pass'),
|
|
30
31
|
'cli-conflicts': fixture('execute', 'sks conflicts check --json', [], 'pass'),
|
|
@@ -55,6 +56,7 @@ const FIXTURES = Object.freeze({
|
|
|
55
56
|
'cli-stats': fixture('execute', 'sks stats --json', [], 'pass'),
|
|
56
57
|
'cli-dollar-commands': fixture('execute', 'sks dollar-commands --json', [], 'pass'),
|
|
57
58
|
'cli-fast-mode': fixture('execute', 'sks fast-mode status --json', [], 'pass'),
|
|
59
|
+
'cli-with-local-llm': fixture('execute', 'sks with-local-llm status --json', [], 'pass'),
|
|
58
60
|
'cli-dfix': fixture('execute_and_validate_artifacts', 'sks dfix fixture --json', ['completion-proof.json', 'dfix-gate.json', 'dfix-verification.json'], 'pass'),
|
|
59
61
|
'cli-wiki': fixture('execute_and_validate_artifacts', 'sks wiki image-ingest test/fixtures/images/one-by-one.png --json', [{ path: '.sneakoscope/wiki/image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1', require_anchors: false }], 'pass'),
|
|
60
62
|
'cli-db': fixture('execute', 'sks db policy', [], 'pass'),
|
|
@@ -112,6 +114,8 @@ const FIXTURES = Object.freeze({
|
|
|
112
114
|
'route-fast-on': fixture('mock', '$Fast-On covered by hermetic fast-mode blackbox toggle test', [], 'pass'),
|
|
113
115
|
'route-fast-off': fixture('mock', '$Fast-Off covered by hermetic fast-mode blackbox toggle test', [], 'pass'),
|
|
114
116
|
'route-local-model': fixture('execute', 'sks with-local-llm status --json', [], 'pass'),
|
|
117
|
+
'route-with-local-llm-on': fixture('mock', '$with-local-llm-on covered by hermetic local-model dollar-command blackbox toggle test', [], 'pass'),
|
|
118
|
+
'route-with-local-llm-off': fixture('mock', '$with-local-llm-off covered by hermetic local-model dollar-command blackbox toggle test', [], 'pass'),
|
|
115
119
|
'route-help': fixture('mock', '$Help lightweight route', [], 'pass'),
|
|
116
120
|
'route-commit': fixture('mock', '$Commit git route', ['completion-proof.json'], 'pass'),
|
|
117
121
|
'route-commit-and-push': fixture('mock', '$Commit-And-Push git route', ['completion-proof.json'], 'pass'),
|
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 = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.5';
|
|
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() {
|
package/dist/core/git-simple.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { runProcess, projectRoot, isGitRepo, nowIso, sha256 } from './fsx.js';
|
|
2
3
|
import { redactSecrets } from './secret-redaction.js';
|
|
4
|
+
import { runOllamaAgent } from './agents/agent-runner-ollama.js';
|
|
5
|
+
import { resolveOllamaWorkerConfig } from './agents/ollama-worker-config.js';
|
|
3
6
|
const TRAILER = 'Co-authored-by: Codex <noreply@openai.com>';
|
|
4
7
|
export async function simpleGitCommitCommand(args = [], opts = {}) {
|
|
5
8
|
const root = await projectRoot();
|
|
@@ -16,17 +19,24 @@ export async function simpleGitCommitCommand(args = [], opts = {}) {
|
|
|
16
19
|
export async function simpleGitCommit(root, { message = null, push = false } = {}) {
|
|
17
20
|
if (!await isGitRepo(root))
|
|
18
21
|
return { schema: 'sks.simple-git.v1', ok: false, reason: 'not_git_repo', root };
|
|
19
|
-
const before = await
|
|
22
|
+
const [before, branch, head] = await Promise.all([
|
|
23
|
+
git(root, ['status', '--short']),
|
|
24
|
+
git(root, ['branch', '--show-current']),
|
|
25
|
+
git(root, ['rev-parse', '--short', 'HEAD'])
|
|
26
|
+
]);
|
|
20
27
|
const changed = statusLines(before.stdout);
|
|
21
28
|
if (!changed.length)
|
|
22
29
|
return { schema: 'sks.simple-git.v1', ok: false, reason: 'no_changes', root };
|
|
30
|
+
const localWorker = message
|
|
31
|
+
? localWorkerSkipped('message_provided')
|
|
32
|
+
: await draftCommitMessageWithLocalWorker(root, changed, { push, branch: branch.stdout.trim(), head: head.stdout.trim() });
|
|
23
33
|
const add = await git(root, ['add', '-A']);
|
|
24
34
|
if (add.code !== 0)
|
|
25
35
|
return failure(root, 'git_add_failed', before, add);
|
|
26
36
|
const stagedCheck = await git(root, ['diff', '--cached', '--quiet']);
|
|
27
37
|
if (stagedCheck.code === 0)
|
|
28
38
|
return { schema: 'sks.simple-git.v1', ok: false, reason: 'no_staged_changes', root, changed };
|
|
29
|
-
const commitMessage = ensureCodexTrailer(message || buildCommitMessage(changed));
|
|
39
|
+
const commitMessage = ensureCodexTrailer(message || localWorker.message || buildCommitMessage(changed));
|
|
30
40
|
const commit = await git(root, ['commit', '-m', commitTitle(commitMessage), '-m', commitBody(commitMessage)]);
|
|
31
41
|
if (commit.code !== 0)
|
|
32
42
|
return failure(root, 'git_commit_failed', before, commit);
|
|
@@ -46,7 +56,8 @@ export async function simpleGitCommit(root, { message = null, push = false } = {
|
|
|
46
56
|
commit: commitSummary(commit),
|
|
47
57
|
hash: hash.stdout.trim(),
|
|
48
58
|
pushed: Boolean(push && pushResult?.code === 0),
|
|
49
|
-
push: pushResult ? { ok: pushResult.code === 0, stdout: pushResult.stdout.trim(), stderr: pushResult.stderr.trim() } : null
|
|
59
|
+
push: pushResult ? { ok: pushResult.code === 0, stdout: pushResult.stdout.trim(), stderr: pushResult.stderr.trim() } : null,
|
|
60
|
+
local_worker: localWorker.report
|
|
50
61
|
});
|
|
51
62
|
}
|
|
52
63
|
function failure(root, reason, before, failed, extra = {}) {
|
|
@@ -77,6 +88,134 @@ function buildCommitMessage(changed = []) {
|
|
|
77
88
|
const more = changed.length > 12 ? `\n- ...and ${changed.length - 12} more` : '';
|
|
78
89
|
return `chore: update project changes\n\nSummary: ${summary}\n\nChanged files:\n${files}${more}`;
|
|
79
90
|
}
|
|
91
|
+
async function draftCommitMessageWithLocalWorker(root, changed, context) {
|
|
92
|
+
const disabled = String(process.env.SKS_SIMPLE_GIT_LOCAL_LLM || '').trim() === '0';
|
|
93
|
+
if (disabled)
|
|
94
|
+
return { message: null, report: localWorkerSkipped('disabled_by_SKS_SIMPLE_GIT_LOCAL_LLM') };
|
|
95
|
+
const config = await resolveOllamaWorkerConfig().catch((error) => null);
|
|
96
|
+
if (!config?.ok || config.enabled !== true) {
|
|
97
|
+
return {
|
|
98
|
+
message: null,
|
|
99
|
+
report: {
|
|
100
|
+
schema: 'sks.simple-git-local-worker.v1',
|
|
101
|
+
generated_at: nowIso(),
|
|
102
|
+
ok: false,
|
|
103
|
+
used: false,
|
|
104
|
+
enabled: config?.enabled === true,
|
|
105
|
+
provider: config?.provider || 'ollama',
|
|
106
|
+
model: config?.model || null,
|
|
107
|
+
worker_only: true,
|
|
108
|
+
parent_owned_git_mutation: true,
|
|
109
|
+
task: context.push ? 'commit-and-push-message-draft' : 'commit-message-draft',
|
|
110
|
+
blockers: config?.blockers || ['ollama_worker_config_unavailable']
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const runId = sha256(`${nowIso()}:${context.branch}:${context.head}:${changed.join('\n')}`).slice(0, 12);
|
|
115
|
+
const workerDirRel = path.join('.git', 'sks-local-workers', 'simple-git', runId);
|
|
116
|
+
const slice = {
|
|
117
|
+
id: `simple-git-message-${runId}`,
|
|
118
|
+
role: 'collector',
|
|
119
|
+
domain: 'git',
|
|
120
|
+
description: [
|
|
121
|
+
'simple collect summarize git status for a commit message draft only',
|
|
122
|
+
'Do not run git commands. Do not choose whether to commit or push.',
|
|
123
|
+
'Return summary as a conventional commit title and proposed_changes as short body bullets.',
|
|
124
|
+
`Action: ${context.push ? 'commit-and-push' : 'commit'}`,
|
|
125
|
+
`Branch: ${context.branch || 'unknown'}`,
|
|
126
|
+
`HEAD before commit: ${context.head || 'unknown'}`,
|
|
127
|
+
'Changed status lines:',
|
|
128
|
+
...changed.slice(0, 80)
|
|
129
|
+
].join('\n')
|
|
130
|
+
};
|
|
131
|
+
const agent = {
|
|
132
|
+
id: 'simple_git_local_worker',
|
|
133
|
+
session_id: `simple-git-${runId}`,
|
|
134
|
+
slot_id: 'local-worker',
|
|
135
|
+
generation_index: 1,
|
|
136
|
+
persona_id: 'local_git_summarizer',
|
|
137
|
+
role: 'collector'
|
|
138
|
+
};
|
|
139
|
+
const result = await runOllamaAgent(agent, slice, {
|
|
140
|
+
missionId: `simple-git-${runId}`,
|
|
141
|
+
agentRoot: root,
|
|
142
|
+
cwd: root,
|
|
143
|
+
workerDirRel,
|
|
144
|
+
route: context.push ? '$Commit-And-Push' : '$Commit',
|
|
145
|
+
fastMode: true,
|
|
146
|
+
serviceTier: 'fast',
|
|
147
|
+
ollamaTimeoutMs: Number(process.env.SKS_SIMPLE_GIT_OLLAMA_TIMEOUT_MS || 15000)
|
|
148
|
+
}).catch((error) => ({
|
|
149
|
+
status: 'blocked',
|
|
150
|
+
summary: '',
|
|
151
|
+
findings: [],
|
|
152
|
+
proposed_changes: [],
|
|
153
|
+
artifacts: [],
|
|
154
|
+
blockers: [error instanceof Error ? error.message : String(error)]
|
|
155
|
+
}));
|
|
156
|
+
const message = result.status === 'done' ? localWorkerCommitMessage(result, changed) : null;
|
|
157
|
+
return {
|
|
158
|
+
message,
|
|
159
|
+
report: {
|
|
160
|
+
schema: 'sks.simple-git-local-worker.v1',
|
|
161
|
+
generated_at: nowIso(),
|
|
162
|
+
ok: result.status === 'done',
|
|
163
|
+
used: Boolean(message),
|
|
164
|
+
enabled: true,
|
|
165
|
+
provider: config.provider,
|
|
166
|
+
model: config.model,
|
|
167
|
+
worker_only: true,
|
|
168
|
+
parent_owned_git_mutation: true,
|
|
169
|
+
task: context.push ? 'commit-and-push-message-draft' : 'commit-message-draft',
|
|
170
|
+
artifacts: result.artifacts || [],
|
|
171
|
+
summary: result.summary || null,
|
|
172
|
+
blockers: result.blockers || [],
|
|
173
|
+
fallback: message ? null : 'deterministic_commit_message'
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function localWorkerCommitMessage(result, changed) {
|
|
178
|
+
const title = normalizeCommitTitle(result.summary);
|
|
179
|
+
if (!title)
|
|
180
|
+
return null;
|
|
181
|
+
const bodyLines = [
|
|
182
|
+
'Summary: local Ollama worker drafted this message from git status; parent SKS performed all git mutations.',
|
|
183
|
+
'',
|
|
184
|
+
...stringArray(result.proposed_changes || result.findings).slice(0, 8).map((line) => `- ${line}`),
|
|
185
|
+
...(stringArray(result.proposed_changes || result.findings).length ? [] : changed.slice(0, 8).map((line) => `- ${line}`))
|
|
186
|
+
];
|
|
187
|
+
return `${title}\n\n${bodyLines.join('\n')}`;
|
|
188
|
+
}
|
|
189
|
+
function normalizeCommitTitle(value) {
|
|
190
|
+
const raw = String(value || '').split(/\r?\n/)[0]?.trim() || '';
|
|
191
|
+
const stripped = raw.replace(/^["']|["']$/g, '').replace(/\s+/g, ' ').trim();
|
|
192
|
+
if (!stripped)
|
|
193
|
+
return '';
|
|
194
|
+
const conventional = /^[a-z][a-z0-9-]*(\([^)]+\))?:\s+\S/.test(stripped);
|
|
195
|
+
const title = conventional ? stripped : `chore: ${stripped.replace(/^(commit message|summary)\s*:\s*/i, '')}`;
|
|
196
|
+
return title.length > 96 ? title.slice(0, 93).trimEnd() + '...' : title;
|
|
197
|
+
}
|
|
198
|
+
function stringArray(value) {
|
|
199
|
+
return Array.isArray(value) ? value.map((line) => String(line || '').trim()).filter(Boolean) : [];
|
|
200
|
+
}
|
|
201
|
+
function localWorkerSkipped(reason) {
|
|
202
|
+
return {
|
|
203
|
+
message: null,
|
|
204
|
+
report: {
|
|
205
|
+
schema: 'sks.simple-git-local-worker.v1',
|
|
206
|
+
generated_at: nowIso(),
|
|
207
|
+
ok: true,
|
|
208
|
+
used: false,
|
|
209
|
+
enabled: false,
|
|
210
|
+
provider: 'ollama',
|
|
211
|
+
model: null,
|
|
212
|
+
worker_only: true,
|
|
213
|
+
parent_owned_git_mutation: true,
|
|
214
|
+
task: 'commit-message-draft',
|
|
215
|
+
blockers: [reason]
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
80
219
|
function ensureCodexTrailer(message = '') {
|
|
81
220
|
const withoutDuplicate = String(message || '')
|
|
82
221
|
.split(/\r?\n/)
|