sneakoscope 3.1.14 → 4.0.1

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 +26 -1
  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/build/build-once-runner.js +66 -0
  10. package/dist/core/codex/codex-config-readability.js +52 -38
  11. package/dist/core/commands/check-command.js +132 -0
  12. package/dist/core/commands/daemon-command.js +14 -0
  13. package/dist/core/commands/mad-sks-command.js +25 -3
  14. package/dist/core/commands/release-command.js +52 -0
  15. package/dist/core/commands/task-command.js +15 -0
  16. package/dist/core/commands/triwiki-command.js +38 -0
  17. package/dist/core/daemon/sksd-client.js +9 -0
  18. package/dist/core/daemon/sksd-ipc.js +9 -0
  19. package/dist/core/daemon/sksd.js +87 -0
  20. package/dist/core/doctor/doctor-dirty-planner.js +95 -0
  21. package/dist/core/doctor/doctor-transaction.js +13 -0
  22. package/dist/core/feature-fixtures.js +1 -0
  23. package/dist/core/fsx.js +1 -1
  24. package/dist/core/init.js +7 -1
  25. package/dist/core/probes/probe-memoization.js +80 -0
  26. package/dist/core/release/critical-path-ledger.js +12 -0
  27. package/dist/core/release/extreme-parallel-scheduler.js +86 -0
  28. package/dist/core/release/gate-pack-fixture-cache.js +39 -0
  29. package/dist/core/release/gate-pack-manifest.js +118 -0
  30. package/dist/core/release/gate-pack-runner.js +257 -0
  31. package/dist/core/release/release-gate-cache-v2.js +78 -16
  32. package/dist/core/release/release-gate-dag.js +89 -3
  33. package/dist/core/release/resource-class-budget.js +22 -0
  34. package/dist/core/release/sla-scheduler.js +22 -0
  35. package/dist/core/routes.js +5 -0
  36. package/dist/core/triwiki/triwiki-affected-graph.js +112 -0
  37. package/dist/core/triwiki/triwiki-cache-key.js +241 -0
  38. package/dist/core/triwiki/triwiki-gate-impact-map.js +124 -0
  39. package/dist/core/triwiki/triwiki-invalidation.js +81 -0
  40. package/dist/core/triwiki/triwiki-module-card.js +74 -0
  41. package/dist/core/triwiki/triwiki-proof-bank.js +210 -0
  42. package/dist/core/triwiki/triwiki-proof-card.js +73 -0
  43. package/dist/core/triwiki/triwiki-sla-certificate.js +53 -0
  44. package/dist/core/version.js +1 -1
  45. package/dist/core/zellij/zellij-launcher.js +13 -0
  46. package/dist/core/zellij/zellij-worker-pane-manager.js +68 -17
  47. package/dist/scripts/fixtures/fake-codex-config-loader.js +12 -1
  48. package/dist/scripts/release-4000-required-gates.js +36 -0
  49. package/dist/scripts/release-4001-required-gates.js +13 -0
  50. package/dist/scripts/release-gate-dag-runner.js +18 -0
  51. package/dist/scripts/release-speed-summary.js +9 -0
  52. package/dist/scripts/sizecheck.js +4 -2
  53. package/package.json +52 -7
package/README.md CHANGED
@@ -35,7 +35,32 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.1.14** is a production-hardening release for Codex 0.140 evidence, transactional `sks doctor --fix` repair, MCP readiness, native capability proof, and protected-secret rollback.
38
+ SKS **4.0.1** is the TriWiki Turbo completion release. It moves default completion toward affected-scope, release-equivalent proof with reusable TriWiki proof cards, real gate pack execution, SLA scheduling, build-once proof, probe memoization, and explicit legacy-runtime migration instead of silent fallback aliases.
39
+
40
+ What changed in 4.0.1:
41
+
42
+ - **TriWiki proof bank.** Proof cards now bind reusable gate proof to input hash, implementation hash, package lock hash, environment allowlist, fixture version, and tool version.
43
+ - **Affected release-equivalent verification.** Changed files map to module cards, gate impact maps, and gate packs so small tasks can run fast without giving up scoped release confidence.
44
+ - **Parallel SLA planning.** Release gate packs, resource budgets, and SLA certificates expose the planned critical path for five-minute foreground verification.
45
+ - **New control surface.** `sks check`, `sks task`, `sks release`, `sks triwiki`, `sks proof bank status`, and `sks daemon` expose the fast proof pipeline.
46
+ - **No silent legacy fallback.** Compatibility aliases are removed; removed runtimes now use explicit migration notices.
47
+
48
+ SKS **3.1.16** was a launch-reliability patch on the 3.1.15 doctor-reliability release. It made `sks --mad` self-bootstrap a fresh project instead of dead-ending on a missing Codex config.
49
+
50
+ What changed in 3.1.16:
51
+
52
+ - **`sks --mad` bootstraps a fresh project.** When the only preflight blocker is a missing managed Codex config (`.codex/config.toml` absent), `sks --mad` now regenerates it — the `sks doctor --fix` equivalent — and re-runs the preflight, instead of blocking and making you run a separate command. An existing but unreadable/EPERM/parse-broken config still blocks and routes you to `sks doctor --fix`.
53
+ - **Missing-config diagnostics are honest.** A missing config no longer cascades into misleading `macos_acl_ls_le_failed` / `macos_flags_ls_lO_failed` / `spawned_child_read_failed` blockers from running file checks on a nonexistent path; the preflight reports only `missing_config` / `missing_codex_dir`.
54
+
55
+ SKS **3.1.15** was a doctor-reliability patch on the 3.1.14 production-hardening release. It ended the endless `sks doctor --fix` loop that kept reporting `codex_cli_config_toml_parse_error` / `cli_ready: no` on the very run that already repaired the config.
56
+
57
+ What changed in 3.1.15:
58
+
59
+ - **`sks doctor --fix` no longer loops on a config it already fixed.** The Codex config-load probe is re-run *after* the Context7/Supabase/startup MCP repairs land, so the readiness verdict reflects the repaired config instead of the stale pre-repair snapshot.
60
+ - **Context7 is seeded on the remote transport.** Managed setup writes `[mcp_servers.context7]` with the streamable-HTTP `url` instead of a local stdio `command`, so the project config never merges with a remote `url` in the global Codex config into the `url is not supported for stdio` error Codex 0.140 rejects.
61
+ - **The config-load operator action is accurate.** A `codex_cli_config_toml_parse_error` now points at both misplaced machine-local keys *and* the Context7/MCP stdio-vs-`url` transport conflict, instead of only suggesting a key hoist that does nothing for a transport conflict.
62
+
63
+ The 3.1.14 production-hardening surface for Codex 0.140 evidence, transactional `sks doctor --fix` repair, MCP readiness, native capability proof, and protected-secret rollback remains intact.
39
64
 
40
65
  What changed in 3.1.14:
41
66
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.14"
79
+ version = "4.0.1"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "3.1.14"
3
+ version = "4.0.1"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 3.1.14"),
7
+ Some("--version") => println!("sks-rs 4.0.1"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.1.14';
2
+ const FAST_PACKAGE_VERSION = '4.0.1';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -77,6 +77,11 @@ export const COMMANDS = {
77
77
  help: entry('stable', 'Show SKS help', 'dist/commands/help.js', directCommand(() => import('../commands/help.js'), 'dist/commands/help.js')),
78
78
  version: entry('stable', 'Show SKS version', 'dist/commands/version.js', directCommand(() => import('../commands/version.js'), 'dist/commands/version.js')),
79
79
  commands: entry('stable', 'List SKS commands', 'dist/core/commands/basic-cli.js', basicArgs('commandsCommand')),
80
+ check: entry('stable', 'Run five-minute proof-bank affected checks', 'dist/core/commands/check-command.js', argsCommand(() => import('../core/commands/check-command.js'), 'checkCommand', 'dist/core/commands/check-command.js')),
81
+ task: entry('stable', 'Run an SLA-bounded SKS task check', 'dist/core/commands/task-command.js', argsCommand(() => import('../core/commands/task-command.js'), 'taskCommand', 'dist/core/commands/task-command.js')),
82
+ release: entry('stable', 'Run affected/full/background release gates', 'dist/core/commands/release-command.js', argsCommand(() => import('../core/commands/release-command.js'), 'releaseCommand', 'dist/core/commands/release-command.js')),
83
+ triwiki: entry('stable', 'Inspect TriWiki index, affected graph, and proof bank', 'dist/core/commands/triwiki-command.js', argsCommand(() => import('../core/commands/triwiki-command.js'), 'triwikiCommand', 'dist/core/commands/triwiki-command.js')),
84
+ daemon: entry('stable', 'Inspect or warm the local SKS daemon cache', 'dist/core/commands/daemon-command.js', argsCommand(() => import('../core/commands/daemon-command.js'), 'daemonCommand', 'dist/core/commands/daemon-command.js')),
80
85
  run: entry('beta', 'Classify and execute a task through the SKS trust kernel', 'dist/core/commands/run-command.js', argsCommand(() => import('../core/commands/run-command.js'), 'runCommand', 'dist/core/commands/run-command.js')),
81
86
  status: entry('stable', 'Show concise active mission and trust status', 'dist/core/commands/status-command.js', argsCommand(() => import('../core/commands/status-command.js'), 'statusCommand', 'dist/core/commands/status-command.js')),
82
87
  root: entry('stable', 'Show active SKS root', 'dist/commands/root.js', directCommand(() => import('../commands/root.js'), 'dist/commands/root.js')),
@@ -159,19 +164,7 @@ export const COMMANDS = {
159
164
  bench: entry('beta', 'Run core trust-kernel benchmark budgets', 'dist/core/commands/bench-command.js', argsCommand(() => import('../core/commands/bench-command.js'), 'benchCommand', 'dist/core/commands/bench-command.js'))
160
165
  };
161
166
  export const TYPED_COMMANDS = COMMANDS;
162
- export const LEGACY_COMMAND_ALIASES = {
163
- auth: 'codex-lb',
164
- mad: 'mad-sks',
165
- autoreview: 'auto-review',
166
- dollars: 'dollar-commands',
167
- '$': 'dollar-commands',
168
- 'ux-review': 'image-ux-review',
169
- 'visual-review': 'image-ux-review',
170
- 'ui-ux-review': 'image-ux-review',
171
- cu: 'computer-use',
172
- grok: 'xai',
173
- memory: 'gc'
174
- };
167
+ export const LEGACY_COMMAND_ALIASES = {};
175
168
  export const COMMAND_ALIASES = {
176
169
  ...LEGACY_COMMAND_ALIASES,
177
170
  '--help': 'help',
@@ -35,6 +35,7 @@ import { repairCodexStartupConfig } from '../core/doctor/codex-startup-config-re
35
35
  import { repairContext7Mcp } from '../core/doctor/context7-mcp-repair.js';
36
36
  import { repairSupabaseMcp } from '../core/doctor/supabase-mcp-repair.js';
37
37
  import { runDoctorFixTransaction } from '../core/doctor/doctor-transaction.js';
38
+ import { planDoctorDirtyRepair } from '../core/doctor/doctor-dirty-planner.js';
38
39
  import { doctorRepairPostcheck } from '../core/doctor/doctor-repair-postcheck.js';
39
40
  import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
40
41
  export async function run(_command, args = []) {
@@ -143,7 +144,7 @@ async function runDoctor(args = [], root, doctorFix) {
143
144
  const migrationJournal = flag(args, '--fix')
144
145
  ? await writeFixMigrationJournal(root, migrationPreFix, configRepair, setupRepair).catch(() => null)
145
146
  : null;
146
- const codexConfig = configRepair?.after || await inspectCodexConfigReadability(root, configProbeOpts);
147
+ let codexConfig = configRepair?.after || await inspectCodexConfigReadability(root, configProbeOpts);
147
148
  const codexDoctor = await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') });
148
149
  const codexDoctorDiff = compareCodexDoctorBridge(codexDoctorBefore, codexDoctor);
149
150
  codexStartupRepair = mergeObservedCodexStartupWarnings(codexStartupRepair, codexDoctor);
@@ -284,9 +285,22 @@ async function runDoctor(args = [], root, doctorFix) {
284
285
  raw_secret_values_recorded: false
285
286
  }))
286
287
  : null;
288
+ const doctorDirtyPlan = doctorFix
289
+ ? planDoctorDirtyRepair(root, [
290
+ 'setup',
291
+ 'codex_startup_repair',
292
+ 'startup_config_repair',
293
+ 'context7_repair',
294
+ 'context7_mcp_repair',
295
+ 'supabase_mcp_repair',
296
+ 'command_alias_cleanup',
297
+ 'native_capability_repair'
298
+ ])
299
+ : null;
287
300
  const doctorFixTransaction = doctorFix
288
301
  ? await runDoctorFixTransaction({
289
302
  root,
303
+ dirtyPlan: doctorDirtyPlan,
290
304
  phases: [
291
305
  {
292
306
  id: 'setup',
@@ -459,6 +473,18 @@ async function runDoctor(args = [], root, doctorFix) {
459
473
  blockers: [err?.message || String(err)],
460
474
  warnings: []
461
475
  }));
476
+ // Re-probe the Codex config AFTER the MCP transport repairs (Context7 remote
477
+ // migration, Supabase read-only, startup config) have landed. `repairCodexConfigEperm`
478
+ // ran its config-load probe ~before~ those repairs, so a config that those repairs
479
+ // fix in THIS run would otherwise keep `codexConfig.ok === false`, making the doctor
480
+ // report `cli_ready: no` / `codex_cli_config_toml_parse_error` on the very run that
481
+ // fixed it — the endless "rerun sks doctor --fix" loop. Only re-probe when the initial
482
+ // probe failed and we are in --fix mode, so healthy configs pay no extra probe cost.
483
+ if (doctorFix && codexConfig?.ok === false) {
484
+ const reinspected = await inspectCodexConfigReadability(root, configProbeOpts).catch(() => null);
485
+ if (reinspected)
486
+ codexConfig = reinspected;
487
+ }
462
488
  const pkgBytes = await dirSize(root).catch(() => 0);
463
489
  const ready = await writeDoctorReadinessMatrix(root, {
464
490
  codex,
@@ -474,6 +500,7 @@ async function runDoctor(args = [], root, doctorFix) {
474
500
  context7_mcp_repair: context7McpRepair,
475
501
  supabase_mcp_repair: supabaseMcpRepair,
476
502
  doctor_fix_transaction: doctorFixTransaction,
503
+ doctor_dirty_plan: doctorDirtyPlan,
477
504
  doctor_fix_postcheck: doctorFixPostcheck,
478
505
  local_model: localModel,
479
506
  agent_role_config: agentRoleConfigRepair,
@@ -540,7 +567,7 @@ async function runDoctor(args = [], root, doctorFix) {
540
567
  ready,
541
568
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
542
569
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
543
- repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, doctor_transaction: doctorFixTransaction, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
570
+ repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, doctor_transaction: doctorFixTransaction, doctor_dirty_plan: doctorDirtyPlan, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
544
571
  };
545
572
  if (flag(args, '--json')) {
546
573
  printJson(result);
@@ -9,10 +9,18 @@ import { finalizeRouteWithProof } from '../core/proof/route-finalizer.js';
9
9
  import { renderProofMarkdown, writeCompletionProof } from '../core/proof/proof-writer.js';
10
10
  import { validateCompletionProof } from '../core/proof/validation.js';
11
11
  import { buildRuntimeProofSummary, renderRuntimeProofSummary } from '../core/agents/runtime-proof-summary.js';
12
+ import { summarizeTriWikiProofBank } from '../core/triwiki/triwiki-proof-bank.js';
12
13
  export async function run(_command, args = []) {
13
14
  const root = await projectRoot();
14
15
  const action = args[0] || 'show';
15
16
  const rest = args.slice(1);
17
+ if (action === 'bank' && rest[0] === 'status') {
18
+ const status = summarizeTriWikiProofBank(root);
19
+ if (flag(args, '--json'))
20
+ return printJson(status);
21
+ console.log(JSON.stringify(status, null, 2));
22
+ return;
23
+ }
16
24
  if (action === 'latest' && !flag(args, '--completion')) {
17
25
  const runtime = await tryRuntimeProofSummary(root);
18
26
  if (runtime) {
@@ -0,0 +1,66 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { computeTriWikiCacheKey } from '../triwiki/triwiki-cache-key.js';
5
+ export const BUILD_ONCE_PROOF_SCHEMA = 'sks.build-once-proof.v1';
6
+ export function runBuildOnce(input) {
7
+ const mode = input.mode || 'incremental';
8
+ const key = computeTriWikiCacheKey({
9
+ root: input.root,
10
+ id: `build-once:${mode}`,
11
+ inputs: ['src/**/*.ts', 'package.json', 'package-lock.json', 'tsconfig.json'],
12
+ implementationFiles: ['tsconfig.json'],
13
+ envAllowlist: ['NODE_ENV', 'CI'],
14
+ fixtureVersion: 'sks-4.0.1'
15
+ });
16
+ const existing = readBuildOnceProof(input.root);
17
+ if (!input.force && existing?.ok === true && existing.cache_key === key.key) {
18
+ return { ...existing, reused: true };
19
+ }
20
+ const started = Date.now();
21
+ const run = spawnSync('npm', ['run', mode === 'clean' ? 'build:clean' : 'build:incremental', '--silent'], {
22
+ cwd: input.root,
23
+ encoding: 'utf8',
24
+ maxBuffer: 1024 * 1024 * 20,
25
+ env: { ...process.env, CI: process.env.CI || 'true' }
26
+ });
27
+ const proof = {
28
+ schema: BUILD_ONCE_PROOF_SCHEMA,
29
+ ok: run.status === 0,
30
+ mode,
31
+ cache_key: key.key,
32
+ source_hash: key.input_hash,
33
+ package_lock_hash: key.package_lock_hash,
34
+ tsconfig_hash: computeTriWikiCacheKey({ root: input.root, id: 'build-once:tsconfig', inputs: ['tsconfig.json'] }).input_hash,
35
+ dist_hash: computeTriWikiCacheKey({ root: input.root, id: 'build-once:dist', inputs: ['dist'] }).input_hash,
36
+ created_at: new Date().toISOString(),
37
+ status: run.status,
38
+ duration_ms: Math.max(0, Date.now() - started),
39
+ reused: false,
40
+ blockers: run.status === 0 ? [] : [`build_failed:${run.status ?? 'signal'}`]
41
+ };
42
+ writeBuildOnceProof(input.root, proof);
43
+ return proof;
44
+ }
45
+ export function readBuildOnceProof(root) {
46
+ const file = buildProofPath(root);
47
+ try {
48
+ if (!fs.existsSync(file))
49
+ return null;
50
+ const json = JSON.parse(fs.readFileSync(file, 'utf8'));
51
+ return json.schema === BUILD_ONCE_PROOF_SCHEMA ? json : null;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ export function writeBuildOnceProof(root, proof) {
58
+ const file = buildProofPath(root);
59
+ fs.mkdirSync(path.dirname(file), { recursive: true });
60
+ fs.writeFileSync(file, `${JSON.stringify(proof, null, 2)}\n`);
61
+ return file;
62
+ }
63
+ function buildProofPath(root) {
64
+ return path.join(root, 'dist', '.sks-build-proof.json');
65
+ }
66
+ //# sourceMappingURL=build-once-runner.js.map
@@ -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,132 @@
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 triwiki = !flag(args, '--no-triwiki');
16
+ const plan = buildCheckPlan({ tier, sla, changedSince, triwiki });
17
+ if (planOnly) {
18
+ const result = { schema: CHECK_SCHEMA, ok: true, mode: 'plan', ...plan };
19
+ if (json)
20
+ return printJson(result);
21
+ printPlan(result);
22
+ return result;
23
+ }
24
+ const steps = [];
25
+ let proofBankSummary = null;
26
+ for (const step of plan.steps) {
27
+ const result = spawnSync(step.command, step.args, {
28
+ cwd: root,
29
+ encoding: 'utf8',
30
+ maxBuffer: 1024 * 1024 * 20,
31
+ shell: false,
32
+ env: { ...process.env, CI: process.env.CI || 'true' }
33
+ });
34
+ const ok = result.status === 0;
35
+ if (step.name === 'proof-bank-summary')
36
+ proofBankSummary = parseJsonObject(String(result.stdout || ''));
37
+ steps.push({ name: step.name, ok, status: result.status, stderr_tail: tail(String(result.stderr || '')) });
38
+ if (!json) {
39
+ if (result.stdout)
40
+ process.stdout.write(result.stdout);
41
+ if (result.stderr)
42
+ process.stderr.write(result.stderr);
43
+ }
44
+ if (!ok) {
45
+ const failed = { schema: CHECK_SCHEMA, ok: false, mode: 'run', ...plan, steps, completion_certificate: latestCertificate(root) || proofBankSummary?.completion_certificate || null, release_speed_summary: proofBankSummary };
46
+ if (json)
47
+ return printJson(failed);
48
+ process.exitCode = result.status || 1;
49
+ return failed;
50
+ }
51
+ }
52
+ const result = { schema: CHECK_SCHEMA, ok: true, mode: 'run', ...plan, steps, completion_certificate: latestCertificate(root) || proofBankSummary?.completion_certificate || null, release_speed_summary: proofBankSummary };
53
+ if (json)
54
+ return printJson(result);
55
+ if (result.completion_certificate) {
56
+ console.log(`SKS check certificate: ${result.completion_certificate.confidence} sla_met=${result.completion_certificate.sla_met}`);
57
+ }
58
+ return result;
59
+ }
60
+ function buildCheckPlan(input) {
61
+ const tier = normalizeTier(input.tier);
62
+ const buildScript = tier === 'release' ? 'build:clean' : 'build:incremental';
63
+ const steps = [];
64
+ if (tier === 'instant') {
65
+ steps.push({ name: 'proof-bank-summary', command: process.execPath, args: ['dist/scripts/release-speed-summary.js'] });
66
+ }
67
+ else if (tier === 'real-check') {
68
+ steps.push({ name: 'real-check', command: process.execPath, args: ['dist/scripts/release-real-check.js'] });
69
+ }
70
+ else {
71
+ steps.push({ name: buildScript, command: 'npm', args: ['run', buildScript, '--silent'] });
72
+ steps.push({ name: `release:${tier}`, command: process.execPath, args: dagArgs(tier, input.changedSince, input.sla) });
73
+ }
74
+ return {
75
+ tier,
76
+ sla: input.sla,
77
+ triwiki: input.triwiki,
78
+ changed_since: input.changedSince,
79
+ build_once: tier === 'release' ? 'clean' : tier === 'real-check' || tier === 'instant' ? 'not_applicable' : 'incremental',
80
+ steps
81
+ };
82
+ }
83
+ function dagArgs(tier, changedSince, sla) {
84
+ if (tier === 'release')
85
+ return ['dist/scripts/release-gate-dag-runner.js', '--preset', 'release', '--full'];
86
+ const preset = tier === 'instant' ? 'fast' : tier === 'affected' || tier === 'confidence' ? 'affected' : tier;
87
+ return ['dist/scripts/release-gate-dag-runner.js', '--preset', preset, '--changed-since', changedSince, '--sla', sla];
88
+ }
89
+ function latestCertificate(root) {
90
+ const direct = path.join(root, '.sneakoscope', 'reports', 'completion-certificate.json');
91
+ if (fs.existsSync(direct))
92
+ return JSON.parse(fs.readFileSync(direct, 'utf8'));
93
+ const base = path.join(root, '.sneakoscope', 'reports', 'release-gates');
94
+ if (!fs.existsSync(base))
95
+ return null;
96
+ const latest = fs.readdirSync(base)
97
+ .map((name) => path.join(base, name, 'completion-certificate.json'))
98
+ .filter((file) => fs.existsSync(file))
99
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0];
100
+ return latest ? JSON.parse(fs.readFileSync(latest, 'utf8')) : null;
101
+ }
102
+ function normalizeTier(value) {
103
+ const tier = String(value || '').trim().toLowerCase();
104
+ if (['instant', 'affected', 'confidence', 'release', 'real-check'].includes(tier))
105
+ return tier;
106
+ return 'confidence';
107
+ }
108
+ function positionalTier(args) {
109
+ const first = args.find((arg) => !arg.startsWith('-'));
110
+ return first || null;
111
+ }
112
+ function readArg(args, name, fallback) {
113
+ const index = args.indexOf(name);
114
+ return index >= 0 && args[index + 1] ? String(args[index + 1]) : fallback;
115
+ }
116
+ function tail(value, limit = 1200) {
117
+ return value.length > limit ? value.slice(-limit) : value;
118
+ }
119
+ function parseJsonObject(value) {
120
+ try {
121
+ return JSON.parse(value);
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ function printPlan(result) {
128
+ console.log(`SKS check plan: tier=${result.tier} sla=${result.sla} build=${result.build_once}`);
129
+ for (const step of result.steps)
130
+ console.log(`- ${step.name}: ${[step.command, ...step.args].join(' ')}`);
131
+ }
132
+ //# 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
@@ -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) {
@@ -217,10 +234,15 @@ export async function madHighCommand(args = [], deps = {}) {
217
234
  SKS_MAD_SKS_PROTECTED_CORE_DIGEST: madLaunch.gate.protected_core_digest
218
235
  };
219
236
  const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, { codexArgs: profile.launch_args, conciseBlockers: true, madSksEnv, launchEnv: madSksEnv });
220
- const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${sanitizeZellijSessionName(process.cwd())}`));
237
+ const explicitWorkspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', null));
238
+ // Only the auto-derived stable `sks-mad-<cwd>` name accumulates panes across
239
+ // runs; when the user names a session explicitly (or codex-lb already minted a
240
+ // fresh unique session) respect it and skip the reset.
241
+ const autoDerivedMadSession = !explicitWorkspace && !launchOpts.session;
242
+ const workspace = explicitWorkspace || launchOpts.session || `sks-mad-${sanitizeZellijSessionName(process.cwd())}`;
221
243
  const launch = headlessZellij
222
244
  ? await writeMadHeadlessZellijFallback(madLaunch, workspace)
223
- : await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), slotCount: 0, requireZellij: process.env.SKS_REQUIRE_ZELLIJ === '1' });
245
+ : await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), slotCount: 0, freshSession: autoDerivedMadSession, requireZellij: process.env.SKS_REQUIRE_ZELLIJ === '1' });
224
246
  const afterLaunchUi = beforeUi ? await writeCodexAppUiSnapshot(launchRoot, `mad-after-launch-${uiSnapshotId}`).catch(() => null) : null;
225
247
  const launchUiDiff = beforeUi && afterLaunchUi ? diffCodexAppUiSnapshots(beforeUi, afterLaunchUi) : null;
226
248
  if (launchUiDiff) {
@@ -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