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.
Files changed (49) hide show
  1. package/README.md +10 -3
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/commands/codex-app.js +20 -2
  8. package/dist/commands/codex-native.js +18 -2
  9. package/dist/commands/doctor.js +106 -12
  10. package/dist/core/codex-app/codex-agent-role-sync.js +9 -5
  11. package/dist/core/codex-app/codex-init-deep.js +6 -2
  12. package/dist/core/codex-app/codex-skill-sync.js +80 -152
  13. package/dist/core/codex-control/codex-0138-capability.js +5 -2
  14. package/dist/core/codex-native/codex-native-feature-broker.js +74 -6
  15. package/dist/core/codex-native/codex-native-pattern-analysis.js +14 -2
  16. package/dist/core/codex-native/codex-native-reference-cache.js +98 -0
  17. package/dist/core/codex-native/codex-native-reference-source.js +50 -11
  18. package/dist/core/codex-native/codex-native-repair-transaction.js +150 -0
  19. package/dist/core/codex-native/core-skill-integrity.js +89 -0
  20. package/dist/core/codex-native/core-skill-manifest.js +156 -0
  21. package/dist/core/codex-native/native-capability-postcheck.js +35 -0
  22. package/dist/core/codex-native/native-capability-repair-matrix.js +210 -0
  23. package/dist/core/codex-native/native-capability-repair.js +47 -0
  24. package/dist/core/codex-native/native-media-computer-repair.js +5 -0
  25. package/dist/core/codex-native/project-skill-dedupe.js +109 -0
  26. package/dist/core/codex-native/skill-name-canonicalizer.js +21 -0
  27. package/dist/core/codex-native/skill-registry-ledger.js +85 -0
  28. package/dist/core/codex-plugins/codex-plugin-json.js +5 -2
  29. package/dist/core/commands/basic-cli.js +15 -9
  30. package/dist/core/commands/mad-sks-command.js +16 -0
  31. package/dist/core/config/config-migration-journal.js +27 -0
  32. package/dist/core/config/managed-config-merge.js +105 -0
  33. package/dist/core/config/secret-preservation.js +169 -0
  34. package/dist/core/config/supabase-secret-preservation.js +29 -0
  35. package/dist/core/doctor/doctor-native-capability-repair.js +48 -0
  36. package/dist/core/fsx.js +1 -1
  37. package/dist/core/init.js +5 -1
  38. package/dist/core/loops/loop-planner.js +1 -1
  39. package/dist/core/loops/loop-worker-prompts.js +2 -0
  40. package/dist/core/loops/loop-worker-runtime.js +8 -1
  41. package/dist/core/version.js +1 -1
  42. package/dist/scripts/codex-native-runtime-e2e-fixture.js +75 -0
  43. package/dist/scripts/loop-worker-fixture-child.js +2 -1
  44. package/dist/scripts/sizecheck.js +8 -2
  45. package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
  46. package/dist/scripts/sks-3-1-6-directive-check-lib.js +2 -2
  47. package/dist/scripts/sks-3-1-7-directive-check-lib.js +58 -0
  48. package/dist/scripts/sks-3-1-8-check-lib.js +30 -0
  49. package/package.json +39 -2
@@ -0,0 +1,210 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
4
+ import { detectImagegenCapability } from '../imagegen/imagegen-capability.js';
5
+ import { buildCodexNativeFeatureMatrix } from './codex-native-feature-broker.js';
6
+ export const NATIVE_CAPABILITY_IDS = [
7
+ 'image_generation',
8
+ 'image_followup_edit',
9
+ 'computer_use',
10
+ 'chrome_web_review',
11
+ 'codex_app_screenshot',
12
+ 'app_handoff',
13
+ 'image_path_exposure',
14
+ 'saved_artifact_path_contract'
15
+ ];
16
+ export async function buildNativeCapabilityRepairMatrix(input) {
17
+ const root = path.resolve(input.root);
18
+ const selected = new Set(input.capabilities || NATIVE_CAPABILITY_IDS);
19
+ const fixture = input.fixture || false;
20
+ const imageCapability = fixture
21
+ ? fixtureImageCapability(fixture)
22
+ : await detectImagegenCapability({ timeoutMs: 2500 }).catch((err) => ({ blockers: [messageOf(err)], auth_readiness: null, codex_app: { available: false } }));
23
+ const nativeFeatureMatrix = fixture
24
+ ? fixtureNativeFeatureMatrix(fixture)
25
+ : await buildCodexNativeFeatureMatrix({ root, mode: 'read-only' }).catch((err) => ({ ok: false, features: {}, blockers: [messageOf(err)], invocation_defaults: {} }));
26
+ const states = await Promise.all(NATIVE_CAPABILITY_IDS.filter((id) => selected.has(id)).map((id) => stateForCapability(root, id, imageCapability, nativeFeatureMatrix)));
27
+ const blockers = states.flatMap((state) => state.blockers);
28
+ const warnings = states.flatMap((state) => state.warnings);
29
+ const matrix = {
30
+ schema: 'sks.native-capability-repair-matrix.v1',
31
+ generated_at: nowIso(),
32
+ ok: blockers.length === 0,
33
+ capabilities: states,
34
+ blockers,
35
+ warnings
36
+ };
37
+ const reportPath = input.reportPath === null
38
+ ? null
39
+ : input.reportPath || path.join(root, '.sneakoscope', 'reports', 'native-capability-repair-matrix.json');
40
+ if (reportPath)
41
+ await writeJsonAtomic(reportPath, matrix).catch(() => undefined);
42
+ return matrix;
43
+ }
44
+ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix) {
45
+ const reports = path.join(root, '.sneakoscope', 'reports');
46
+ if (id === 'image_generation') {
47
+ const verified = imageCapability?.codex_app?.available === true || imageCapability?.auth_readiness?.headless_auto_available === true;
48
+ return {
49
+ id,
50
+ before: verified ? 'verified' : 'blocked',
51
+ repairability: verified ? 'auto' : 'manual-required',
52
+ repair_actions: verified ? ['postcheck-imagegen-path-contract'] : ['Sign in to Codex App and enable/use the built-in $imagegen surface, or provide explicit imagegen auth for a non-Codex fallback.'],
53
+ after: null,
54
+ artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
55
+ blockers: verified ? [] : ['imagegen_auth_or_codex_app_builtin_missing'],
56
+ warnings: verified ? [] : ['image_generation_not_verified_without_real_capability']
57
+ };
58
+ }
59
+ if (id === 'image_followup_edit') {
60
+ const contractReady = await fileExists(path.join(root, '.sneakoscope', 'reports', 'saved-artifact-path-contract.json'));
61
+ return autoState(id, contractReady, path.join(reports, 'saved-artifact-path-contract.json'), ['create-saved-artifact-path-contract']);
62
+ }
63
+ if (id === 'codex_app_screenshot') {
64
+ const dir = path.join(root, '.sneakoscope', 'app-screenshots');
65
+ const ready = await dirWritable(dir);
66
+ return autoState(id, ready, path.join(root, '.sneakoscope', 'app-screenshots', 'screenshot-registry.json'), ['create-app-screenshot-directory', 'create-screenshot-registry']);
67
+ }
68
+ if (id === 'saved_artifact_path_contract') {
69
+ const ready = await fileExists(path.join(reports, 'saved-artifact-path-contract.json'));
70
+ return autoState(id, ready, path.join(reports, 'saved-artifact-path-contract.json'), ['create-saved-artifact-path-contract']);
71
+ }
72
+ if (id === 'app_handoff') {
73
+ const ok = featureOk(nativeFeatureMatrix, 'app_handoff');
74
+ return {
75
+ id,
76
+ before: ok ? 'verified' : 'unknown',
77
+ repairability: ok ? 'auto' : 'manual-required',
78
+ repair_actions: ok ? ['postcheck-app-handoff'] : ['Open Codex App and approve/enable app handoff; rerun `sks doctor --fix --repair-native-capabilities --yes`.'],
79
+ after: null,
80
+ artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
81
+ blockers: ok ? [] : ['codex_app_handoff_not_verified'],
82
+ warnings: ok ? [] : ['manual_app_handoff_approval_required']
83
+ };
84
+ }
85
+ if (id === 'image_path_exposure') {
86
+ const ok = featureOk(nativeFeatureMatrix, 'image_path_exposure');
87
+ const fallback = await fileExists(path.join(reports, 'saved-artifact-path-contract.json'));
88
+ return {
89
+ id,
90
+ before: ok ? 'verified' : fallback ? 'degraded' : 'missing',
91
+ repairability: ok ? 'auto' : 'doctor-fix',
92
+ repair_actions: ok ? ['postcheck-image-path-exposure'] : ['create-saved-artifact-path-contract'],
93
+ after: null,
94
+ artifact_path: path.join(reports, 'saved-artifact-path-contract.json'),
95
+ blockers: ok || fallback ? [] : ['image_path_exposure_missing_without_fallback_contract'],
96
+ warnings: ok ? [] : ['using_saved_artifact_path_contract_fallback']
97
+ };
98
+ }
99
+ if (id === 'computer_use') {
100
+ const envVerified = process.env.SKS_COMPUTER_USE_CAPABILITY === 'verified';
101
+ return {
102
+ id,
103
+ before: envVerified ? 'verified' : 'unknown',
104
+ repairability: envVerified ? 'auto' : 'manual-required',
105
+ repair_actions: envVerified ? ['postcheck-computer-use'] : ['Enable Codex Computer Use and macOS Screen Recording/Accessibility permissions, then rerun doctor.'],
106
+ after: null,
107
+ artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
108
+ blockers: envVerified ? [] : ['computer_use_os_permission_or_capability_unknown'],
109
+ warnings: envVerified ? [] : ['manual_os_permission_required']
110
+ };
111
+ }
112
+ const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1' || featureOk(nativeFeatureMatrix, 'plugin_json');
113
+ return {
114
+ id,
115
+ before: chromeReady ? 'verified' : 'unknown',
116
+ repairability: chromeReady ? 'auto' : 'manual-required',
117
+ repair_actions: chromeReady ? ['postcheck-chrome-extension-readiness'] : ['Install/enable the official Codex Chrome Extension, approve it in Codex App, then rerun QA/browser verification.'],
118
+ after: null,
119
+ artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
120
+ blockers: chromeReady ? [] : ['codex_chrome_extension_readiness_not_verified'],
121
+ warnings: chromeReady ? [] : ['manual_chrome_extension_setup_required']
122
+ };
123
+ }
124
+ export async function writeSavedArtifactPathContract(root) {
125
+ const artifactPath = path.join(root, '.sneakoscope', 'reports', 'saved-artifact-path-contract.json');
126
+ await writeJsonAtomic(artifactPath, {
127
+ schema: 'sks.saved-artifact-path-contract.v1',
128
+ generated_at: nowIso(),
129
+ root,
130
+ image_artifacts: path.join(root, '.sneakoscope', 'image-artifacts'),
131
+ app_screenshots: path.join(root, '.sneakoscope', 'app-screenshots'),
132
+ model_visible_path_strategy: 'saved-artifact-path',
133
+ raw_secret_values_recorded: false
134
+ });
135
+ return artifactPath;
136
+ }
137
+ export async function ensureNativeCapabilityArtifactRoots(root) {
138
+ const imageDir = path.join(root, '.sneakoscope', 'image-artifacts');
139
+ const appDir = path.join(root, '.sneakoscope', 'app-screenshots');
140
+ await ensureDir(imageDir);
141
+ await ensureDir(appDir);
142
+ const imageRegistry = path.join(imageDir, 'image-artifact-registry.json');
143
+ const screenshotRegistry = path.join(appDir, 'screenshot-registry.json');
144
+ const created = [];
145
+ if (!(await fileExists(imageRegistry))) {
146
+ await writeJsonAtomic(imageRegistry, { schema: 'sks.image-artifact-registry.v1', generated_at: nowIso(), images: [] });
147
+ created.push(imageRegistry);
148
+ }
149
+ if (!(await fileExists(screenshotRegistry))) {
150
+ await writeJsonAtomic(screenshotRegistry, { schema: 'sks.app-screenshot-registry.v1', generated_at: nowIso(), screenshots: [] });
151
+ created.push(screenshotRegistry);
152
+ }
153
+ created.push(await writeSavedArtifactPathContract(root));
154
+ return created;
155
+ }
156
+ function autoState(id, ready, artifactPath, actions) {
157
+ return {
158
+ id,
159
+ before: ready ? 'verified' : 'missing',
160
+ repairability: ready ? 'auto' : 'doctor-fix',
161
+ repair_actions: ready ? [`postcheck-${id}`] : actions,
162
+ after: null,
163
+ artifact_path: artifactPath,
164
+ blockers: ready ? [] : [`${id}_repair_required`],
165
+ warnings: []
166
+ };
167
+ }
168
+ function featureOk(matrix, feature) {
169
+ return matrix?.features?.[feature]?.ok === true;
170
+ }
171
+ async function fileExists(file) {
172
+ try {
173
+ const stat = await fs.stat(file);
174
+ return stat.isFile();
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ async function dirWritable(dir) {
181
+ try {
182
+ await ensureDir(dir);
183
+ await fs.access(dir);
184
+ return true;
185
+ }
186
+ catch {
187
+ return false;
188
+ }
189
+ }
190
+ function fixtureImageCapability(mode) {
191
+ return mode === 'all-repairable'
192
+ ? { codex_app: { available: true }, auth_readiness: { headless_auto_available: true }, blockers: [] }
193
+ : { codex_app: { available: false }, auth_readiness: { headless_auto_available: false }, blockers: ['fixture_manual_required'] };
194
+ }
195
+ function fixtureNativeFeatureMatrix(mode) {
196
+ const ok = mode === 'all-repairable';
197
+ return {
198
+ ok,
199
+ features: {
200
+ app_handoff: { ok },
201
+ image_path_exposure: { ok },
202
+ plugin_json: { ok }
203
+ },
204
+ blockers: ok ? [] : ['fixture_manual_required']
205
+ };
206
+ }
207
+ function messageOf(err) {
208
+ return err instanceof Error ? err.message : String(err);
209
+ }
210
+ //# sourceMappingURL=native-capability-repair-matrix.js.map
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { syncCoreSkillsIntegrity } from './core-skill-integrity.js';
4
+ import { buildNativeCapabilityRepairMatrix, ensureNativeCapabilityArtifactRoots } from './native-capability-repair-matrix.js';
5
+ import { postcheckNativeCapabilities } from './native-capability-postcheck.js';
6
+ export async function repairNativeCapabilities(input) {
7
+ const root = path.resolve(input.root);
8
+ const capabilitySelection = input.capabilities ? { capabilities: input.capabilities } : {};
9
+ const before = await buildNativeCapabilityRepairMatrix({
10
+ root,
11
+ ...capabilitySelection,
12
+ fixture: input.fixture || false,
13
+ reportPath: path.join(root, '.sneakoscope', 'reports', 'native-capability-repair-matrix-before.json')
14
+ });
15
+ const repaired = [];
16
+ if (input.fix) {
17
+ repaired.push(...await ensureNativeCapabilityArtifactRoots(root));
18
+ await syncCoreSkillsIntegrity({ root, apply: true }).catch(() => undefined);
19
+ }
20
+ const afterMatrix = await buildNativeCapabilityRepairMatrix({
21
+ root,
22
+ ...capabilitySelection,
23
+ fixture: input.fixture || false,
24
+ reportPath: path.join(root, '.sneakoscope', 'reports', 'native-capability-repair-matrix.json')
25
+ });
26
+ const postcheck = await postcheckNativeCapabilities({
27
+ root,
28
+ matrix: afterMatrix,
29
+ fixture: input.fixture || false,
30
+ reportPath: path.join(root, '.sneakoscope', 'reports', 'native-capability-postcheck.json')
31
+ });
32
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'native-capability-repair.json'), {
33
+ schema: 'sks.native-capability-repair.v1',
34
+ generated_at: nowIso(),
35
+ ok: postcheck.ok,
36
+ fix: input.fix,
37
+ yes: input.yes,
38
+ before,
39
+ after: postcheck,
40
+ repaired_artifacts: repaired,
41
+ manual_required: postcheck.capabilities
42
+ .filter((state) => state.repairability === 'manual-required' && state.after !== 'verified')
43
+ .map((state) => ({ id: state.id, actions: state.repair_actions }))
44
+ }).catch(() => undefined);
45
+ return postcheck;
46
+ }
47
+ //# sourceMappingURL=native-capability-repair.js.map
@@ -0,0 +1,5 @@
1
+ import { repairNativeCapabilities } from './native-capability-repair.js';
2
+ export async function repairNativeMediaComputerCapabilities(input) {
3
+ return repairNativeCapabilities(input);
4
+ }
5
+ //# sourceMappingURL=native-media-computer-repair.js.map
@@ -0,0 +1,109 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
4
+ import { buildSkillRegistryLedger, groupByCanonical } from './skill-registry-ledger.js';
5
+ export async function dedupeProjectSkills(input) {
6
+ const root = path.resolve(input.root);
7
+ const fix = input.fix === true;
8
+ const yes = input.yes === true;
9
+ const ledger = await buildSkillRegistryLedger({ root });
10
+ const grouped = groupByCanonical(ledger.entries);
11
+ const actions = [];
12
+ const unresolvedUserDuplicates = [];
13
+ for (const [canonical, group] of grouped.entries()) {
14
+ if (group.length <= 1)
15
+ continue;
16
+ group.sort(compareSkillPriority);
17
+ const userEntries = group.filter((entry) => !entry.managed_by_sks);
18
+ const managedEntries = group.filter((entry) => entry.managed_by_sks);
19
+ if (userEntries.length > 0 && managedEntries.length > 0) {
20
+ for (const user of userEntries)
21
+ actions.push(actionRow(canonical, 'kept', user, null, 'user-authored skill preserved'));
22
+ for (const managed of managedEntries) {
23
+ const quarantine = await maybeQuarantine(root, canonical, managed, fix, 'managed collision with user-authored skill');
24
+ actions.push(actionRow(canonical, quarantine ? 'quarantined' : 'reported', managed, quarantine, 'managed collision with user-authored skill'));
25
+ }
26
+ continue;
27
+ }
28
+ if (managedEntries.length > 1) {
29
+ const current = managedEntries.find((entry) => entry.status === 'managed-current') || managedEntries[0];
30
+ if (current)
31
+ actions.push(actionRow(canonical, 'kept', current, null, 'highest-priority SKS-managed skill kept'));
32
+ for (const duplicate of managedEntries.filter((entry) => entry !== current)) {
33
+ const quarantine = await maybeQuarantine(root, canonical, duplicate, fix, 'duplicate SKS-managed skill');
34
+ actions.push(actionRow(canonical, quarantine ? 'quarantined' : 'reported', duplicate, quarantine, 'duplicate SKS-managed skill'));
35
+ }
36
+ continue;
37
+ }
38
+ if (userEntries.length > 1) {
39
+ const keep = userEntries[0];
40
+ if (keep)
41
+ actions.push(actionRow(canonical, 'kept', keep, null, 'highest-priority user-authored skill kept'));
42
+ const shouldMove = fix && yes && input.quarantineUserDuplicates === true;
43
+ for (const duplicate of userEntries.slice(1)) {
44
+ const quarantine = shouldMove ? await quarantineSkill(root, canonical, duplicate, 'user-authored duplicate skill') : null;
45
+ actions.push(actionRow(canonical, quarantine ? 'quarantined' : 'reported', duplicate, quarantine, 'user-authored duplicate skill requires --quarantine-user-duplicates --yes'));
46
+ }
47
+ if (!shouldMove)
48
+ unresolvedUserDuplicates.push(canonical);
49
+ }
50
+ }
51
+ const duplicateNames = [...new Set(actions.filter((action) => action.action !== 'kept').map((action) => action.canonical_name))].sort();
52
+ const blockers = unresolvedUserDuplicates.map((name) => `user_duplicate_requires_confirmation:${name}`);
53
+ const report = {
54
+ schema: 'sks.project-skill-dedupe.v1',
55
+ generated_at: nowIso(),
56
+ ok: blockers.length === 0,
57
+ root,
58
+ fix,
59
+ yes,
60
+ actions,
61
+ duplicate_canonical_names: duplicateNames,
62
+ unresolved_user_duplicates: unresolvedUserDuplicates,
63
+ blockers
64
+ };
65
+ const reportPath = input.reportPath === null
66
+ ? null
67
+ : input.reportPath || path.join(root, '.sneakoscope', 'reports', 'project-skill-dedupe.json');
68
+ if (reportPath)
69
+ await writeJsonAtomic(reportPath, report).catch(() => undefined);
70
+ return report;
71
+ }
72
+ function compareSkillPriority(a, b) {
73
+ const currentA = a.status === 'managed-current' ? 1 : 0;
74
+ const currentB = b.status === 'managed-current' ? 1 : 0;
75
+ return currentB - currentA || b.active_priority - a.active_priority || a.path.localeCompare(b.path);
76
+ }
77
+ function actionRow(canonicalName, action, entry, quarantinePath, reason) {
78
+ return {
79
+ canonical_name: canonicalName,
80
+ action,
81
+ path: entry.path,
82
+ quarantine_path: quarantinePath,
83
+ reason
84
+ };
85
+ }
86
+ async function maybeQuarantine(root, canonical, entry, fix, reason) {
87
+ if (!fix)
88
+ return null;
89
+ return quarantineSkill(root, canonical, entry, reason);
90
+ }
91
+ async function quarantineSkill(root, canonical, entry, reason) {
92
+ const sourceDir = path.dirname(entry.path);
93
+ const stamp = `${Date.now()}-${process.pid}`;
94
+ const target = path.join(root, '.sneakoscope', 'quarantine', 'skills', canonical, stamp, path.basename(sourceDir));
95
+ await ensureDir(path.dirname(target));
96
+ await fs.cp(sourceDir, target, { recursive: true, force: false });
97
+ await fs.rm(sourceDir, { recursive: true, force: true });
98
+ await writeJsonAtomic(path.join(target, 'quarantine-record.json'), {
99
+ schema: 'sks.skill-quarantine-record.v1',
100
+ generated_at: nowIso(),
101
+ source_path: sourceDir,
102
+ quarantine_path: target,
103
+ canonical_name: canonical,
104
+ reason,
105
+ content_sha256: entry.content_sha256
106
+ });
107
+ return target;
108
+ }
109
+ //# sourceMappingURL=project-skill-dedupe.js.map
@@ -0,0 +1,21 @@
1
+ export function canonicalSkillName(name) {
2
+ const normalizedPath = String(name || '').replace(/\\/g, '/').trim();
3
+ const parts = normalizedPath.split('/').filter(Boolean);
4
+ let leaf = parts.length ? String(parts[parts.length - 1]) : normalizedPath;
5
+ if (/^SKILL\.md$/i.test(leaf) && parts.length > 1)
6
+ leaf = String(parts[parts.length - 2]);
7
+ leaf = leaf.replace(/\.md$/i, '');
8
+ return leaf
9
+ .trim()
10
+ .toLowerCase()
11
+ .replace(/^[$@#]+/, '')
12
+ .replace(/[_\s]+/g, '-')
13
+ .replace(/[^a-z0-9-]/g, '-')
14
+ .replace(/-+/g, '-')
15
+ .replace(/^-|-$/g, '');
16
+ }
17
+ export function skillDisplayNameFromMarkdown(text, fallback) {
18
+ const match = String(text || '').match(/^name:\s*([^\n\r]+)/m);
19
+ return String(match?.[1] || fallback || '').trim().replace(/^["']|["']$/g, '');
20
+ }
21
+ //# sourceMappingURL=skill-name-canonicalizer.js.map
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { nowIso, readText, sha256, writeJsonAtomic } from '../fsx.js';
5
+ import { buildSksCoreSkillManifest, isSksManagedCoreSkillContent } from './core-skill-manifest.js';
6
+ import { canonicalSkillName, skillDisplayNameFromMarkdown } from './skill-name-canonicalizer.js';
7
+ export async function buildSkillRegistryLedger(input) {
8
+ const root = path.resolve(input.root);
9
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
10
+ const scanRoots = [
11
+ { root: path.join(root, '.agents', 'skills'), scope: 'project', priority: 100 },
12
+ { root: path.join(root, '.codex', 'skills'), scope: 'project', priority: 80 },
13
+ { root: path.join(codexHome, 'skills'), scope: 'codex-home', priority: 60 },
14
+ ...(input.extraRoots || [])
15
+ ];
16
+ const manifestByName = new Map(buildSksCoreSkillManifest().skills.map((skill) => [skill.canonical_name, skill.content_sha256]));
17
+ const entries = [];
18
+ for (const scanRoot of scanRoots) {
19
+ const rows = await fs.readdir(scanRoot.root, { withFileTypes: true }).catch(() => []);
20
+ for (const row of rows) {
21
+ if (!row.isDirectory())
22
+ continue;
23
+ const skillPath = path.join(scanRoot.root, row.name, 'SKILL.md');
24
+ const text = await readText(skillPath, null);
25
+ if (typeof text !== 'string')
26
+ continue;
27
+ const displayName = skillDisplayNameFromMarkdown(text, row.name);
28
+ const canonical = canonicalSkillName(displayName || row.name);
29
+ const hash = sha256(text);
30
+ const managed = isSksManagedCoreSkillContent(text) || text.includes('BEGIN SKS MANAGED SKILL');
31
+ const expected = manifestByName.get(canonical);
32
+ const status = managed
33
+ ? expected && expected === hash ? 'managed-current' : 'managed-drift'
34
+ : 'user-owned';
35
+ entries.push({
36
+ schema: 'sks.skill-registry-entry.v1',
37
+ root: scanRoot.root,
38
+ scope: scanRoot.scope,
39
+ canonical_name: canonical,
40
+ display_name: displayName || row.name,
41
+ path: skillPath,
42
+ managed_by_sks: managed,
43
+ content_sha256: hash,
44
+ active_priority: scanRoot.priority,
45
+ status,
46
+ blockers: []
47
+ });
48
+ }
49
+ }
50
+ const grouped = groupByCanonical(entries);
51
+ const duplicates = [...grouped.entries()].filter(([, group]) => group.length > 1).map(([name]) => name).sort();
52
+ for (const group of grouped.values()) {
53
+ group.sort((a, b) => b.active_priority - a.active_priority || a.path.localeCompare(b.path));
54
+ group.forEach((entry, index) => {
55
+ if (group.length > 1 && index > 0)
56
+ entry.status = entry.status === 'user-owned' ? 'duplicate' : 'duplicate';
57
+ });
58
+ }
59
+ const blockers = duplicates.map((name) => `duplicate_skill_name:${name}`);
60
+ const ledger = {
61
+ schema: 'sks.skill-registry-ledger.v1',
62
+ generated_at: nowIso(),
63
+ ok: blockers.length === 0,
64
+ root,
65
+ entries,
66
+ duplicate_canonical_names: duplicates,
67
+ blockers
68
+ };
69
+ const reportPath = input.reportPath === null
70
+ ? null
71
+ : input.reportPath || path.join(root, '.sneakoscope', 'reports', 'skill-registry-ledger.json');
72
+ if (reportPath)
73
+ await writeJsonAtomic(reportPath, ledger).catch(() => undefined);
74
+ return ledger;
75
+ }
76
+ export function groupByCanonical(entries) {
77
+ const grouped = new Map();
78
+ for (const entry of entries) {
79
+ const current = grouped.get(entry.canonical_name) || [];
80
+ current.push(entry);
81
+ grouped.set(entry.canonical_name, current);
82
+ }
83
+ return grouped;
84
+ }
85
+ //# sourceMappingURL=skill-registry-ledger.js.map
@@ -33,7 +33,8 @@ export async function buildCodexPluginInventory() {
33
33
  });
34
34
  const blockers = [
35
35
  ...(capability.supports_plugin_json ? [] : ['codex_0_138_plugin_json_unavailable']),
36
- ...normalizeList(listJson?.blockers)
36
+ ...normalizeList(listJson?.blockers),
37
+ ...(process.env.SKS_CODEX_PLUGIN_JSON_FAKE_NO_MCP === '1' ? ['fixture_mcp_candidates_disabled'] : [])
37
38
  ];
38
39
  return {
39
40
  schema: 'sks.codex-plugin-inventory.v1',
@@ -169,7 +170,9 @@ function fakePluginDetail(pluginId) {
169
170
  installed: true,
170
171
  enabled: true,
171
172
  default_prompts: ['Use the fixture plugin safely.'],
172
- remote_mcp_servers: [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
173
+ remote_mcp_servers: process.env.SKS_CODEX_PLUGIN_JSON_FAKE_NO_MCP === '1'
174
+ ? []
175
+ : [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
173
176
  unavailable_app_templates: ['fixture-desktop-template']
174
177
  };
175
178
  }
@@ -11,6 +11,7 @@ import { hooksExplainReport } from '../../cli/feature-commands.js';
11
11
  import { writeSelftestRouteProof } from '../proof/selftest-proof-fixtures.js';
12
12
  import { createMission } from '../mission.js';
13
13
  import { formatSksUpdateCheckText, runSksUpdateCheck, runSksUpdateNow } from '../update-check.js';
14
+ import { withSecretPreservationGuard } from '../config/config-migration-journal.js';
14
15
  export async function helpCommand(args = []) {
15
16
  const topic = args[0];
16
17
  if (topic)
@@ -118,12 +119,13 @@ export async function updateCommand(sub = 'check', args = []) {
118
119
  process.exitCode = 1;
119
120
  return;
120
121
  }
121
- const result = await runSksUpdateNow({
122
+ const root = await projectRoot();
123
+ const result = await withSecretPreservationGuard(root, 'update-now', async () => runSksUpdateNow({
122
124
  version: valueAfter(args, '--version') || valueAfter(args, '-v'),
123
125
  dryRun: flag(args, '--dry-run'),
124
126
  timeoutMs: 10 * 60 * 1000,
125
127
  maxOutputBytes: 128 * 1024
126
- });
128
+ }));
127
129
  if (flag(args, '--json'))
128
130
  return printJson(result);
129
131
  console.log(`${sksTextLogo()}\n`);
@@ -140,14 +142,18 @@ export async function updateCommand(sub = 'check', args = []) {
140
142
  export async function setupCommand(args = []) {
141
143
  const root = await projectRoot();
142
144
  const installScope = installScopeFromArgs(args);
143
- const res = await initProject(root, {
144
- force: flag(args, '--force'),
145
- installScope,
146
- localOnly: flag(args, '--local-only'),
147
- globalCommand: 'sks'
145
+ let res = null;
146
+ let cliTools = null;
147
+ await withSecretPreservationGuard(root, 'setup-command', async () => {
148
+ res = await initProject(root, {
149
+ force: flag(args, '--force'),
150
+ installScope,
151
+ localOnly: flag(args, '--local-only'),
152
+ globalCommand: 'sks'
153
+ });
154
+ const { ensureRelatedCliTools } = await import('../../cli/install-helpers.js');
155
+ cliTools = await ensureRelatedCliTools(args);
148
156
  });
149
- const { ensureRelatedCliTools } = await import('../../cli/install-helpers.js');
150
- const cliTools = await ensureRelatedCliTools(args);
151
157
  const result = {
152
158
  schema: 'sks.setup.v1',
153
159
  ok: true,
@@ -22,6 +22,7 @@ import { checkSksUpdateNotice } from '../update/update-notice.js';
22
22
  import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
23
23
  import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
24
24
  import { writeCodex0139CapabilityArtifacts } from '../codex-control/codex-0139-capability.js';
25
+ import { resolveCodexNativeInvocationPlan } from '../codex-native/codex-native-invocation-router.js';
25
26
  import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
26
27
  export async function madHighCommand(args = [], deps = {}) {
27
28
  const subcommand = firstSubcommand(args);
@@ -478,6 +479,14 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
478
479
  const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
479
480
  await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
480
481
  await writeCodex0139CapabilityArtifacts(root, { missionId: id }).catch(() => null);
482
+ const codexNativeInvocation = await resolveCodexNativeInvocationPlan({
483
+ root,
484
+ missionId: id,
485
+ route: '$MAD',
486
+ desiredCapability: 'hook-evidence'
487
+ }).catch(() => null);
488
+ if (codexNativeInvocation)
489
+ await writeJsonAtomic(path.join(dir, 'mad-codex-native-invocation.json'), codexNativeInvocation).catch(() => undefined);
481
490
  const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
482
491
  // The interactive launch 'before' snapshot is only persisted (env + policy json)
483
492
  // and is never compared against an 'after' snapshot during the session, so the
@@ -521,6 +530,13 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
521
530
  protected_core_policy: protectedCorePolicyPath,
522
531
  protected_core_before: protectedCoreBeforePath,
523
532
  protected_core_digest: protectedCoreBefore.digest,
533
+ codex_native_invocation_plan: codexNativeInvocation ? {
534
+ selected_strategy: codexNativeInvocation.selected_strategy,
535
+ hook_evidence_policy: codexNativeInvocation.env.SKS_CODEX_NATIVE_HOOK_EVIDENCE_POLICY,
536
+ blockers: codexNativeInvocation.blockers,
537
+ warnings: codexNativeInvocation.warnings,
538
+ artifact_path: 'mad-codex-native-invocation.json'
539
+ } : null,
524
540
  activated_by: 'sks --mad',
525
541
  cwd: path.resolve(cwd || process.cwd())
526
542
  };
@@ -0,0 +1,27 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ import { captureSecretPreservationSnapshot, withSecretPreservationGuard } from './secret-preservation.js';
4
+ export { withSecretPreservationGuard };
5
+ export async function writeSecretMigrationJournal(root, operationName, filesTouched = []) {
6
+ const resolvedRoot = path.resolve(root);
7
+ const snapshot = await captureSecretPreservationSnapshot({ root: resolvedRoot });
8
+ const journalPath = path.join(resolvedRoot, '.sneakoscope', 'reports', 'secret-migration-journal.json');
9
+ const current = await readJson(journalPath, { entries: [] }).catch(() => ({ entries: [] }));
10
+ const entries = Array.isArray(current.entries) ? current.entries : [];
11
+ const entry = {
12
+ schema: 'sks.secret-migration-journal-entry.v1',
13
+ generated_at: nowIso(),
14
+ operation: operationName,
15
+ files_touched: filesTouched,
16
+ protected_keys_present: snapshot.fingerprints.filter((fp) => fp.present).map((fp) => ({ key: fp.key, source: fp.source })),
17
+ raw_values_recorded: false
18
+ };
19
+ const journal = {
20
+ schema: 'sks.secret-migration-journal.v1',
21
+ generated_at: nowIso(),
22
+ entries: [...entries, entry]
23
+ };
24
+ await writeJsonAtomic(journalPath, journal);
25
+ return { journal_path: journalPath, entry };
26
+ }
27
+ //# sourceMappingURL=config-migration-journal.js.map