sneakoscope 3.1.13 → 4.0.0
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 +28 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +6 -13
- package/dist/commands/doctor.js +29 -2
- package/dist/commands/proof.js +8 -0
- package/dist/core/agents/agent-cleanup-executor.js +75 -1
- package/dist/core/agents/agent-command-surface.js +29 -4
- package/dist/core/agents/agent-orchestrator.js +25 -6
- package/dist/core/agents/native-cli-session-swarm.js +1 -1
- package/dist/core/build/build-once-runner.js +62 -0
- package/dist/core/codex/codex-config-readability.js +52 -38
- package/dist/core/commands/check-command.js +130 -0
- package/dist/core/commands/daemon-command.js +14 -0
- package/dist/core/commands/local-model-command.js +1 -1
- package/dist/core/commands/mad-sks-command.js +18 -1
- package/dist/core/commands/naruto-command.js +14 -5
- package/dist/core/commands/release-command.js +52 -0
- package/dist/core/commands/task-command.js +15 -0
- package/dist/core/commands/triwiki-command.js +38 -0
- package/dist/core/daemon/sksd-client.js +9 -0
- package/dist/core/daemon/sksd.js +55 -0
- package/dist/core/doctor/doctor-codex-startup-repair.js +3 -2
- package/dist/core/doctor/doctor-dirty-planner.js +30 -0
- package/dist/core/doctor/doctor-transaction.js +13 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +7 -1
- package/dist/core/probes/probe-memoization.js +42 -0
- package/dist/core/release/extreme-parallel-scheduler.js +33 -0
- package/dist/core/release/gate-pack-manifest.js +118 -0
- package/dist/core/release/gate-pack-runner.js +113 -0
- package/dist/core/release/release-gate-cache-v2.js +73 -16
- package/dist/core/release/release-gate-dag.js +81 -2
- package/dist/core/release/resource-class-budget.js +22 -0
- package/dist/core/release/sla-scheduler.js +22 -0
- package/dist/core/routes.js +5 -0
- package/dist/core/triwiki/triwiki-affected-graph.js +56 -0
- package/dist/core/triwiki/triwiki-cache-key.js +221 -0
- package/dist/core/triwiki/triwiki-gate-impact-map.js +79 -0
- package/dist/core/triwiki/triwiki-module-card.js +37 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +132 -0
- package/dist/core/triwiki/triwiki-proof-card.js +42 -0
- package/dist/core/triwiki/triwiki-sla-certificate.js +30 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +3 -2
- package/dist/scripts/fixtures/fake-codex-config-loader.js +12 -1
- package/dist/scripts/release-4000-required-gates.js +36 -0
- package/dist/scripts/release-gate-dag-runner.js +18 -0
- package/dist/scripts/release-speed-summary.js +9 -0
- package/package.json +43 -7
|
@@ -17,50 +17,64 @@ export async function inspectCodexConfigReadability(rootInput = process.cwd(), o
|
|
|
17
17
|
classifyBlocker(check).forEach((blocker) => blockers.add(blocker));
|
|
18
18
|
};
|
|
19
19
|
add(await accessCheck('codex_dir_exists', configDir, fs.constants.R_OK | fs.constants.X_OK));
|
|
20
|
-
|
|
20
|
+
const projectConfigExists = await accessCheck('project_config_exists', configPath, fs.constants.R_OK);
|
|
21
|
+
add(projectConfigExists);
|
|
21
22
|
for (const dir of parentDirsFor(root, configPath)) {
|
|
22
23
|
add(await accessCheck('parent_traverse', dir, fs.constants.X_OK));
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
// Fresh project: the managed config simply does not exist yet. Every downstream check
|
|
26
|
+
// (stat, symlink, macOS ACL/flags/xattr, node/child read, codex config load) would
|
|
27
|
+
// operate on a nonexistent path and fail with ENOENT, turning the single real cause —
|
|
28
|
+
// a missing config — into a misleading cascade (`macos_acl_ls_le_failed`,
|
|
29
|
+
// `macos_flags_ls_lO_failed`, `spawned_child_read_failed`, ...). Skip them and report
|
|
30
|
+
// only the honest `missing_config` / `missing_codex_dir` blocker so the caller can
|
|
31
|
+
// regenerate the managed config instead of chasing phantom ACL/permission problems.
|
|
32
|
+
const configMissing = projectConfigExists.error?.code === 'ENOENT';
|
|
33
|
+
if (!configMissing) {
|
|
34
|
+
const lstatCheck = await statCheck('config_lstat', configPath, 'lstat');
|
|
35
|
+
add(lstatCheck);
|
|
36
|
+
const stat = await statCheck('config_stat', configPath, 'stat');
|
|
37
|
+
add(stat);
|
|
38
|
+
if (stat.ok) {
|
|
39
|
+
add({ name: 'config_owner', ok: true, detail: { uid: stat.detail.uid, gid: stat.detail.gid } });
|
|
40
|
+
add({ name: 'config_mode', ok: true, detail: { mode: stat.detail.mode_octal } });
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
if (lstatCheck.ok) {
|
|
43
|
+
const isSymlink = Boolean(lstatCheck.detail.is_symbolic_link);
|
|
44
|
+
const symlinkDetail = { is_symlink: isSymlink };
|
|
45
|
+
if (isSymlink) {
|
|
46
|
+
symlinkDetail.realpath = await fsp.realpath(configPath).catch((err) => ({ error: errorDetail(err) }));
|
|
47
|
+
symlinkDetail.allowed = typeof symlinkDetail.realpath === 'string' && symlinkTargetAllowed(symlinkDetail.realpath, root, opts);
|
|
48
|
+
if (!symlinkDetail.allowed)
|
|
49
|
+
blockers.add('symlink_escape');
|
|
50
|
+
}
|
|
51
|
+
add({ name: 'config_symlink', ok: !isSymlink || symlinkDetail.allowed === true, detail: symlinkDetail });
|
|
52
|
+
}
|
|
53
|
+
if (process.platform === 'darwin') {
|
|
54
|
+
add(await commandCheck('macos_acl_ls_le', 'ls', ['-le', configPath], root));
|
|
55
|
+
const acl = checks.find((check) => check.name === 'macos_acl_ls_le');
|
|
56
|
+
if (/\bdeny\b.*\b(read|readattr|readextattr|readsecurity|search)\b/i.test(String(acl?.detail?.stdout || '')))
|
|
57
|
+
blockers.add('acl_denied');
|
|
58
|
+
const flags = await commandCheck('macos_flags_ls_lO', 'ls', ['-lO', configPath], root);
|
|
59
|
+
add(flags);
|
|
60
|
+
if (/\b(uchg|schg|restricted)\b/.test(String(flags.detail?.stdout || '')))
|
|
61
|
+
blockers.add('flags_locked');
|
|
62
|
+
const xattrs = await commandCheck('macos_xattr', 'xattr', ['-l', configPath], root, { allowExitCodes: [0, 1] });
|
|
63
|
+
add(xattrs);
|
|
64
|
+
add({ name: 'macos_quarantine_xattr', ok: !/com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')), detail: { present: /com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')) } });
|
|
65
|
+
if (/com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')))
|
|
66
|
+
blockers.add('quarantine');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
add({ name: 'macos_metadata', ok: true, status: 'skipped_non_macos' });
|
|
70
|
+
}
|
|
71
|
+
add(await nodeReadCheck(configPath));
|
|
72
|
+
add(await childReadCheck(configPath, root));
|
|
73
|
+
add(await codexCliConfigLoadCheck(root, configPath, opts));
|
|
57
74
|
}
|
|
58
75
|
else {
|
|
59
|
-
add({ name: '
|
|
76
|
+
add({ name: 'config_file_checks', ok: true, status: 'skipped_config_missing', detail: { config_path: configPath } });
|
|
60
77
|
}
|
|
61
|
-
add(await nodeReadCheck(configPath));
|
|
62
|
-
add(await childReadCheck(configPath, root));
|
|
63
|
-
add(await codexCliConfigLoadCheck(root, configPath, opts));
|
|
64
78
|
const report = {
|
|
65
79
|
schema: CODEX_CONFIG_READABILITY_SCHEMA,
|
|
66
80
|
generated_at: nowIso(),
|
|
@@ -176,7 +190,7 @@ function operatorActions(blockers) {
|
|
|
176
190
|
if (blockers.some((item) => /^missing_/.test(item)))
|
|
177
191
|
actions.add('Run `sks doctor --fix` to regenerate the managed Codex project config, then rerun the preflight.');
|
|
178
192
|
if (blockers.includes('codex_cli_config_toml_parse_error'))
|
|
179
|
-
actions.add('Run `sks doctor --fix` (or `sks mad repair-config --apply`) to
|
|
193
|
+
actions.add('Run `sks doctor --fix` (or `sks mad repair-config --apply`) to restore a loadable config.toml. This hoists misplaced machine-local keys back to the top of the file AND migrates a local-stdio Context7/MCP server that conflicts with a remote `url` (the "url is not supported for stdio" error, caused by the global and project configs merging) to the supported transport. If `sks doctor --fix` already reports the Context7/MCP transport repaired, the config is fixed — rerun once to confirm `cli_ready`.');
|
|
180
194
|
if (blockers.includes('codex_cli_config_eperm'))
|
|
181
195
|
actions.add('Run `sks mad repair-config --apply`; if it still fails on macOS, grant Full Disk Access/Files and Folders access to the launching terminal, Warp, iTerm, Terminal, Codex app, or Codex CLI context.');
|
|
182
196
|
if (blockers.includes('EPERM') || blockers.includes('tcc_possible'))
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { flag } from '../../cli/args.js';
|
|
5
|
+
import { printJson } from '../../cli/output.js';
|
|
6
|
+
import { projectRoot } from '../fsx.js';
|
|
7
|
+
const CHECK_SCHEMA = 'sks.check.v1';
|
|
8
|
+
export async function checkCommand(args = []) {
|
|
9
|
+
const root = await projectRoot();
|
|
10
|
+
const tier = readArg(args, '--tier', positionalTier(args) || 'confidence');
|
|
11
|
+
const sla = readArg(args, '--sla', '5m');
|
|
12
|
+
const changedSince = readArg(args, '--changed-since', 'auto');
|
|
13
|
+
const json = flag(args, '--json');
|
|
14
|
+
const planOnly = flag(args, '--plan');
|
|
15
|
+
const plan = buildCheckPlan({ tier, sla, changedSince });
|
|
16
|
+
if (planOnly) {
|
|
17
|
+
const result = { schema: CHECK_SCHEMA, ok: true, mode: 'plan', ...plan };
|
|
18
|
+
if (json)
|
|
19
|
+
return printJson(result);
|
|
20
|
+
printPlan(result);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
const steps = [];
|
|
24
|
+
let proofBankSummary = null;
|
|
25
|
+
for (const step of plan.steps) {
|
|
26
|
+
const result = spawnSync(step.command, step.args, {
|
|
27
|
+
cwd: root,
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
30
|
+
shell: false,
|
|
31
|
+
env: { ...process.env, CI: process.env.CI || 'true' }
|
|
32
|
+
});
|
|
33
|
+
const ok = result.status === 0;
|
|
34
|
+
if (step.name === 'proof-bank-summary')
|
|
35
|
+
proofBankSummary = parseJsonObject(String(result.stdout || ''));
|
|
36
|
+
steps.push({ name: step.name, ok, status: result.status, stderr_tail: tail(String(result.stderr || '')) });
|
|
37
|
+
if (!json) {
|
|
38
|
+
if (result.stdout)
|
|
39
|
+
process.stdout.write(result.stdout);
|
|
40
|
+
if (result.stderr)
|
|
41
|
+
process.stderr.write(result.stderr);
|
|
42
|
+
}
|
|
43
|
+
if (!ok) {
|
|
44
|
+
const failed = { schema: CHECK_SCHEMA, ok: false, mode: 'run', ...plan, steps, completion_certificate: latestCertificate(root) || proofBankSummary?.completion_certificate || null, release_speed_summary: proofBankSummary };
|
|
45
|
+
if (json)
|
|
46
|
+
return printJson(failed);
|
|
47
|
+
process.exitCode = result.status || 1;
|
|
48
|
+
return failed;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const result = { schema: CHECK_SCHEMA, ok: true, mode: 'run', ...plan, steps, completion_certificate: latestCertificate(root) || proofBankSummary?.completion_certificate || null, release_speed_summary: proofBankSummary };
|
|
52
|
+
if (json)
|
|
53
|
+
return printJson(result);
|
|
54
|
+
if (result.completion_certificate) {
|
|
55
|
+
console.log(`SKS check certificate: ${result.completion_certificate.confidence} sla_met=${result.completion_certificate.sla_met}`);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function buildCheckPlan(input) {
|
|
60
|
+
const tier = normalizeTier(input.tier);
|
|
61
|
+
const buildScript = tier === 'release' ? 'build:clean' : 'build:incremental';
|
|
62
|
+
const steps = [];
|
|
63
|
+
if (tier === 'instant') {
|
|
64
|
+
steps.push({ name: 'proof-bank-summary', command: process.execPath, args: ['dist/scripts/release-speed-summary.js'] });
|
|
65
|
+
}
|
|
66
|
+
else if (tier === 'real-check') {
|
|
67
|
+
steps.push({ name: 'real-check', command: process.execPath, args: ['dist/scripts/release-real-check.js'] });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
steps.push({ name: buildScript, command: 'npm', args: ['run', buildScript, '--silent'] });
|
|
71
|
+
steps.push({ name: `release:${tier}`, command: process.execPath, args: dagArgs(tier, input.changedSince, input.sla) });
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
tier,
|
|
75
|
+
sla: input.sla,
|
|
76
|
+
changed_since: input.changedSince,
|
|
77
|
+
build_once: tier === 'release' ? 'clean' : tier === 'real-check' || tier === 'instant' ? 'not_applicable' : 'incremental',
|
|
78
|
+
steps
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function dagArgs(tier, changedSince, sla) {
|
|
82
|
+
if (tier === 'release')
|
|
83
|
+
return ['dist/scripts/release-gate-dag-runner.js', '--preset', 'release', '--full'];
|
|
84
|
+
const preset = tier === 'instant' ? 'fast' : tier === 'affected' || tier === 'confidence' ? 'affected' : tier;
|
|
85
|
+
return ['dist/scripts/release-gate-dag-runner.js', '--preset', preset, '--changed-since', changedSince, '--sla', sla];
|
|
86
|
+
}
|
|
87
|
+
function latestCertificate(root) {
|
|
88
|
+
const direct = path.join(root, '.sneakoscope', 'reports', 'completion-certificate.json');
|
|
89
|
+
if (fs.existsSync(direct))
|
|
90
|
+
return JSON.parse(fs.readFileSync(direct, 'utf8'));
|
|
91
|
+
const base = path.join(root, '.sneakoscope', 'reports', 'release-gates');
|
|
92
|
+
if (!fs.existsSync(base))
|
|
93
|
+
return null;
|
|
94
|
+
const latest = fs.readdirSync(base)
|
|
95
|
+
.map((name) => path.join(base, name, 'completion-certificate.json'))
|
|
96
|
+
.filter((file) => fs.existsSync(file))
|
|
97
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0];
|
|
98
|
+
return latest ? JSON.parse(fs.readFileSync(latest, 'utf8')) : null;
|
|
99
|
+
}
|
|
100
|
+
function normalizeTier(value) {
|
|
101
|
+
const tier = String(value || '').trim().toLowerCase();
|
|
102
|
+
if (['instant', 'affected', 'confidence', 'release', 'real-check'].includes(tier))
|
|
103
|
+
return tier;
|
|
104
|
+
return 'confidence';
|
|
105
|
+
}
|
|
106
|
+
function positionalTier(args) {
|
|
107
|
+
const first = args.find((arg) => !arg.startsWith('-'));
|
|
108
|
+
return first || null;
|
|
109
|
+
}
|
|
110
|
+
function readArg(args, name, fallback) {
|
|
111
|
+
const index = args.indexOf(name);
|
|
112
|
+
return index >= 0 && args[index + 1] ? String(args[index + 1]) : fallback;
|
|
113
|
+
}
|
|
114
|
+
function tail(value, limit = 1200) {
|
|
115
|
+
return value.length > limit ? value.slice(-limit) : value;
|
|
116
|
+
}
|
|
117
|
+
function parseJsonObject(value) {
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(value);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function printPlan(result) {
|
|
126
|
+
console.log(`SKS check plan: tier=${result.tier} sla=${result.sla} build=${result.build_once}`);
|
|
127
|
+
for (const step of result.steps)
|
|
128
|
+
console.log(`- ${step.name}: ${[step.command, ...step.args].join(' ')}`);
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=check-command.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { flag } from '../../cli/args.js';
|
|
2
|
+
import { printJson } from '../../cli/output.js';
|
|
3
|
+
import { projectRoot } from '../fsx.js';
|
|
4
|
+
import { runSksdClient } from '../daemon/sksd-client.js';
|
|
5
|
+
export async function daemonCommand(args = []) {
|
|
6
|
+
const root = await projectRoot();
|
|
7
|
+
const action = args[0] === 'warm' || args[0] === 'stop' || args[0] === 'status' ? args[0] : 'status';
|
|
8
|
+
const state = runSksdClient(root, action);
|
|
9
|
+
if (flag(args, '--json'))
|
|
10
|
+
return printJson(state);
|
|
11
|
+
console.log(JSON.stringify(state, null, 2));
|
|
12
|
+
return state;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=daemon-command.js.map
|
|
@@ -72,7 +72,7 @@ async function enable(args) {
|
|
|
72
72
|
: await runLocalLlmGenerationSmoke(config, {
|
|
73
73
|
prompt: 'Return strict JSON: {"status":"ok","summary":"local smoke passed"}',
|
|
74
74
|
schema: localLlmSmokeSchema,
|
|
75
|
-
timeoutMs:
|
|
75
|
+
timeoutMs: Math.max(60_000, Number(config.timeout_ms || 0) || 0)
|
|
76
76
|
});
|
|
77
77
|
const next = await writeLocalModelConfig(applyLocalLlmSmokeResult(config, smoke));
|
|
78
78
|
if (!skipSmoke && smoke.ok !== true)
|
|
@@ -135,7 +135,24 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
135
135
|
// readability + repair checks still run. SKS_LAUNCH_FULL_CODEX_PROBE=1 restores the
|
|
136
136
|
// old behavior.
|
|
137
137
|
const allowMadRepair = rawArgs.includes('--repair-config') || rawArgs.includes('--fix') || rawArgs.includes('--yes-repair');
|
|
138
|
-
const
|
|
138
|
+
const launchPreflightOpts = { fix: allowMadRepair, launchFast: process.env.SKS_LAUNCH_FULL_CODEX_PROBE !== '1', profile: profile.profile_name, sandbox: 'danger-full-access', serviceTier: 'fast' };
|
|
139
|
+
let launchPreflight = await runCodexLaunchPreflight(launchRoot, launchPreflightOpts);
|
|
140
|
+
// Fresh-project bootstrap: when the ONLY blocker is that the managed Codex config does
|
|
141
|
+
// not exist yet (`.codex/config.toml` absent), regenerate it — exactly what the blocker
|
|
142
|
+
// action tells the user to run via `sks doctor --fix` — and re-run the preflight once,
|
|
143
|
+
// instead of blocking the launch on a trivially-fixable missing config. An EXISTING but
|
|
144
|
+
// unreadable/broken config is NOT auto-fixed here: it still blocks and routes the user
|
|
145
|
+
// to `sks doctor --fix`, so genuine permission/EPERM/parse problems are never masked.
|
|
146
|
+
if (!launchPreflight.ok && !fs.existsSync(path.join(launchRoot, '.codex', 'config.toml'))) {
|
|
147
|
+
try {
|
|
148
|
+
await initProject(launchRoot, { installScope: rawArgs.includes('--local-only') ? 'project' : 'global', localOnly: rawArgs.includes('--local-only'), globalCommand: 'sks' });
|
|
149
|
+
console.error('SKS MAD bootstrapped the missing managed Codex config (`sks doctor --fix` equivalent) and re-ran preflight.');
|
|
150
|
+
launchPreflight = await runCodexLaunchPreflight(launchRoot, launchPreflightOpts);
|
|
151
|
+
}
|
|
152
|
+
catch (bootstrapErr) {
|
|
153
|
+
console.error(`SKS MAD could not bootstrap the managed Codex config: ${bootstrapErr?.message || bootstrapErr}. Run \`sks doctor --fix\`.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
139
156
|
const afterPreflightUi = beforeUi ? await writeCodexAppUiSnapshot(launchRoot, `mad-after-preflight-${uiSnapshotId}`).catch(() => null) : null;
|
|
140
157
|
const preflightUiDiff = beforeUi && afterPreflightUi ? diffCodexAppUiSnapshots(beforeUi, afterPreflightUi) : null;
|
|
141
158
|
if (preflightUiDiff && !preflightUiDiff.ok) {
|
|
@@ -108,7 +108,7 @@ async function narutoRun(parsed) {
|
|
|
108
108
|
// system-safe number so naruto never spawns the whole count at once unless an
|
|
109
109
|
// explicit operator override asks for a higher target.
|
|
110
110
|
const localWorker = await resolveNarutoLocalWorkerMode(parsed);
|
|
111
|
-
const schedulerBackend = localWorker.auto_select_eligible ? '
|
|
111
|
+
const schedulerBackend = localWorker.auto_select_eligible ? 'local-llm' : parsed.backend;
|
|
112
112
|
const safe = systemSafeNarutoConcurrency({ backend: schedulerBackend });
|
|
113
113
|
const baseWorkGraph = buildNarutoWorkGraph({
|
|
114
114
|
prompt: parsed.prompt,
|
|
@@ -658,7 +658,7 @@ function summarizeNarutoLocalWorkerResult(localWorker, result) {
|
|
|
658
658
|
}
|
|
659
659
|
return {
|
|
660
660
|
...localWorker,
|
|
661
|
-
selected_worker_count: backendCounts.ollama || 0,
|
|
661
|
+
selected_worker_count: (backendCounts['local-llm'] || 0) + (backendCounts.ollama || 0),
|
|
662
662
|
backend_counts: backendCounts
|
|
663
663
|
};
|
|
664
664
|
}
|
|
@@ -770,7 +770,7 @@ async function narutoHelp(parsed) {
|
|
|
770
770
|
mode: 'NARUTO',
|
|
771
771
|
description: 'Shadow Clone Swarm: fan out up to ' + MAX_NARUTO_AGENT_COUNT + ' parallel clone sessions.',
|
|
772
772
|
usage: [
|
|
773
|
-
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--write-mode parallel|serial|off] [--apply-patches] [--dry-run-patches] [--real] [--readonly] [--json]',
|
|
773
|
+
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama|local-llm] [--local-model|--ollama|--no-ollama] [--work-items N] [--write-mode parallel|serial|off] [--apply-patches] [--dry-run-patches] [--real] [--readonly] [--json]',
|
|
774
774
|
'sks naruto status [--mission <id>] [--json]',
|
|
775
775
|
'sks naruto proof latest [--messages 20] [--json]'
|
|
776
776
|
],
|
|
@@ -796,10 +796,19 @@ function parseNarutoArgs(args = []) {
|
|
|
796
796
|
const workItemsExplicit = hasOption(args, '--work-items');
|
|
797
797
|
const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones * 2)), clones);
|
|
798
798
|
const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
|
|
799
|
-
const
|
|
799
|
+
const useOllamaProtocol = hasFlag(args, '--ollama');
|
|
800
|
+
const useLocalModel = hasFlag(args, '--local-model');
|
|
801
|
+
const useOllama = useOllamaProtocol || useLocalModel;
|
|
800
802
|
const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
|
|
801
803
|
const backendExplicit = hasOption(args, '--backend') || useOllama || noOllama;
|
|
802
|
-
const
|
|
804
|
+
const defaultBackend = hasFlag(args, '--mock')
|
|
805
|
+
? 'fake'
|
|
806
|
+
: useLocalModel && !noOllama
|
|
807
|
+
? 'local-llm'
|
|
808
|
+
: useOllamaProtocol && !noOllama
|
|
809
|
+
? 'ollama'
|
|
810
|
+
: 'codex-sdk';
|
|
811
|
+
const backend = String(readOption(args, '--backend', defaultBackend));
|
|
803
812
|
const mock = hasFlag(args, '--mock') || backend === 'fake';
|
|
804
813
|
const real = hasFlag(args, '--real');
|
|
805
814
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { flag } from '../../cli/args.js';
|
|
3
|
+
import { printJson } from '../../cli/output.js';
|
|
4
|
+
import { projectRoot } from '../fsx.js';
|
|
5
|
+
export async function releaseCommand(args = []) {
|
|
6
|
+
const root = await projectRoot();
|
|
7
|
+
const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'affected';
|
|
8
|
+
const json = flag(args, '--json');
|
|
9
|
+
const command = commandForSubcommand(sub);
|
|
10
|
+
if (!command) {
|
|
11
|
+
console.error('Usage: sks release affected|full|background [--json]');
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const result = spawnSync('npm', ['run', command, '--silent'], {
|
|
16
|
+
cwd: root,
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
19
|
+
env: { ...process.env, CI: process.env.CI || 'true' }
|
|
20
|
+
});
|
|
21
|
+
const report = {
|
|
22
|
+
schema: 'sks.release-command.v1',
|
|
23
|
+
ok: result.status === 0,
|
|
24
|
+
subcommand: sub,
|
|
25
|
+
script: command,
|
|
26
|
+
status: result.status,
|
|
27
|
+
stdout_tail: tail(String(result.stdout || '')),
|
|
28
|
+
stderr_tail: tail(String(result.stderr || ''))
|
|
29
|
+
};
|
|
30
|
+
if (json)
|
|
31
|
+
return printJson(report);
|
|
32
|
+
if (result.stdout)
|
|
33
|
+
process.stdout.write(result.stdout);
|
|
34
|
+
if (result.stderr)
|
|
35
|
+
process.stderr.write(result.stderr);
|
|
36
|
+
if (!report.ok)
|
|
37
|
+
process.exitCode = result.status || 1;
|
|
38
|
+
return report;
|
|
39
|
+
}
|
|
40
|
+
function commandForSubcommand(sub) {
|
|
41
|
+
if (sub === 'affected')
|
|
42
|
+
return 'release:check:affected';
|
|
43
|
+
if (sub === 'full')
|
|
44
|
+
return 'release:check:full';
|
|
45
|
+
if (sub === 'background')
|
|
46
|
+
return 'release:check:full';
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function tail(value, limit = 4000) {
|
|
50
|
+
return value.length > limit ? value.slice(-limit) : value;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=release-command.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { checkCommand } from './check-command.js';
|
|
2
|
+
export async function taskCommand(args = []) {
|
|
3
|
+
const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'run';
|
|
4
|
+
const rest = sub === args[0] ? args.slice(1) : args;
|
|
5
|
+
if (sub === 'run')
|
|
6
|
+
return checkCommand(['--tier', 'confidence', ...rest]);
|
|
7
|
+
if (sub === 'affected')
|
|
8
|
+
return checkCommand(['--tier', 'affected', ...rest]);
|
|
9
|
+
if (sub === 'instant')
|
|
10
|
+
return checkCommand(['--tier', 'instant', ...rest]);
|
|
11
|
+
console.error('Usage: sks task run|affected|instant [--sla 5m] [--json]');
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=task-command.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { flag } from '../../cli/args.js';
|
|
2
|
+
import { printJson } from '../../cli/output.js';
|
|
3
|
+
import { projectRoot } from '../fsx.js';
|
|
4
|
+
import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.js';
|
|
5
|
+
import { buildTriWikiGateImpactMap } from '../triwiki/triwiki-gate-impact-map.js';
|
|
6
|
+
import { DEFAULT_TRIWIKI_MODULE_CARDS } from '../triwiki/triwiki-module-card.js';
|
|
7
|
+
import { summarizeTriWikiProofBank } from '../triwiki/triwiki-proof-bank.js';
|
|
8
|
+
export async function triwikiCommand(args = []) {
|
|
9
|
+
const root = await projectRoot();
|
|
10
|
+
const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'index';
|
|
11
|
+
const json = flag(args, '--json');
|
|
12
|
+
let result;
|
|
13
|
+
if (sub === 'index') {
|
|
14
|
+
result = {
|
|
15
|
+
schema: 'sks.triwiki-index.v1',
|
|
16
|
+
ok: true,
|
|
17
|
+
modules: DEFAULT_TRIWIKI_MODULE_CARDS,
|
|
18
|
+
impact_map: buildTriWikiGateImpactMap(root),
|
|
19
|
+
proof_bank: summarizeTriWikiProofBank(root)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
else if (sub === 'affected') {
|
|
23
|
+
result = computeTriWikiAffectedGraph({ root, tier: 'affected' });
|
|
24
|
+
}
|
|
25
|
+
else if (sub === 'proof-bank') {
|
|
26
|
+
result = summarizeTriWikiProofBank(root);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.error('Usage: sks triwiki index|affected|proof-bank [--json]');
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (json)
|
|
34
|
+
return printJson(result);
|
|
35
|
+
console.log(JSON.stringify(result, null, 2));
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=triwiki-command.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { sksdStatus, sksdStop, sksdWarm } from './sksd.js';
|
|
2
|
+
export function runSksdClient(root, action) {
|
|
3
|
+
if (action === 'warm')
|
|
4
|
+
return sksdWarm(root);
|
|
5
|
+
if (action === 'stop')
|
|
6
|
+
return sksdStop(root);
|
|
7
|
+
return sksdStatus(root);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=sksd-client.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const SKSD_STATE_SCHEMA = 'sks.sksd-state.v1';
|
|
4
|
+
export function sksdStatus(root) {
|
|
5
|
+
const state = readState(root);
|
|
6
|
+
return state || emptyState();
|
|
7
|
+
}
|
|
8
|
+
export function sksdWarm(root) {
|
|
9
|
+
const state = {
|
|
10
|
+
schema: SKSD_STATE_SCHEMA,
|
|
11
|
+
status: 'warm',
|
|
12
|
+
pid: process.pid,
|
|
13
|
+
warmed_at: new Date().toISOString(),
|
|
14
|
+
proof_bank_ready: true,
|
|
15
|
+
build_proof_ready: fs.existsSync(path.join(root, 'dist', '.sks-build-proof.json'))
|
|
16
|
+
};
|
|
17
|
+
writeState(root, state);
|
|
18
|
+
return state;
|
|
19
|
+
}
|
|
20
|
+
export function sksdStop(root) {
|
|
21
|
+
const state = { ...emptyState(), status: 'stopped' };
|
|
22
|
+
writeState(root, state);
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
function readState(root) {
|
|
26
|
+
const file = statePath(root);
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(file))
|
|
29
|
+
return null;
|
|
30
|
+
const json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
31
|
+
return json.schema === SKSD_STATE_SCHEMA ? json : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function writeState(root, state) {
|
|
38
|
+
const file = statePath(root);
|
|
39
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
40
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
41
|
+
}
|
|
42
|
+
function statePath(root) {
|
|
43
|
+
return path.join(root, '.sneakoscope', 'cache', 'sksd-state.json');
|
|
44
|
+
}
|
|
45
|
+
function emptyState() {
|
|
46
|
+
return {
|
|
47
|
+
schema: SKSD_STATE_SCHEMA,
|
|
48
|
+
status: 'stopped',
|
|
49
|
+
pid: null,
|
|
50
|
+
warmed_at: null,
|
|
51
|
+
proof_bank_ready: false,
|
|
52
|
+
build_proof_ready: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=sksd.js.map
|
|
@@ -104,9 +104,10 @@ async function inspectOrRepairConfig(candidate, fix, nodeReplCommandCandidates,
|
|
|
104
104
|
const currentValid = Boolean(current && path.isAbsolute(current) && await exists(current));
|
|
105
105
|
if (currentValid && current === target)
|
|
106
106
|
continue;
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
if (!fix) {
|
|
108
|
+
warnings.push(`agent_config_file_stale:${tableName}`);
|
|
109
109
|
continue;
|
|
110
|
+
}
|
|
110
111
|
if (!targetExists) {
|
|
111
112
|
await ensureDir(path.dirname(target));
|
|
112
113
|
await writeTextAtomic(target, roleConfigToml(tableName, role.description, role.sandbox));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const DOCTOR_DIRTY_PLAN_SCHEMA = 'sks.doctor-dirty-plan.v1';
|
|
4
|
+
export function planDoctorDirtyRepair(root, phaseIds) {
|
|
5
|
+
const phases = phaseIds.map((id) => {
|
|
6
|
+
const marker = markerPath(root, id);
|
|
7
|
+
if (!fs.existsSync(marker))
|
|
8
|
+
return { id, status: 'dirty', reason: 'no_clean_marker' };
|
|
9
|
+
return { id, status: 'clean', reason: 'clean_marker_present' };
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
schema: DOCTOR_DIRTY_PLAN_SCHEMA,
|
|
13
|
+
root,
|
|
14
|
+
phases,
|
|
15
|
+
dirty_count: phases.filter((phase) => phase.status === 'dirty').length,
|
|
16
|
+
clean_count: phases.filter((phase) => phase.status === 'clean').length
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function markDoctorPhaseClean(root, id) {
|
|
20
|
+
const file = markerPath(root, id);
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
fs.writeFileSync(file, `${new Date().toISOString()}\n`);
|
|
23
|
+
}
|
|
24
|
+
export function isDoctorPhaseClean(plan, id) {
|
|
25
|
+
return plan?.phases.find((phase) => phase.id === id)?.status === 'clean';
|
|
26
|
+
}
|
|
27
|
+
function markerPath(root, id) {
|
|
28
|
+
return path.join(root, '.sneakoscope', 'cache', 'doctor-dirty', `${id.replace(/[^a-zA-Z0-9._-]+/g, '_')}.clean`);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=doctor-dirty-planner.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { isDoctorPhaseClean, markDoctorPhaseClean } from './doctor-dirty-planner.js';
|
|
3
4
|
export async function runDoctorFixTransaction(input) {
|
|
4
5
|
const startedAt = nowIso();
|
|
5
6
|
const phases = [];
|
|
@@ -18,6 +19,16 @@ export async function runDoctorFixTransaction(input) {
|
|
|
18
19
|
artifact_path: null,
|
|
19
20
|
started_at: phaseStarted
|
|
20
21
|
};
|
|
22
|
+
if (isDoctorPhaseClean(input.dirtyPlan, definition.id)) {
|
|
23
|
+
phases.push({
|
|
24
|
+
...phase,
|
|
25
|
+
ok: true,
|
|
26
|
+
warnings: ['dirty_plan_skipped_clean_phase'],
|
|
27
|
+
completed_at: nowIso(),
|
|
28
|
+
duration_ms: Math.max(0, Date.now() - startedMs)
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
21
32
|
try {
|
|
22
33
|
const result = await definition.run();
|
|
23
34
|
phase = normalizePhase(definition, result, phase, startedMs);
|
|
@@ -48,6 +59,8 @@ export async function runDoctorFixTransaction(input) {
|
|
|
48
59
|
}
|
|
49
60
|
phase.completed_at = phase.completed_at || nowIso();
|
|
50
61
|
phase.duration_ms = phase.duration_ms ?? Math.max(0, Date.now() - startedMs);
|
|
62
|
+
if (phase.ok)
|
|
63
|
+
markDoctorPhaseClean(input.root, definition.id);
|
|
51
64
|
phases.push(phase);
|
|
52
65
|
}
|
|
53
66
|
const writeInput = {
|
|
@@ -21,6 +21,7 @@ const FIXTURES = Object.freeze({
|
|
|
21
21
|
'cli-hooks': fixture('mock', 'sks hooks trust-report --json', [], 'pass'),
|
|
22
22
|
'cli-features': fixture('execute', 'sks features check --json', [], 'pass'),
|
|
23
23
|
'cli-commands': fixture('execute', 'sks commands --json', [], 'pass'),
|
|
24
|
+
'cli-check': fixture('execute', 'sks check --tier confidence --sla 5m --plan --json', [], 'pass'),
|
|
24
25
|
'cli-run': fixture('execute_and_validate_artifacts', 'sks run "fixture" --mock --json', ['run-classification.json', 'completion-proof.json', 'evidence-index.json', 'route-completion-contract.json', 'trust-report.json'], 'pass'),
|
|
25
26
|
'cli-status': fixture('execute', 'sks status --json', [], 'pass'),
|
|
26
27
|
'cli-usage': fixture('execute', 'sks usage overview', [], '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 = '
|
|
8
|
+
export const PACKAGE_VERSION = '4.0.0';
|
|
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/init.js
CHANGED
|
@@ -806,7 +806,13 @@ export async function initProject(root, opts = {}) {
|
|
|
806
806
|
// Context7 credentials may live directly in this table as args/env/headers/url
|
|
807
807
|
// depending on the user's MCP client setup. Seed the default only when absent;
|
|
808
808
|
// never replace an existing Context7 block during setup/update.
|
|
809
|
-
|
|
809
|
+
// Seed the REMOTE (streamable HTTP `url`) transport, not local stdio: Codex
|
|
810
|
+
// merges the global ~/.codex/config.toml and the project config per-key, so a
|
|
811
|
+
// local-stdio `command` here merging with a remote `url` in the global config
|
|
812
|
+
// yields a stdio server that also carries a `url` — which Codex 0.140 rejects
|
|
813
|
+
// with `url is not supported for stdio`. Remote is also the transport the doctor
|
|
814
|
+
// migrates everyone to (local stdio can block interactive Codex launch).
|
|
815
|
+
{ table: 'mcp_servers.context7', text: context7ConfigToml('remote').trim(), preserveExisting: true },
|
|
810
816
|
{ table: 'agents.native_agent', text: agentConfigBlock('native_agent', 'Read-only SKS analysis agent.', './agents/native-agent-intake.toml', ['Analysis', 'Mapper']) },
|
|
811
817
|
{ table: 'agents.team_consensus', text: agentConfigBlock('team_consensus', 'SKS planning/debate agent.', './agents/team-consensus.toml', ['Consensus', 'Atlas']) },
|
|
812
818
|
{ table: 'agents.implementation_worker', text: agentConfigBlock('implementation_worker', 'SKS bounded implementation worker.', './agents/implementation-worker.toml', ['Builder', 'Mason']) },
|