sneakoscope 2.0.5 → 2.0.7
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 -4
- 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 +37 -8
- package/dist/cli/install-helpers.js +23 -0
- package/dist/commands/codex-app.js +25 -3
- package/dist/commands/doctor.js +19 -4
- package/dist/commands/mad-sks.js +2 -2
- package/dist/core/agents/agent-orchestrator.js +160 -6
- package/dist/core/agents/agent-patch-schema.js +16 -2
- package/dist/core/agents/agent-proof-evidence.js +27 -2
- package/dist/core/agents/agent-worker-pipeline.js +9 -1
- package/dist/core/agents/native-cli-session-swarm.js +25 -4
- package/dist/core/agents/native-cli-worker.js +28 -1
- package/dist/core/agents/native-worker-backend-router.js +19 -1
- package/dist/core/codex-app.js +124 -2
- package/dist/core/codex-control/python-codex-sdk-adapter.js +28 -4
- package/dist/core/commands/naruto-command.js +48 -14
- package/dist/core/feature-registry.js +2 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-integration-worktree.js +15 -0
- package/dist/core/git/git-repo-detection.js +72 -0
- package/dist/core/git/git-worktree-cache-policy.js +36 -0
- package/dist/core/git/git-worktree-capability.js +54 -0
- package/dist/core/git/git-worktree-cleanup.js +51 -0
- package/dist/core/git/git-worktree-conflict-resolver.js +13 -0
- package/dist/core/git/git-worktree-diff.js +50 -0
- package/dist/core/git/git-worktree-manager.js +86 -0
- package/dist/core/git/git-worktree-merge-queue.js +55 -0
- package/dist/core/git/git-worktree-patch-envelope.js +35 -0
- package/dist/core/git/git-worktree-pool.js +23 -0
- package/dist/core/git/git-worktree-root.js +52 -0
- package/dist/core/git/git-worktree-runner.js +40 -0
- package/dist/core/hooks-runtime.js +2 -233
- package/dist/core/init.js +8 -8
- package/dist/core/naruto/naruto-active-pool.js +55 -4
- package/dist/core/naruto/naruto-gpt-final-pack.js +2 -0
- package/dist/core/naruto/naruto-work-graph.js +16 -1
- package/dist/core/pipeline-internals/runtime-core.js +1 -1
- package/dist/core/ppt.js +31 -8
- package/dist/core/product-design-app-server.js +410 -0
- package/dist/core/product-design-plugin.js +139 -0
- package/dist/core/routes.js +8 -8
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-naruto-dashboard.js +10 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +6 -4
- package/dist/scripts/git-worktree-cache-performance-check.js +25 -0
- package/dist/scripts/git-worktree-capability-check.js +27 -0
- package/dist/scripts/git-worktree-cleanup-check.js +27 -0
- package/dist/scripts/git-worktree-diff-export-check.js +43 -0
- package/dist/scripts/git-worktree-manager-check.js +37 -0
- package/dist/scripts/git-worktree-merge-queue-check.js +30 -0
- package/dist/scripts/git-worktree-pool-performance-check.js +20 -0
- package/dist/scripts/lib/git-worktree-fixture.js +33 -0
- package/dist/scripts/naruto-active-pool-check.js +13 -1
- package/dist/scripts/naruto-readonly-routing-check.js +116 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +16 -5
- package/dist/scripts/naruto-worktree-coding-check.js +44 -0
- package/dist/scripts/naruto-worktree-gpt-final-check.js +45 -0
- package/dist/scripts/naruto-worktree-zellij-ui-check.js +28 -0
- package/dist/scripts/product-design-auto-install-check.js +119 -0
- package/dist/scripts/product-design-plugin-routing-check.js +101 -0
- package/dist/scripts/release-parallel-check.js +16 -2
- package/dist/scripts/release-provenance-check.js +21 -0
- package/package.json +17 -3
- package/schemas/git/git-worktree-capability.schema.json +19 -0
- package/schemas/git/git-worktree-manifest.schema.json +36 -0
|
@@ -9,10 +9,11 @@ export const ZELLIJ_WORKER_PANE_EVENT_SCHEMA = 'sks.zellij-worker-pane-event.v1'
|
|
|
9
9
|
export function buildWorkerPaneName(slotId, generationIndex) {
|
|
10
10
|
return `${slotId}/gen-${Math.max(1, Math.floor(Number(generationIndex) || 1))}`;
|
|
11
11
|
}
|
|
12
|
-
export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status) {
|
|
12
|
+
export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status, worktree) {
|
|
13
13
|
const base = buildWorkerPaneName(slotId, generationIndex);
|
|
14
14
|
const normalized = normalizePaneProviderContext(context, serviceTier);
|
|
15
|
-
|
|
15
|
+
const wt = worktree ? ` · WT:${worktree.id} · branch:${worktree.branch}` : '';
|
|
16
|
+
return `${base}${wt} · ${backend || 'codex-sdk'} · ${providerPaneLabel(normalized)} · ${status || 'launching'}`;
|
|
16
17
|
}
|
|
17
18
|
export function isRealZellijWorkerPaneIdSource(value) {
|
|
18
19
|
return value === 'zellij_worker_new_pane_stdout' || value === 'zellij_worker_list_panes';
|
|
@@ -22,7 +23,7 @@ export function buildWorkerPaneArtifact(input) {
|
|
|
22
23
|
const paneIdSource = input.paneIdSource || 'zellij_worker_pane_launch_failed';
|
|
23
24
|
const blockers = input.blockers || [];
|
|
24
25
|
const providerContext = normalizePaneProviderContext(input.providerContext, input.serviceTier);
|
|
25
|
-
const paneTitle = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.status || input.statusLabel);
|
|
26
|
+
const paneTitle = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.status || input.statusLabel, input.worktree || null);
|
|
26
27
|
return {
|
|
27
28
|
schema: ZELLIJ_WORKER_PANE_SCHEMA,
|
|
28
29
|
generated_at: now,
|
|
@@ -42,6 +43,7 @@ export function buildWorkerPaneArtifact(input) {
|
|
|
42
43
|
provider: providerContext.provider,
|
|
43
44
|
service_tier: providerContext.service_tier,
|
|
44
45
|
provider_context: providerContext,
|
|
46
|
+
worktree: input.worktree || null,
|
|
45
47
|
worker_artifact_dir: input.workerArtifactDir,
|
|
46
48
|
worker_result_path: input.resultPath,
|
|
47
49
|
heartbeat_path: input.heartbeatPath,
|
|
@@ -82,7 +84,7 @@ export async function openWorkerPane(input) {
|
|
|
82
84
|
timeoutMs: 5000,
|
|
83
85
|
optional: false
|
|
84
86
|
});
|
|
85
|
-
const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running');
|
|
87
|
+
const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running', input.worktree || null);
|
|
86
88
|
let launch = createSession.ok
|
|
87
89
|
? await runZellij(['--session', input.sessionName, 'action', 'new-pane', '--direction', 'right', '--name', paneName, '--', 'sh', '-lc', input.workerCommand], {
|
|
88
90
|
cwd,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const policyMod = await importDist('core/git/git-worktree-cache-policy.js');
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
const entries = Array.from({ length: 1000 }, (_, index) => ({
|
|
7
|
+
path: `/tmp/wt-${index}`,
|
|
8
|
+
updated_at_ms: now - index * 60000,
|
|
9
|
+
bytes: 1024 * (index + 1),
|
|
10
|
+
dirty: index % 137 === 0
|
|
11
|
+
}));
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
const plan = policyMod.planGitWorktreeCachePolicy({ entries, nowMs: now, maxEntries: 120, maxBytes: 40 * 1024 * 1024, ttlMs: 2 * 60 * 60 * 1000 });
|
|
14
|
+
const elapsed = Date.now() - start;
|
|
15
|
+
assertGate(plan.ok === true, 'cache policy plan must pass', plan);
|
|
16
|
+
assertGate(plan.keep.length <= 120 || plan.dirty_retained.length > 0, 'cache policy must bound retained clean entries', plan);
|
|
17
|
+
assertGate(plan.dirty_retained.length > 0, 'dirty entries must be retained by cache policy', plan);
|
|
18
|
+
assertGate(elapsed < 250, 'cache policy must stay fast on 1000 entries', { elapsed });
|
|
19
|
+
emitGate('git:worktree-cache-performance', {
|
|
20
|
+
elapsed_ms: elapsed,
|
|
21
|
+
keep: plan.keep.length,
|
|
22
|
+
prune: plan.prune.length,
|
|
23
|
+
dirty_retained: plan.dirty_retained.length
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=git-worktree-cache-performance-check.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture, makeNonGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const capabilityMod = await importDist('core/git/git-worktree-capability.js');
|
|
9
|
+
const nonGit = makeNonGitFixture('worktree-capability-non-git');
|
|
10
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-capability-'));
|
|
11
|
+
delete process.env.SKS_ALLOW_IN_REPO_WORKTREES;
|
|
12
|
+
const nonGitCapability = await capabilityMod.evaluateGitWorktreeCapability({ root: nonGit, missionId: 'M-non-git' });
|
|
13
|
+
assertGate(nonGitCapability.mode === 'patch-envelope-only', 'non-Git projects must degrade to patch-envelope-only', nonGitCapability);
|
|
14
|
+
assertGate(nonGitCapability.worktree_probe_attempted === false, 'non-Git projects must not probe git worktree', nonGitCapability);
|
|
15
|
+
const repo = makeGitFixture('worktree-capability-git');
|
|
16
|
+
const gitCapability = await capabilityMod.evaluateGitWorktreeCapability({ root: repo, missionId: 'M-git' });
|
|
17
|
+
assertGate(gitCapability.ok === true && gitCapability.mode === 'git-worktree', 'Git fixture must support worktree mode', gitCapability);
|
|
18
|
+
assertGate(gitCapability.root_resolution?.in_repo === false, 'default worktree root must be outside main repo', gitCapability.root_resolution);
|
|
19
|
+
process.env.SKS_WORKTREE_ROOT = path.join(repo, '.sneakoscope', 'worktrees');
|
|
20
|
+
const blocked = await capabilityMod.evaluateGitWorktreeCapability({ root: repo, missionId: 'M-in-repo' });
|
|
21
|
+
assertGate(blocked.ok === false && blocked.blockers.includes('git_worktree_root_inside_repo_blocked'), 'in-repo worktree root must be blocked by default', blocked);
|
|
22
|
+
emitGate('git:worktree-capability', {
|
|
23
|
+
non_git_mode: nonGitCapability.mode,
|
|
24
|
+
git_mode: gitCapability.mode,
|
|
25
|
+
in_repo_blocked: true
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=git-worktree-capability-check.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const cleanupMod = await importDist('core/git/git-worktree-cleanup.js');
|
|
10
|
+
const repo = makeGitFixture('worktree-cleanup');
|
|
11
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-cleanup-'));
|
|
12
|
+
const cleanAllocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-cleanup', workerId: 'clean', slotId: 'slot-001' });
|
|
13
|
+
const clean = await cleanupMod.cleanupGitWorktree({ repoRoot: repo, worktreePath: cleanAllocation.worktree_path, branch: cleanAllocation.branch, deleteBranch: true });
|
|
14
|
+
assertGate(clean.ok === true && clean.action === 'removed', 'clean worktree must be removed by cleanup manager', clean);
|
|
15
|
+
assertGate(!fs.existsSync(cleanAllocation.worktree_path), 'removed clean worktree path should be gone');
|
|
16
|
+
const dirtyAllocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-cleanup', workerId: 'dirty', slotId: 'slot-002' });
|
|
17
|
+
fs.writeFileSync(path.join(dirtyAllocation.worktree_path, 'a.txt'), 'dirty\n');
|
|
18
|
+
const dirty = await cleanupMod.cleanupGitWorktree({ repoRoot: repo, worktreePath: dirtyAllocation.worktree_path, branch: dirtyAllocation.branch, deleteBranch: true });
|
|
19
|
+
assertGate(dirty.ok === true && dirty.action === 'retained_dirty', 'dirty worktree must be retained', dirty);
|
|
20
|
+
assertGate(fs.existsSync(dirtyAllocation.worktree_path), 'dirty worktree path must still exist');
|
|
21
|
+
assertGate(dirty.retention_lock_path && fs.existsSync(dirty.retention_lock_path), 'dirty retention lock must be written outside the retained worktree', dirty);
|
|
22
|
+
emitGate('git:worktree-cleanup', {
|
|
23
|
+
clean_action: clean.action,
|
|
24
|
+
dirty_action: dirty.action,
|
|
25
|
+
retention_lock_path: dirty.retention_lock_path
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=git-worktree-cleanup-check.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture, run } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const diffMod = await importDist('core/git/git-worktree-diff.js');
|
|
10
|
+
const envelopeMod = await importDist('core/git/git-worktree-patch-envelope.js');
|
|
11
|
+
const schemaMod = await importDist('core/agents/agent-patch-schema.js');
|
|
12
|
+
const cleanupMod = await importDist('core/git/git-worktree-cleanup.js');
|
|
13
|
+
const repo = makeGitFixture('worktree-diff');
|
|
14
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-diff-'));
|
|
15
|
+
const allocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-diff', workerId: 'worker-1', slotId: 'slot-001' });
|
|
16
|
+
fs.writeFileSync(path.join(allocation.worktree_path, 'a.txt'), 'alpha\nchanged\n');
|
|
17
|
+
const diff = await diffMod.exportGitWorktreeDiff({
|
|
18
|
+
mainRepoRoot: repo,
|
|
19
|
+
worktreePath: allocation.worktree_path,
|
|
20
|
+
missionId: 'M-diff',
|
|
21
|
+
workerId: 'worker-1'
|
|
22
|
+
});
|
|
23
|
+
const envelope = envelopeMod.buildGitWorktreePatchEnvelope({
|
|
24
|
+
diff,
|
|
25
|
+
agentId: 'agent-1',
|
|
26
|
+
sessionId: 'session-1',
|
|
27
|
+
slotId: 'slot-001',
|
|
28
|
+
generationIndex: 1
|
|
29
|
+
});
|
|
30
|
+
const validation = schemaMod.validateAgentPatchEnvelope(schemaMod.normalizeAgentPatchEnvelope(envelope));
|
|
31
|
+
assertGate(diff.ok === true && diff.clean === false, 'diff export must detect changed worktree', diff);
|
|
32
|
+
assertGate(diff.changed_files.includes('a.txt'), 'diff export must include changed file', diff);
|
|
33
|
+
assertGate(/index [0-9a-f]{40}\.\.[0-9a-f]{40}/.test(diff.diff), 'diff export must use full-index diff hashes', { diff: diff.diff });
|
|
34
|
+
assertGate(envelope.source === 'git-worktree-diff' && validation.ok === true, 'git worktree diff patch envelope must validate', { envelope, validation });
|
|
35
|
+
assertGate(run('git', ['status', '--porcelain=v1'], repo).trim() === '', 'main repo must stay clean after diff export');
|
|
36
|
+
run('git', ['checkout', '--', 'a.txt'], allocation.worktree_path);
|
|
37
|
+
await cleanupMod.cleanupGitWorktree({ repoRoot: repo, worktreePath: allocation.worktree_path, branch: allocation.branch, deleteBranch: true });
|
|
38
|
+
emitGate('git:worktree-diff-export', {
|
|
39
|
+
changed_files: diff.changed_files,
|
|
40
|
+
diff_bytes: diff.diff_bytes,
|
|
41
|
+
envelope_source: envelope.source
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=git-worktree-diff-export-check.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture, run } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const cleanupMod = await importDist('core/git/git-worktree-cleanup.js');
|
|
10
|
+
const repo = makeGitFixture('worktree-manager');
|
|
11
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-manager-'));
|
|
12
|
+
delete process.env.SKS_ALLOW_IN_REPO_WORKTREES;
|
|
13
|
+
const allocation = await managerMod.allocateWorkerWorktree({
|
|
14
|
+
repoRoot: repo,
|
|
15
|
+
missionId: 'M-manager',
|
|
16
|
+
workerId: 'worker-1',
|
|
17
|
+
slotId: 'slot-001',
|
|
18
|
+
generationIndex: 1
|
|
19
|
+
});
|
|
20
|
+
assertGate(allocation.ok === true, 'worker worktree allocation must pass', allocation);
|
|
21
|
+
assertGate(fs.existsSync(allocation.worktree_path), 'allocated worktree path must exist', allocation);
|
|
22
|
+
assertGate(!path.resolve(allocation.worktree_path).startsWith(path.resolve(repo) + path.sep), 'worktree must be outside main repo by default', allocation);
|
|
23
|
+
assertGate(fs.existsSync(allocation.manifest_path), 'worktree manifest must be written', allocation);
|
|
24
|
+
assertGate(run('git', ['status', '--porcelain=v1'], repo).trim() === '', 'main repo must stay clean after allocation');
|
|
25
|
+
const cleanup = await cleanupMod.cleanupGitWorktree({
|
|
26
|
+
repoRoot: repo,
|
|
27
|
+
worktreePath: allocation.worktree_path,
|
|
28
|
+
branch: allocation.branch,
|
|
29
|
+
deleteBranch: true
|
|
30
|
+
});
|
|
31
|
+
assertGate(cleanup.ok === true && cleanup.action === 'removed', 'clean worktree cleanup must remove allocation', cleanup);
|
|
32
|
+
emitGate('git:worktree-manager', {
|
|
33
|
+
worktree_outside_repo: true,
|
|
34
|
+
branch: allocation.branch,
|
|
35
|
+
cleanup: cleanup.action
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=git-worktree-manager-check.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const diffMod = await importDist('core/git/git-worktree-diff.js');
|
|
10
|
+
const integrationMod = await importDist('core/git/git-integration-worktree.js');
|
|
11
|
+
const queueMod = await importDist('core/git/git-worktree-merge-queue.js');
|
|
12
|
+
const repo = makeGitFixture('worktree-merge');
|
|
13
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-merge-'));
|
|
14
|
+
const a = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-merge', workerId: 'a', slotId: 'slot-001' });
|
|
15
|
+
const b = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-merge', workerId: 'b', slotId: 'slot-002' });
|
|
16
|
+
fs.writeFileSync(path.join(a.worktree_path, 'a.txt'), 'alpha\nfrom-a\n');
|
|
17
|
+
fs.writeFileSync(path.join(b.worktree_path, 'b.txt'), 'bravo\nfrom-b\n');
|
|
18
|
+
const diffA = await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: a.worktree_path, missionId: 'M-merge', workerId: 'a' });
|
|
19
|
+
const diffB = await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: b.worktree_path, missionId: 'M-merge', workerId: 'b' });
|
|
20
|
+
const integration = await integrationMod.createGitIntegrationWorktree({ repoRoot: repo, missionId: 'M-merge' });
|
|
21
|
+
const report = await queueMod.applyGitWorktreeMergeQueue({ integrationWorktreePath: integration.worktree_path, diffs: [diffA, diffB] });
|
|
22
|
+
assertGate(report.ok === true && report.applied_count === 2, 'merge queue must apply non-conflicting worktree diffs', report);
|
|
23
|
+
assertGate(fs.readFileSync(path.join(integration.worktree_path, 'a.txt'), 'utf8').includes('from-a'), 'integration worktree must include first diff');
|
|
24
|
+
assertGate(fs.readFileSync(path.join(integration.worktree_path, 'b.txt'), 'utf8').includes('from-b'), 'integration worktree must include second diff');
|
|
25
|
+
assertGate(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8') === 'alpha\n', 'main worktree must remain unchanged before integration commit');
|
|
26
|
+
emitGate('git:worktree-merge-queue', {
|
|
27
|
+
applied_count: report.applied_count,
|
|
28
|
+
changed_files: report.changed_files
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=git-worktree-merge-queue-check.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const poolMod = await importDist('core/git/git-worktree-pool.js');
|
|
5
|
+
const workerIds = Array.from({ length: 100 }, (_, index) => `worker-${index + 1}`);
|
|
6
|
+
const reusable = Array.from({ length: 32 }, (_, index) => `/tmp/reuse-${index + 1}`);
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
const plan = poolMod.planGitWorktreePool({ workerIds, reusableWorktrees: reusable });
|
|
9
|
+
const elapsed = Date.now() - start;
|
|
10
|
+
assertGate(plan.ok === true, 'pool plan must pass', plan);
|
|
11
|
+
assertGate(plan.assignments.length === 100, 'pool must assign every requested worker', plan);
|
|
12
|
+
assertGate(plan.assignments.filter((row) => row.action === 'reuse').length === 32, 'pool must reuse available worktrees first', plan);
|
|
13
|
+
assertGate(plan.allocate_count === 68, 'pool must allocate only the remaining workers', plan);
|
|
14
|
+
assertGate(elapsed < 100, 'pool plan must stay fast for 100 workers', { elapsed });
|
|
15
|
+
emitGate('git:worktree-pool-performance', {
|
|
16
|
+
elapsed_ms: elapsed,
|
|
17
|
+
reusable: 32,
|
|
18
|
+
allocate_count: plan.allocate_count
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=git-worktree-pool-performance-check.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
export function makeGitFixture(name) {
|
|
6
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `sks-${name}-`));
|
|
7
|
+
run('git', ['init'], root);
|
|
8
|
+
run('git', ['config', 'user.email', 'sks@example.invalid'], root);
|
|
9
|
+
run('git', ['config', 'user.name', 'SKS Test'], root);
|
|
10
|
+
fs.writeFileSync(path.join(root, 'a.txt'), 'alpha\n');
|
|
11
|
+
fs.writeFileSync(path.join(root, 'b.txt'), 'bravo\n');
|
|
12
|
+
run('git', ['add', 'a.txt', 'b.txt'], root);
|
|
13
|
+
run('git', ['commit', '-m', 'fixture'], root);
|
|
14
|
+
return root;
|
|
15
|
+
}
|
|
16
|
+
export function makeNonGitFixture(name) {
|
|
17
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `sks-${name}-`));
|
|
18
|
+
fs.writeFileSync(path.join(root, 'plain.txt'), 'not git\n');
|
|
19
|
+
return root;
|
|
20
|
+
}
|
|
21
|
+
export function run(command, args, cwd, input) {
|
|
22
|
+
const result = spawnSync(command, args, {
|
|
23
|
+
cwd,
|
|
24
|
+
input,
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
stdio: input === undefined ? ['ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe']
|
|
27
|
+
});
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
throw new Error(`${command} ${args.join(' ')} failed in ${cwd}: ${result.stderr || result.stdout}`);
|
|
30
|
+
}
|
|
31
|
+
return result.stdout || '';
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=git-worktree-fixture.js.map
|
|
@@ -15,13 +15,25 @@ const governor = governorMod.decideNarutoConcurrency({
|
|
|
15
15
|
const report = activePool.simulateNarutoActivePool({ graph, governor: { ...governor, safe_active_workers: 5 } });
|
|
16
16
|
assertGate(report.ok === true, 'active pool must drain cleanly', report);
|
|
17
17
|
assertGate(report.max_observed_active_workers <= 5, 'active pool must never exceed safe cap', report);
|
|
18
|
+
assertGate(report.max_observed_write_lease_conflicts === 0, 'active pool must never run overlapping write leases concurrently', report);
|
|
18
19
|
assertGate(report.completed_count >= graph.total_work_items, 'active pool must complete all base work items', report);
|
|
19
20
|
assertGate(report.refill_events >= 5, 'active pool must refill slots as work drains', report);
|
|
20
21
|
assertGate(report.duplicate_execution_count === 0, 'active pool must not duplicate work without retry', report);
|
|
22
|
+
const sameLeaseGraph = workGraph.buildNarutoWorkGraph({
|
|
23
|
+
requestedClones: 10,
|
|
24
|
+
totalWorkItems: 10,
|
|
25
|
+
writeCapable: true,
|
|
26
|
+
targetPaths: ['src/shared-fixture.ts'],
|
|
27
|
+
maxActiveWorkers: 5
|
|
28
|
+
});
|
|
29
|
+
const sameLeaseReport = activePool.simulateNarutoActivePool({ graph: sameLeaseGraph, governor: { ...governor, safe_active_workers: 5 } });
|
|
30
|
+
assertGate(sameLeaseReport.ok === true, 'same-file work graph must drain without overlapping write leases', sameLeaseReport);
|
|
31
|
+
assertGate(sameLeaseReport.max_observed_write_lease_conflicts === 0, 'same-file write items must be serialized or interleaved with read-only work only', sameLeaseReport);
|
|
21
32
|
emitGate('naruto:active-pool', {
|
|
22
33
|
safe_active_workers: report.safe_active_workers,
|
|
23
34
|
completed_count: report.completed_count,
|
|
24
35
|
refill_events: report.refill_events,
|
|
25
|
-
max_observed_active_workers: report.max_observed_active_workers
|
|
36
|
+
max_observed_active_workers: report.max_observed_active_workers,
|
|
37
|
+
same_lease_max_write_conflicts: sameLeaseReport.max_observed_write_lease_conflicts
|
|
26
38
|
});
|
|
27
39
|
//# sourceMappingURL=naruto-active-pool-check.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { assertGate, emitGate, exists, importDist, root } from './sks-1-18-gate-lib.js';
|
|
7
|
+
const worker = await importDist('core/agents/agent-worker-pipeline.js');
|
|
8
|
+
const cli = path.join(root, 'dist', 'bin', 'sks.js');
|
|
9
|
+
assertGate(exists('dist/bin/sks.js'), 'dist/bin/sks.js missing (build first)');
|
|
10
|
+
const readOnlyWorker = worker.validateAgentWorkerResult({
|
|
11
|
+
mission_id: 'M-readonly-routing',
|
|
12
|
+
agent_id: 'readonly-agent',
|
|
13
|
+
session_id: 'readonly-session',
|
|
14
|
+
persona_id: 'verifier',
|
|
15
|
+
task_slice_id: 'readonly-slice',
|
|
16
|
+
status: 'done',
|
|
17
|
+
backend: 'codex-sdk',
|
|
18
|
+
summary: 'Read-only worker inspected files without proposing a patch.',
|
|
19
|
+
findings: ['read-only inspection completed'],
|
|
20
|
+
proposed_changes: [],
|
|
21
|
+
changed_files: ['src/core/agents/agent-proof-evidence.ts'],
|
|
22
|
+
lease_compliance: { ok: true, violations: [] },
|
|
23
|
+
artifacts: [],
|
|
24
|
+
blockers: [],
|
|
25
|
+
confidence: 'fixture',
|
|
26
|
+
handoff_notes: '',
|
|
27
|
+
unverified: [],
|
|
28
|
+
writes: [],
|
|
29
|
+
no_patch_reason: {
|
|
30
|
+
ok: true,
|
|
31
|
+
reason: 'read_only_or_no_write_paths',
|
|
32
|
+
read_only_or_noop_evidence: true
|
|
33
|
+
},
|
|
34
|
+
verification: { status: 'passed', checks: ['readonly-no-patch-routing'] }
|
|
35
|
+
});
|
|
36
|
+
assertGate(readOnlyWorker.status === 'done' && !readOnlyWorker.blockers.includes('no_patch_generated'), 'read-only/no-write worker changed_files must not require patch envelopes', readOnlyWorker);
|
|
37
|
+
const writeWorker = worker.validateAgentWorkerResult({
|
|
38
|
+
...readOnlyWorker,
|
|
39
|
+
agent_id: 'write-agent',
|
|
40
|
+
session_id: 'write-session',
|
|
41
|
+
task_slice_id: 'write-slice',
|
|
42
|
+
changed_files: ['src/core/agents/agent-proof-evidence.ts'],
|
|
43
|
+
no_patch_reason: undefined
|
|
44
|
+
});
|
|
45
|
+
assertGate(writeWorker.status === 'blocked' && writeWorker.blockers.includes('no_patch_generated'), 'write-capable changed_files without patch envelope must still block', writeWorker);
|
|
46
|
+
const readonlyRun = spawnSync(process.execPath, [
|
|
47
|
+
cli,
|
|
48
|
+
'naruto',
|
|
49
|
+
'run',
|
|
50
|
+
'readonly routing must inspect `src/core/agents/agent-proof-evidence.ts` and `package.json` without writes',
|
|
51
|
+
'--clones',
|
|
52
|
+
'4',
|
|
53
|
+
'--work-items',
|
|
54
|
+
'4',
|
|
55
|
+
'--backend',
|
|
56
|
+
'fake',
|
|
57
|
+
'--readonly',
|
|
58
|
+
'--json'
|
|
59
|
+
], { cwd: root, encoding: 'utf8', timeout: 120000, maxBuffer: 6 * 1024 * 1024 });
|
|
60
|
+
const readonlyJson = parseJson(readonlyRun.stdout);
|
|
61
|
+
assertGate(readonlyRun.status === 0 && readonlyJson?.ok === true, 'readonly Naruto fake blackbox must exit ok', { status: readonlyRun.status, stdout: tail(readonlyRun.stdout), stderr: tail(readonlyRun.stderr) });
|
|
62
|
+
assertGate(readonlyJson.proof === 'passed', 'readonly Naruto proof must pass', { proof: readonlyJson.proof, blockers: readonlyJson.run?.proof?.blockers });
|
|
63
|
+
assertGate(readonlyJson.work_graph?.write_allowed_count === 0, 'readonly Naruto work graph must have zero write-allowed items', readonlyJson.work_graph);
|
|
64
|
+
assertGate(Number(readonlyJson.role_distribution?.implementation_like_workers || 0) === 0, 'readonly Naruto role distribution must have zero implementation-like workers', readonlyJson.role_distribution);
|
|
65
|
+
assertGate((readonlyJson.role_distribution?.entries || []).every((entry) => entry.write_allowed === false), 'readonly Naruto role distribution must deny writes for every role', readonlyJson.role_distribution);
|
|
66
|
+
const readonlyRoot = path.join(root, '.sneakoscope', 'missions', readonlyJson.mission_id, 'agents');
|
|
67
|
+
const readonlyProof = readJson(path.join(readonlyRoot, 'agent-proof-evidence.json'));
|
|
68
|
+
const readonlyStrategy = readJson(path.join(readonlyRoot, 'user-request-strategy.json'));
|
|
69
|
+
const readonlyPolicy = readJson(path.join(readonlyRoot, 'agent-parallel-write-policy.json'));
|
|
70
|
+
assertGate(readonlyPolicy.write_mode === 'off' && readonlyPolicy.readonly === true, 'readonly run must force native write policy off', readonlyPolicy);
|
|
71
|
+
assertGate(readonlyStrategy.gate?.write_task_count === 0, 'readonly strategy must not infer write targets from file mentions', readonlyStrategy.gate);
|
|
72
|
+
assertGate(readonlyProof.changed_files_lease_checked === false, 'readonly no-write proof must skip changed_files write-lease checks', readonlyProof);
|
|
73
|
+
assertGate(!(readonlyProof.blockers || []).some((blocker) => /no_patch_generated|lease_changed_file_violation/.test(String(blocker))), 'readonly proof must not contain patch/lease write blockers', readonlyProof.blockers || []);
|
|
74
|
+
const writeRun = spawnSync(process.execPath, [
|
|
75
|
+
cli,
|
|
76
|
+
'naruto',
|
|
77
|
+
'run',
|
|
78
|
+
'write-capable routing must patch `README.md` through leased envelopes',
|
|
79
|
+
'--clones',
|
|
80
|
+
'3',
|
|
81
|
+
'--work-items',
|
|
82
|
+
'3',
|
|
83
|
+
'--backend',
|
|
84
|
+
'fake',
|
|
85
|
+
'--parallel-write',
|
|
86
|
+
'--json'
|
|
87
|
+
], { cwd: root, encoding: 'utf8', timeout: 120000, maxBuffer: 6 * 1024 * 1024 });
|
|
88
|
+
const writeJson = parseJson(writeRun.stdout);
|
|
89
|
+
assertGate(writeRun.status === 0 && writeJson?.ok === true, 'write-capable Naruto fake blackbox must exit ok', { status: writeRun.status, stdout: tail(writeRun.stdout), stderr: tail(writeRun.stderr) });
|
|
90
|
+
const writeRoot = path.join(root, '.sneakoscope', 'missions', writeJson.mission_id, 'agents');
|
|
91
|
+
const writeStrategy = readJson(path.join(writeRoot, 'user-request-strategy.json'));
|
|
92
|
+
const writePolicy = readJson(path.join(writeRoot, 'agent-parallel-write-policy.json'));
|
|
93
|
+
assertGate(writePolicy.write_mode === 'parallel' && writePolicy.readonly === false, 'write-capable Naruto run must carry parallel write policy', writePolicy);
|
|
94
|
+
assertGate(writeStrategy.gate?.write_task_count >= 1, 'write-capable strategy must retain explicit write target inference', writeStrategy.gate);
|
|
95
|
+
emitGate('naruto:readonly-routing', {
|
|
96
|
+
readonly_mission_id: readonlyJson.mission_id,
|
|
97
|
+
write_mission_id: writeJson.mission_id,
|
|
98
|
+
readonly_write_tasks: readonlyStrategy.gate?.write_task_count,
|
|
99
|
+
write_write_tasks: writeStrategy.gate?.write_task_count
|
|
100
|
+
});
|
|
101
|
+
function readJson(file) {
|
|
102
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
103
|
+
}
|
|
104
|
+
function parseJson(text) {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(text);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function tail(value, limit = 2000) {
|
|
113
|
+
const text = String(value || '');
|
|
114
|
+
return text.length <= limit ? text : text.slice(-limit);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=naruto-readonly-routing-check.js.map
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// changing it for any other route, and that an end-to-end `sks naruto run` actually
|
|
6
6
|
// schedules >20 concurrent clone sessions to completion with proof.
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import os from 'node:os';
|
|
8
10
|
import path from 'node:path';
|
|
9
11
|
import { assertGate, emitGate, importDist, root, exists } from './sks-1-18-gate-lib.js';
|
|
10
12
|
const schema = await importDist('core/agents/agent-schema.js');
|
|
@@ -70,8 +72,10 @@ assertGate(bigMemoryHost.cap >= 64, 'a 64 GB host must allow >= 64 parallel code
|
|
|
70
72
|
// concurrency is throttled to the host-safe cap (never the full 24 unless the host allows).
|
|
71
73
|
const proofClones = 24;
|
|
72
74
|
const cli = path.join(root, 'dist', 'bin', 'sks.js');
|
|
75
|
+
const isolatedWorktreeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-naruto-shadow-wt-'));
|
|
76
|
+
const childEnv = { ...process.env, SKS_WORKTREE_ROOT: isolatedWorktreeRoot };
|
|
73
77
|
assertGate(exists('dist/bin/sks.js'), 'dist/bin/sks.js missing (build first)');
|
|
74
|
-
const helpRun = spawnSync(process.execPath, [cli, 'naruto', '--help', '--json'], { cwd: root, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024 });
|
|
78
|
+
const helpRun = spawnSync(process.execPath, [cli, 'naruto', '--help', '--json'], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024 });
|
|
75
79
|
const helpParsed = parseJson(helpRun.stdout);
|
|
76
80
|
assertGate(helpRun.status === 0 && helpParsed?.action === 'help', 'sks naruto --help must emit help instead of launching a run', { status: helpRun.status, stdout: tail(helpRun.stdout), stderr: tail(helpRun.stderr) });
|
|
77
81
|
const run = spawnSync(process.execPath, [
|
|
@@ -80,7 +84,7 @@ const run = spawnSync(process.execPath, [
|
|
|
80
84
|
'--backend', 'fake',
|
|
81
85
|
'--work-items', String(proofClones),
|
|
82
86
|
'--json'
|
|
83
|
-
], { cwd: root, encoding: 'utf8', timeout: 240000, maxBuffer: 8 * 1024 * 1024 });
|
|
87
|
+
], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 240000, maxBuffer: 8 * 1024 * 1024 });
|
|
84
88
|
assertGate(run.status === 0, 'sks naruto run must exit 0', { status: run.status, stderr: tail(run.stderr) });
|
|
85
89
|
const parsed = parseJson(run.stdout);
|
|
86
90
|
assertGate(parsed !== null, 'sks naruto run must emit JSON', { stdout: tail(run.stdout) });
|
|
@@ -98,15 +102,21 @@ assertGate(typeof parsed.concurrency_capped === 'boolean', 'naruto run must repo
|
|
|
98
102
|
assertGate(parsed.concurrency_capped === (parsed.clones > parsed.target_active_slots), 'concurrency_capped must reflect clones > live slots', { clones: parsed.clones, target_active_slots: parsed.target_active_slots, concurrency_capped: parsed.concurrency_capped });
|
|
99
103
|
assertGate(parsed.system && Number(parsed.system.safe_concurrency) >= 1, 'naruto run must report system safe_concurrency (host-derived cap)', { system: parsed.system });
|
|
100
104
|
assertGate(parsed.work_graph?.write_allowed_count > 0 && parsed.work_graph?.mixed_work_kinds?.length > 1, 'naruto run must report a mixed work graph with write-capable items', { work_graph: parsed.work_graph });
|
|
105
|
+
assertGate(Number(parsed.work_graph?.parallel_write_wave_count || 0) > 0, 'naruto run must expose at least one parallel write-capable wave with non-overlapping leases', { work_graph: parsed.work_graph });
|
|
101
106
|
assertGate(parsed.role_distribution?.verifier_only === false, 'naruto run proof/status must distinguish active worker roles beyond verifier-only', { role_distribution: parsed.role_distribution });
|
|
102
107
|
assertGate(Number(parsed.role_distribution?.implementation_like_ratio || 0) >= 0.4, 'naruto run must include implementation-like role distribution', { role_distribution: parsed.role_distribution });
|
|
108
|
+
const commandGraphPath = path.join(root, '.sneakoscope', 'missions', parsed.mission_id, 'agents', 'naruto-work-graph.json');
|
|
109
|
+
const commandGraph = JSON.parse(fs.readFileSync(commandGraphPath, 'utf8'));
|
|
110
|
+
const writePaths = commandGraph.work_items.filter((row) => row.write_allowed).flatMap((row) => row.write_paths);
|
|
111
|
+
assertGate(new Set(writePaths).size > 1, 'naruto command default patch-envelope leases must be per work item, not one shared write path', { sample: writePaths.slice(0, 8), unique: new Set(writePaths).size });
|
|
112
|
+
assertGate(commandGraph.active_waves.some((wave) => wave.write_paths.length > 1), 'naruto command graph must contain a parallel write wave when route-local patch envelopes do not overlap', { waves: commandGraph.active_waves.slice(0, 3) });
|
|
103
113
|
const state = parsed.run?.scheduler?.state || parsed.run?.scheduler || {};
|
|
104
114
|
assertGate(Number(state.completed_count) === proofClones, 'all clone work items must complete despite throttling', { completed_count: state.completed_count });
|
|
105
|
-
const explicitConcurrency = spawnSync(process.execPath, [cli, 'naruto', 'run', 'explicit concurrency', '--clones', '6', '--backend', 'fake', '--work-items', '6', '--concurrency', '6', '--json'], { cwd: root, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
|
|
115
|
+
const explicitConcurrency = spawnSync(process.execPath, [cli, 'naruto', 'run', 'explicit concurrency', '--clones', '6', '--backend', 'fake', '--work-items', '6', '--concurrency', '6', '--json'], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
|
|
106
116
|
const explicitParsed = parseJson(explicitConcurrency.stdout);
|
|
107
117
|
assertGate(explicitConcurrency.status === 0 && explicitParsed?.target_active_slots === 6, 'explicit --concurrency must let Naruto use the requested parallel slot count', { status: explicitConcurrency.status, target_active_slots: explicitParsed?.target_active_slots });
|
|
108
118
|
// 7) A small request is NOT throttled below what was asked (cap only ever reduces, never inflates).
|
|
109
|
-
const small = spawnSync(process.execPath, [cli, 'naruto', 'run', 'tiny', '--clones', '2', '--backend', 'fake', '--work-items', '2', '--json'], { cwd: root, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
|
|
119
|
+
const small = spawnSync(process.execPath, [cli, 'naruto', 'run', 'tiny', '--clones', '2', '--backend', 'fake', '--work-items', '2', '--json'], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
|
|
110
120
|
const smallParsed = parseJson(small.stdout);
|
|
111
121
|
assertGate(small.status === 0 && smallParsed?.target_active_slots === 2, 'a 2-clone run must run 2 concurrently (no over-throttle)', { status: small.status, target_active_slots: smallParsed?.target_active_slots });
|
|
112
122
|
emitGate('naruto:shadow-clone-swarm', {
|
|
@@ -121,7 +131,8 @@ emitGate('naruto:shadow-clone-swarm', {
|
|
|
121
131
|
low_free_capable_cap: lowFreeButCapable.cap,
|
|
122
132
|
cores: heavySafe.cores,
|
|
123
133
|
completed_count: state.completed_count,
|
|
124
|
-
mission_id: parsed.mission_id
|
|
134
|
+
mission_id: parsed.mission_id,
|
|
135
|
+
isolated_worktree_root: isolatedWorktreeRoot
|
|
125
136
|
});
|
|
126
137
|
function parseJson(text) {
|
|
127
138
|
try {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture, makeNonGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const capabilityMod = await importDist('core/git/git-worktree-capability.js');
|
|
9
|
+
const workGraphMod = await importDist('core/naruto/naruto-work-graph.js');
|
|
10
|
+
const activePoolMod = await importDist('core/naruto/naruto-active-pool.js');
|
|
11
|
+
const governorMod = await importDist('core/naruto/naruto-concurrency-governor.js');
|
|
12
|
+
const repo = makeGitFixture('naruto-worktree-coding');
|
|
13
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-naruto-wt-'));
|
|
14
|
+
const capability = await capabilityMod.evaluateGitWorktreeCapability({ root: repo, missionId: 'M-naruto-wt', requireGitWorktree: process.argv.includes('--require-real') });
|
|
15
|
+
const policy = {
|
|
16
|
+
mode: capability.mode,
|
|
17
|
+
required: capability.mode === 'git-worktree',
|
|
18
|
+
main_repo_root: capability.detection.root,
|
|
19
|
+
worktree_root: capability.root_resolution?.root || null,
|
|
20
|
+
fallback_reason: capability.mode === 'git-worktree' ? null : capability.blockers.join(';')
|
|
21
|
+
};
|
|
22
|
+
const graph = workGraphMod.buildNarutoWorkGraph({
|
|
23
|
+
requestedClones: 8,
|
|
24
|
+
totalWorkItems: 12,
|
|
25
|
+
writeCapable: true,
|
|
26
|
+
targetPaths: Array.from({ length: 12 }, (_, index) => `src/wt-${index}.ts`),
|
|
27
|
+
maxActiveWorkers: 4,
|
|
28
|
+
worktreePolicy: policy
|
|
29
|
+
});
|
|
30
|
+
const governor = governorMod.decideNarutoConcurrency({ requestedClones: 8, totalWorkItems: 12, pendingWorkQueueSize: 12, backend: 'fake' });
|
|
31
|
+
const pool = await activePoolMod.runNarutoActivePool({ graph, governor: { ...governor, safe_active_workers: 4 } });
|
|
32
|
+
assertGate(capability.ok === true && capability.mode === 'git-worktree', 'Git Naruto coding fixture must use git-worktree mode', capability);
|
|
33
|
+
assertGate(graph.worktree_policy.mode === 'git-worktree', 'Naruto work graph must carry git-worktree policy', graph.worktree_policy);
|
|
34
|
+
assertGate(graph.work_items.filter((item) => item.write_allowed).every((item) => item.worktree?.allocation_required === true), 'write-capable work items must require worktree allocation', graph.work_items);
|
|
35
|
+
assertGate(pool.worktree_allocation_required_count > 0, 'active pool must plan worktree allocations for write work', pool);
|
|
36
|
+
const nonGit = makeNonGitFixture('naruto-worktree-non-git');
|
|
37
|
+
const nonGitCapability = await capabilityMod.evaluateGitWorktreeCapability({ root: nonGit, missionId: 'M-naruto-non-git' });
|
|
38
|
+
assertGate(nonGitCapability.mode === 'patch-envelope-only' && nonGitCapability.worktree_probe_attempted === false, 'non-Git Naruto must degrade without worktree probe', nonGitCapability);
|
|
39
|
+
emitGate('naruto:worktree-coding', {
|
|
40
|
+
worktree_mode: graph.worktree_policy.mode,
|
|
41
|
+
allocation_required_count: pool.worktree_allocation_required_count,
|
|
42
|
+
non_git_mode: nonGitCapability.mode
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=naruto-worktree-coding-check.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const workGraphMod = await importDist('core/naruto/naruto-work-graph.js');
|
|
5
|
+
const rolesMod = await importDist('core/naruto/naruto-role-policy.js');
|
|
6
|
+
const packMod = await importDist('core/naruto/naruto-gpt-final-pack.js');
|
|
7
|
+
const policy = {
|
|
8
|
+
mode: 'git-worktree',
|
|
9
|
+
required: true,
|
|
10
|
+
main_repo_root: '/repo',
|
|
11
|
+
worktree_root: '/cache/sks/worktrees/repo/M-final',
|
|
12
|
+
fallback_reason: null
|
|
13
|
+
};
|
|
14
|
+
const graph = workGraphMod.buildNarutoWorkGraph({ requestedClones: 6, totalWorkItems: 8, writeCapable: true, worktreePolicy: policy });
|
|
15
|
+
const roleDistribution = rolesMod.buildNarutoRoleDistribution(graph.work_items);
|
|
16
|
+
const worktreeDiff = {
|
|
17
|
+
schema: 'sks.git-worktree-diff.v1',
|
|
18
|
+
ok: true,
|
|
19
|
+
mission_id: 'M-final',
|
|
20
|
+
worker_id: 'worker-1',
|
|
21
|
+
main_repo_root: '/repo',
|
|
22
|
+
worktree_path: '/cache/wt/worker-1',
|
|
23
|
+
branch: 'sks/M/worker-1',
|
|
24
|
+
changed_files: ['src/a.ts'],
|
|
25
|
+
diff: 'diff --git a/src/a.ts b/src/a.ts\n',
|
|
26
|
+
diff_bytes: 37
|
|
27
|
+
};
|
|
28
|
+
const pack = packMod.buildNarutoGptFinalPack({
|
|
29
|
+
missionId: 'M-final',
|
|
30
|
+
graph,
|
|
31
|
+
roleDistribution,
|
|
32
|
+
changedFiles: ['src/a.ts'],
|
|
33
|
+
worktreePolicy: policy,
|
|
34
|
+
worktreeDiffs: [worktreeDiff],
|
|
35
|
+
localLlmMetrics: { participated: true, final_status: 'draft_until_gpt_final' }
|
|
36
|
+
});
|
|
37
|
+
assertGate(pack.worktree_policy.mode === 'git-worktree', 'GPT final pack must include worktree policy', pack.worktree_policy);
|
|
38
|
+
assertGate(pack.worktree_diffs.length === 1, 'GPT final pack must include bounded worktree diffs', pack);
|
|
39
|
+
assertGate(pack.local_llm_metrics.final_status === 'draft_until_gpt_final', 'local LLM output must remain draft until GPT final', pack.local_llm_metrics);
|
|
40
|
+
assertGate(pack.bounded === true && pack.secrets_redacted === true, 'GPT final pack must stay bounded/redacted', pack);
|
|
41
|
+
emitGate('naruto:worktree-gpt-final', {
|
|
42
|
+
worktree_mode: pack.worktree_policy.mode,
|
|
43
|
+
worktree_diffs: pack.worktree_diffs.length
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=naruto-worktree-gpt-final-check.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const dashboardMod = await importDist('core/zellij/zellij-naruto-dashboard.js');
|
|
5
|
+
const plan = dashboardMod.planNarutoZellijDashboard({
|
|
6
|
+
targetActiveWorkers: 10,
|
|
7
|
+
visiblePaneCap: 6,
|
|
8
|
+
roles: ['implementer', 'verifier'],
|
|
9
|
+
backend: 'codex-sdk',
|
|
10
|
+
worktreePolicy: {
|
|
11
|
+
mode: 'git-worktree',
|
|
12
|
+
required: true,
|
|
13
|
+
main_repo_root: '/repo',
|
|
14
|
+
worktree_root: '/cache/sks/worktrees/repo/M',
|
|
15
|
+
fallback_reason: null
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
assertGate(plan.ok === true, 'Naruto worktree Zellij dashboard must pass', plan);
|
|
19
|
+
assertGate(plan.worktree_mode === 'git-worktree', 'dashboard must carry worktree mode', plan);
|
|
20
|
+
assertGate(plan.worktree_labels.length === plan.visible_worker_panes, 'visible panes must have worktree labels', plan);
|
|
21
|
+
assertGate(plan.pane_titles.every((title) => title.includes('WT:') && title.includes('branch:')), 'pane titles must include worktree id and branch', plan.pane_titles);
|
|
22
|
+
assertGate(plan.headless_workers === 4, 'dashboard must still expose headless worker count', plan);
|
|
23
|
+
emitGate('naruto:worktree-zellij-ui', {
|
|
24
|
+
visible_worker_panes: plan.visible_worker_panes,
|
|
25
|
+
headless_workers: plan.headless_workers,
|
|
26
|
+
sample_title: plan.pane_titles[0]
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=naruto-worktree-zellij-ui-check.js.map
|