sneakoscope 3.1.6 → 3.1.8
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 +10 -3
- 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/commands/codex-app.js +20 -2
- package/dist/commands/codex-native.js +18 -2
- package/dist/commands/doctor.js +106 -12
- package/dist/core/codex-app/codex-agent-role-sync.js +9 -5
- package/dist/core/codex-app/codex-init-deep.js +6 -2
- package/dist/core/codex-app/codex-skill-sync.js +80 -152
- package/dist/core/codex-control/codex-0138-capability.js +5 -2
- package/dist/core/codex-native/codex-native-feature-broker.js +74 -6
- package/dist/core/codex-native/codex-native-pattern-analysis.js +14 -2
- package/dist/core/codex-native/codex-native-reference-cache.js +98 -0
- package/dist/core/codex-native/codex-native-reference-source.js +50 -11
- package/dist/core/codex-native/codex-native-repair-transaction.js +150 -0
- package/dist/core/codex-native/core-skill-integrity.js +89 -0
- package/dist/core/codex-native/core-skill-manifest.js +156 -0
- package/dist/core/codex-native/native-capability-postcheck.js +35 -0
- package/dist/core/codex-native/native-capability-repair-matrix.js +210 -0
- package/dist/core/codex-native/native-capability-repair.js +47 -0
- package/dist/core/codex-native/native-media-computer-repair.js +5 -0
- package/dist/core/codex-native/project-skill-dedupe.js +109 -0
- package/dist/core/codex-native/skill-name-canonicalizer.js +21 -0
- package/dist/core/codex-native/skill-registry-ledger.js +85 -0
- package/dist/core/codex-plugins/codex-plugin-json.js +5 -2
- package/dist/core/commands/basic-cli.js +15 -9
- package/dist/core/commands/mad-sks-command.js +16 -0
- package/dist/core/config/config-migration-journal.js +27 -0
- package/dist/core/config/managed-config-merge.js +105 -0
- package/dist/core/config/secret-preservation.js +169 -0
- package/dist/core/config/supabase-secret-preservation.js +29 -0
- package/dist/core/doctor/doctor-native-capability-repair.js +48 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +5 -1
- package/dist/core/loops/loop-planner.js +1 -1
- package/dist/core/loops/loop-worker-prompts.js +2 -0
- package/dist/core/loops/loop-worker-runtime.js +8 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-native-runtime-e2e-fixture.js +75 -0
- package/dist/scripts/loop-worker-fixture-child.js +2 -1
- package/dist/scripts/sizecheck.js +8 -2
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
- package/dist/scripts/sks-3-1-6-directive-check-lib.js +2 -2
- package/dist/scripts/sks-3-1-7-directive-check-lib.js +58 -0
- package/dist/scripts/sks-3-1-8-check-lib.js +30 -0
- package/package.json +39 -2
|
@@ -1,167 +1,95 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'$Naruto',
|
|
9
|
-
'$QA-LOOP',
|
|
10
|
-
'$Research',
|
|
11
|
-
'$DFix',
|
|
12
|
-
'$Image-UX-Review',
|
|
13
|
-
'$Computer-Use',
|
|
14
|
-
'$Init-Deep'
|
|
15
|
-
];
|
|
4
|
+
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { buildSksCoreSkillManifest } from '../codex-native/core-skill-manifest.js';
|
|
6
|
+
import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
|
|
7
|
+
import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
|
|
16
8
|
const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
|
|
17
9
|
export async function syncCodexSksSkills(input) {
|
|
18
10
|
const root = path.resolve(input.root);
|
|
19
11
|
const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
12
|
+
return withSkillSyncLock(root, async () => {
|
|
13
|
+
const beforeExisting = await listSkillNames(skillsRoot);
|
|
14
|
+
const reserved = beforeExisting.filter((name) => EXTERNAL_ROUTE_RESERVED.has(name));
|
|
15
|
+
const manifest = buildSksCoreSkillManifest();
|
|
16
|
+
const desired = manifest.skills.map((skill) => skill.canonical_name);
|
|
17
|
+
const integrity = await syncCoreSkillsIntegrity({
|
|
18
|
+
root,
|
|
19
|
+
apply: input.apply === true,
|
|
20
|
+
skillsRoot,
|
|
21
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'core-skill-integrity.json')
|
|
22
|
+
});
|
|
23
|
+
const dedupe = await dedupeProjectSkills({
|
|
24
|
+
root,
|
|
25
|
+
fix: input.apply === true,
|
|
26
|
+
yes: true,
|
|
27
|
+
quarantineUserDuplicates: false,
|
|
28
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'project-skill-dedupe.json')
|
|
29
|
+
}).catch((err) => ({
|
|
30
|
+
ok: false,
|
|
31
|
+
blockers: [err instanceof Error ? err.message : String(err)],
|
|
32
|
+
unresolved_user_duplicates: [],
|
|
33
|
+
actions: []
|
|
34
|
+
}));
|
|
35
|
+
const report = {
|
|
36
|
+
schema: 'sks.codex-skill-sync.v1',
|
|
37
|
+
generated_at: nowIso(),
|
|
38
|
+
ok: integrity.ok && dedupe.ok !== false,
|
|
39
|
+
apply: input.apply === true,
|
|
40
|
+
skills_root: skillsRoot,
|
|
41
|
+
desired_skills: desired,
|
|
42
|
+
existing_skills: beforeExisting,
|
|
43
|
+
created: integrity.installed,
|
|
44
|
+
skipped: integrity.skipped_user_authored,
|
|
45
|
+
external_route_names_preserved: reserved,
|
|
46
|
+
integrity_report: '.sneakoscope/reports/core-skill-integrity.json',
|
|
47
|
+
dedupe_report: '.sneakoscope/reports/project-skill-dedupe.json',
|
|
48
|
+
interop: {
|
|
49
|
+
mode: 'coexist',
|
|
50
|
+
clobbered_external_routes: false,
|
|
51
|
+
clobbered_user_skills: false,
|
|
52
|
+
skipped_user_skills: integrity.skipped_user_authored,
|
|
53
|
+
managed_skills: desired
|
|
54
|
+
},
|
|
55
|
+
blockers: [...integrity.blockers, ...(dedupe.blockers || [])]
|
|
56
|
+
};
|
|
57
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-skill-sync.json'), report).catch(() => undefined);
|
|
58
|
+
return report;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export async function withSkillSyncLock(root, fn) {
|
|
62
|
+
const lockPath = path.join(path.resolve(root), '.sneakoscope', 'locks', 'skill-sync.lock');
|
|
63
|
+
await ensureDir(path.dirname(lockPath));
|
|
64
|
+
const started = Date.now();
|
|
65
|
+
while (true) {
|
|
66
|
+
try {
|
|
67
|
+
await fs.mkdir(lockPath);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
72
|
+
if (code !== 'EEXIST')
|
|
73
|
+
throw err;
|
|
74
|
+
if (Date.now() - started > 30000)
|
|
75
|
+
throw new Error(`Timed out waiting for skill sync lock: ${lockPath}`);
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
43
77
|
}
|
|
44
78
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
interop: {
|
|
57
|
-
mode: 'coexist',
|
|
58
|
-
clobbered_external_routes: false,
|
|
59
|
-
clobbered_user_skills: false
|
|
60
|
-
},
|
|
61
|
-
blockers: []
|
|
62
|
-
};
|
|
63
|
-
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-skill-sync.json'), report).catch(() => undefined);
|
|
64
|
-
return report;
|
|
79
|
+
try {
|
|
80
|
+
await writeJsonAtomic(path.join(lockPath, 'owner.json'), {
|
|
81
|
+
schema: 'sks.skill-sync-lock.v1',
|
|
82
|
+
pid: process.pid,
|
|
83
|
+
acquired_at: nowIso()
|
|
84
|
+
}).catch(() => undefined);
|
|
85
|
+
return await fn();
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
89
|
+
}
|
|
65
90
|
}
|
|
66
91
|
async function listSkillNames(root) {
|
|
67
92
|
const rows = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
68
93
|
return rows.filter((row) => row.isDirectory()).map((row) => row.name).sort();
|
|
69
94
|
}
|
|
70
|
-
function skillName(value) {
|
|
71
|
-
return value.replace(/^\$/, '').toLowerCase();
|
|
72
|
-
}
|
|
73
|
-
function skillContent(name) {
|
|
74
|
-
const profile = skillProfile(name);
|
|
75
|
-
const body = [
|
|
76
|
-
'---',
|
|
77
|
-
`name: ${name}`,
|
|
78
|
-
`description: SKS managed Codex App route bridge for ${profile.command}.`,
|
|
79
|
-
'---',
|
|
80
|
-
'',
|
|
81
|
-
'<!-- BEGIN SKS MANAGED SKILL -->',
|
|
82
|
-
`Command: ${profile.command}`,
|
|
83
|
-
`Purpose: ${profile.purpose}`,
|
|
84
|
-
`Use when: ${profile.when}`,
|
|
85
|
-
`Route: ${profile.command}`,
|
|
86
|
-
`Evidence: ${profile.evidence}`,
|
|
87
|
-
'Safety: keep route state bounded, preserve user and external route assets, and stop on hard blockers instead of fabricating fallback behavior.',
|
|
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.',
|
|
90
|
-
`Fallback: ${profile.fallback}`,
|
|
91
|
-
`checksum: ${hash(`${name}:${profile.command}:${profile.evidence}:${profile.fallback}`)}`,
|
|
92
|
-
'<!-- END SKS MANAGED SKILL -->',
|
|
93
|
-
''
|
|
94
|
-
].join('\n');
|
|
95
|
-
return body;
|
|
96
|
-
}
|
|
97
|
-
function skillProfile(name) {
|
|
98
|
-
const table = {
|
|
99
|
-
loop: {
|
|
100
|
-
command: '$Loop',
|
|
101
|
-
purpose: 'compile persisted route work into bounded loop plans with continuation evidence.',
|
|
102
|
-
when: 'a mission needs stage-by-stage execution, memory hints, or resume-safe artifacts.',
|
|
103
|
-
evidence: '.sneakoscope/loops/** plus codex-app-execution-profile.json',
|
|
104
|
-
fallback: 'use message-role routing when native agent_type is not verified.'
|
|
105
|
-
},
|
|
106
|
-
naruto: {
|
|
107
|
-
command: '$Naruto',
|
|
108
|
-
purpose: 'fan out bounded native worker lanes for high-scale review or implementation.',
|
|
109
|
-
when: 'parallel lanes are explicitly selected by the route and parent integration remains owner.',
|
|
110
|
-
evidence: 'naruto work graph, worker ledgers, and execution profile payloads.',
|
|
111
|
-
fallback: 'degrade to message-role workers without dropping proof artifacts.'
|
|
112
|
-
},
|
|
113
|
-
'qa-loop': {
|
|
114
|
-
command: '$QA-LOOP',
|
|
115
|
-
purpose: 'dogfood UI/API behavior with gate artifacts and current execution profile.',
|
|
116
|
-
when: 'route completion needs human-proxy verification or app handoff checks.',
|
|
117
|
-
evidence: 'qa-loop gate/result ledgers and codex-app-execution-profile.json.',
|
|
118
|
-
fallback: 'record the unavailable surface as blocked rather than inventing visual proof.'
|
|
119
|
-
},
|
|
120
|
-
research: {
|
|
121
|
-
command: '$Research',
|
|
122
|
-
purpose: 'run evidence-bound research cycles with source routing and synthesis ledgers.',
|
|
123
|
-
when: 'the request depends on discovery, evaluation, or external-source claims.',
|
|
124
|
-
evidence: 'research plan, source ledger, cycle record, and execution profile routing.',
|
|
125
|
-
fallback: 'mark unavailable source tools explicitly and avoid unsupported live-accuracy claims.'
|
|
126
|
-
},
|
|
127
|
-
dfix: {
|
|
128
|
-
command: '$DFix',
|
|
129
|
-
purpose: 'perform tiny direct fixes without the full Team route.',
|
|
130
|
-
when: 'copy/config/docs/labels/spacing/translation/mechanical edits are truly narrow.',
|
|
131
|
-
evidence: 'focused diff plus DFix Honest check.',
|
|
132
|
-
fallback: 'route broad implementation through Team/Loop instead.'
|
|
133
|
-
},
|
|
134
|
-
'image-ux-review': {
|
|
135
|
-
command: '$Image-UX-Review',
|
|
136
|
-
purpose: 'produce generated annotated UI review images and extract issue ledgers.',
|
|
137
|
-
when: 'visual UX critique is requested from screenshots or app captures.',
|
|
138
|
-
evidence: 'source inventory, generated annotation images, extracted issue ledger.',
|
|
139
|
-
fallback: 'block if raster annotation cannot be produced.'
|
|
140
|
-
},
|
|
141
|
-
'computer-use': {
|
|
142
|
-
command: '$Computer-Use',
|
|
143
|
-
purpose: 'operate native macOS desktop apps through the fast Computer Use lane.',
|
|
144
|
-
when: 'the task depends on non-web desktop UI or OS settings.',
|
|
145
|
-
evidence: 'desktop interaction notes/screenshots where available.',
|
|
146
|
-
fallback: 'use Browser/Chrome only for web targets.'
|
|
147
|
-
},
|
|
148
|
-
'init-deep': {
|
|
149
|
-
command: '$Init-Deep',
|
|
150
|
-
purpose: 'refresh project-local memory, directory AGENTS sections, and loop memory hints.',
|
|
151
|
-
when: 'a route needs deeper local context or directory-specific instruction recall.',
|
|
152
|
-
evidence: '.sneakoscope/context/AGENTS.generated.md and managed directory AGENTS blocks.',
|
|
153
|
-
fallback: 'preserve user content and skip directories that cannot be safely updated.'
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
return table[name] || {
|
|
157
|
-
command: `$${name}`,
|
|
158
|
-
purpose: 'bridge an SKS managed Codex App route.',
|
|
159
|
-
when: 'the matching SKS route is explicitly requested.',
|
|
160
|
-
evidence: '.sneakoscope route artifacts.',
|
|
161
|
-
fallback: 'record blockers with evidence.'
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
function hash(value) {
|
|
165
|
-
return crypto.createHash('sha256').update(`sks-skill:${value}`).digest('hex').slice(0, 12);
|
|
166
|
-
}
|
|
167
95
|
//# sourceMappingURL=codex-skill-sync.js.map
|
|
@@ -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:
|
|
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.
|
|
82
|
+
app_handoff_platform: process.env.SKS_CODEX_0138_FAKE_APP_HANDOFF_FAIL === '1'
|
|
83
|
+
? 'failed'
|
|
84
|
+
: process.platform === 'darwin' || process.platform === 'win32' ? 'passed' : 'failed',
|
|
82
85
|
image_path_exposure_contract: 'sks-enforced'
|
|
83
86
|
};
|
|
84
87
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
2
3
|
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
4
|
import { codexAppIntegrationStatus } from '../codex-app.js';
|
|
4
|
-
import { syncCodexAgentRoles } from '../codex-app/codex-agent-role-sync.js';
|
|
5
5
|
import { probeCodexAgentTypeSupport } from '../codex-app/codex-agent-type-probe.js';
|
|
6
6
|
import { probeCodexHookApprovalState } from '../codex-app/codex-hook-approval-probe.js';
|
|
7
|
-
import { syncCodexSksSkills } from '../codex-app/codex-skill-sync.js';
|
|
8
7
|
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
9
8
|
import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
|
|
10
9
|
import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
|
|
@@ -12,8 +11,13 @@ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
|
12
11
|
import { buildMcpPluginServerCandidates } from '../mcp/mcp-plugin-inventory.js';
|
|
13
12
|
import { codexNativeFeatureState, computeCodexNativeInvocationDefaults } from './codex-native-feature-matrix.js';
|
|
14
13
|
const REPORT_PATH = '.sneakoscope/reports/codex-native-feature-matrix.json';
|
|
14
|
+
const REQUIRED_SKILL_NAMES = ['loop', 'naruto', 'qa-loop', 'research', 'dfix', 'image-ux-review', 'computer-use', 'init-deep'];
|
|
15
|
+
const REQUIRED_AGENT_ROLES = ['sks-explorer', 'sks-planner', 'sks-implementer', 'sks-checker', 'sks-release-verifier', 'sks-zellij-ui-verifier', 'sks-codex-probe-verifier'];
|
|
15
16
|
export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd() }) {
|
|
16
17
|
const root = path.resolve(input.root || process.cwd());
|
|
18
|
+
const deprecatedApplyRepairs = input.applyRepairs === true;
|
|
19
|
+
const mode = input.mode || (deprecatedApplyRepairs || input.repairManagedAssets === true ? 'repair' : 'read-only');
|
|
20
|
+
const repairManagedAssets = mode === 'repair' && (input.repairManagedAssets === true || deprecatedApplyRepairs);
|
|
17
21
|
const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
|
|
18
22
|
const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
|
|
19
23
|
const version = codexBin ? await codexVersion(codexBin) : null;
|
|
@@ -57,8 +61,8 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
57
61
|
blockers: [messageOf(err)],
|
|
58
62
|
warnings: ['agent_type_probe_failed_message_role_fallback']
|
|
59
63
|
}));
|
|
60
|
-
const skillSync = await
|
|
61
|
-
const agentRoles = await
|
|
64
|
+
const skillSync = await inspectManagedSkillState(root);
|
|
65
|
+
const agentRoles = await inspectManagedAgentRoleState(root);
|
|
62
66
|
const appRecord = isRecord(app) ? app : {};
|
|
63
67
|
const requiredSkills = isRecord(appRecord.required_skills) ? appRecord.required_skills : {};
|
|
64
68
|
const skills = isRecord(appRecord.skills) ? appRecord.skills : {};
|
|
@@ -91,7 +95,7 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
91
95
|
unavailableStatus: 'fallback'
|
|
92
96
|
}),
|
|
93
97
|
mcp_inventory: codexNativeFeatureState({
|
|
94
|
-
ok: mcpCandidates.candidates.length > 0
|
|
98
|
+
ok: mcpCandidates.candidates.length > 0,
|
|
95
99
|
source: 'plugin-inventory',
|
|
96
100
|
artifact_path: '.sneakoscope/mcp-plugin-server-candidates.json',
|
|
97
101
|
evidence: [`candidate_count:${mcpCandidates.candidates.length}`],
|
|
@@ -134,7 +138,11 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
134
138
|
...(!codexBin ? ['codex_cli_missing'] : []),
|
|
135
139
|
...Object.values(features).flatMap((feature) => feature.blockers)
|
|
136
140
|
],
|
|
137
|
-
warnings:
|
|
141
|
+
warnings: [
|
|
142
|
+
...Object.values(features).flatMap((feature) => feature.warnings),
|
|
143
|
+
...(deprecatedApplyRepairs ? ['deprecated_apply_repairs_input'] : []),
|
|
144
|
+
...(mode === 'repair' && !repairManagedAssets ? ['repair_mode_without_managed_asset_repair'] : [])
|
|
145
|
+
]
|
|
138
146
|
};
|
|
139
147
|
const matrix = {
|
|
140
148
|
...matrixBase,
|
|
@@ -144,6 +152,66 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
144
152
|
await writeCodexNativeFeatureMatrix(root, matrix, input.missionDir);
|
|
145
153
|
return matrix;
|
|
146
154
|
}
|
|
155
|
+
async function inspectManagedSkillState(root) {
|
|
156
|
+
const skillRoots = [
|
|
157
|
+
path.join(root, '.agents', 'skills'),
|
|
158
|
+
...(process.env.CODEX_HOME ? [path.join(process.env.CODEX_HOME, 'skills')] : [])
|
|
159
|
+
];
|
|
160
|
+
let existingCount = 0;
|
|
161
|
+
const managed = new Set();
|
|
162
|
+
for (const dir of skillRoots) {
|
|
163
|
+
const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
164
|
+
existingCount += rows.filter((row) => row.isDirectory()).length;
|
|
165
|
+
for (const name of REQUIRED_SKILL_NAMES) {
|
|
166
|
+
if (managed.has(name))
|
|
167
|
+
continue;
|
|
168
|
+
const text = await fs.readFile(path.join(dir, name, 'SKILL.md'), 'utf8').catch(() => '');
|
|
169
|
+
if (text.includes('BEGIN SKS MANAGED SKILL'))
|
|
170
|
+
managed.add(name);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const missing = REQUIRED_SKILL_NAMES.filter((name) => !managed.has(name));
|
|
174
|
+
return {
|
|
175
|
+
ok: missing.length === 0,
|
|
176
|
+
apply: false,
|
|
177
|
+
artifact_path: '.sneakoscope/reports/codex-skill-sync.json',
|
|
178
|
+
existing_count: existingCount,
|
|
179
|
+
managed_count: managed.size,
|
|
180
|
+
missing_required: missing,
|
|
181
|
+
blockers: missing.length ? [`managed_skills_missing:${missing.join(',')}`] : [],
|
|
182
|
+
warnings: existingCount > managed.size ? ['non_sks_skill_dirs_ignored'] : []
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function inspectManagedAgentRoleState(root) {
|
|
186
|
+
const dirs = [
|
|
187
|
+
path.join(root, '.codex', 'agents'),
|
|
188
|
+
...(process.env.CODEX_HOME ? [path.join(process.env.CODEX_HOME, 'agents')] : [])
|
|
189
|
+
];
|
|
190
|
+
let existingCount = 0;
|
|
191
|
+
const managed = new Set();
|
|
192
|
+
for (const dir of dirs) {
|
|
193
|
+
const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
194
|
+
existingCount += rows.filter((row) => row.isFile() && row.name.endsWith('.toml')).length;
|
|
195
|
+
for (const role of REQUIRED_AGENT_ROLES) {
|
|
196
|
+
if (managed.has(role))
|
|
197
|
+
continue;
|
|
198
|
+
const text = await fs.readFile(path.join(dir, `${role}.toml`), 'utf8').catch(() => '');
|
|
199
|
+
if (text.includes('SKS managed 3.1.7 directive role'))
|
|
200
|
+
managed.add(role);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const missing = REQUIRED_AGENT_ROLES.filter((role) => !managed.has(role));
|
|
204
|
+
return {
|
|
205
|
+
ok: missing.length === 0,
|
|
206
|
+
apply: false,
|
|
207
|
+
artifact_path: '.sneakoscope/reports/codex-agent-role-sync.json',
|
|
208
|
+
existing_count: existingCount,
|
|
209
|
+
managed_count: managed.size,
|
|
210
|
+
missing_required: missing,
|
|
211
|
+
blockers: missing.length ? [`managed_agent_roles_missing:${missing.join(',')}`] : [],
|
|
212
|
+
warnings: existingCount > managed.size ? ['non_sks_agent_roles_ignored'] : []
|
|
213
|
+
};
|
|
214
|
+
}
|
|
147
215
|
export async function writeCodexNativeFeatureMatrix(root, matrix, missionDir) {
|
|
148
216
|
await writeJsonAtomic(path.join(root, REPORT_PATH), matrix);
|
|
149
217
|
if (missionDir)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
3
4
|
import { analyzeCodexNativeReferenceSource, renderCodexNativeReferenceMarkdown } from './codex-native-reference-source.js';
|
|
4
5
|
const PATTERN_ROWS = [
|
|
@@ -19,22 +20,26 @@ export async function buildCodexNativePatternAnalysis(input) {
|
|
|
19
20
|
const root = path.resolve(input.root);
|
|
20
21
|
const evidence = input.evidence || await analyzeCodexNativeReferenceSource({ root, sourceDir: input.sourceDir || null, writeReport: true }).catch(() => null);
|
|
21
22
|
const evidenceByPattern = new Map();
|
|
23
|
+
const confidenceByPattern = new Map();
|
|
22
24
|
for (const item of evidence?.evidence || []) {
|
|
23
25
|
const list = evidenceByPattern.get(item.pattern_id) || [];
|
|
24
26
|
list.push(item.snippet_hash);
|
|
25
27
|
evidenceByPattern.set(item.pattern_id, list);
|
|
28
|
+
confidenceByPattern.set(item.pattern_id, strongerConfidence(confidenceByPattern.get(item.pattern_id), item.confidence));
|
|
26
29
|
}
|
|
27
30
|
return {
|
|
28
31
|
schema: 'sks.codex-native-pattern-analysis.v1',
|
|
29
32
|
generated_at: nowIso(),
|
|
30
33
|
source_kind: 'external-reference-source',
|
|
31
|
-
source_ref: evidence?.source_ref || input.sourceDir || '.sneakoscope/cache/codex-native-reference',
|
|
34
|
+
source_ref: evidence?.source_ref || neutralSourceRef(input.sourceDir || '.sneakoscope/cache/codex-native-reference'),
|
|
32
35
|
source_sha: evidence?.source_sha || null,
|
|
36
|
+
source_url_hash: evidence?.source_url_hash || null,
|
|
37
|
+
cache_report_path: evidence?.cache_report_path || null,
|
|
33
38
|
patterns: PATTERN_ROWS.map((pattern) => {
|
|
34
39
|
const hashes = evidenceByPattern.get(pattern.id) || [];
|
|
35
40
|
return {
|
|
36
41
|
...pattern,
|
|
37
|
-
confidence: hashes.length ? '
|
|
42
|
+
confidence: confidenceByPattern.get(pattern.id) || (hashes.length ? 'medium' : evidence ? 'low' : 'low'),
|
|
38
43
|
evidence_hashes: hashes
|
|
39
44
|
};
|
|
40
45
|
}),
|
|
@@ -42,6 +47,13 @@ export async function buildCodexNativePatternAnalysis(input) {
|
|
|
42
47
|
warnings: evidence?.warnings || ['reference_evidence_unavailable']
|
|
43
48
|
};
|
|
44
49
|
}
|
|
50
|
+
function neutralSourceRef(value) {
|
|
51
|
+
return `source:${createHash('sha256').update(value).digest('hex').slice(0, 16)}`;
|
|
52
|
+
}
|
|
53
|
+
function strongerConfidence(current, next) {
|
|
54
|
+
const rank = { low: 0, medium: 1, high: 2 };
|
|
55
|
+
return current && rank[current] >= rank[next] ? current : next;
|
|
56
|
+
}
|
|
45
57
|
export async function writeCodexNativePatternAnalysis(root, input = {}) {
|
|
46
58
|
const evidence = await analyzeCodexNativeReferenceSource({ root, sourceDir: input.sourceDir || null, writeReport: true }).catch(() => null);
|
|
47
59
|
const report = await buildCodexNativePatternAnalysis({ root, evidence, sourceDir: input.sourceDir || null });
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists, nowIso, runProcess, sha256, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
export async function ensureCodexNativeReferenceSnapshot(input) {
|
|
5
|
+
const root = path.resolve(input.root);
|
|
6
|
+
const cacheDir = path.join(root, '.sneakoscope', 'cache', 'codex-native-reference');
|
|
7
|
+
const reportPath = path.join(root, '.sneakoscope', 'reports', 'codex-native-reference-cache.json');
|
|
8
|
+
const sourceUrl = input.sourceUrl ?? process.env.SKS_CODEX_NATIVE_REFERENCE_SOURCE_URL ?? null;
|
|
9
|
+
const sourceRef = input.ref || process.env.SKS_CODEX_NATIVE_REFERENCE_REF || 'HEAD';
|
|
10
|
+
const offline = input.offline === true || process.env.SKS_CODEX_NATIVE_REFERENCE_OFFLINE === '1' || !sourceUrl;
|
|
11
|
+
const blockers = [];
|
|
12
|
+
const warnings = [];
|
|
13
|
+
let refreshed = false;
|
|
14
|
+
if (!offline && sourceUrl) {
|
|
15
|
+
const parent = path.dirname(cacheDir);
|
|
16
|
+
await fs.mkdir(parent, { recursive: true });
|
|
17
|
+
const gitDir = path.join(cacheDir, '.git');
|
|
18
|
+
const timeoutMs = input.timeoutMs || 60_000;
|
|
19
|
+
if (!(await exists(gitDir))) {
|
|
20
|
+
if (await exists(cacheDir))
|
|
21
|
+
await fs.rm(cacheDir, { recursive: true, force: true });
|
|
22
|
+
const cloneArgs = [
|
|
23
|
+
'clone',
|
|
24
|
+
'--depth',
|
|
25
|
+
'1',
|
|
26
|
+
'--filter=blob:none',
|
|
27
|
+
...(sourceRef === 'HEAD' ? [] : ['--branch', sourceRef]),
|
|
28
|
+
sourceUrl,
|
|
29
|
+
cacheDir
|
|
30
|
+
];
|
|
31
|
+
const cloned = await runProcess('git', cloneArgs, {
|
|
32
|
+
timeoutMs,
|
|
33
|
+
maxOutputBytes: 128 * 1024
|
|
34
|
+
}).catch((err) => ({ code: 1, stderr: messageOf(err), stdout: '' }));
|
|
35
|
+
if (cloned.code === 0)
|
|
36
|
+
refreshed = true;
|
|
37
|
+
else
|
|
38
|
+
blockers.push('source_snapshot_fetch_failed');
|
|
39
|
+
}
|
|
40
|
+
else if (input.refresh === true) {
|
|
41
|
+
const fetched = await runProcess('git', ['fetch', '--depth', '1', 'origin', sourceRef], {
|
|
42
|
+
cwd: cacheDir,
|
|
43
|
+
timeoutMs,
|
|
44
|
+
maxOutputBytes: 128 * 1024
|
|
45
|
+
}).catch((err) => ({ code: 1, stderr: messageOf(err), stdout: '' }));
|
|
46
|
+
if (fetched.code === 0) {
|
|
47
|
+
await runProcess('git', ['checkout', 'FETCH_HEAD'], { cwd: cacheDir, timeoutMs, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
48
|
+
refreshed = true;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
blockers.push('source_snapshot_refresh_failed');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
warnings.push(sourceUrl ? 'reference_cache_offline_mode' : 'reference_source_url_missing_offline_cache_only');
|
|
57
|
+
}
|
|
58
|
+
const cacheExists = await hasTextFiles(cacheDir);
|
|
59
|
+
if (!cacheExists)
|
|
60
|
+
blockers.push('source_snapshot_missing');
|
|
61
|
+
const report = {
|
|
62
|
+
schema: 'sks.codex-native-reference-cache.v1',
|
|
63
|
+
generated_at: nowIso(),
|
|
64
|
+
ok: blockers.length === 0,
|
|
65
|
+
cache_dir: path.relative(root, cacheDir),
|
|
66
|
+
source_url_hash: sourceUrl ? sha256(sourceUrl) : null,
|
|
67
|
+
source_ref: sourceRef,
|
|
68
|
+
source_sha: await gitSha(cacheDir),
|
|
69
|
+
refreshed,
|
|
70
|
+
offline,
|
|
71
|
+
blockers: [...new Set(blockers)],
|
|
72
|
+
warnings: [...new Set(warnings)]
|
|
73
|
+
};
|
|
74
|
+
await writeJsonAtomic(reportPath, report).catch(() => undefined);
|
|
75
|
+
return report;
|
|
76
|
+
}
|
|
77
|
+
async function hasTextFiles(dir) {
|
|
78
|
+
const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
79
|
+
for (const row of rows) {
|
|
80
|
+
if (row.name === '.git' || row.name === 'node_modules' || row.name === 'dist')
|
|
81
|
+
continue;
|
|
82
|
+
const full = path.join(dir, row.name);
|
|
83
|
+
if (row.isFile() && /\.(md|txt|json|toml|ya?ml|js|ts|mjs|cjs)$/i.test(row.name))
|
|
84
|
+
return true;
|
|
85
|
+
if (row.isDirectory() && await hasTextFiles(full))
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
async function gitSha(sourceDir) {
|
|
91
|
+
const run = await runProcess('git', ['rev-parse', 'HEAD'], { cwd: sourceDir, timeoutMs: 5000, maxOutputBytes: 4096 }).catch(() => null);
|
|
92
|
+
const sha = run?.code === 0 ? `${run.stdout || ''}`.trim() : '';
|
|
93
|
+
return /^[0-9a-f]{40}$/i.test(sha) ? sha : null;
|
|
94
|
+
}
|
|
95
|
+
function messageOf(err) {
|
|
96
|
+
return err instanceof Error ? err.message : String(err);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=codex-native-reference-cache.js.map
|