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.
- package/README.md +8 -36
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -2
- package/dist/commands/codex-app.js +1 -11
- package/dist/commands/codex-native.js +68 -0
- package/dist/commands/doctor.js +64 -0
- package/dist/core/codex-app/codex-agent-role-sync.js +11 -5
- package/dist/core/codex-app/codex-app-execution-profile.js +38 -16
- package/dist/core/codex-app/codex-app-harness-matrix.js +4 -117
- package/dist/core/codex-app/codex-hook-lifecycle.js +4 -1
- package/dist/core/codex-app/codex-init-deep.js +62 -4
- package/dist/core/codex-app/codex-skill-sync.js +10 -7
- package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +182 -0
- package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
- package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
- package/dist/core/{codex-app/lazycodex-interop-policy.js → codex-native/codex-native-interop-policy.js} +13 -15
- package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
- package/dist/core/codex-native/codex-native-pattern-analysis.js +56 -0
- package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
- package/dist/core/codex-native/codex-native-reference-source.js +110 -0
- package/dist/core/codex-native/codex-native-rename-map.js +25 -0
- package/dist/core/feature-fixtures.js +2 -4
- package/dist/core/feature-registry.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/image/image-artifact-path-contract.js +18 -1
- package/dist/core/loops/loop-owner-inference.js +3 -0
- package/dist/core/loops/loop-planner.js +8 -2
- package/dist/core/loops/loop-worker-runtime.js +34 -6
- package/dist/core/qa-loop.js +24 -1
- package/dist/core/research.js +36 -3
- package/dist/core/routes.js +2 -3
- package/dist/core/version.js +1 -1
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +4 -33
- package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
- package/package.json +32 -12
- package/dist/cli/hermes-command.js +0 -99
- package/dist/cli/openclaw-command.js +0 -83
- package/dist/commands/hermes.js +0 -5
- package/dist/commands/openclaw.js +0 -3
- package/dist/core/codex-app/lazycodex-analysis.js +0 -72
- package/dist/core/codex-app/lazycodex-live-analyzer.js +0 -98
- package/dist/core/hermes.js +0 -192
- 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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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 (
|
|
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
|
-
|
|
55
|
+
external_route_names_preserved: collisions,
|
|
56
56
|
interop: {
|
|
57
57
|
mode: 'coexist',
|
|
58
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
22
|
+
preserved_skill_names: preservedSkillNames
|
|
25
23
|
},
|
|
26
24
|
policy: {
|
|
27
|
-
clobber_lazycodex_skills: false,
|
|
28
25
|
clobber_user_skills: false,
|
|
29
|
-
|
|
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:
|
|
30
|
+
actions: preservedSkillNames.map((name) => `preserve_existing_skill:${name}`),
|
|
33
31
|
blockers: []
|
|
34
32
|
};
|
|
35
|
-
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', '
|
|
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=
|
|
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
|