sneakoscope 3.1.8 → 3.1.10

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 (42) hide show
  1. package/README.md +6 -5
  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/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/args.js +17 -0
  8. package/dist/cli/command-registry.js +16 -13
  9. package/dist/cli/router.js +8 -5
  10. package/dist/commands/doctor.js +54 -4
  11. package/dist/core/codex-app/codex-skill-sync.js +37 -2
  12. package/dist/core/codex-native/core-skill-integrity.js +6 -1
  13. package/dist/core/codex-native/core-skill-manifest.js +1 -1
  14. package/dist/core/codex-native/native-capability-postcheck.js +143 -15
  15. package/dist/core/codex-native/native-capability-repair-matrix.js +1 -1
  16. package/dist/core/codex-native/project-skill-dedupe.js +18 -3
  17. package/dist/core/codex-native/skill-registry-ledger.js +9 -2
  18. package/dist/core/commands/basic-cli.js +7 -2
  19. package/dist/core/commands/mad-sks-command.js +36 -13
  20. package/dist/core/commands/naruto-command.js +4 -1
  21. package/dist/core/commands/pipeline-command.js +3 -4
  22. package/dist/core/commands/qa-loop-command.js +36 -1
  23. package/dist/core/commands/research-command.js +61 -1
  24. package/dist/core/commands/team-command.js +63 -3
  25. package/dist/core/config/managed-config-merge.js +59 -10
  26. package/dist/core/config/secret-preservation.js +145 -37
  27. package/dist/core/decision-contract.js +28 -4
  28. package/dist/core/doctor/command-alias-cleanup.js +64 -0
  29. package/dist/core/feature-fixtures.js +2 -0
  30. package/dist/core/feature-registry.js +2 -2
  31. package/dist/core/fsx.js +1 -1
  32. package/dist/core/init.js +31 -6
  33. package/dist/core/naruto/naruto-work-graph.js +4 -1
  34. package/dist/core/pipeline-internals/runtime-core.js +50 -4
  35. package/dist/core/pipeline-internals/runtime-gates.js +10 -1
  36. package/dist/core/proof/route-proof-gate.js +1 -1
  37. package/dist/core/qa-loop.js +227 -11
  38. package/dist/core/questions.js +239 -2
  39. package/dist/core/routes.js +3 -4
  40. package/dist/core/version.js +1 -1
  41. package/dist/scripts/agent-native-release-gate.js +13 -4
  42. package/package.json +5 -1
package/README.md CHANGED
@@ -35,14 +35,15 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.1.8** hardens update/setup/doctor safety around four operator-facing failure modes: immutable core skills, duplicate project skills, real native capability repair status, and Supabase/secret preservation.
38
+ SKS **3.1.10** is a release-ready hardening pass for release wiring parity, immutable core skills, duplicate skill prevention, native capability postchecks, and protected secret rollback.
39
39
 
40
- What changed in 3.1.8:
40
+ What changed in 3.1.10:
41
41
 
42
42
  - **Core SKS skills are content-addressed and immutable.** The eight built-in route skills now have a manifest and no-drift gates; setup/update/doctor may install missing managed copies or restore corrupted managed copies, but they do not overwrite user-authored collisions.
43
- - **Duplicate skills are detected and repaired safely.** Canonical skill names collapse variants such as `Loop`, `loop`, and `loop/SKILL.md`; SKS-managed duplicates can be quarantined automatically, while user-authored duplicates require explicit confirmation.
44
- - **`sks doctor --fix` reports native capability truthfully.** Image generation, image follow-up edit paths, Computer Use, Chrome/web review, app screenshots, app handoff, and image path exposure now run through a repair matrix and postcheck instead of capability assumptions.
45
- - **Supabase keys survive setup/update/doctor.** Protected secret surfaces are fingerprinted before and after guarded operations; reports store only redacted previews and hashes, never raw values.
43
+ - **Release wiring is self-checking.** `release:gate-script-parity`, `release:wiring-3110-blackbox`, and `sks:3110-all-feature-regression` prove required ids, package scripts, release gates, source scripts, and built dist targets stay aligned.
44
+ - **Duplicate skills are detected and repaired safely.** Canonical skill names collapse variants such as `Loop`, `loop`, and `loop/SKILL.md`; SKS-managed duplicates can be quarantined automatically, while user-authored duplicates require explicit confirmation and produce active-name proof.
45
+ - **`sks doctor --fix` reports native capability truthfully.** Image generation, image follow-up edit paths, Computer Use, Chrome/web review, app screenshots, app handoff, and image path exposure now run capability-specific postchecks; manual-only and fallback surfaces do not become `verified`.
46
+ - **Supabase keys survive setup/update/doctor.** Protected secret surfaces are fingerprinted before and after guarded operations; missing or changed values are restored from backup or hard-fail without writing raw values to reports.
46
47
 
47
48
  SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
48
49
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.8"
79
+ version = "3.1.10"
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.8"
3
+ version = "3.1.10"
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.8"),
7
+ Some("--version") => println!("sks-rs 3.1.10"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "3.1.8",
5
- "source_digest": "94742dc3eb63f280df98aa286ac2a94a14fd140f27c89db85880e4f92c630454",
6
- "source_file_count": 2602,
7
- "built_at_source_time": 1781513577024
4
+ "package_version": "3.1.10",
5
+ "source_digest": "57a7f5ee1f3ac797f46e3dfd59365b1e147e4637a8ddd18fde2cbee802fec0ac",
6
+ "source_file_count": 2609,
7
+ "built_at_source_time": 1781550268084
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.1.8';
2
+ const FAST_PACKAGE_VERSION = '3.1.10';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
package/dist/cli/args.js CHANGED
@@ -28,6 +28,23 @@ export function positionalArgs(args = []) {
28
28
  '--command',
29
29
  '--project-ref',
30
30
  '--agent',
31
+ '--agents',
32
+ '--target-active-slots',
33
+ '--work-items',
34
+ '--minimum-work-items',
35
+ '--max-queue-expansion',
36
+ '--concurrency',
37
+ '--backend',
38
+ '--route',
39
+ '--mission',
40
+ '--mission-id',
41
+ '--profile',
42
+ '--write-mode',
43
+ '--max-write-agents',
44
+ '--service-tier',
45
+ '--zellij-session-name',
46
+ '--worker-placement',
47
+ '--zellij-visible-pane-cap',
31
48
  '--phase',
32
49
  '--message',
33
50
  '--role',
@@ -99,21 +99,16 @@ export const COMMANDS = {
99
99
  'codex-app': entry('beta', 'Check Codex App readiness', 'dist/commands/codex-app.js', directCommand(() => import('../commands/codex-app.js'), 'dist/commands/codex-app.js')),
100
100
  'codex-native': entry('beta', 'Inspect Codex Native broker and routing readiness', 'dist/commands/codex-native.js', directCommand(() => import('../commands/codex-native.js'), 'dist/commands/codex-native.js')),
101
101
  'codex-lb': entry('beta', 'Inspect codex-lb status and circuit health', 'dist/commands/codex-lb.js', directCommand(() => import('../commands/codex-lb.js'), 'dist/commands/codex-lb.js')),
102
- auth: entry('beta', 'Alias for codex-lb auth commands', 'dist/commands/codex-lb.js', directCommand(() => import('../commands/codex-lb.js'), 'dist/commands/codex-lb.js')),
103
102
  hooks: entry('beta', 'Explain and inspect Codex hooks', 'dist/commands/hooks.js', directCommand(() => import('../commands/hooks.js'), 'dist/commands/hooks.js')),
104
103
  tmux: entry('beta', 'Show removed-runtime migration notice', 'dist/commands/tmux.js', directCommand(() => import('../commands/tmux.js'), 'dist/commands/tmux.js')),
105
104
  'zellij-lane': entry('beta', 'Render a Zellij lane frame for SKS sessions', 'dist/commands/zellij-lane.js', directCommand(() => import('../commands/zellij-lane.js'), 'dist/commands/zellij-lane.js')),
106
105
  'zellij-slot-pane': entry('beta', 'Render a compact Zellij worker slot pane', 'dist/commands/zellij-slot-pane.js', directCommand(() => import('../commands/zellij-slot-pane.js'), 'dist/commands/zellij-slot-pane.js')),
107
106
  'zellij-slot-column-anchor': entry('beta', 'Render the compact SLOTS anchor pane for first-slot-down Zellij stacks', 'dist/commands/zellij-slot-column-anchor.js', directCommand(() => import('../commands/zellij-slot-column-anchor.js'), 'dist/commands/zellij-slot-column-anchor.js')),
108
107
  zellij: entry('beta', 'Inspect Zellij runtime status and explain repair (no auto-install)', 'dist/commands/zellij.js', directCommand(() => import('../commands/zellij.js'), 'dist/commands/zellij.js')),
109
- mad: entry('beta', 'MAD-SKS Zellij permission launcher', 'dist/commands/mad-sks.js', directCommand(() => import('../commands/mad-sks.js'), 'dist/commands/mad-sks.js')),
110
108
  'mad-sks': entry('beta', 'MAD-SKS scoped permission modifier', 'dist/commands/mad-sks.js', directCommand(() => import('../commands/mad-sks.js'), 'dist/commands/mad-sks.js')),
111
109
  'mad-db': entry('beta', 'Create or inspect one-cycle Mad-DB break-glass capability tokens', 'dist/commands/mad-db.js', directCommand(() => import('../commands/mad-db.js'), 'dist/commands/mad-db.js')),
112
110
  'auto-review': entry('beta', 'Manage auto-review profile', 'dist/commands/auto-review.js', directCommand(() => import('../commands/auto-review.js'), 'dist/commands/auto-review.js')),
113
- autoreview: entry('beta', 'Alias for auto-review', 'dist/commands/auto-review.js', directCommand(() => import('../commands/auto-review.js'), 'dist/commands/auto-review.js')),
114
111
  'dollar-commands': entry('stable', 'List Codex App dollar commands', 'dist/core/commands/basic-cli.js', basicArgs('dollarCommandsCommand')),
115
- dollars: entry('stable', 'Alias for dollar-commands', 'dist/core/commands/basic-cli.js', basicArgs('dollarCommandsCommand')),
116
- '$': entry('stable', 'Alias for dollar-commands', 'dist/core/commands/basic-cli.js', basicArgs('dollarCommandsCommand')),
117
112
  'fast-mode': entry('stable', 'Toggle SKS Fast mode default for dollar-command routes', 'dist/core/commands/fast-mode-command.js', argsCommand(() => import('../core/commands/fast-mode-command.js'), 'fastModeCommand', 'dist/core/commands/fast-mode-command.js')),
118
113
  commit: entry('stable', 'Create a simple git commit', 'dist/commands/commit.js', directCommand(() => import('../commands/commit.js'), 'dist/commands/commit.js')),
119
114
  'commit-and-push': entry('stable', 'Create a simple git commit and push', 'dist/commands/commit-and-push.js', directCommand(() => import('../commands/commit-and-push.js'), 'dist/commands/commit-and-push.js')),
@@ -128,14 +123,9 @@ export const COMMANDS = {
128
123
  autoresearch: entry('labs', 'Alias for research/autoresearch route', 'dist/core/commands/autoresearch-command.js', subcommand(() => import('../core/commands/autoresearch-command.js'), 'autoresearchCommand', 'dist/core/commands/autoresearch-command.js', 'status')),
129
124
  ppt: entry('labs', 'Inspect/build PPT artifacts', 'dist/core/commands/ppt-command.js', commandArgsCommand(() => import('../core/commands/ppt-command.js'), 'pptCommand', 'dist/core/commands/ppt-command.js')),
130
125
  'image-ux-review': entry('labs', 'Inspect image UX artifacts', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
131
- 'ux-review': entry('labs', 'Alias for image UX review', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
132
- 'visual-review': entry('labs', 'Alias for image UX review', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
133
- 'ui-ux-review': entry('labs', 'Alias for image UX review', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
134
126
  'computer-use': entry('beta', 'Record native Mac/non-web Computer Use visual evidence', 'dist/core/commands/computer-use-command.js', commandArgsCommand(() => import('../core/commands/computer-use-command.js'), 'computerUseCommand', 'dist/core/commands/computer-use-command.js')),
135
- cu: entry('beta', 'Alias for native Computer Use', 'dist/core/commands/computer-use-command.js', commandArgsCommand(() => import('../core/commands/computer-use-command.js'), 'computerUseCommand', 'dist/core/commands/computer-use-command.js')),
136
127
  context7: entry('beta', 'Context7 checks and docs', 'dist/cli/context7-command.js', subcommand(() => import('./context7-command.js'), 'context7Command', 'dist/cli/context7-command.js', 'check')),
137
128
  xai: entry('beta', 'Set up and check xAI/Grok search MCP integration', 'dist/cli/xai-command.js', subcommand(() => import('./xai-command.js'), 'xaiCommand', 'dist/cli/xai-command.js', 'check')),
138
- grok: entry('beta', 'Alias for xAI/Grok search setup', 'dist/cli/xai-command.js', subcommand(() => import('./xai-command.js'), 'xaiCommand', 'dist/cli/xai-command.js', 'check')),
139
129
  recallpulse: entry('labs', 'RecallPulse evidence route', 'dist/commands/recallpulse.js', directCommand(() => import('../commands/recallpulse.js'), 'dist/commands/recallpulse.js')),
140
130
  pipeline: entry('beta', 'Inspect pipeline missions', 'dist/commands/pipeline.js', directCommand(() => import('../commands/pipeline.js'), 'dist/commands/pipeline.js')),
141
131
  guard: entry('beta', 'Check harness guard', 'dist/commands/guard.js', directCommand(() => import('../commands/guard.js'), 'dist/commands/guard.js')),
@@ -156,7 +146,6 @@ export const COMMANDS = {
156
146
  'skill-dream': entry('labs', 'Track skill dream counters', 'dist/core/commands/skill-dream-command.js', subcommand(() => import('../core/commands/skill-dream-command.js'), 'skillDreamCommand', 'dist/core/commands/skill-dream-command.js', 'status')),
157
147
  'code-structure': entry('labs', 'Scan source structure', 'dist/core/commands/code-structure-command.js', subcommand(() => import('../core/commands/code-structure-command.js'), 'codeStructureCommand', 'dist/core/commands/code-structure-command.js', 'scan')),
158
148
  rust: entry('beta', 'Inspect optional Rust accelerator status and smoke parity', 'dist/commands/rust.js', directCommand(() => import('../commands/rust.js'), 'dist/commands/rust.js')),
159
- memory: entry('labs', 'Run retention checks', 'dist/core/commands/gc-command.js', subcommand(() => import('../core/commands/gc-command.js'), 'memoryCommand', 'dist/core/commands/gc-command.js')),
160
149
  gx: entry('labs', 'Render/validate GX cartridges', 'dist/core/commands/gx-command.js', subcommand(() => import('../core/commands/gx-command.js'), 'gxCommand', 'dist/core/commands/gx-command.js', 'validate')),
161
150
  db: entry('beta', 'Inspect DB safety policy', 'dist/core/commands/db-command.js', subcommand(() => import('../core/commands/db-command.js'), 'dbCommand', 'dist/core/commands/db-command.js', 'policy')),
162
151
  eval: entry('labs', 'Run eval reports', 'dist/core/commands/eval-command.js', subcommand(() => import('../core/commands/eval-command.js'), 'evalCommand', 'dist/core/commands/eval-command.js', 'run')),
@@ -170,13 +159,27 @@ export const COMMANDS = {
170
159
  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'))
171
160
  };
172
161
  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
+ };
173
175
  export const COMMAND_ALIASES = {
176
+ ...LEGACY_COMMAND_ALIASES,
174
177
  '--help': 'help',
175
178
  '-h': 'help',
176
179
  '--version': 'version',
177
180
  '-v': 'version',
178
- '--mad': 'mad',
179
- '--MAD': 'mad',
181
+ '--mad': 'mad-sks',
182
+ '--MAD': 'mad-sks',
180
183
  '--mad-sks': 'mad-sks',
181
184
  '--agent': 'agent',
182
185
  '--naruto': 'naruto'
@@ -5,17 +5,20 @@ export function isCommandName(value) {
5
5
  export function normalizeCommand(args = []) {
6
6
  const cmd = args[0];
7
7
  if (!cmd)
8
- return { command: null, args: [...args] };
9
- let mapped = cmd in COMMAND_ALIASES ? COMMAND_ALIASES[cmd] : cmd;
8
+ return { command: null, rawCommand: null, aliasTarget: null, args: [...args] };
9
+ const mapped = cmd in COMMAND_ALIASES ? COMMAND_ALIASES[cmd] : cmd;
10
10
  const rest = args.slice(1);
11
+ const command = isCommandName(mapped) ? mapped : null;
11
12
  return {
12
- command: isCommandName(mapped) ? mapped : null,
13
+ command,
14
+ rawCommand: cmd,
15
+ aliasTarget: command && mapped !== cmd ? command : null,
13
16
  args: rest,
14
17
  };
15
18
  }
16
19
  export async function dispatch(args) {
17
20
  const argv = args ?? process.argv.slice(2);
18
- const { command, args: rest } = normalizeCommand(argv);
21
+ const { command, rawCommand, args: rest } = normalizeCommand(argv);
19
22
  if (!command) {
20
23
  if (!argv.length) {
21
24
  const mod = await import('../commands/doctor.js');
@@ -36,6 +39,6 @@ export async function dispatch(args) {
36
39
  const mod = await entry.lazy();
37
40
  if (typeof mod.run !== 'function')
38
41
  throw new Error(`Command ${command} must export run(command, args)`);
39
- return mod.run(command, rest);
42
+ return mod.run(rawCommand || command, rest);
40
43
  }
41
44
  //# sourceMappingURL=router.js.map
@@ -28,8 +28,16 @@ import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-
28
28
  import { buildCodexNativeFeatureMatrix } from '../core/codex-native/codex-native-feature-broker.js';
29
29
  import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
30
30
  import { runDoctorNativeCapabilityRepair } from '../core/doctor/doctor-native-capability-repair.js';
31
+ import { runDoctorCommandAliasCleanup } from '../core/doctor/command-alias-cleanup.js';
32
+ import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
31
33
  export async function run(_command, args = []) {
34
+ const root = await projectRoot();
32
35
  const doctorFix = flag(args, '--fix');
36
+ if (doctorFix)
37
+ return withSecretPreservationGuard(root, 'doctor-fix', () => runDoctor(args, root, doctorFix));
38
+ return runDoctor(args, root, doctorFix);
39
+ }
40
+ async function runDoctor(args = [], root, doctorFix) {
33
41
  let setupRepair = null;
34
42
  const sksUpdate = doctorFix
35
43
  ? {
@@ -69,7 +77,23 @@ export async function run(_command, args = []) {
69
77
  : await ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) }))
70
78
  };
71
79
  }
72
- const root = await projectRoot();
80
+ const commandAliasCleanup = await runDoctorCommandAliasCleanup({
81
+ root,
82
+ fix: doctorFix
83
+ }).catch((err) => ({
84
+ schema: 'sks.command-alias-cleanup.v1',
85
+ ok: false,
86
+ status: 'blocked',
87
+ root,
88
+ fix: doctorFix,
89
+ report_path: `${root}/.sneakoscope/reports/command-alias-cleanup.json`,
90
+ canonical_command_count: 0,
91
+ legacy_alias_count: 0,
92
+ aliases: [],
93
+ detected: { registered_alias_commands: [], catalog_alias_rows: [], missing_canonical_targets: [] },
94
+ actions: [],
95
+ blockers: [err?.message || String(err)]
96
+ }));
73
97
  const doctorNativeCapabilityRepair = await runDoctorNativeCapabilityRepair({
74
98
  root,
75
99
  fix: doctorFix || flag(args, '--repair-native-capabilities'),
@@ -287,7 +311,7 @@ export async function run(_command, args = []) {
287
311
  const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
288
312
  const result = {
289
313
  schema: 'sks.doctor-status.v1',
290
- ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false),
314
+ ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false,
291
315
  root,
292
316
  node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
293
317
  codex,
@@ -305,6 +329,7 @@ export async function run(_command, args = []) {
305
329
  agent_role_config: agentRoleConfigRepair,
306
330
  zellij_readiness: zellijReadiness,
307
331
  codex_permission_profiles: permissionProfiles,
332
+ command_aliases: commandAliasCleanup,
308
333
  imagegen: {
309
334
  ok: imagegen.auth_readiness?.available_paths?.length > 0,
310
335
  auth_readiness: imagegen.auth_readiness || null,
@@ -323,7 +348,7 @@ export async function run(_command, args = []) {
323
348
  ready,
324
349
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
325
350
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
326
- repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair }
351
+ repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
327
352
  };
328
353
  if (flag(args, '--json')) {
329
354
  printJson(result);
@@ -378,12 +403,24 @@ export async function run(_command, args = []) {
378
403
  console.log(` app screenshot: ${nativeCapabilityStatus(nativeCapabilityRows, 'codex_app_screenshot', 'degraded')}`);
379
404
  console.log(` app handoff: ${nativeCapabilityStatus(nativeCapabilityRows, 'app_handoff', 'unavailable')}`);
380
405
  console.log(` image path exposure: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_path_exposure', 'fallback')}`);
406
+ const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
407
+ if (nativeManualActions.length) {
408
+ console.log(' manual next actions:');
409
+ for (const action of nativeManualActions)
410
+ console.log(` - ${action}`);
411
+ }
381
412
  console.log('SKS Skills:');
382
413
  console.log(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
383
414
  console.log(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
415
+ console.log('SKS Command Aliases:');
416
+ console.log(` status: ${commandAliasCleanup.status || (commandAliasCleanup.ok ? 'clean' : 'blocked')}`);
417
+ console.log(` canonical commands: ${commandAliasCleanup.canonical_command_count ?? 0}`);
418
+ console.log(` compatibility aliases: ${commandAliasCleanup.legacy_alias_count ?? 0}`);
419
+ if (commandAliasCleanup.report_path)
420
+ console.log(` report: ${commandAliasCleanup.report_path}`);
384
421
  console.log('Secret preservation:');
385
422
  console.log(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
386
- console.log(' secret values: redacted');
423
+ console.log(' raw secret values: never recorded');
387
424
  console.log(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
388
425
  console.log('Codex App Harness:');
389
426
  console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
@@ -515,6 +552,13 @@ function nativeCapabilityStatus(rows, id, fallback) {
515
552
  return fallback;
516
553
  if (row.after === 'verified' || row.before === 'verified')
517
554
  return 'verified';
555
+ if (id === 'image_path_exposure') {
556
+ if (row.before === 'degraded' || row.after === 'degraded' || row.repairability === 'doctor-fix')
557
+ return 'fallback';
558
+ return fallback;
559
+ }
560
+ if (id === 'app_handoff')
561
+ return 'unavailable';
518
562
  if (row.repairability === 'manual-required')
519
563
  return 'manual_required';
520
564
  if (row.before === 'degraded' || row.after === 'degraded')
@@ -525,6 +569,12 @@ function nativeCapabilityStatus(rows, id, fallback) {
525
569
  return 'unavailable';
526
570
  return fallback;
527
571
  }
572
+ function uniqueNativeManualActions(rows) {
573
+ return [...new Set(rows
574
+ .filter((row) => row?.repairability === 'manual-required' && row?.after !== 'verified')
575
+ .flatMap((row) => Array.isArray(row.repair_actions) ? row.repair_actions : [])
576
+ .filter((action) => typeof action === 'string' && action.trim()))];
577
+ }
528
578
  function doctorSkillStatus(coreSkills) {
529
579
  if (!coreSkills)
530
580
  return 'drift_detected';
@@ -1,11 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
- import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
4
+ import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
5
5
  import { buildSksCoreSkillManifest } from '../codex-native/core-skill-manifest.js';
6
6
  import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
7
7
  import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
8
8
  const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
9
+ const SKILL_SYNC_LOCK_STALE_AFTER_MS = 30000;
9
10
  export async function syncCodexSksSkills(input) {
10
11
  const root = path.resolve(input.root);
11
12
  const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
@@ -71,6 +72,8 @@ export async function withSkillSyncLock(root, fn) {
71
72
  const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
72
73
  if (code !== 'EEXIST')
73
74
  throw err;
75
+ if (await recoverStaleSkillSyncLock(lockPath))
76
+ continue;
74
77
  if (Date.now() - started > 30000)
75
78
  throw new Error(`Timed out waiting for skill sync lock: ${lockPath}`);
76
79
  await new Promise((resolve) => setTimeout(resolve, 25));
@@ -80,7 +83,8 @@ export async function withSkillSyncLock(root, fn) {
80
83
  await writeJsonAtomic(path.join(lockPath, 'owner.json'), {
81
84
  schema: 'sks.skill-sync-lock.v1',
82
85
  pid: process.pid,
83
- acquired_at: nowIso()
86
+ acquired_at: nowIso(),
87
+ stale_after_ms: SKILL_SYNC_LOCK_STALE_AFTER_MS
84
88
  }).catch(() => undefined);
85
89
  return await fn();
86
90
  }
@@ -88,6 +92,37 @@ export async function withSkillSyncLock(root, fn) {
88
92
  await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
89
93
  }
90
94
  }
95
+ async function recoverStaleSkillSyncLock(lockPath) {
96
+ const ownerPath = path.join(lockPath, 'owner.json');
97
+ const stat = await fs.stat(lockPath).catch(() => null);
98
+ const owner = await readJson(ownerPath, null).catch(() => null);
99
+ const staleAfterMs = Number(owner?.stale_after_ms || SKILL_SYNC_LOCK_STALE_AFTER_MS);
100
+ const acquiredAt = owner?.acquired_at ? Date.parse(owner.acquired_at) : NaN;
101
+ const ageMs = Number.isFinite(acquiredAt) ? Date.now() - acquiredAt : stat ? Date.now() - stat.mtimeMs : 0;
102
+ if (owner?.schema === 'sks.skill-sync-lock.v1' && Number.isFinite(owner.pid)) {
103
+ if (ageMs <= staleAfterMs || pidAlive(Number(owner.pid)))
104
+ return false;
105
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
106
+ return true;
107
+ }
108
+ if (stat && Date.now() - stat.mtimeMs > staleAfterMs) {
109
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+ function pidAlive(pid) {
115
+ if (!Number.isFinite(pid) || pid <= 0)
116
+ return false;
117
+ try {
118
+ process.kill(pid, 0);
119
+ return true;
120
+ }
121
+ catch (err) {
122
+ const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
123
+ return code === 'EPERM';
124
+ }
125
+ }
91
126
  async function listSkillNames(root) {
92
127
  const rows = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
93
128
  return rows.filter((row) => row.isDirectory()).map((row) => row.name).sort();
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { ensureDir, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
- import { buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
4
+ import { CORE_SKILL_TEMPLATE_VERSION, buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
5
5
  import { canonicalSkillName } from './skill-name-canonicalizer.js';
6
6
  export async function syncCoreSkillsIntegrity(input) {
7
7
  const root = path.resolve(input.root);
@@ -69,7 +69,12 @@ export async function syncCoreSkillsIntegrity(input) {
69
69
  root,
70
70
  apply,
71
71
  skills_root: skillsRoot,
72
+ template_version: CORE_SKILL_TEMPLATE_VERSION,
72
73
  manifest_sha256: sha256(JSON.stringify(manifest.skills.map((skill) => [skill.canonical_name, skill.content_sha256]))),
74
+ drift_detected_count: rows.filter((row) => row.action === 'restore-corrupted-managed-copy' || row.action === 'skip-user-authored').length,
75
+ restored_count: restored.length,
76
+ installed_count: installed.length,
77
+ user_collision_count: skippedUserAuthored.length,
73
78
  rows,
74
79
  installed,
75
80
  restored,
@@ -1,6 +1,6 @@
1
1
  import { PACKAGE_VERSION, nowIso, sha256 } from '../fsx.js';
2
2
  import { canonicalSkillName } from './skill-name-canonicalizer.js';
3
- export const CORE_SKILL_TEMPLATE_VERSION = '3.1.8-core-skill-template.v1';
3
+ export const CORE_SKILL_TEMPLATE_VERSION = 'sks-core-skill-template.v1';
4
4
  export const CORE_SKILL_MANAGED_BEGIN = '<!-- BEGIN SKS IMMUTABLE CORE SKILL -->';
5
5
  export const CORE_SKILL_MANAGED_END = '<!-- END SKS IMMUTABLE CORE SKILL -->';
6
6
  const CORE_SKILL_DEFINITIONS = [
@@ -1,22 +1,13 @@
1
+ import fs from 'node:fs/promises';
1
2
  import path from 'node:path';
2
- import { writeJsonAtomic } from '../fsx.js';
3
+ import { ensureDir, readJson, writeJsonAtomic } from '../fsx.js';
3
4
  import { buildNativeCapabilityRepairMatrix } from './native-capability-repair-matrix.js';
4
5
  export async function postcheckNativeCapabilities(input) {
5
6
  const root = path.resolve(input.root);
6
- const matrix = input.matrix || await buildNativeCapabilityRepairMatrix({ root, fixture: input.fixture || false, reportPath: null });
7
- const capabilities = matrix.capabilities.map((state) => {
8
- const verifiedAfterRepair = state.repairability === 'auto' || state.repairability === 'doctor-fix';
9
- if (state.id === 'computer_use' && process.env.SKS_COMPUTER_USE_CAPABILITY !== 'verified') {
10
- return { ...state, after: 'unknown', blockers: ['computer_use_os_permission_or_capability_unknown'] };
11
- }
12
- if (state.id === 'chrome_web_review' && process.env.SKS_CHROME_EXTENSION_READY !== '1' && input.fixture !== 'all-repairable') {
13
- return { ...state, after: 'unknown', blockers: ['codex_chrome_extension_readiness_not_verified'] };
14
- }
15
- if (state.blockers.length === 0 || verifiedAfterRepair)
16
- return { ...state, after: state.repairability === 'manual-required' ? 'unknown' : 'verified', blockers: state.repairability === 'manual-required' ? state.blockers : [] };
17
- return { ...state, after: 'blocked' };
18
- });
19
- const blockers = capabilities.flatMap((state) => state.after === 'verified' ? [] : state.blockers);
7
+ const fixture = input.fixture || false;
8
+ const matrix = input.matrix || await buildNativeCapabilityRepairMatrix({ root, fixture, reportPath: null });
9
+ const capabilities = await Promise.all(matrix.capabilities.map((state) => postcheckCapability(root, state, fixture)));
10
+ const blockers = capabilities.flatMap((state) => state.after === 'verified' || state.after === 'degraded' ? [] : state.blockers);
20
11
  const checked = {
21
12
  ...matrix,
22
13
  generated_at: new Date().toISOString(),
@@ -32,4 +23,141 @@ export async function postcheckNativeCapabilities(input) {
32
23
  await writeJsonAtomic(reportPath, checked).catch(() => undefined);
33
24
  return checked;
34
25
  }
26
+ async function postcheckCapability(root, state, fixture) {
27
+ if (state.id === 'image_generation')
28
+ return postcheckImageGeneration(state, fixture);
29
+ if (state.id === 'image_followup_edit')
30
+ return postcheckImageFollowupEdit(root, state);
31
+ if (state.id === 'computer_use')
32
+ return postcheckComputerUse(state, fixture);
33
+ if (state.id === 'chrome_web_review')
34
+ return postcheckChromeWebReview(state, fixture);
35
+ if (state.id === 'codex_app_screenshot')
36
+ return postcheckAppScreenshot(root, state);
37
+ if (state.id === 'app_handoff')
38
+ return postcheckAppHandoff(state, fixture);
39
+ if (state.id === 'image_path_exposure')
40
+ return postcheckImagePathExposure(root, state, fixture);
41
+ if (state.id === 'saved_artifact_path_contract')
42
+ return postcheckSavedArtifactPathContract(root, state);
43
+ return { ...state, after: 'blocked', blockers: [...state.blockers, `unknown_capability:${state.id}`] };
44
+ }
45
+ function postcheckImageGeneration(state, fixture) {
46
+ if (fixture === 'all-repairable' || state.before === 'verified')
47
+ return verified(state);
48
+ return {
49
+ ...state,
50
+ after: 'unknown',
51
+ blockers: ['imagegen_auth_or_codex_app_builtin_missing'],
52
+ warnings: [...new Set([...state.warnings, 'image_generation_not_verified_without_real_capability'])]
53
+ };
54
+ }
55
+ async function postcheckImageFollowupEdit(root, state) {
56
+ const contract = await validateSavedArtifactPathContract(root);
57
+ if (!contract.ok)
58
+ return { ...state, after: 'blocked', blockers: contract.blockers };
59
+ const sample = path.join(contract.imageArtifacts, 'postcheck-followup-sample.txt');
60
+ if (!(await writeReadSample(sample)))
61
+ return { ...state, after: 'blocked', blockers: ['image_followup_sample_artifact_unwritable'] };
62
+ return verified(state);
63
+ }
64
+ function postcheckComputerUse(state, _fixture) {
65
+ if (process.env.SKS_COMPUTER_USE_CAPABILITY === 'verified')
66
+ return verified(state);
67
+ return {
68
+ ...state,
69
+ after: 'unknown',
70
+ blockers: ['computer_use_os_permission_or_capability_unknown'],
71
+ warnings: [...new Set([...state.warnings, 'manual_os_permission_required'])]
72
+ };
73
+ }
74
+ function postcheckChromeWebReview(state, fixture) {
75
+ if (fixture === 'all-repairable' || process.env.SKS_CHROME_EXTENSION_READY === '1')
76
+ return verified(state);
77
+ return {
78
+ ...state,
79
+ after: 'unknown',
80
+ blockers: ['codex_chrome_extension_readiness_not_verified'],
81
+ warnings: [...new Set([...state.warnings, 'manual_chrome_extension_setup_required'])]
82
+ };
83
+ }
84
+ async function postcheckAppScreenshot(root, state) {
85
+ const dir = path.join(root, '.sneakoscope', 'app-screenshots');
86
+ const registry = path.join(dir, 'screenshot-registry.json');
87
+ if (!(await writeReadSample(path.join(dir, 'postcheck-screenshot-sample.txt')))) {
88
+ return { ...state, after: 'blocked', blockers: ['app_screenshot_directory_unwritable'] };
89
+ }
90
+ await writeJsonAtomic(registry, { schema: 'sks.app-screenshot-registry.v1', generated_at: new Date().toISOString(), screenshots: [] }).catch(() => undefined);
91
+ const json = await readJson(registry, {}).catch(() => ({}));
92
+ if (json.schema !== 'sks.app-screenshot-registry.v1')
93
+ return { ...state, after: 'blocked', blockers: ['app_screenshot_registry_invalid'] };
94
+ return verified(state);
95
+ }
96
+ function postcheckAppHandoff(state, fixture) {
97
+ if (fixture === 'all-repairable' || state.before === 'verified')
98
+ return verified(state);
99
+ return {
100
+ ...state,
101
+ after: 'unknown',
102
+ blockers: ['codex_app_handoff_not_verified'],
103
+ warnings: [...new Set([...state.warnings, 'manual_app_handoff_approval_required'])]
104
+ };
105
+ }
106
+ async function postcheckImagePathExposure(root, state, fixture) {
107
+ if (fixture === 'all-repairable' || state.before === 'verified')
108
+ return verified(state);
109
+ const contract = await validateSavedArtifactPathContract(root);
110
+ if (contract.ok) {
111
+ return {
112
+ ...state,
113
+ after: 'degraded',
114
+ blockers: [],
115
+ warnings: [...new Set([...state.warnings, 'using_saved_artifact_path_contract_fallback'])]
116
+ };
117
+ }
118
+ return { ...state, after: 'blocked', blockers: ['image_path_exposure_missing_without_fallback_contract', ...contract.blockers] };
119
+ }
120
+ async function postcheckSavedArtifactPathContract(root, state) {
121
+ const contract = await validateSavedArtifactPathContract(root);
122
+ if (!contract.ok)
123
+ return { ...state, after: 'blocked', blockers: contract.blockers };
124
+ if (!(await writeReadSample(path.join(contract.imageArtifacts, 'postcheck-contract-image.txt'))))
125
+ return { ...state, after: 'blocked', blockers: ['image_artifacts_directory_unwritable'] };
126
+ if (!(await writeReadSample(path.join(contract.appScreenshots, 'postcheck-contract-screenshot.txt'))))
127
+ return { ...state, after: 'blocked', blockers: ['app_screenshots_directory_unwritable'] };
128
+ return verified(state);
129
+ }
130
+ function verified(state) {
131
+ return { ...state, after: 'verified', blockers: [] };
132
+ }
133
+ async function validateSavedArtifactPathContract(root) {
134
+ const contractPath = path.join(root, '.sneakoscope', 'reports', 'saved-artifact-path-contract.json');
135
+ const contract = await readJson(contractPath, null).catch(() => null);
136
+ const imageArtifacts = String(contract?.image_artifacts || path.join(root, '.sneakoscope', 'image-artifacts'));
137
+ const appScreenshots = String(contract?.app_screenshots || path.join(root, '.sneakoscope', 'app-screenshots'));
138
+ const blockers = [];
139
+ if (contract?.schema !== 'sks.saved-artifact-path-contract.v1')
140
+ blockers.push('saved_artifact_path_contract_schema_invalid');
141
+ for (const dir of [imageArtifacts, appScreenshots]) {
142
+ try {
143
+ await ensureDir(dir);
144
+ await fs.access(dir);
145
+ }
146
+ catch {
147
+ blockers.push(`directory_unwritable:${path.basename(dir)}`);
148
+ }
149
+ }
150
+ return { ok: blockers.length === 0, imageArtifacts, appScreenshots, blockers };
151
+ }
152
+ async function writeReadSample(file) {
153
+ try {
154
+ await ensureDir(path.dirname(file));
155
+ await fs.writeFile(file, 'sks-native-capability-postcheck\n', 'utf8');
156
+ const text = await fs.readFile(file, 'utf8');
157
+ return text === 'sks-native-capability-postcheck\n';
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ }
35
163
  //# sourceMappingURL=native-capability-postcheck.js.map
@@ -109,7 +109,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
109
109
  warnings: envVerified ? [] : ['manual_os_permission_required']
110
110
  };
111
111
  }
112
- const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1' || featureOk(nativeFeatureMatrix, 'plugin_json');
112
+ const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1';
113
113
  return {
114
114
  id,
115
115
  before: chromeReady ? 'verified' : 'unknown',