sneakoscope 3.1.6 → 3.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +2 -2
  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/commands/codex-app.js +20 -2
  8. package/dist/commands/codex-native.js +18 -2
  9. package/dist/commands/doctor.js +36 -12
  10. package/dist/core/codex-app/codex-agent-role-sync.js +9 -5
  11. package/dist/core/codex-app/codex-init-deep.js +6 -2
  12. package/dist/core/codex-app/codex-skill-sync.js +4 -2
  13. package/dist/core/codex-control/codex-0138-capability.js +5 -2
  14. package/dist/core/codex-native/codex-native-feature-broker.js +74 -6
  15. package/dist/core/codex-native/codex-native-pattern-analysis.js +14 -2
  16. package/dist/core/codex-native/codex-native-reference-cache.js +98 -0
  17. package/dist/core/codex-native/codex-native-reference-source.js +50 -11
  18. package/dist/core/codex-native/codex-native-repair-transaction.js +150 -0
  19. package/dist/core/codex-plugins/codex-plugin-json.js +5 -2
  20. package/dist/core/commands/mad-sks-command.js +16 -0
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/loops/loop-planner.js +1 -1
  23. package/dist/core/loops/loop-worker-prompts.js +2 -0
  24. package/dist/core/loops/loop-worker-runtime.js +8 -1
  25. package/dist/core/version.js +1 -1
  26. package/dist/scripts/codex-native-runtime-e2e-fixture.js +75 -0
  27. package/dist/scripts/loop-worker-fixture-child.js +2 -1
  28. package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
  29. package/dist/scripts/sks-3-1-6-directive-check-lib.js +2 -2
  30. package/dist/scripts/sks-3-1-7-directive-check-lib.js +58 -0
  31. package/package.json +13 -2
package/README.md CHANGED
@@ -35,7 +35,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.1.6** removes external reference branding from user-visible release surfaces and routes Codex-native feature decisions through one broker for Loop, QA, Research, Image, MAD, and Doctor.
38
+ SKS **3.1.7** closes Codex-native runtime-proof gaps with real routing blackboxes, neutral reference cache refresh, read-only broker checks, explicit managed-asset repair transactions, and final brand-neutral artifact scans.
39
39
 
40
40
  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.
41
41
 
@@ -79,7 +79,7 @@ npm run runtime:ts-python-boundary
79
79
  npm run codex-control:all-pipelines
80
80
  ```
81
81
 
82
- Broader release checks still live behind `npm run release:check`. Detailed release history is in [CHANGELOG.md](CHANGELOG.md), and release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
82
+ Change-aware release checks live behind `npm run release:check`; publish-authorizing full DAG checks use `npm run release:check:full`. Detailed release history is in [CHANGELOG.md](CHANGELOG.md), and release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
83
83
 
84
84
  ## 🍥 Parallelism, UX, And Integrations
85
85
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.6"
79
+ version = "3.1.7"
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.6"
3
+ version = "3.1.7"
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.6"),
7
+ Some("--version") => println!("sks-rs 3.1.7"),
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.6",
5
- "source_digest": "98f9f4c6a64ddd6b1d43f757e16358768f24b27cd9c3285eab0bdacce7cbf5d6",
6
- "source_file_count": 2543,
7
- "built_at_source_time": 1781450666248
4
+ "package_version": "3.1.7",
5
+ "source_digest": "8927dd50bf67d1f2ce1df5744a3b21645a870f8f9182fe7b86a92affedf416aa",
6
+ "source_file_count": 2558,
7
+ "built_at_source_time": 1781491235171
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.6';
2
+ const FAST_PACKAGE_VERSION = '3.1.7';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -9,12 +9,15 @@ import { syncCodexAgentRoles } from '../core/codex-app/codex-agent-role-sync.js'
9
9
  import { runCodexInitDeep } from '../core/codex-app/codex-init-deep.js';
10
10
  import { buildCodexHookLifecycle } from '../core/codex-app/codex-hook-lifecycle.js';
11
11
  import { resolveCodexAppExecutionProfile } from '../core/codex-app/codex-app-execution-profile.js';
12
+ import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
12
13
  export async function run(_command, args = []) {
13
14
  const action = args[0] || 'check';
14
15
  if (action === 'remote-control' || action === 'remote')
15
16
  return codexAppRemoteControlCommand(args.slice(1));
16
- if (action === 'harness-matrix')
17
- return printCodexAppResult(args, await buildCodexAppHarnessMatrix({ root: await sksRoot(), applyRepairs: flag(args, '--fix') || flag(args, '--apply') }));
17
+ if (action === 'harness-matrix') {
18
+ const root = await sksRoot();
19
+ return printCodexAppResult(args, await maybeRepairThenReadOnlyHarness(args, root));
20
+ }
18
21
  if (action === 'skill-sync')
19
22
  return printCodexAppResult(args, await syncCodexSksSkills({ root: await sksRoot(), apply: flag(args, '--apply') || flag(args, '--fix') }));
20
23
  if (action === 'agent-role-sync')
@@ -103,4 +106,19 @@ function printCodexAppResult(args = [], result) {
103
106
  if (result?.ok === false)
104
107
  process.exitCode = 1;
105
108
  }
109
+ async function maybeRepairThenReadOnlyHarness(args = [], root) {
110
+ const wantsRepair = flag(args, '--fix') || flag(args, '--apply') || flag(args, '--repair-codex-native');
111
+ if (!wantsRepair)
112
+ return buildCodexAppHarnessMatrix({ root, mode: 'read-only' });
113
+ const repair = await repairCodexNativeManagedAssets({ root, requestedBy: 'manual', yes: flag(args, '--yes') });
114
+ const matrix = await buildCodexAppHarnessMatrix({ root, mode: 'read-only' });
115
+ return {
116
+ schema: 'sks.codex-app-harness-read-repair-split.v1',
117
+ ok: repair.ok && matrix?.ok !== false,
118
+ repair,
119
+ matrix,
120
+ blockers: [...(repair.blockers || []), ...(matrix?.blockers || [])],
121
+ warnings: [...(repair.warnings || []), 'harness_probe_after_explicit_repair_transaction']
122
+ };
123
+ }
106
124
  //# sourceMappingURL=codex-app.js.map
@@ -12,14 +12,15 @@ import { resolveCodexNativeInvocationPlan } from '../core/codex-native/codex-nat
12
12
  import { buildCodexNativeInteropPolicy } from '../core/codex-native/codex-native-interop-policy.js';
13
13
  import { analyzeCodexNativeReferenceSource } from '../core/codex-native/codex-native-reference-evidence.js';
14
14
  import { writeCodexNativePatternAnalysis } from '../core/codex-native/codex-native-pattern-analysis.js';
15
+ import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
15
16
  export async function run(_command, args = []) {
16
17
  const root = await sksRoot();
17
18
  const action = String(args[0] || 'status');
18
19
  if (action === 'status' || action === 'check' || action === 'feature-broker' || action === 'feature-matrix') {
19
- return printCodexNativeResult(args, await buildCodexNativeFeatureMatrix({ root, applyRepairs: flag(args, '--fix') || flag(args, '--apply') }));
20
+ return printCodexNativeResult(args, await maybeRepairThenReadOnlyMatrix(args, root, () => buildCodexNativeFeatureMatrix({ root, mode: 'read-only' })));
20
21
  }
21
22
  if (action === 'harness-matrix' || action === 'harness-compat') {
22
- return printCodexNativeResult(args, await buildCodexAppHarnessMatrix({ root, applyRepairs: flag(args, '--fix') || flag(args, '--apply') }));
23
+ return printCodexNativeResult(args, await maybeRepairThenReadOnlyMatrix(args, root, () => buildCodexAppHarnessMatrix({ root, mode: 'read-only' })));
23
24
  }
24
25
  if (action === 'skill-sync')
25
26
  return printCodexNativeResult(args, await syncCodexSksSkills({ root, apply: flag(args, '--apply') || flag(args, '--fix') }));
@@ -46,6 +47,21 @@ export async function run(_command, args = []) {
46
47
  console.error('Usage: sks codex-native status|feature-broker|harness-compat|skill-sync|agent-role-sync|init-deep|hook-lifecycle|execution-profile|interop-policy|reference-evidence|pattern-analysis|invocation-plan [--json]');
47
48
  process.exitCode = 1;
48
49
  }
50
+ async function maybeRepairThenReadOnlyMatrix(args = [], root, matrixFn) {
51
+ const wantsRepair = flag(args, '--fix') || flag(args, '--apply') || flag(args, '--repair-codex-native');
52
+ if (!wantsRepair)
53
+ return matrixFn();
54
+ const repair = await repairCodexNativeManagedAssets({ root, requestedBy: 'manual', yes: flag(args, '--yes') });
55
+ const matrix = await matrixFn();
56
+ return {
57
+ schema: 'sks.codex-native-read-repair-split.v1',
58
+ ok: repair.ok && matrix?.ok !== false,
59
+ repair,
60
+ matrix,
61
+ blockers: [...(repair.blockers || []), ...(matrix?.blockers || [])],
62
+ warnings: [...(repair.warnings || []), 'matrix_probe_after_explicit_repair_transaction']
63
+ };
64
+ }
49
65
  function printCodexNativeResult(args = [], result) {
50
66
  if (flag(args, '--json')) {
51
67
  printJson(result);
@@ -26,6 +26,7 @@ import { writeMcpPluginInventoryArtifacts } from '../core/mcp/mcp-plugin-invento
26
26
  import { runDoctorZellijRepair, doctorZellijRepairConsoleLine } from '../core/doctor/doctor-zellij-repair.js';
27
27
  import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
28
28
  import { buildCodexNativeFeatureMatrix } from '../core/codex-native/codex-native-feature-broker.js';
29
+ import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
29
30
  export async function run(_command, args = []) {
30
31
  const doctorFix = flag(args, '--fix');
31
32
  let setupRepair = null;
@@ -198,7 +199,23 @@ export async function run(_command, args = []) {
198
199
  const mcpPluginInventory = pluginInventory?.report
199
200
  ? await writeMcpPluginInventoryArtifacts(root, { inventory: pluginInventory.report }).catch((err) => ({ error: err?.message || String(err), candidates: null }))
200
201
  : null;
201
- const codexAppHarnessMatrix = await buildCodexAppHarnessMatrix({ root, applyRepairs: doctorFix }).catch((err) => ({
202
+ const repairCodexNative = doctorFix && flag(args, '--repair-codex-native');
203
+ const codexNativeRepair = repairCodexNative
204
+ ? await repairCodexNativeManagedAssets({
205
+ root,
206
+ requestedBy: 'doctor --fix',
207
+ yes: flag(args, '--yes') || flag(args, '-y')
208
+ }).catch((err) => ({
209
+ schema: 'sks.codex-native-repair-transaction.v1',
210
+ ok: false,
211
+ generated_at: new Date().toISOString(),
212
+ requested_by: 'doctor --fix',
213
+ repaired: [],
214
+ blockers: [err?.message || String(err)],
215
+ warnings: []
216
+ }))
217
+ : null;
218
+ const codexAppHarnessMatrix = await buildCodexAppHarnessMatrix({ root, mode: 'read-only' }).catch((err) => ({
202
219
  schema: 'sks.codex-app-harness-matrix.v1',
203
220
  ok: false,
204
221
  codex_cli: { available: false, version: null },
@@ -207,7 +224,7 @@ export async function run(_command, args = []) {
207
224
  blockers: [err?.message || String(err)],
208
225
  warnings: []
209
226
  }));
210
- const codexNativeFeatureMatrix = await buildCodexNativeFeatureMatrix({ root, applyRepairs: doctorFix }).catch((err) => ({
227
+ const codexNativeFeatureMatrix = await buildCodexNativeFeatureMatrix({ root, mode: 'read-only' }).catch((err) => ({
211
228
  schema: 'sks.codex-native-feature-matrix.v1',
212
229
  ok: false,
213
230
  codex_cli: { available: Boolean(codex.bin), version: codex.version || null, bin: codex.bin || null },
@@ -288,7 +305,7 @@ export async function run(_command, args = []) {
288
305
  ready,
289
306
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
290
307
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
291
- repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair }
308
+ 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 }
292
309
  };
293
310
  if (flag(args, '--json')) {
294
311
  printJson(result);
@@ -324,8 +341,14 @@ export async function run(_command, args = []) {
324
341
  console.log(` Loop Mesh: ${runtimeReadiness.loop_mesh}`);
325
342
  console.log(` QA Visual: ${runtimeReadiness.qa_visual}`);
326
343
  console.log(` Research Sources: ${runtimeReadiness.research_sources}`);
327
- for (const action of runtimeReadiness.repair_actions)
328
- console.log(` Repair: ${action}`);
344
+ console.log(` Image Follow-up: ${runtimeReadiness.image_followup}`);
345
+ for (const note of runtimeReadiness.notes)
346
+ console.log(` ${note}`);
347
+ if (runtimeReadiness.repair_actions.length) {
348
+ console.log('Repair actions:');
349
+ for (const action of runtimeReadiness.repair_actions)
350
+ console.log(` - ${action}`);
351
+ }
329
352
  console.log('Codex App Harness:');
330
353
  console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
331
354
  console.log(` hook approval: ${codexAppHarnessMatrix.app_features?.hook_approval_state_detectable ? 'ok' : 'unknown'}`);
@@ -424,14 +447,14 @@ function buildRuntimeReadiness(zellijReadiness, matrix) {
424
447
  ? 'ok'
425
448
  : matrix?.codex_cli?.available ? 'degraded' : 'blocked';
426
449
  const repairActions = [];
427
- if (zellijStatus === 'repair_required')
428
- repairActions.push('sks doctor --fix --yes');
450
+ if (zellijStatus !== 'ok') {
451
+ repairActions.push('Zellij: sks doctor --fix --yes');
452
+ repairActions.push('Homebrew + Zellij: sks doctor --fix --install-homebrew --yes');
453
+ }
429
454
  if (codexNative !== 'ok')
430
- repairActions.push('sks doctor --fix --repair-codex-native --yes');
455
+ repairActions.push('Codex Native managed assets: sks doctor --fix --repair-codex-native --yes');
431
456
  if (matrix?.features?.project_memory?.ok !== true)
432
- repairActions.push('sks codex-native init-deep --apply --directory-local');
433
- if (hookPolicy !== 'approved-only')
434
- repairActions.push('hook-derived evidence will not count until Codex hook approval is approved');
457
+ repairActions.push('Project memory: sks codex-native init-deep --apply --directory-local');
435
458
  return {
436
459
  schema: 'sks.runtime-readiness-story.v1',
437
460
  zellij: zellijStatus,
@@ -439,6 +462,7 @@ function buildRuntimeReadiness(zellijReadiness, matrix) {
439
462
  loop_mesh: agentStrategy === 'agent_type' ? 'ok' : 'fallback',
440
463
  qa_visual: defaults.qa_visual_review_strategy || 'blocked',
441
464
  research_sources: defaults.research_source_strategy || 'local-files',
465
+ image_followup: defaults.image_followup_strategy || 'blocked',
442
466
  hook_evidence_policy: hookPolicy,
443
467
  agent_role_strategy: agentStrategy,
444
468
  notes: [
@@ -446,7 +470,7 @@ function buildRuntimeReadiness(zellijReadiness, matrix) {
446
470
  ...(hookPolicy !== 'approved-only' ? ['hook-derived evidence will not count'] : []),
447
471
  ...(agentStrategy !== 'agent_type' ? ['message-role fallback active'] : [])
448
472
  ],
449
- repair_actions: repairActions
473
+ repair_actions: [...new Set(repairActions)]
450
474
  };
451
475
  }
452
476
  // Assemble the explicit Zellij readiness block for `doctor --json` from the
@@ -56,7 +56,7 @@ export async function syncCodexAgentRoles(input) {
56
56
  for (const role of DIRECTIVE_ROLES) {
57
57
  const file = path.join(targetDir, `${role}.toml`);
58
58
  const current = await fs.readFile(file, 'utf8').catch(() => '');
59
- if (current && !current.includes('SKS managed 3.1.4 directive role') && !current.includes('SKS managed 3.1.5 directive role') && !current.includes('SKS managed 3.1.6 directive role') && !current.includes('SKS managed 3.1.6 bounded role'))
59
+ if (current && !current.includes('SKS managed 3.1.4 directive role') && !current.includes('SKS managed 3.1.5 directive role') && !current.includes('SKS managed 3.1.6 directive role') && !current.includes('SKS managed 3.1.6 bounded role') && !current.includes('SKS managed 3.1.7 directive role'))
60
60
  continue;
61
61
  await writeTextAtomic(file, roleToml(role, rolePayloads[role]));
62
62
  created.push(file);
@@ -69,6 +69,9 @@ export async function syncCodexAgentRoles(input) {
69
69
  apply: input.apply === true,
70
70
  agent_type_supported: agentTypeProbe.supported,
71
71
  fallback: agentTypeProbe.supported ? 'agent_type' : 'message-role',
72
+ strategy: agentTypeProbe.supported ? 'agent_type' : 'message-role',
73
+ probe_artifact_path: '.sneakoscope/reports/codex-agent-type-probe.json',
74
+ clobbered_user_roles: false,
72
75
  codex_home: codexHome,
73
76
  directive_roles: DIRECTIVE_ROLES,
74
77
  role_payloads: rolePayloads,
@@ -86,21 +89,22 @@ function roleToml(role, payload) {
86
89
  : `message_role_prefix = "${escapeToml(payload?.message_role_prefix || `Role: ${role}.`)}"`;
87
90
  return [
88
91
  `name = "${role}"`,
89
- `description = "SKS managed 3.1.6 directive role: ${role}"`,
92
+ `description = "SKS managed 3.1.7 directive role: ${role}"`,
90
93
  strategyLine,
91
94
  'model_reasoning_effort = "medium"',
92
95
  role.includes('implementer') ? 'sandbox_mode = "workspace-write"' : 'sandbox_mode = "read-only"',
93
96
  'approval_policy = "never"',
94
97
  'developer_instructions = """',
95
- `You are ${role}. SKS managed 3.1.6 directive role with bounded ownership.`,
98
+ `You are ${role}. SKS managed 3.1.7 directive role with bounded ownership.`,
96
99
  'Bounded ownership: use only the assigned owner files/directories and treat memory as guidance, not permission.',
97
100
  role.includes('implementer') ? 'Maker/checker separation: implementer may patch only owner scope and cannot self-approve.' : 'Maker/checker separation: checker is read-only and must reject missing gates or missing proof artifacts.',
101
+ role.includes('implementer') ? 'Allowed sandbox: workspace-write only within assigned owner scope.' : 'Allowed sandbox: read-only; checker roles cannot mutate.',
98
102
  role.includes('release') ? 'Release verifier: verify version truth, release DAG coverage, package scripts, packlist, and changelog evidence.' : '',
99
103
  role.includes('zellij') ? 'UI/Zellij verifier: inspect readiness status, headless fallback, repair_required, pane proof, and slot telemetry without mutating unrelated UI state.' : '',
100
104
  role.includes('codex') ? 'Codex native verifier: inspect hook approval, agent_type, skill sync, plugin inventory, MCP candidates, and invocation plan artifacts.' : '',
101
- 'Side effects: no destructive shell, package publish, global config mutation, database mutation, or external service write unless the sealed route contract explicitly allows it.',
105
+ 'Side-effect restrictions: no destructive shell, package publish, global config mutation, database mutation, or external service write unless the sealed route contract explicitly allows it.',
102
106
  'Required proof artifacts: cite concrete repo paths, command outputs, and route-local JSON proof before claiming completion.',
103
- 'Final arbiter: parent integration owns final acceptance; this role supplies evidence and cannot override missing gates.',
107
+ 'Final arbiter constraints: parent integration owns final acceptance; this role supplies evidence and cannot override missing gates.',
104
108
  `Execution role strategy: ${payload?.strategy || 'message-role'}. Probe: ${payload?.probe_artifact_path || '.sneakoscope/reports/codex-agent-type-probe.json'}.`,
105
109
  '"""',
106
110
  ''
@@ -28,7 +28,7 @@ export async function runCodexInitDeep(input = {}) {
28
28
  }
29
29
  if (existing.trim()) {
30
30
  const beforeHash = hashText(existing);
31
- const backup = `${agentsPath}.sks-backup-${beforeHash.slice(0, 12)}-${Date.now()}`;
31
+ const backup = `${agentsPath}.sks-backup-${Date.now()}-${beforeHash.slice(0, 12)}`;
32
32
  await fs.copyFile(agentsPath, backup);
33
33
  directoryLocalAgents.backup_paths.push(path.relative(root, backup));
34
34
  directoryLocalAgents.backups_created += 1;
@@ -83,9 +83,10 @@ async function pruneBackups(root, agentsPath, keep) {
83
83
  return [];
84
84
  const dir = path.dirname(agentsPath);
85
85
  const base = path.basename(agentsPath);
86
+ const backupPattern = new RegExp(`^${escapeRegExp(base)}\\.sks-backup-\\d{13}-[0-9a-f]{8,12}$`);
86
87
  const rows = await fs.readdir(dir).catch(() => []);
87
88
  const backups = rows
88
- .filter((name) => name.startsWith(`${base}.sks-backup-`))
89
+ .filter((name) => backupPattern.test(name))
89
90
  .map((name) => path.join(dir, name))
90
91
  .sort();
91
92
  const remove = backups.slice(0, Math.max(0, backups.length - keep));
@@ -99,6 +100,9 @@ async function pruneBackups(root, agentsPath, keep) {
99
100
  await guardedRm(guard, file, { force: true }).catch(() => undefined);
100
101
  return remove;
101
102
  }
103
+ function escapeRegExp(value) {
104
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
105
+ }
102
106
  function hashText(text) {
103
107
  let hash = 2166136261;
104
108
  for (let index = 0; index < text.length; index += 1) {
@@ -56,7 +56,9 @@ export async function syncCodexSksSkills(input) {
56
56
  interop: {
57
57
  mode: 'coexist',
58
58
  clobbered_external_routes: false,
59
- clobbered_user_skills: false
59
+ clobbered_user_skills: false,
60
+ skipped_user_skills: skipped,
61
+ managed_skills: desired
60
62
  },
61
63
  blockers: []
62
64
  };
@@ -84,7 +86,7 @@ function skillContent(name) {
84
86
  `Use when: ${profile.when}`,
85
87
  `Route: ${profile.command}`,
86
88
  `Evidence: ${profile.evidence}`,
87
- 'Safety: keep route state bounded, preserve user and external route assets, and stop on hard blockers instead of fabricating fallback behavior.',
89
+ 'Safety rules: keep route state bounded, preserve user and external route assets, and stop on hard blockers instead of fabricating fallback behavior.',
88
90
  'Proof paths: write the route-local mission artifact named in Evidence before claiming completion.',
89
91
  'Failure recovery: if a proof path cannot be produced, record the blocker and continue only when the selected SKS route has another allowed evidence path.',
90
92
  `Fallback: ${profile.fallback}`,
@@ -22,6 +22,7 @@ export async function detectCodex0138Capability(input = {}) {
22
22
  };
23
23
  const pluginJsonOk = atLeast138 && (probeMode === 'version-only' || featureProbeResults.plugin_json !== 'failed');
24
24
  const appHandoffOk = atLeast138 && (probeMode === 'version-only' || featureProbeResults.app_handoff_platform === 'passed');
25
+ const imagePathExposureOk = atLeast138 && process.env.SKS_CODEX_0138_FAKE_IMAGE_PATH_FAIL !== '1';
25
26
  const blockers = [
26
27
  ...(!codexBin ? ['codex_cli_missing'] : []),
27
28
  ...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features']),
@@ -36,7 +37,7 @@ export async function detectCodex0138Capability(input = {}) {
36
37
  parsed_version: parsed,
37
38
  supports_app_handoff: appHandoffOk,
38
39
  supports_plugin_json: pluginJsonOk,
39
- supports_image_path_exposure: atLeast138,
40
+ supports_image_path_exposure: imagePathExposureOk,
40
41
  supports_model_defined_efforts: atLeast138,
41
42
  supports_app_server_token_usage: atLeast138,
42
43
  supports_v2_pat_auth: atLeast138,
@@ -78,7 +79,9 @@ async function probeCodex0138Features(codexBin, opts = {}) {
78
79
  if (opts.fake) {
79
80
  return {
80
81
  plugin_json: process.env.SKS_CODEX_0138_FAKE_PLUGIN_JSON_FAIL === '1' ? 'failed' : 'passed',
81
- app_handoff_platform: process.platform === 'darwin' || process.platform === 'win32' ? 'passed' : 'failed',
82
+ app_handoff_platform: process.env.SKS_CODEX_0138_FAKE_APP_HANDOFF_FAIL === '1'
83
+ ? 'failed'
84
+ : process.platform === 'darwin' || process.platform === 'win32' ? 'passed' : 'failed',
82
85
  image_path_exposure_contract: 'sks-enforced'
83
86
  };
84
87
  }
@@ -1,10 +1,9 @@
1
1
  import path from 'node:path';
2
+ import fs from 'node:fs/promises';
2
3
  import { findCodexBinary } from '../codex-adapter.js';
3
4
  import { codexAppIntegrationStatus } from '../codex-app.js';
4
- import { syncCodexAgentRoles } from '../codex-app/codex-agent-role-sync.js';
5
5
  import { probeCodexAgentTypeSupport } from '../codex-app/codex-agent-type-probe.js';
6
6
  import { probeCodexHookApprovalState } from '../codex-app/codex-hook-approval-probe.js';
7
- import { syncCodexSksSkills } from '../codex-app/codex-skill-sync.js';
8
7
  import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
9
8
  import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
10
9
  import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
@@ -12,8 +11,13 @@ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
12
11
  import { buildMcpPluginServerCandidates } from '../mcp/mcp-plugin-inventory.js';
13
12
  import { codexNativeFeatureState, computeCodexNativeInvocationDefaults } from './codex-native-feature-matrix.js';
14
13
  const REPORT_PATH = '.sneakoscope/reports/codex-native-feature-matrix.json';
14
+ const REQUIRED_SKILL_NAMES = ['loop', 'naruto', 'qa-loop', 'research', 'dfix', 'image-ux-review', 'computer-use', 'init-deep'];
15
+ const REQUIRED_AGENT_ROLES = ['sks-explorer', 'sks-planner', 'sks-implementer', 'sks-checker', 'sks-release-verifier', 'sks-zellij-ui-verifier', 'sks-codex-probe-verifier'];
15
16
  export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd() }) {
16
17
  const root = path.resolve(input.root || process.cwd());
18
+ const deprecatedApplyRepairs = input.applyRepairs === true;
19
+ const mode = input.mode || (deprecatedApplyRepairs || input.repairManagedAssets === true ? 'repair' : 'read-only');
20
+ const repairManagedAssets = mode === 'repair' && (input.repairManagedAssets === true || deprecatedApplyRepairs);
17
21
  const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
18
22
  const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
19
23
  const version = codexBin ? await codexVersion(codexBin) : null;
@@ -57,8 +61,8 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
57
61
  blockers: [messageOf(err)],
58
62
  warnings: ['agent_type_probe_failed_message_role_fallback']
59
63
  }));
60
- const skillSync = await syncCodexSksSkills({ root, apply: input.applyRepairs === true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
61
- const agentRoles = await syncCodexAgentRoles({ root, apply: input.applyRepairs === true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
64
+ const skillSync = await inspectManagedSkillState(root);
65
+ const agentRoles = await inspectManagedAgentRoleState(root);
62
66
  const appRecord = isRecord(app) ? app : {};
63
67
  const requiredSkills = isRecord(appRecord.required_skills) ? appRecord.required_skills : {};
64
68
  const skills = isRecord(appRecord.skills) ? appRecord.skills : {};
@@ -91,7 +95,7 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
91
95
  unavailableStatus: 'fallback'
92
96
  }),
93
97
  mcp_inventory: codexNativeFeatureState({
94
- ok: mcpCandidates.candidates.length > 0 || Array.isArray(plugins.plugins),
98
+ ok: mcpCandidates.candidates.length > 0,
95
99
  source: 'plugin-inventory',
96
100
  artifact_path: '.sneakoscope/mcp-plugin-server-candidates.json',
97
101
  evidence: [`candidate_count:${mcpCandidates.candidates.length}`],
@@ -134,7 +138,11 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
134
138
  ...(!codexBin ? ['codex_cli_missing'] : []),
135
139
  ...Object.values(features).flatMap((feature) => feature.blockers)
136
140
  ],
137
- warnings: Object.values(features).flatMap((feature) => feature.warnings)
141
+ warnings: [
142
+ ...Object.values(features).flatMap((feature) => feature.warnings),
143
+ ...(deprecatedApplyRepairs ? ['deprecated_apply_repairs_input'] : []),
144
+ ...(mode === 'repair' && !repairManagedAssets ? ['repair_mode_without_managed_asset_repair'] : [])
145
+ ]
138
146
  };
139
147
  const matrix = {
140
148
  ...matrixBase,
@@ -144,6 +152,66 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
144
152
  await writeCodexNativeFeatureMatrix(root, matrix, input.missionDir);
145
153
  return matrix;
146
154
  }
155
+ async function inspectManagedSkillState(root) {
156
+ const skillRoots = [
157
+ path.join(root, '.agents', 'skills'),
158
+ ...(process.env.CODEX_HOME ? [path.join(process.env.CODEX_HOME, 'skills')] : [])
159
+ ];
160
+ let existingCount = 0;
161
+ const managed = new Set();
162
+ for (const dir of skillRoots) {
163
+ const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
164
+ existingCount += rows.filter((row) => row.isDirectory()).length;
165
+ for (const name of REQUIRED_SKILL_NAMES) {
166
+ if (managed.has(name))
167
+ continue;
168
+ const text = await fs.readFile(path.join(dir, name, 'SKILL.md'), 'utf8').catch(() => '');
169
+ if (text.includes('BEGIN SKS MANAGED SKILL'))
170
+ managed.add(name);
171
+ }
172
+ }
173
+ const missing = REQUIRED_SKILL_NAMES.filter((name) => !managed.has(name));
174
+ return {
175
+ ok: missing.length === 0,
176
+ apply: false,
177
+ artifact_path: '.sneakoscope/reports/codex-skill-sync.json',
178
+ existing_count: existingCount,
179
+ managed_count: managed.size,
180
+ missing_required: missing,
181
+ blockers: missing.length ? [`managed_skills_missing:${missing.join(',')}`] : [],
182
+ warnings: existingCount > managed.size ? ['non_sks_skill_dirs_ignored'] : []
183
+ };
184
+ }
185
+ async function inspectManagedAgentRoleState(root) {
186
+ const dirs = [
187
+ path.join(root, '.codex', 'agents'),
188
+ ...(process.env.CODEX_HOME ? [path.join(process.env.CODEX_HOME, 'agents')] : [])
189
+ ];
190
+ let existingCount = 0;
191
+ const managed = new Set();
192
+ for (const dir of dirs) {
193
+ const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
194
+ existingCount += rows.filter((row) => row.isFile() && row.name.endsWith('.toml')).length;
195
+ for (const role of REQUIRED_AGENT_ROLES) {
196
+ if (managed.has(role))
197
+ continue;
198
+ const text = await fs.readFile(path.join(dir, `${role}.toml`), 'utf8').catch(() => '');
199
+ if (text.includes('SKS managed 3.1.7 directive role'))
200
+ managed.add(role);
201
+ }
202
+ }
203
+ const missing = REQUIRED_AGENT_ROLES.filter((role) => !managed.has(role));
204
+ return {
205
+ ok: missing.length === 0,
206
+ apply: false,
207
+ artifact_path: '.sneakoscope/reports/codex-agent-role-sync.json',
208
+ existing_count: existingCount,
209
+ managed_count: managed.size,
210
+ missing_required: missing,
211
+ blockers: missing.length ? [`managed_agent_roles_missing:${missing.join(',')}`] : [],
212
+ warnings: existingCount > managed.size ? ['non_sks_agent_roles_ignored'] : []
213
+ };
214
+ }
147
215
  export async function writeCodexNativeFeatureMatrix(root, matrix, missionDir) {
148
216
  await writeJsonAtomic(path.join(root, REPORT_PATH), matrix);
149
217
  if (missionDir)
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { createHash } from 'node:crypto';
2
3
  import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
4
  import { analyzeCodexNativeReferenceSource, renderCodexNativeReferenceMarkdown } from './codex-native-reference-source.js';
4
5
  const PATTERN_ROWS = [
@@ -19,22 +20,26 @@ export async function buildCodexNativePatternAnalysis(input) {
19
20
  const root = path.resolve(input.root);
20
21
  const evidence = input.evidence || await analyzeCodexNativeReferenceSource({ root, sourceDir: input.sourceDir || null, writeReport: true }).catch(() => null);
21
22
  const evidenceByPattern = new Map();
23
+ const confidenceByPattern = new Map();
22
24
  for (const item of evidence?.evidence || []) {
23
25
  const list = evidenceByPattern.get(item.pattern_id) || [];
24
26
  list.push(item.snippet_hash);
25
27
  evidenceByPattern.set(item.pattern_id, list);
28
+ confidenceByPattern.set(item.pattern_id, strongerConfidence(confidenceByPattern.get(item.pattern_id), item.confidence));
26
29
  }
27
30
  return {
28
31
  schema: 'sks.codex-native-pattern-analysis.v1',
29
32
  generated_at: nowIso(),
30
33
  source_kind: 'external-reference-source',
31
- source_ref: evidence?.source_ref || input.sourceDir || '.sneakoscope/cache/codex-native-reference',
34
+ source_ref: evidence?.source_ref || neutralSourceRef(input.sourceDir || '.sneakoscope/cache/codex-native-reference'),
32
35
  source_sha: evidence?.source_sha || null,
36
+ source_url_hash: evidence?.source_url_hash || null,
37
+ cache_report_path: evidence?.cache_report_path || null,
33
38
  patterns: PATTERN_ROWS.map((pattern) => {
34
39
  const hashes = evidenceByPattern.get(pattern.id) || [];
35
40
  return {
36
41
  ...pattern,
37
- confidence: hashes.length ? 'high' : evidence ? 'medium' : 'low',
42
+ confidence: confidenceByPattern.get(pattern.id) || (hashes.length ? 'medium' : evidence ? 'low' : 'low'),
38
43
  evidence_hashes: hashes
39
44
  };
40
45
  }),
@@ -42,6 +47,13 @@ export async function buildCodexNativePatternAnalysis(input) {
42
47
  warnings: evidence?.warnings || ['reference_evidence_unavailable']
43
48
  };
44
49
  }
50
+ function neutralSourceRef(value) {
51
+ return `source:${createHash('sha256').update(value).digest('hex').slice(0, 16)}`;
52
+ }
53
+ function strongerConfidence(current, next) {
54
+ const rank = { low: 0, medium: 1, high: 2 };
55
+ return current && rank[current] >= rank[next] ? current : next;
56
+ }
45
57
  export async function writeCodexNativePatternAnalysis(root, input = {}) {
46
58
  const evidence = await analyzeCodexNativeReferenceSource({ root, sourceDir: input.sourceDir || null, writeReport: true }).catch(() => null);
47
59
  const report = await buildCodexNativePatternAnalysis({ root, evidence, sourceDir: input.sourceDir || null });