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.
Files changed (53) hide show
  1. package/README.md +28 -3
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +6 -13
  7. package/dist/commands/doctor.js +29 -2
  8. package/dist/commands/proof.js +8 -0
  9. package/dist/core/agents/agent-cleanup-executor.js +75 -1
  10. package/dist/core/agents/agent-command-surface.js +29 -4
  11. package/dist/core/agents/agent-orchestrator.js +25 -6
  12. package/dist/core/agents/native-cli-session-swarm.js +1 -1
  13. package/dist/core/build/build-once-runner.js +62 -0
  14. package/dist/core/codex/codex-config-readability.js +52 -38
  15. package/dist/core/commands/check-command.js +130 -0
  16. package/dist/core/commands/daemon-command.js +14 -0
  17. package/dist/core/commands/local-model-command.js +1 -1
  18. package/dist/core/commands/mad-sks-command.js +18 -1
  19. package/dist/core/commands/naruto-command.js +14 -5
  20. package/dist/core/commands/release-command.js +52 -0
  21. package/dist/core/commands/task-command.js +15 -0
  22. package/dist/core/commands/triwiki-command.js +38 -0
  23. package/dist/core/daemon/sksd-client.js +9 -0
  24. package/dist/core/daemon/sksd.js +55 -0
  25. package/dist/core/doctor/doctor-codex-startup-repair.js +3 -2
  26. package/dist/core/doctor/doctor-dirty-planner.js +30 -0
  27. package/dist/core/doctor/doctor-transaction.js +13 -0
  28. package/dist/core/feature-fixtures.js +1 -0
  29. package/dist/core/fsx.js +1 -1
  30. package/dist/core/init.js +7 -1
  31. package/dist/core/probes/probe-memoization.js +42 -0
  32. package/dist/core/release/extreme-parallel-scheduler.js +33 -0
  33. package/dist/core/release/gate-pack-manifest.js +118 -0
  34. package/dist/core/release/gate-pack-runner.js +113 -0
  35. package/dist/core/release/release-gate-cache-v2.js +73 -16
  36. package/dist/core/release/release-gate-dag.js +81 -2
  37. package/dist/core/release/resource-class-budget.js +22 -0
  38. package/dist/core/release/sla-scheduler.js +22 -0
  39. package/dist/core/routes.js +5 -0
  40. package/dist/core/triwiki/triwiki-affected-graph.js +56 -0
  41. package/dist/core/triwiki/triwiki-cache-key.js +221 -0
  42. package/dist/core/triwiki/triwiki-gate-impact-map.js +79 -0
  43. package/dist/core/triwiki/triwiki-module-card.js +37 -0
  44. package/dist/core/triwiki/triwiki-proof-bank.js +132 -0
  45. package/dist/core/triwiki/triwiki-proof-card.js +42 -0
  46. package/dist/core/triwiki/triwiki-sla-certificate.js +30 -0
  47. package/dist/core/version.js +1 -1
  48. package/dist/core/zellij/zellij-worker-pane-manager.js +3 -2
  49. package/dist/scripts/fixtures/fake-codex-config-loader.js +12 -1
  50. package/dist/scripts/release-4000-required-gates.js +36 -0
  51. package/dist/scripts/release-gate-dag-runner.js +18 -0
  52. package/dist/scripts/release-speed-summary.js +9 -0
  53. 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
- add(await accessCheck('project_config_exists', configPath, fs.constants.R_OK));
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
- const lstatCheck = await statCheck('config_lstat', configPath, 'lstat');
25
- add(lstatCheck);
26
- const stat = await statCheck('config_stat', configPath, 'stat');
27
- add(stat);
28
- if (stat.ok) {
29
- add({ name: 'config_owner', ok: true, detail: { uid: stat.detail.uid, gid: stat.detail.gid } });
30
- add({ name: 'config_mode', ok: true, detail: { mode: stat.detail.mode_octal } });
31
- }
32
- if (lstatCheck.ok) {
33
- const isSymlink = Boolean(lstatCheck.detail.is_symbolic_link);
34
- const symlinkDetail = { is_symlink: isSymlink };
35
- if (isSymlink) {
36
- symlinkDetail.realpath = await fsp.realpath(configPath).catch((err) => ({ error: errorDetail(err) }));
37
- symlinkDetail.allowed = typeof symlinkDetail.realpath === 'string' && symlinkTargetAllowed(symlinkDetail.realpath, root, opts);
38
- if (!symlinkDetail.allowed)
39
- blockers.add('symlink_escape');
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
- add({ name: 'config_symlink', ok: !isSymlink || symlinkDetail.allowed === true, detail: symlinkDetail });
42
- }
43
- if (process.platform === 'darwin') {
44
- add(await commandCheck('macos_acl_ls_le', 'ls', ['-le', configPath], root));
45
- const acl = checks.find((check) => check.name === 'macos_acl_ls_le');
46
- if (/\bdeny\b.*\b(read|readattr|readextattr|readsecurity|search)\b/i.test(String(acl?.detail?.stdout || '')))
47
- blockers.add('acl_denied');
48
- const flags = await commandCheck('macos_flags_ls_lO', 'ls', ['-lO', configPath], root);
49
- add(flags);
50
- if (/\b(uchg|schg|restricted)\b/.test(String(flags.detail?.stdout || '')))
51
- blockers.add('flags_locked');
52
- const xattrs = await commandCheck('macos_xattr', 'xattr', ['-l', configPath], root, { allowExitCodes: [0, 1] });
53
- add(xattrs);
54
- add({ name: 'macos_quarantine_xattr', ok: !/com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')), detail: { present: /com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')) } });
55
- if (/com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')))
56
- blockers.add('quarantine');
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: 'macos_metadata', ok: true, status: 'skipped_non_macos' });
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 hoist misplaced machine-local keys back to the top of the Codex config and restore a loadable config.toml.');
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: 20_000
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 launchPreflight = await runCodexLaunchPreflight(launchRoot, { fix: allowMadRepair, launchFast: process.env.SKS_LAUNCH_FULL_CODEX_PROBE !== '1', profile: profile.profile_name, sandbox: 'danger-full-access', serviceTier: 'fast' });
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 ? 'ollama' : parsed.backend;
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 useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
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 backend = String(readOption(args, '--backend', hasFlag(args, '--mock') ? 'fake' : useOllama && !noOllama ? 'ollama' : 'codex-sdk'));
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
- warnings.push(`agent_config_file_stale:${tableName}`);
108
- if (!fix)
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 = '3.1.13';
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
- { table: 'mcp_servers.context7', text: context7ConfigToml().trim(), preserveExisting: true },
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']) },