sneakoscope 3.1.5 → 3.1.6

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 (49) hide show
  1. package/README.md +8 -36
  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 +1 -11
  9. package/dist/commands/codex-native.js +68 -0
  10. package/dist/commands/doctor.js +64 -0
  11. package/dist/core/codex-app/codex-agent-role-sync.js +11 -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 +62 -4
  16. package/dist/core/codex-app/codex-skill-sync.js +10 -7
  17. package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
  18. package/dist/core/codex-native/codex-native-feature-broker.js +182 -0
  19. package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
  20. package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
  21. package/dist/core/{codex-app/lazycodex-interop-policy.js → codex-native/codex-native-interop-policy.js} +13 -15
  22. package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
  23. package/dist/core/codex-native/codex-native-pattern-analysis.js +56 -0
  24. package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
  25. package/dist/core/codex-native/codex-native-reference-source.js +110 -0
  26. package/dist/core/codex-native/codex-native-rename-map.js +25 -0
  27. package/dist/core/feature-fixtures.js +2 -4
  28. package/dist/core/feature-registry.js +1 -1
  29. package/dist/core/fsx.js +1 -1
  30. package/dist/core/image/image-artifact-path-contract.js +18 -1
  31. package/dist/core/loops/loop-owner-inference.js +3 -0
  32. package/dist/core/loops/loop-planner.js +8 -2
  33. package/dist/core/loops/loop-worker-runtime.js +34 -6
  34. package/dist/core/qa-loop.js +24 -1
  35. package/dist/core/research.js +36 -3
  36. package/dist/core/routes.js +2 -3
  37. package/dist/core/version.js +1 -1
  38. package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
  39. package/dist/scripts/sks-3-1-5-directive-check-lib.js +4 -33
  40. package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
  41. package/package.json +32 -12
  42. package/dist/cli/hermes-command.js +0 -99
  43. package/dist/cli/openclaw-command.js +0 -83
  44. package/dist/commands/hermes.js +0 -5
  45. package/dist/commands/openclaw.js +0 -3
  46. package/dist/core/codex-app/lazycodex-analysis.js +0 -72
  47. package/dist/core/codex-app/lazycodex-live-analyzer.js +0 -98
  48. package/dist/core/hermes.js +0 -192
  49. package/dist/core/openclaw.js +0 -171
@@ -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-${beforeHash.slice(0, 12)}-${Date.now()}`;
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,49 @@ 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 rows = await fs.readdir(dir).catch(() => []);
87
+ const backups = rows
88
+ .filter((name) => name.startsWith(`${base}.sks-backup-`))
89
+ .map((name) => path.join(dir, name))
90
+ .sort();
91
+ const remove = backups.slice(0, Math.max(0, backups.length - keep));
92
+ const contract = createRequestedScopeContract({
93
+ route: 'codex-app:init-deep',
94
+ userRequest: 'Prune only SKS init-deep AGENTS.md backup files after creating a fresh backup.',
95
+ projectRoot: root
96
+ });
97
+ const guard = guardContextForRoute(root, contract, 'prune SKS init-deep backup retention');
98
+ for (const file of remove)
99
+ await guardedRm(guard, file, { force: true }).catch(() => undefined);
100
+ return remove;
101
+ }
102
+ function hashText(text) {
103
+ let hash = 2166136261;
104
+ for (let index = 0; index < text.length; index += 1) {
105
+ hash ^= text.charCodeAt(index);
106
+ hash = Math.imul(hash, 16777619);
107
+ }
108
+ return (hash >>> 0).toString(16).padStart(8, '0');
109
+ }
54
110
  export async function readInitDeepMemory(root) {
55
111
  const file = path.join(root, '.sneakoscope', 'context', 'AGENTS.generated.md');
56
112
  const text = await fs.readFile(file, 'utf8').catch(() => '');
@@ -130,6 +186,8 @@ async function walk(dir, root, counts, depth = 0) {
130
186
  await walk(full, root, counts, depth + 1);
131
187
  }
132
188
  else if (row.isFile()) {
189
+ if (row.name.includes('.sks-backup-'))
190
+ continue;
133
191
  const relDir = path.relative(root, path.dirname(full)).split(path.sep).join('/');
134
192
  const entry = counts.get(relDir) || { file_count: 0, langs: new Set() };
135
193
  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,10 +52,10 @@ 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,
58
+ clobbered_external_routes: false,
59
59
  clobbered_user_skills: false
60
60
  },
61
61
  blockers: []
@@ -82,10 +82,13 @@ function skillContent(name) {
82
82
  `Command: ${profile.command}`,
83
83
  `Purpose: ${profile.purpose}`,
84
84
  `Use when: ${profile.when}`,
85
+ `Route: ${profile.command}`,
85
86
  `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.',
87
+ 'Safety: keep route state bounded, preserve user and external route assets, and stop on hard blockers instead of fabricating fallback behavior.',
88
+ 'Proof paths: write the route-local mission artifact named in Evidence before claiming completion.',
89
+ '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
90
  `Fallback: ${profile.fallback}`,
88
- `checksum: ${hash(name)}`,
91
+ `checksum: ${hash(`${name}:${profile.command}:${profile.evidence}:${profile.fallback}`)}`,
89
92
  '<!-- END SKS MANAGED SKILL -->',
90
93
  ''
91
94
  ].join('\n');
@@ -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,182 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { codexAppIntegrationStatus } from '../codex-app.js';
4
+ import { syncCodexAgentRoles } from '../codex-app/codex-agent-role-sync.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 { syncCodexSksSkills } from '../codex-app/codex-skill-sync.js';
8
+ import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
9
+ import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
10
+ import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
11
+ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
12
+ import { buildMcpPluginServerCandidates } from '../mcp/mcp-plugin-inventory.js';
13
+ import { codexNativeFeatureState, computeCodexNativeInvocationDefaults } from './codex-native-feature-matrix.js';
14
+ const REPORT_PATH = '.sneakoscope/reports/codex-native-feature-matrix.json';
15
+ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd() }) {
16
+ const root = path.resolve(input.root || process.cwd());
17
+ 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
+ const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
19
+ const version = codexBin ? await codexVersion(codexBin) : null;
20
+ const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
21
+ const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
22
+ const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
23
+ const plugins = await buildCodexPluginInventory().catch((err) => ({
24
+ schema: 'sks.codex-plugin-inventory.v1',
25
+ generated_at: nowIso(),
26
+ codex_0138_capability: null,
27
+ fetch_concurrency: 0,
28
+ detail_fetch_count: 0,
29
+ detail_fetch_failed_count: 0,
30
+ duration_ms: 0,
31
+ plugins: [],
32
+ marketplace_available: false,
33
+ blockers: [messageOf(err)]
34
+ }));
35
+ const mcpCandidates = buildMcpPluginServerCandidates(plugins);
36
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'codex-plugin-inventory.json'), plugins).catch(() => undefined);
37
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'mcp-plugin-server-candidates.json'), mcpCandidates).catch(() => undefined);
38
+ const hookApproval = await probeCodexHookApprovalState(root, { codexBin }).catch((err) => ({
39
+ schema: 'sks.codex-hook-approval-probe.v1',
40
+ generated_at: nowIso(),
41
+ ok: false,
42
+ detectable: false,
43
+ approval_state: 'unknown',
44
+ sources_checked: [],
45
+ blockers: [messageOf(err)],
46
+ warnings: ['hook_approval_probe_failed']
47
+ }));
48
+ const agentType = await probeCodexAgentTypeSupport(root, { codexBin }).catch((err) => ({
49
+ schema: 'sks.codex-agent-type-probe.v1',
50
+ generated_at: nowIso(),
51
+ ok: false,
52
+ supported: false,
53
+ source: 'unknown',
54
+ spawn_tool_name: 'unknown',
55
+ schema_path: null,
56
+ evidence: [],
57
+ blockers: [messageOf(err)],
58
+ warnings: ['agent_type_probe_failed_message_role_fallback']
59
+ }));
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)] }));
62
+ const appRecord = isRecord(app) ? app : {};
63
+ const requiredSkills = isRecord(appRecord.required_skills) ? appRecord.required_skills : {};
64
+ const skills = isRecord(appRecord.skills) ? appRecord.skills : {};
65
+ const skillShadows = isRecord(appRecord.skill_shadows) ? appRecord.skill_shadows : {};
66
+ const skillPickerReady = requiredSkills.ok === true || skills.ok === true || skillShadows.ok !== false;
67
+ const hookApproved = hookApproval.approval_state === 'approved';
68
+ const hookInstalled = hookApproval.approval_state !== 'not_installed';
69
+ const features = {
70
+ plugin_json: boolState(booleanFeature(cap0138, 'supports_plugin_json'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
71
+ plugin_marketplace: boolState(booleanFeature(cap0139, 'supports_marketplace_source_field') || plugins.marketplace_available, 'plugin-inventory', '.sneakoscope/codex-plugin-inventory.json', blockersOf(plugins)),
72
+ hook_approval: codexNativeFeatureState({
73
+ ok: hookApproved,
74
+ source: 'actual-probe',
75
+ artifact_path: '.sneakoscope/reports/codex-hook-approval-probe.json',
76
+ evidence: [`approval_state:${hookApproval.approval_state}`],
77
+ blockers: hookApproval.approval_state === 'modified_requires_reapproval' ? ['hook_modified_requires_reapproval'] : [],
78
+ warnings: [...hookApproval.warnings, ...(!hookApproved && hookInstalled ? ['hook_derived_evidence_not_counted'] : [])],
79
+ unavailableStatus: hookInstalled ? 'unknown' : 'unavailable'
80
+ }),
81
+ skill_picker: boolState(skillPickerReady, 'config', '.sneakoscope/reports/codex-native-feature-matrix.json', [], skillPickerReady ? [] : ['skill_picker_unverified']),
82
+ skill_sync: boolState(recordOk(skillSync) !== false, 'actual-probe', '.sneakoscope/reports/codex-skill-sync.json', blockersOf(skillSync)),
83
+ agent_roles: boolState(recordOk(agentRoles) !== false, 'actual-probe', '.sneakoscope/reports/codex-agent-role-sync.json', blockersOf(agentRoles)),
84
+ agent_type: codexNativeFeatureState({
85
+ ok: agentType.supported === true,
86
+ source: agentType.source === 'fixture' ? 'fixture' : 'actual-probe',
87
+ artifact_path: '.sneakoscope/reports/codex-agent-type-probe.json',
88
+ evidence: agentType.evidence,
89
+ blockers: [],
90
+ warnings: [...agentType.warnings, ...(agentType.supported ? [] : ['agent_type_unavailable_message_role_fallback'])],
91
+ unavailableStatus: 'fallback'
92
+ }),
93
+ mcp_inventory: codexNativeFeatureState({
94
+ ok: mcpCandidates.candidates.length > 0 || Array.isArray(plugins.plugins),
95
+ source: 'plugin-inventory',
96
+ artifact_path: '.sneakoscope/mcp-plugin-server-candidates.json',
97
+ evidence: [`candidate_count:${mcpCandidates.candidates.length}`],
98
+ blockers: [...plugins.blockers, ...mcpCandidates.blockers],
99
+ warnings: mcpCandidates.candidates.length ? [] : ['mcp_plugin_candidates_empty'],
100
+ unavailableStatus: 'fallback'
101
+ }),
102
+ app_handoff: boolState(booleanFeature(cap0138, 'supports_app_handoff'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
103
+ image_path_exposure: boolState(booleanFeature(cap0138, 'supports_image_path_exposure'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
104
+ code_mode_web_search: boolState(booleanFeature(cap0139, 'supports_code_mode_web_search'), 'actual-probe', '.sneakoscope/codex-0139-capability.json', blockersOf(cap0139)),
105
+ slash_command_bridge: boolState(true, 'config', '.sneakoscope/reports/codex-native-feature-matrix.json'),
106
+ project_memory: boolState(true, 'config', '.sneakoscope/context/AGENTS.generated.md')
107
+ };
108
+ const matrixBase = {
109
+ schema: 'sks.codex-native-feature-matrix.v1',
110
+ generated_at: nowIso(),
111
+ ok: false,
112
+ codex_cli: { available: Boolean(codexBin), version, bin: codexBin },
113
+ features,
114
+ probes: {
115
+ codex_0138: cap0138,
116
+ codex_0139: cap0139,
117
+ app,
118
+ plugin_inventory: plugins,
119
+ mcp_candidates: mcpCandidates,
120
+ hook_approval: hookApproval,
121
+ agent_type: agentType,
122
+ skill_sync: skillSync,
123
+ agent_roles: agentRoles
124
+ },
125
+ invocation_defaults: {
126
+ loop_worker_role_strategy: 'message-role',
127
+ qa_visual_review_strategy: 'headless-artifact',
128
+ research_source_strategy: 'local-files',
129
+ image_followup_strategy: 'artifact-path',
130
+ hook_evidence_policy: 'unknown-do-not-count',
131
+ skill_bridge_strategy: 'cli-only'
132
+ },
133
+ blockers: [
134
+ ...(!codexBin ? ['codex_cli_missing'] : []),
135
+ ...Object.values(features).flatMap((feature) => feature.blockers)
136
+ ],
137
+ warnings: Object.values(features).flatMap((feature) => feature.warnings)
138
+ };
139
+ const matrix = {
140
+ ...matrixBase,
141
+ ok: matrixBase.blockers.length === 0,
142
+ invocation_defaults: computeCodexNativeInvocationDefaults(matrixBase)
143
+ };
144
+ await writeCodexNativeFeatureMatrix(root, matrix, input.missionDir);
145
+ return matrix;
146
+ }
147
+ export async function writeCodexNativeFeatureMatrix(root, matrix, missionDir) {
148
+ await writeJsonAtomic(path.join(root, REPORT_PATH), matrix);
149
+ if (missionDir)
150
+ await writeJsonAtomic(path.join(missionDir, 'codex-native-feature-matrix.json'), matrix).catch(() => undefined);
151
+ }
152
+ function boolState(ok, source, artifactPath, blockers = [], warnings = []) {
153
+ return codexNativeFeatureState({
154
+ ok,
155
+ source,
156
+ artifact_path: artifactPath,
157
+ blockers: ok ? [] : blockers,
158
+ warnings: ok ? warnings : [...warnings, ...(!blockers.length ? ['feature_unavailable'] : [])]
159
+ });
160
+ }
161
+ async function codexVersion(bin) {
162
+ const run = await runProcess(bin, ['--version'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch(() => null);
163
+ return run?.code === 0 ? `${run.stdout || run.stderr || ''}`.trim() || null : null;
164
+ }
165
+ function booleanFeature(value, key) {
166
+ return isRecord(value) && value[key] === true;
167
+ }
168
+ function blockersOf(value) {
169
+ if (!isRecord(value) || !Array.isArray(value.blockers))
170
+ return [];
171
+ return value.blockers.map((item) => String(item)).filter(Boolean);
172
+ }
173
+ function recordOk(value) {
174
+ return isRecord(value) && typeof value.ok === 'boolean' ? value.ok : undefined;
175
+ }
176
+ function isRecord(value) {
177
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
178
+ }
179
+ function messageOf(err) {
180
+ return err instanceof Error ? err.message : String(err);
181
+ }
182
+ //# 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
@@ -0,0 +1,54 @@
1
+ import { buildCodexNativeFeatureMatrix } from './codex-native-feature-broker.js';
2
+ export async function buildCodexAppHarnessMatrixFromNative(input) {
3
+ const matrix = await buildCodexNativeFeatureMatrix(input);
4
+ return codexAppHarnessMatrixFromNative(matrix);
5
+ }
6
+ export function codexAppHarnessMatrixFromNative(matrix) {
7
+ const hookApproval = probeRecord(matrix.probes.hook_approval);
8
+ const agentType = probeRecord(matrix.probes.agent_type);
9
+ const hookState = typeof hookApproval.approval_state === 'string' ? hookApproval.approval_state : 'unknown';
10
+ return {
11
+ schema: 'sks.codex-app-harness-matrix.v1',
12
+ generated_at: matrix.generated_at,
13
+ ok: matrix.ok,
14
+ codex_cli: {
15
+ available: matrix.codex_cli.available,
16
+ version: matrix.codex_cli.version
17
+ },
18
+ app_features: {
19
+ plugin_json: matrix.features.plugin_json.ok,
20
+ marketplace_add: matrix.features.plugin_marketplace.ok,
21
+ marketplace_upgrade: matrix.features.plugin_marketplace.ok,
22
+ startup_review_detectable: hookState !== 'unknown',
23
+ hook_approval_state_detectable: hookState !== 'unknown',
24
+ hook_approval_state: hookState === 'approved'
25
+ || hookState === 'pending_review'
26
+ || hookState === 'modified_requires_reapproval'
27
+ || hookState === 'not_installed'
28
+ ? hookState
29
+ : 'unknown',
30
+ skill_picker_ready: matrix.features.skill_picker.ok,
31
+ agent_type_supported: matrix.features.agent_type.ok,
32
+ mcp_inventory_ready: matrix.features.mcp_inventory.ok,
33
+ app_handoff_ready: matrix.features.app_handoff.ok,
34
+ image_path_exposure_ready: matrix.features.image_path_exposure.ok
35
+ },
36
+ sks_integrations: {
37
+ dollar_skills_synced: matrix.features.skill_sync.ok,
38
+ agent_roles_synced: matrix.features.agent_roles.ok,
39
+ hooks_synced: hookState === 'approved',
40
+ init_deep_available: matrix.features.project_memory.ok,
41
+ loop_mesh_app_profile_available: true
42
+ },
43
+ probes: {
44
+ hook_approval: hookApproval,
45
+ agent_type: agentType
46
+ },
47
+ blockers: matrix.blockers,
48
+ warnings: matrix.warnings
49
+ };
50
+ }
51
+ function probeRecord(value) {
52
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
53
+ }
54
+ //# sourceMappingURL=codex-native-harness-compat.js.map
@@ -1,38 +1,36 @@
1
- import fs from 'node:fs/promises';
2
1
  import path from 'node:path';
3
2
  import os from 'node:os';
3
+ import fs from 'node:fs/promises';
4
4
  import { nowIso, writeJsonAtomic } from '../fsx.js';
5
5
  import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
6
- export async function buildLazyCodexInteropPolicy(input) {
6
+ const RESERVED_EXTERNAL_ROUTE_SKILLS = ['ulw-loop', 'ulw-plan', 'start-work'];
7
+ export async function buildCodexNativeInteropPolicy(input) {
7
8
  const root = path.resolve(input.root);
8
9
  const inventory = normalizeInventory(input.inventory || await buildCodexPluginInventory().catch((err) => ({ plugins: [], blockers: [messageOf(err)] })));
9
10
  const codexHome = input.codexHome || process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
10
11
  const skillNames = await discoverSkillNames([path.join(root, '.agents', 'skills'), path.join(codexHome, 'skills')]);
11
- const pluginIds = (inventory.plugins || []).map((plugin) => `${plugin.id || ''} ${plugin.name || ''}`.toLowerCase());
12
- const lazycodexInstalled = pluginIds.some((id) => id.includes('omo') || id.includes('lazycodex'))
13
- || ['ulw-loop', 'ulw-plan', 'start-work'].some((name) => skillNames.includes(name));
14
- const collisions = ['ulw-loop', 'ulw-plan', 'start-work'].filter((name) => skillNames.includes(name));
12
+ const pluginIds = (inventory.plugins || []).map((plugin) => `${plugin.id || ''} ${plugin.name || ''}`.toLowerCase()).filter(Boolean);
13
+ const preservedSkillNames = RESERVED_EXTERNAL_ROUTE_SKILLS.filter((name) => skillNames.includes(name));
15
14
  const report = {
16
- schema: 'sks.lazycodex-interop-policy.v1',
15
+ schema: 'sks.codex-native-interop-policy.v1',
17
16
  generated_at: nowIso(),
18
17
  ok: true,
19
18
  mode: input.mode || 'coexist',
20
- lazycodex_detected: lazycodexInstalled,
21
19
  detection: {
22
20
  plugin_inventory_ids: pluginIds,
23
21
  skill_names: skillNames,
24
- collisions
22
+ preserved_skill_names: preservedSkillNames
25
23
  },
26
24
  policy: {
27
- clobber_lazycodex_skills: false,
28
25
  clobber_user_skills: false,
29
- default_mode: 'coexist',
30
- explicit_handoff_required: true
26
+ clobber_external_route_assets: false,
27
+ explicit_handoff_required: true,
28
+ artifact_names_brand_neutral: true
31
29
  },
32
- actions: collisions.map((name) => `preserve_existing_skill:${name}`),
30
+ actions: preservedSkillNames.map((name) => `preserve_existing_skill:${name}`),
33
31
  blockers: []
34
32
  };
35
- await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'lazycodex-interop-policy.json'), report).catch(() => undefined);
33
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-interop-policy.json'), report).catch(() => undefined);
36
34
  return report;
37
35
  }
38
36
  function normalizeInventory(value) {
@@ -57,4 +55,4 @@ async function discoverSkillNames(roots) {
57
55
  }
58
56
  return [...names].sort();
59
57
  }
60
- //# sourceMappingURL=lazycodex-interop-policy.js.map
58
+ //# sourceMappingURL=codex-native-interop-policy.js.map
@@ -0,0 +1,112 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { buildCodexNativeFeatureMatrix } from './codex-native-feature-broker.js';
4
+ export async function resolveCodexNativeInvocationPlan(input) {
5
+ const root = path.resolve(input.root || process.cwd());
6
+ const matrix = input.matrix || await buildCodexNativeFeatureMatrix({
7
+ root,
8
+ missionDir: input.missionId ? path.join(root, '.sneakoscope', 'missions', input.missionId) : null
9
+ });
10
+ const plan = planFor(matrix, input.route, input.desiredCapability);
11
+ if (input.missionId) {
12
+ const filename = `codex-native-invocation-plan.${routeSlug(input.route)}.${capabilitySlug(input.desiredCapability)}.json`;
13
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'missions', input.missionId, filename), plan).catch(() => undefined);
14
+ }
15
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-invocation-plan.json'), plan).catch(() => undefined);
16
+ return plan;
17
+ }
18
+ function planFor(matrix, route, capability) {
19
+ const blockers = [];
20
+ const warnings = [];
21
+ let selected = 'sks-managed-artifact';
22
+ const artifacts = ['.sneakoscope/reports/codex-native-feature-matrix.json'];
23
+ const proofPolicy = ['record selected strategy and blockers before counting route evidence'];
24
+ if (capability === 'agent-role') {
25
+ if (matrix.invocation_defaults.loop_worker_role_strategy === 'agent_type') {
26
+ selected = 'codex-app-native';
27
+ proofPolicy.push('include native agent_type payload in worker proof');
28
+ }
29
+ else {
30
+ selected = 'message-role-fallback';
31
+ warnings.push('agent_type unavailable; message-role fallback active');
32
+ proofPolicy.push('include message-role fallback in worker proof');
33
+ }
34
+ }
35
+ else if (capability === 'visual-review') {
36
+ if (matrix.invocation_defaults.hook_evidence_policy === 'unknown-do-not-count')
37
+ warnings.push('hook-derived evidence will not count');
38
+ selected = matrix.invocation_defaults.qa_visual_review_strategy === 'app-handoff' ? 'codex-app-native' : 'sks-managed-artifact';
39
+ if (matrix.invocation_defaults.qa_visual_review_strategy === 'blocked') {
40
+ selected = 'blocked';
41
+ blockers.push('qa_visual_review_unavailable');
42
+ }
43
+ proofPolicy.push('do not pass visual review without accepted artifact or app handoff confirmation');
44
+ }
45
+ else if (capability === 'plugin-source' || capability === 'mcp-source') {
46
+ if (matrix.features.mcp_inventory.ok) {
47
+ selected = 'codex-app-native';
48
+ artifacts.push('.sneakoscope/mcp-plugin-server-candidates.json');
49
+ proofPolicy.push('treat remote MCP servers as candidates only until explicitly enabled');
50
+ }
51
+ else if (matrix.features.code_mode_web_search.ok) {
52
+ selected = 'codex-cli-headless';
53
+ warnings.push('plugin inventory unavailable; code/web source fallback selected');
54
+ }
55
+ else {
56
+ selected = 'sks-managed-artifact';
57
+ warnings.push('plugin inventory unavailable; local/source-ledger fallback selected');
58
+ }
59
+ }
60
+ else if (capability === 'web-search') {
61
+ selected = matrix.features.code_mode_web_search.ok ? 'codex-cli-headless' : 'sks-managed-artifact';
62
+ if (!matrix.features.code_mode_web_search.ok)
63
+ warnings.push('code-mode web search not verified');
64
+ }
65
+ else if (capability === 'image-followup') {
66
+ selected = matrix.features.image_path_exposure.ok ? 'codex-app-native' : 'sks-managed-artifact';
67
+ proofPolicy.push(matrix.features.image_path_exposure.ok ? 'include model-visible image path' : 'use saved artifact path contract');
68
+ }
69
+ else if (capability === 'hook-evidence') {
70
+ if (matrix.invocation_defaults.hook_evidence_policy !== 'approved-only') {
71
+ selected = 'blocked';
72
+ blockers.push('hook_approval_not_approved');
73
+ warnings.push('hook-derived proof cannot count');
74
+ }
75
+ else {
76
+ selected = 'codex-app-native';
77
+ }
78
+ proofPolicy.push('approved hooks only; unknown hook state does not count');
79
+ }
80
+ else if (capability === 'project-memory') {
81
+ selected = matrix.features.project_memory.ok ? 'sks-managed-artifact' : 'blocked';
82
+ if (!matrix.features.project_memory.ok)
83
+ blockers.push('project_memory_unavailable');
84
+ proofPolicy.push('project memory is guidance only and never expands write scope');
85
+ }
86
+ return {
87
+ schema: 'sks.codex-native-invocation-plan.v1',
88
+ generated_at: nowIso(),
89
+ ok: blockers.length === 0,
90
+ route,
91
+ desired_capability: capability,
92
+ selected_strategy: selected,
93
+ required_artifacts: artifacts,
94
+ proof_policy: proofPolicy,
95
+ env: {
96
+ SKS_CODEX_NATIVE_STRATEGY: selected,
97
+ SKS_CODEX_NATIVE_FEATURE_MATRIX: '.sneakoscope/reports/codex-native-feature-matrix.json',
98
+ SKS_CODEX_NATIVE_AGENT_ROLE_STRATEGY: matrix.invocation_defaults.loop_worker_role_strategy,
99
+ SKS_CODEX_NATIVE_HOOK_EVIDENCE_POLICY: matrix.invocation_defaults.hook_evidence_policy
100
+ },
101
+ blockers,
102
+ warnings,
103
+ feature_matrix_artifact: '.sneakoscope/reports/codex-native-feature-matrix.json'
104
+ };
105
+ }
106
+ function routeSlug(route) {
107
+ return route.replace(/^\$/, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
108
+ }
109
+ function capabilitySlug(capability) {
110
+ return capability.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
111
+ }
112
+ //# sourceMappingURL=codex-native-invocation-router.js.map