sneakoscope 3.1.5 → 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 (58) hide show
  1. package/README.md +9 -37
  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/command-registry.js +1 -2
  8. package/dist/commands/codex-app.js +20 -12
  9. package/dist/commands/codex-native.js +84 -0
  10. package/dist/commands/doctor.js +90 -2
  11. package/dist/core/codex-app/codex-agent-role-sync.js +15 -5
  12. package/dist/core/codex-app/codex-app-execution-profile.js +38 -16
  13. package/dist/core/codex-app/codex-app-harness-matrix.js +4 -117
  14. package/dist/core/codex-app/codex-hook-lifecycle.js +4 -1
  15. package/dist/core/codex-app/codex-init-deep.js +66 -4
  16. package/dist/core/codex-app/codex-skill-sync.js +13 -8
  17. package/dist/core/codex-control/codex-0138-capability.js +5 -2
  18. package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
  19. package/dist/core/codex-native/codex-native-feature-broker.js +250 -0
  20. package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
  21. package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
  22. package/dist/core/{codex-app/lazycodex-interop-policy.js → codex-native/codex-native-interop-policy.js} +13 -15
  23. package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
  24. package/dist/core/codex-native/codex-native-pattern-analysis.js +68 -0
  25. package/dist/core/codex-native/codex-native-reference-cache.js +98 -0
  26. package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
  27. package/dist/core/codex-native/codex-native-reference-source.js +149 -0
  28. package/dist/core/codex-native/codex-native-rename-map.js +25 -0
  29. package/dist/core/codex-native/codex-native-repair-transaction.js +150 -0
  30. package/dist/core/codex-plugins/codex-plugin-json.js +5 -2
  31. package/dist/core/commands/mad-sks-command.js +16 -0
  32. package/dist/core/feature-fixtures.js +2 -4
  33. package/dist/core/feature-registry.js +1 -1
  34. package/dist/core/fsx.js +1 -1
  35. package/dist/core/image/image-artifact-path-contract.js +18 -1
  36. package/dist/core/loops/loop-owner-inference.js +3 -0
  37. package/dist/core/loops/loop-planner.js +8 -2
  38. package/dist/core/loops/loop-worker-prompts.js +2 -0
  39. package/dist/core/loops/loop-worker-runtime.js +42 -7
  40. package/dist/core/qa-loop.js +24 -1
  41. package/dist/core/research.js +36 -3
  42. package/dist/core/routes.js +2 -3
  43. package/dist/core/version.js +1 -1
  44. package/dist/scripts/codex-native-runtime-e2e-fixture.js +75 -0
  45. package/dist/scripts/loop-worker-fixture-child.js +2 -1
  46. package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
  47. package/dist/scripts/sks-3-1-5-directive-check-lib.js +4 -33
  48. package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
  49. package/dist/scripts/sks-3-1-7-directive-check-lib.js +58 -0
  50. package/package.json +44 -13
  51. package/dist/cli/hermes-command.js +0 -99
  52. package/dist/cli/openclaw-command.js +0 -83
  53. package/dist/commands/hermes.js +0 -5
  54. package/dist/commands/openclaw.js +0 -3
  55. package/dist/core/codex-app/lazycodex-analysis.js +0 -72
  56. package/dist/core/codex-app/lazycodex-live-analyzer.js +0 -98
  57. package/dist/core/hermes.js +0 -192
  58. package/dist/core/openclaw.js +0 -171
@@ -1,104 +1,9 @@
1
1
  import path from 'node:path';
2
- import { findCodexBinary } from '../codex-adapter.js';
3
- import { codexAppIntegrationStatus } from '../codex-app.js';
4
- import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
5
- import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
6
- import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
7
- import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
8
- import { repairAgentRoleConfigs } from '../agents/agent-role-config.js';
9
- import { buildLazyCodexInteropPolicy } from './lazycodex-interop-policy.js';
10
- import { probeCodexAgentTypeSupport } from './codex-agent-type-probe.js';
11
- import { probeCodexHookApprovalState } from './codex-hook-approval-probe.js';
12
- import { isRecord } from './codex-app-types.js';
2
+ import { writeJsonAtomic } from '../fsx.js';
3
+ import { buildCodexAppHarnessMatrixFromNative } from '../codex-native/codex-native-harness-compat.js';
13
4
  export async function buildCodexAppHarnessMatrix(input = { root: process.cwd() }) {
14
- const root = path.resolve(input.root || process.cwd());
15
- const codexBin = await findCodexBinary().catch(() => null);
16
- const version = codexBin ? await codexVersion(codexBin) : null;
17
- const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
18
- const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
19
- const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
20
- const plugins = await buildCodexPluginInventory().catch((err) => ({ plugins: [], marketplace_available: false, blockers: [messageOf(err)] }));
21
- const hookApproval = await probeCodexHookApprovalState(root, { codexBin }).catch((err) => ({
22
- schema: 'sks.codex-hook-approval-probe.v1',
23
- generated_at: nowIso(),
24
- ok: false,
25
- detectable: false,
26
- approval_state: 'unknown',
27
- sources_checked: [],
28
- blockers: [messageOf(err)],
29
- warnings: ['hook_approval_probe_failed']
30
- }));
31
- const agentType = await probeCodexAgentTypeSupport(root, { codexBin }).catch((err) => ({
32
- schema: 'sks.codex-agent-type-probe.v1',
33
- generated_at: nowIso(),
34
- ok: false,
35
- supported: false,
36
- source: 'unknown',
37
- spawn_tool_name: 'unknown',
38
- schema_path: null,
39
- evidence: [],
40
- blockers: [messageOf(err)],
41
- warnings: ['agent_type_probe_failed_message_role_fallback']
42
- }));
43
- const agents = await repairAgentRoleConfigs({ root, apply: input.applyRepairs === true, reportPath: path.join(root, '.sneakoscope', 'reports', 'codex-agent-role-sync.json') }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
44
- const interop = await buildLazyCodexInteropPolicy({ root, inventory: plugins }).catch(() => null);
45
- const appRecord = isRecord(app) ? app : {};
46
- const requiredSkills = isRecord(appRecord.required_skills) ? appRecord.required_skills : {};
47
- const skills = isRecord(appRecord.skills) ? appRecord.skills : {};
48
- const skillShadows = isRecord(appRecord.skill_shadows) ? appRecord.skill_shadows : {};
49
- const hasSkills = requiredSkills.ok === true || skills.ok === true || skillShadows.ok !== false;
50
- const hookApprovalKnown = hookApproval.detectable === true;
51
- const hookBlockers = hookApproval.approval_state === 'modified_requires_reapproval'
52
- ? ['hook_modified_requires_reapproval']
53
- : [];
54
- const matrix = {
55
- schema: 'sks.codex-app-harness-matrix.v1',
56
- generated_at: nowIso(),
57
- ok: Boolean(codexBin) && feature(cap0138, 'supports_plugin_json') !== false && recordOk(agents) !== false,
58
- codex_cli: { available: Boolean(codexBin), version },
59
- app_features: {
60
- plugin_json: feature(cap0138, 'supports_plugin_json') === true,
61
- marketplace_add: feature(cap0139, 'supports_marketplace_source_field') === true || feature(plugins, 'marketplace_available') === true,
62
- marketplace_upgrade: feature(cap0139, 'supports_marketplace_source_field') === true,
63
- startup_review_detectable: hookApprovalKnown,
64
- hook_approval_state_detectable: hookApprovalKnown,
65
- hook_approval_state: hookApproval.approval_state,
66
- skill_picker_ready: Boolean(hasSkills),
67
- agent_type_supported: agentType.supported,
68
- mcp_inventory_ready: Array.isArray(isRecord(plugins) ? plugins.plugins : null),
69
- app_handoff_ready: feature(cap0138, 'supports_app_handoff') === true,
70
- image_path_exposure_ready: feature(cap0138, 'supports_image_path_exposure') === true
71
- },
72
- sks_integrations: {
73
- dollar_skills_synced: Boolean(hasSkills),
74
- agent_roles_synced: recordOk(agents) !== false,
75
- hooks_synced: hookApproval.approval_state === 'approved',
76
- init_deep_available: true,
77
- loop_mesh_app_profile_available: true
78
- },
79
- probes: {
80
- hook_approval: hookApproval,
81
- agent_type: agentType
82
- },
83
- blockers: [
84
- ...(!codexBin ? ['codex_cli_missing'] : []),
85
- ...blockersOf(cap0138),
86
- ...blockersOf(plugins),
87
- ...blockersOf(agents),
88
- ...hookBlockers
89
- ],
90
- warnings: [
91
- ...blockersOf(cap0139).map((b) => `codex_0139:${b}`),
92
- ...hookApproval.blockers.map((b) => `hooks:${b}`),
93
- ...hookApproval.warnings,
94
- ...agentType.warnings,
95
- ...(hookApproval.approval_state === 'pending_review' ? ['hook_approval_pending_review'] : []),
96
- ...(hookApproval.approval_state === 'not_installed' ? ['hooks_not_installed'] : []),
97
- ...(interop?.lazycodex_detected ? ['lazycodex_detected_coexist_mode'] : [])
98
- ]
99
- };
100
- matrix.ok = matrix.blockers.length === 0;
101
- await writeCodexAppHarnessMatrix(root, matrix, input.missionDir);
5
+ const matrix = await buildCodexAppHarnessMatrixFromNative(input);
6
+ await writeCodexAppHarnessMatrix(path.resolve(input.root || process.cwd()), matrix, input.missionDir);
102
7
  return matrix;
103
8
  }
104
9
  export async function writeCodexAppHarnessMatrix(root, matrix, missionDir) {
@@ -106,22 +11,4 @@ export async function writeCodexAppHarnessMatrix(root, matrix, missionDir) {
106
11
  if (missionDir)
107
12
  await writeJsonAtomic(path.join(missionDir, 'codex-app-harness-matrix.json'), matrix).catch(() => undefined);
108
13
  }
109
- async function codexVersion(bin) {
110
- const run = await runProcess(bin, ['--version'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch(() => null);
111
- return run?.code === 0 ? `${run.stdout || run.stderr || ''}`.trim() || null : null;
112
- }
113
- function blockersOf(value) {
114
- if (!isRecord(value) || !Array.isArray(value.blockers))
115
- return [];
116
- return value.blockers.map((item) => String(item)).filter(Boolean);
117
- }
118
- function feature(value, key) {
119
- return isRecord(value) && typeof value[key] === 'boolean' ? value[key] : undefined;
120
- }
121
- function recordOk(value) {
122
- return isRecord(value) && typeof value.ok === 'boolean' ? value.ok : undefined;
123
- }
124
- function messageOf(err) {
125
- return err instanceof Error ? err.message : String(err);
126
- }
127
14
  //# sourceMappingURL=codex-app-harness-matrix.js.map
@@ -35,9 +35,12 @@ export async function buildCodexHookLifecycle(input = {}) {
35
35
  approval_state: probe.approval_state,
36
36
  approval_state_detectable: probe.detectable,
37
37
  lifecycle: Object.fromEntries(Object.entries(events).map(([event, actions]) => [event, {
38
+ event,
38
39
  actions,
40
+ sks_action: actions.join(','),
39
41
  installed: installedEvents.has(event),
40
- approval_state: installedEvents.has(event) ? probe.approval_state : 'not_installed'
42
+ approval_state: installedEvents.has(event) ? probe.approval_state : 'not_installed',
43
+ counted_as_evidence: installedEvents.has(event) && probe.approval_state === 'approved'
41
44
  }])),
42
45
  actual_state: actual,
43
46
  install,
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { ensureDir, mergeManagedBlock, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ import { guardContextForRoute, guardedRm } from '../safety/mutation-guard.js';
5
+ import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
4
6
  export async function runCodexInitDeep(input = {}) {
5
7
  const root = path.resolve(input.root || process.cwd());
6
8
  const dirs = await scoreDirectories(root);
@@ -8,7 +10,8 @@ export async function runCodexInitDeep(input = {}) {
8
10
  const contextDir = path.join(root, '.sneakoscope', 'context');
9
11
  const generatedPath = path.join(contextDir, 'AGENTS.generated.md');
10
12
  const markdown = renderGeneratedAgents(selected);
11
- const directoryLocalAgents = { created: [], updated: [], skipped: [], backup_paths: [], blockers: [] };
13
+ const directoryLocalAgents = { created: [], updated: [], skipped: [], backup_paths: [], backups_created: 0, backups_pruned: [], unchanged_files: [], changed_only_backup: true, blockers: [] };
14
+ const backupRetention = Math.max(0, Number(process.env.SKS_INIT_DEEP_BACKUP_RETENTION || 5) || 5);
12
15
  if (input.apply === true) {
13
16
  await ensureDir(contextDir);
14
17
  await writeTextAtomic(generatedPath, markdown);
@@ -18,12 +21,22 @@ export async function runCodexInitDeep(input = {}) {
18
21
  try {
19
22
  await ensureDir(path.dirname(agentsPath));
20
23
  const existing = await fs.readFile(agentsPath, 'utf8').catch(() => '');
24
+ const next = mergeManagedBlockPreview(existing, 'SKS INIT-DEEP MANAGED SECTION', renderDirectoryAgentsBlock(row));
25
+ if (existing === next) {
26
+ directoryLocalAgents.unchanged_files.push(path.relative(root, agentsPath));
27
+ continue;
28
+ }
21
29
  if (existing.trim()) {
22
- const backup = `${agentsPath}.sks-backup-${Date.now()}`;
30
+ const beforeHash = hashText(existing);
31
+ const backup = `${agentsPath}.sks-backup-${Date.now()}-${beforeHash.slice(0, 12)}`;
23
32
  await fs.copyFile(agentsPath, backup);
24
33
  directoryLocalAgents.backup_paths.push(path.relative(root, backup));
34
+ directoryLocalAgents.backups_created += 1;
25
35
  }
26
- const status = await mergeManagedBlock(agentsPath, 'SKS INIT-DEEP MANAGED SECTION', renderDirectoryAgentsBlock(row));
36
+ await writeTextAtomic(agentsPath, next);
37
+ const pruned = await pruneBackups(root, agentsPath, backupRetention);
38
+ directoryLocalAgents.backups_pruned.push(...pruned.map((file) => path.relative(root, file)));
39
+ const status = existing.trim() ? (existing.includes('BEGIN SKS INIT-DEEP MANAGED SECTION') ? 'updated' : 'appended') : 'created';
27
40
  if (status === 'created')
28
41
  directoryLocalAgents.created.push(path.relative(root, agentsPath));
29
42
  else
@@ -51,6 +64,53 @@ export async function runCodexInitDeep(input = {}) {
51
64
  await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-init-deep.json'), report).catch(() => undefined);
52
65
  return report;
53
66
  }
67
+ function mergeManagedBlockPreview(current, markerName, content) {
68
+ const begin = `<!-- BEGIN ${markerName} -->`;
69
+ const end = `<!-- END ${markerName} -->`;
70
+ const block = `${begin}\n${content.trim()}\n${end}\n`;
71
+ if (!current.trim())
72
+ return `${block}\n`;
73
+ const beginIdx = current.indexOf(begin);
74
+ const endIdx = current.indexOf(end);
75
+ if (beginIdx >= 0 && endIdx >= beginIdx) {
76
+ const afterEnd = endIdx + end.length;
77
+ return `${current.slice(0, beginIdx)}${block}${current.slice(afterEnd).replace(/^\n/, '')}`;
78
+ }
79
+ return `${current.replace(/\s*$/, '\n\n')}${block}\n`;
80
+ }
81
+ async function pruneBackups(root, agentsPath, keep) {
82
+ if (keep < 1)
83
+ return [];
84
+ const dir = path.dirname(agentsPath);
85
+ const base = path.basename(agentsPath);
86
+ const backupPattern = new RegExp(`^${escapeRegExp(base)}\\.sks-backup-\\d{13}-[0-9a-f]{8,12}$`);
87
+ const rows = await fs.readdir(dir).catch(() => []);
88
+ const backups = rows
89
+ .filter((name) => backupPattern.test(name))
90
+ .map((name) => path.join(dir, name))
91
+ .sort();
92
+ const remove = backups.slice(0, Math.max(0, backups.length - keep));
93
+ const contract = createRequestedScopeContract({
94
+ route: 'codex-app:init-deep',
95
+ userRequest: 'Prune only SKS init-deep AGENTS.md backup files after creating a fresh backup.',
96
+ projectRoot: root
97
+ });
98
+ const guard = guardContextForRoute(root, contract, 'prune SKS init-deep backup retention');
99
+ for (const file of remove)
100
+ await guardedRm(guard, file, { force: true }).catch(() => undefined);
101
+ return remove;
102
+ }
103
+ function escapeRegExp(value) {
104
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
105
+ }
106
+ function hashText(text) {
107
+ let hash = 2166136261;
108
+ for (let index = 0; index < text.length; index += 1) {
109
+ hash ^= text.charCodeAt(index);
110
+ hash = Math.imul(hash, 16777619);
111
+ }
112
+ return (hash >>> 0).toString(16).padStart(8, '0');
113
+ }
54
114
  export async function readInitDeepMemory(root) {
55
115
  const file = path.join(root, '.sneakoscope', 'context', 'AGENTS.generated.md');
56
116
  const text = await fs.readFile(file, 'utf8').catch(() => '');
@@ -130,6 +190,8 @@ async function walk(dir, root, counts, depth = 0) {
130
190
  await walk(full, root, counts, depth + 1);
131
191
  }
132
192
  else if (row.isFile()) {
193
+ if (row.name.includes('.sks-backup-'))
194
+ continue;
133
195
  const relDir = path.relative(root, path.dirname(full)).split(path.sep).join('/');
134
196
  const entry = counts.get(relDir) || { file_count: 0, langs: new Set() };
135
197
  entry.file_count += 1;
@@ -13,19 +13,19 @@ const SKS_SKILLS = [
13
13
  '$Computer-Use',
14
14
  '$Init-Deep'
15
15
  ];
16
- const LAZYCODEX_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
16
+ const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
17
17
  export async function syncCodexSksSkills(input) {
18
18
  const root = path.resolve(input.root);
19
19
  const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
20
20
  const existing = await listSkillNames(skillsRoot);
21
- const collisions = existing.filter((name) => LAZYCODEX_RESERVED.has(name));
21
+ const collisions = existing.filter((name) => EXTERNAL_ROUTE_RESERVED.has(name));
22
22
  const desired = SKS_SKILLS.map((skill) => skillName(skill));
23
23
  const created = [];
24
24
  const skipped = [];
25
25
  if (input.apply === true) {
26
26
  await ensureDir(skillsRoot);
27
27
  for (const name of desired) {
28
- if (LAZYCODEX_RESERVED.has(name)) {
28
+ if (EXTERNAL_ROUTE_RESERVED.has(name)) {
29
29
  skipped.push(name);
30
30
  continue;
31
31
  }
@@ -52,11 +52,13 @@ export async function syncCodexSksSkills(input) {
52
52
  existing_skills: existing,
53
53
  created,
54
54
  skipped,
55
- lazycodex_reserved_present: collisions,
55
+ external_route_names_preserved: collisions,
56
56
  interop: {
57
57
  mode: 'coexist',
58
- clobbered_lazycodex: false,
59
- clobbered_user_skills: false
58
+ clobbered_external_routes: false,
59
+ clobbered_user_skills: false,
60
+ skipped_user_skills: skipped,
61
+ managed_skills: desired
60
62
  },
61
63
  blockers: []
62
64
  };
@@ -82,10 +84,13 @@ function skillContent(name) {
82
84
  `Command: ${profile.command}`,
83
85
  `Purpose: ${profile.purpose}`,
84
86
  `Use when: ${profile.when}`,
87
+ `Route: ${profile.command}`,
85
88
  `Evidence: ${profile.evidence}`,
86
- 'Safety: keep route state bounded, preserve user and LazyCodex/OmO skills, 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.',
90
+ 'Proof paths: write the route-local mission artifact named in Evidence before claiming completion.',
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.',
87
92
  `Fallback: ${profile.fallback}`,
88
- `checksum: ${hash(name)}`,
93
+ `checksum: ${hash(`${name}:${profile.command}:${profile.evidence}:${profile.fallback}`)}`,
89
94
  '<!-- END SKS MANAGED SKILL -->',
90
95
  ''
91
96
  ].join('\n');
@@ -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
  }
@@ -0,0 +1,21 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { writeJsonAtomic } from '../fsx.js';
4
+ const CACHE_RELATIVE_PATH = '.sneakoscope/reports/codex-native-feature-matrix.json';
5
+ export function codexNativeCapabilityCachePath(root) {
6
+ return path.join(root, CACHE_RELATIVE_PATH);
7
+ }
8
+ export async function readCodexNativeCapabilityCache(root) {
9
+ const file = codexNativeCapabilityCachePath(root);
10
+ const text = await fs.readFile(file, 'utf8').catch(() => null);
11
+ if (!text)
12
+ return null;
13
+ const parsed = JSON.parse(text);
14
+ return parsed?.schema === 'sks.codex-native-feature-matrix.v1' ? parsed : null;
15
+ }
16
+ export async function writeCodexNativeCapabilityCache(root, matrix) {
17
+ const file = codexNativeCapabilityCachePath(root);
18
+ await writeJsonAtomic(file, matrix);
19
+ return file;
20
+ }
21
+ //# sourceMappingURL=codex-native-capability-cache.js.map
@@ -0,0 +1,250 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { findCodexBinary } from '../codex-adapter.js';
4
+ import { codexAppIntegrationStatus } from '../codex-app.js';
5
+ import { probeCodexAgentTypeSupport } from '../codex-app/codex-agent-type-probe.js';
6
+ import { probeCodexHookApprovalState } from '../codex-app/codex-hook-approval-probe.js';
7
+ import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
8
+ import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
9
+ import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
10
+ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
11
+ import { buildMcpPluginServerCandidates } from '../mcp/mcp-plugin-inventory.js';
12
+ import { codexNativeFeatureState, computeCodexNativeInvocationDefaults } from './codex-native-feature-matrix.js';
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'];
16
+ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd() }) {
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);
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';
22
+ const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
23
+ const version = codexBin ? await codexVersion(codexBin) : null;
24
+ const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
25
+ const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
26
+ const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
27
+ const plugins = await buildCodexPluginInventory().catch((err) => ({
28
+ schema: 'sks.codex-plugin-inventory.v1',
29
+ generated_at: nowIso(),
30
+ codex_0138_capability: null,
31
+ fetch_concurrency: 0,
32
+ detail_fetch_count: 0,
33
+ detail_fetch_failed_count: 0,
34
+ duration_ms: 0,
35
+ plugins: [],
36
+ marketplace_available: false,
37
+ blockers: [messageOf(err)]
38
+ }));
39
+ const mcpCandidates = buildMcpPluginServerCandidates(plugins);
40
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'codex-plugin-inventory.json'), plugins).catch(() => undefined);
41
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'mcp-plugin-server-candidates.json'), mcpCandidates).catch(() => undefined);
42
+ const hookApproval = await probeCodexHookApprovalState(root, { codexBin }).catch((err) => ({
43
+ schema: 'sks.codex-hook-approval-probe.v1',
44
+ generated_at: nowIso(),
45
+ ok: false,
46
+ detectable: false,
47
+ approval_state: 'unknown',
48
+ sources_checked: [],
49
+ blockers: [messageOf(err)],
50
+ warnings: ['hook_approval_probe_failed']
51
+ }));
52
+ const agentType = await probeCodexAgentTypeSupport(root, { codexBin }).catch((err) => ({
53
+ schema: 'sks.codex-agent-type-probe.v1',
54
+ generated_at: nowIso(),
55
+ ok: false,
56
+ supported: false,
57
+ source: 'unknown',
58
+ spawn_tool_name: 'unknown',
59
+ schema_path: null,
60
+ evidence: [],
61
+ blockers: [messageOf(err)],
62
+ warnings: ['agent_type_probe_failed_message_role_fallback']
63
+ }));
64
+ const skillSync = await inspectManagedSkillState(root);
65
+ const agentRoles = await inspectManagedAgentRoleState(root);
66
+ const appRecord = isRecord(app) ? app : {};
67
+ const requiredSkills = isRecord(appRecord.required_skills) ? appRecord.required_skills : {};
68
+ const skills = isRecord(appRecord.skills) ? appRecord.skills : {};
69
+ const skillShadows = isRecord(appRecord.skill_shadows) ? appRecord.skill_shadows : {};
70
+ const skillPickerReady = requiredSkills.ok === true || skills.ok === true || skillShadows.ok !== false;
71
+ const hookApproved = hookApproval.approval_state === 'approved';
72
+ const hookInstalled = hookApproval.approval_state !== 'not_installed';
73
+ const features = {
74
+ plugin_json: boolState(booleanFeature(cap0138, 'supports_plugin_json'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
75
+ plugin_marketplace: boolState(booleanFeature(cap0139, 'supports_marketplace_source_field') || plugins.marketplace_available, 'plugin-inventory', '.sneakoscope/codex-plugin-inventory.json', blockersOf(plugins)),
76
+ hook_approval: codexNativeFeatureState({
77
+ ok: hookApproved,
78
+ source: 'actual-probe',
79
+ artifact_path: '.sneakoscope/reports/codex-hook-approval-probe.json',
80
+ evidence: [`approval_state:${hookApproval.approval_state}`],
81
+ blockers: hookApproval.approval_state === 'modified_requires_reapproval' ? ['hook_modified_requires_reapproval'] : [],
82
+ warnings: [...hookApproval.warnings, ...(!hookApproved && hookInstalled ? ['hook_derived_evidence_not_counted'] : [])],
83
+ unavailableStatus: hookInstalled ? 'unknown' : 'unavailable'
84
+ }),
85
+ skill_picker: boolState(skillPickerReady, 'config', '.sneakoscope/reports/codex-native-feature-matrix.json', [], skillPickerReady ? [] : ['skill_picker_unverified']),
86
+ skill_sync: boolState(recordOk(skillSync) !== false, 'actual-probe', '.sneakoscope/reports/codex-skill-sync.json', blockersOf(skillSync)),
87
+ agent_roles: boolState(recordOk(agentRoles) !== false, 'actual-probe', '.sneakoscope/reports/codex-agent-role-sync.json', blockersOf(agentRoles)),
88
+ agent_type: codexNativeFeatureState({
89
+ ok: agentType.supported === true,
90
+ source: agentType.source === 'fixture' ? 'fixture' : 'actual-probe',
91
+ artifact_path: '.sneakoscope/reports/codex-agent-type-probe.json',
92
+ evidence: agentType.evidence,
93
+ blockers: [],
94
+ warnings: [...agentType.warnings, ...(agentType.supported ? [] : ['agent_type_unavailable_message_role_fallback'])],
95
+ unavailableStatus: 'fallback'
96
+ }),
97
+ mcp_inventory: codexNativeFeatureState({
98
+ ok: mcpCandidates.candidates.length > 0,
99
+ source: 'plugin-inventory',
100
+ artifact_path: '.sneakoscope/mcp-plugin-server-candidates.json',
101
+ evidence: [`candidate_count:${mcpCandidates.candidates.length}`],
102
+ blockers: [...plugins.blockers, ...mcpCandidates.blockers],
103
+ warnings: mcpCandidates.candidates.length ? [] : ['mcp_plugin_candidates_empty'],
104
+ unavailableStatus: 'fallback'
105
+ }),
106
+ app_handoff: boolState(booleanFeature(cap0138, 'supports_app_handoff'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
107
+ image_path_exposure: boolState(booleanFeature(cap0138, 'supports_image_path_exposure'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
108
+ code_mode_web_search: boolState(booleanFeature(cap0139, 'supports_code_mode_web_search'), 'actual-probe', '.sneakoscope/codex-0139-capability.json', blockersOf(cap0139)),
109
+ slash_command_bridge: boolState(true, 'config', '.sneakoscope/reports/codex-native-feature-matrix.json'),
110
+ project_memory: boolState(true, 'config', '.sneakoscope/context/AGENTS.generated.md')
111
+ };
112
+ const matrixBase = {
113
+ schema: 'sks.codex-native-feature-matrix.v1',
114
+ generated_at: nowIso(),
115
+ ok: false,
116
+ codex_cli: { available: Boolean(codexBin), version, bin: codexBin },
117
+ features,
118
+ probes: {
119
+ codex_0138: cap0138,
120
+ codex_0139: cap0139,
121
+ app,
122
+ plugin_inventory: plugins,
123
+ mcp_candidates: mcpCandidates,
124
+ hook_approval: hookApproval,
125
+ agent_type: agentType,
126
+ skill_sync: skillSync,
127
+ agent_roles: agentRoles
128
+ },
129
+ invocation_defaults: {
130
+ loop_worker_role_strategy: 'message-role',
131
+ qa_visual_review_strategy: 'headless-artifact',
132
+ research_source_strategy: 'local-files',
133
+ image_followup_strategy: 'artifact-path',
134
+ hook_evidence_policy: 'unknown-do-not-count',
135
+ skill_bridge_strategy: 'cli-only'
136
+ },
137
+ blockers: [
138
+ ...(!codexBin ? ['codex_cli_missing'] : []),
139
+ ...Object.values(features).flatMap((feature) => feature.blockers)
140
+ ],
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
+ ]
146
+ };
147
+ const matrix = {
148
+ ...matrixBase,
149
+ ok: matrixBase.blockers.length === 0,
150
+ invocation_defaults: computeCodexNativeInvocationDefaults(matrixBase)
151
+ };
152
+ await writeCodexNativeFeatureMatrix(root, matrix, input.missionDir);
153
+ return matrix;
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
+ }
215
+ export async function writeCodexNativeFeatureMatrix(root, matrix, missionDir) {
216
+ await writeJsonAtomic(path.join(root, REPORT_PATH), matrix);
217
+ if (missionDir)
218
+ await writeJsonAtomic(path.join(missionDir, 'codex-native-feature-matrix.json'), matrix).catch(() => undefined);
219
+ }
220
+ function boolState(ok, source, artifactPath, blockers = [], warnings = []) {
221
+ return codexNativeFeatureState({
222
+ ok,
223
+ source,
224
+ artifact_path: artifactPath,
225
+ blockers: ok ? [] : blockers,
226
+ warnings: ok ? warnings : [...warnings, ...(!blockers.length ? ['feature_unavailable'] : [])]
227
+ });
228
+ }
229
+ async function codexVersion(bin) {
230
+ const run = await runProcess(bin, ['--version'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch(() => null);
231
+ return run?.code === 0 ? `${run.stdout || run.stderr || ''}`.trim() || null : null;
232
+ }
233
+ function booleanFeature(value, key) {
234
+ return isRecord(value) && value[key] === true;
235
+ }
236
+ function blockersOf(value) {
237
+ if (!isRecord(value) || !Array.isArray(value.blockers))
238
+ return [];
239
+ return value.blockers.map((item) => String(item)).filter(Boolean);
240
+ }
241
+ function recordOk(value) {
242
+ return isRecord(value) && typeof value.ok === 'boolean' ? value.ok : undefined;
243
+ }
244
+ function isRecord(value) {
245
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
246
+ }
247
+ function messageOf(err) {
248
+ return err instanceof Error ? err.message : String(err);
249
+ }
250
+ //# sourceMappingURL=codex-native-feature-broker.js.map
@@ -0,0 +1,31 @@
1
+ export function codexNativeFeatureState(input) {
2
+ const blockers = uniq(input.blockers || []);
3
+ return {
4
+ ok: input.ok && blockers.length === 0,
5
+ status: input.ok && blockers.length === 0 ? 'available' : input.unavailableStatus || (blockers.length ? 'blocked' : 'unavailable'),
6
+ source: input.source,
7
+ artifact_path: input.artifact_path ?? null,
8
+ evidence: uniq(input.evidence || []),
9
+ blockers,
10
+ warnings: uniq(input.warnings || [])
11
+ };
12
+ }
13
+ export function computeCodexNativeInvocationDefaults(matrix) {
14
+ const features = matrix.features;
15
+ const hookStatus = features.hook_approval.status;
16
+ return {
17
+ loop_worker_role_strategy: features.agent_type.ok ? 'agent_type' : 'message-role',
18
+ qa_visual_review_strategy: features.app_handoff.ok ? 'app-handoff' : 'headless-artifact',
19
+ research_source_strategy: features.mcp_inventory.ok ? 'mcp-plugin-candidates' : features.code_mode_web_search.ok ? 'web-sources' : 'local-files',
20
+ image_followup_strategy: features.image_path_exposure.ok ? 'model-visible-path' : 'artifact-path',
21
+ hook_evidence_policy: hookStatus === 'available' ? 'approved-only' : hookStatus === 'unavailable' ? 'not-installed' : 'unknown-do-not-count',
22
+ skill_bridge_strategy: features.skill_sync.ok || features.skill_picker.ok ? 'sks-managed-skills' : 'cli-only'
23
+ };
24
+ }
25
+ export function matrixFeatureOk(matrix, key) {
26
+ return matrix.features[key].ok;
27
+ }
28
+ export function uniq(values) {
29
+ return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
30
+ }
31
+ //# sourceMappingURL=codex-native-feature-matrix.js.map