sneakoscope 3.1.6 → 3.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +2 -2
  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 +36 -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 +4 -2
  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-plugins/codex-plugin-json.js +5 -2
  20. package/dist/core/commands/mad-sks-command.js +16 -0
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/loops/loop-planner.js +1 -1
  23. package/dist/core/loops/loop-worker-prompts.js +2 -0
  24. package/dist/core/loops/loop-worker-runtime.js +8 -1
  25. package/dist/core/version.js +1 -1
  26. package/dist/scripts/codex-native-runtime-e2e-fixture.js +75 -0
  27. package/dist/scripts/loop-worker-fixture-child.js +2 -1
  28. package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
  29. package/dist/scripts/sks-3-1-6-directive-check-lib.js +2 -2
  30. package/dist/scripts/sks-3-1-7-directive-check-lib.js +58 -0
  31. package/package.json +13 -2
@@ -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
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
5
+ import { ensureCodexNativeReferenceSnapshot } from './codex-native-reference-cache.js';
5
6
  const PATTERNS = [
6
7
  { id: 'no-global-optional-tooling', claim: 'optional tooling avoids mandatory global install', re: /\bnpx\b|no[- ]global|global install/i },
7
8
  { id: 'plugin-lifecycle-state-separation', claim: 'plugin install state is separated from approval/readiness', re: /\bplugin\b.+\b(install|enable|marketplace|lifecycle)\b/i },
@@ -18,7 +19,26 @@ const PATTERNS = [
18
19
  ];
19
20
  export async function analyzeCodexNativeReferenceSource(input) {
20
21
  const root = path.resolve(input.root);
21
- const sourceDir = input.sourceDir ? path.resolve(input.sourceDir) : path.join(root, '.sneakoscope', 'cache', 'codex-native-reference');
22
+ const cacheInput = input.sourceRef ? { root, ref: input.sourceRef } : { root };
23
+ const cache = input.sourceDir
24
+ ? null
25
+ : await ensureCodexNativeReferenceSnapshot(cacheInput).catch((err) => ({
26
+ schema: 'sks.codex-native-reference-cache.v1',
27
+ generated_at: nowIso(),
28
+ ok: false,
29
+ cache_dir: '.sneakoscope/cache/codex-native-reference',
30
+ source_url_hash: null,
31
+ source_ref: input.sourceRef || 'HEAD',
32
+ source_sha: null,
33
+ refreshed: false,
34
+ offline: true,
35
+ blockers: [messageOf(err)],
36
+ warnings: ['reference_cache_exception']
37
+ }));
38
+ const sourceDir = input.sourceDir
39
+ ? path.resolve(input.sourceDir)
40
+ : path.join(root, cache?.cache_dir || '.sneakoscope/cache/codex-native-reference');
41
+ const confidence = input.sourceDir ? 'high' : cache?.ok ? cache.refreshed ? 'high' : 'medium' : 'low';
22
42
  const files = await listTextFiles(sourceDir);
23
43
  const evidence = [];
24
44
  const blockers = [];
@@ -26,7 +46,7 @@ export async function analyzeCodexNativeReferenceSource(input) {
26
46
  const rel = path.relative(sourceDir, file).split(path.sep).join('/');
27
47
  const text = await fs.readFile(file, 'utf8').catch(() => '');
28
48
  if (text)
29
- evidence.push(...extractCodexNativeEvidence(rel, text));
49
+ evidence.push(...extractCodexNativeEvidence(rel, text).map((row) => ({ ...row, confidence })));
30
50
  }
31
51
  if (!files.length || !evidence.length)
32
52
  blockers.push('source_snapshot_missing');
@@ -34,11 +54,14 @@ export async function analyzeCodexNativeReferenceSource(input) {
34
54
  schema: 'sks.codex-native-reference-evidence.v1',
35
55
  generated_at: nowIso(),
36
56
  source_kind: 'external-reference-source',
37
- source_ref: input.sourceRef || sourceDir,
38
- source_sha: await gitSha(sourceDir),
57
+ source_ref: neutralSourceRef(input, cache, sourceDir),
58
+ source_sha: cache?.source_sha || await gitSha(sourceDir),
59
+ source_url_hash: cache?.source_url_hash || null,
60
+ cache_report_path: cache ? '.sneakoscope/reports/codex-native-reference-cache.json' : null,
61
+ cache,
39
62
  evidence,
40
- blockers,
41
- warnings: blockers.length ? ['reference_evidence_incomplete'] : []
63
+ blockers: [...new Set([...blockers, ...(cache?.blockers || [])])],
64
+ warnings: [...new Set([...(cache?.warnings || []), ...(blockers.length ? ['reference_evidence_incomplete'] : [])])]
42
65
  };
43
66
  if (input.writeReport !== false) {
44
67
  await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-reference-evidence.json'), report).catch(() => undefined);
@@ -74,6 +97,8 @@ export function renderCodexNativeReferenceMarkdown(report) {
74
97
  '',
75
98
  `Generated at: \`${report.generated_at}\``,
76
99
  `Source kind: \`${report.source_kind}\``,
100
+ `Source URL hash: \`${report.source_url_hash || 'none'}\``,
101
+ `Source SHA: \`${report.source_sha || 'none'}\``,
77
102
  '',
78
103
  '| Pattern | File | Lines | Snippet Hash | Confidence |',
79
104
  '|---|---|---:|---|---|',
@@ -81,21 +106,32 @@ export function renderCodexNativeReferenceMarkdown(report) {
81
106
  ''
82
107
  ].join('\n');
83
108
  }
109
+ function neutralSourceRef(input, cache, sourceDir) {
110
+ if (cache)
111
+ return `cache:${createHash('sha256').update(`${cache.cache_dir}:${cache.source_ref}:${cache.source_sha || ''}`).digest('hex').slice(0, 16)}`;
112
+ if (input.sourceRef)
113
+ return `explicit:${createHash('sha256').update(input.sourceRef).digest('hex').slice(0, 16)}`;
114
+ return `explicit-source-dir:${createHash('sha256').update(sourceDir).digest('hex').slice(0, 16)}`;
115
+ }
84
116
  async function listTextFiles(dir) {
85
117
  const out = [];
86
- await walk(dir, out);
87
- return out.filter((file) => /\.(md|txt|json|toml|ya?ml|js|ts|mjs|cjs)$/i.test(file)).slice(0, 500);
118
+ await walk(dir, out, 500);
119
+ return out;
88
120
  }
89
- async function walk(dir, out) {
121
+ async function walk(dir, out, maxFiles) {
122
+ if (out.length >= maxFiles)
123
+ return;
90
124
  const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
91
125
  for (const row of rows) {
126
+ if (out.length >= maxFiles)
127
+ return;
92
128
  const full = path.join(dir, row.name);
93
129
  if (row.isDirectory()) {
94
130
  if (['.git', 'node_modules', 'dist'].includes(row.name))
95
131
  continue;
96
- await walk(full, out);
132
+ await walk(full, out, maxFiles);
97
133
  }
98
- else if (row.isFile()) {
134
+ else if (row.isFile() && /\.(md|txt|json|toml|ya?ml|js|ts|mjs|cjs)$/i.test(row.name)) {
99
135
  out.push(full);
100
136
  }
101
137
  }
@@ -107,4 +143,7 @@ async function gitSha(sourceDir) {
107
143
  return (await fs.readFile(path.join(sourceDir, '.git', ref), 'utf8').catch(() => '')).trim() || null;
108
144
  return /^[0-9a-f]{40}$/i.test(head.trim()) ? head.trim() : null;
109
145
  }
146
+ function messageOf(err) {
147
+ return err instanceof Error ? err.message : String(err);
148
+ }
110
149
  //# sourceMappingURL=codex-native-reference-source.js.map
@@ -0,0 +1,150 @@
1
+ import path from 'node:path';
2
+ import { buildCodexHookLifecycle } from '../codex-app/codex-hook-lifecycle.js';
3
+ import { runCodexInitDeep } from '../codex-app/codex-init-deep.js';
4
+ import { syncCodexAgentRoles } from '../codex-app/codex-agent-role-sync.js';
5
+ import { syncCodexSksSkills } from '../codex-app/codex-skill-sync.js';
6
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
7
+ import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
8
+ import { evaluateMutation, mutationLedgerPath, recordMutation } from '../safety/mutation-ledger.js';
9
+ export async function repairCodexNativeManagedAssets(input) {
10
+ const root = path.resolve(input.root);
11
+ const requested = {
12
+ skills: input.repairSkills !== false,
13
+ agent_roles: input.repairAgentRoles !== false,
14
+ hooks: input.repairHooks !== false,
15
+ project_memory: input.repairProjectMemory !== false
16
+ };
17
+ const requestedAssets = Object.entries(requested)
18
+ .filter(([, enabled]) => enabled)
19
+ .map(([asset]) => asset);
20
+ if (input.yes !== true) {
21
+ const report = {
22
+ schema: 'sks.codex-native-repair-transaction.v1',
23
+ ok: false,
24
+ generated_at: nowIso(),
25
+ requested_by: input.requestedBy,
26
+ repaired: requestedAssets.map((asset) => ({
27
+ asset,
28
+ ok: false,
29
+ changed: false,
30
+ artifact_path: artifactPathFor(asset),
31
+ blockers: ['repair_transaction_requires_yes']
32
+ })),
33
+ confirmed: false,
34
+ mutation_ledger_path: null,
35
+ blockers: ['repair_transaction_requires_yes'],
36
+ warnings: []
37
+ };
38
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-repair-transaction.json'), report).catch(() => undefined);
39
+ return report;
40
+ }
41
+ const rows = [];
42
+ if (requested.skills) {
43
+ const report = await syncCodexSksSkills({ root, apply: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
44
+ rows.push({
45
+ asset: 'skills',
46
+ ok: recordOk(report) !== false,
47
+ changed: listLength(report, 'created') > 0,
48
+ artifact_path: '.sneakoscope/reports/codex-skill-sync.json',
49
+ blockers: blockersOf(report)
50
+ });
51
+ }
52
+ if (requested.agent_roles) {
53
+ const report = await syncCodexAgentRoles({ root, apply: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
54
+ rows.push({
55
+ asset: 'agent_roles',
56
+ ok: recordOk(report) !== false,
57
+ changed: listLength(report, 'created') > 0,
58
+ artifact_path: '.sneakoscope/reports/codex-agent-role-sync.json',
59
+ blockers: blockersOf(report)
60
+ });
61
+ }
62
+ if (requested.hooks) {
63
+ const report = await buildCodexHookLifecycle({ root, apply: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
64
+ rows.push({
65
+ asset: 'hooks',
66
+ ok: recordOk(report) !== false,
67
+ changed: recordOk(report) !== false,
68
+ artifact_path: '.sneakoscope/reports/codex-hook-lifecycle.json',
69
+ blockers: blockersOf(report)
70
+ });
71
+ }
72
+ if (requested.project_memory) {
73
+ const report = await runCodexInitDeep({ root, apply: true, directoryLocal: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
74
+ rows.push({
75
+ asset: 'project_memory',
76
+ ok: recordOk(report) !== false,
77
+ changed: listLength(report, 'directory_local_agents.created') + listLength(report, 'directory_local_agents.updated') > 0,
78
+ artifact_path: '.sneakoscope/reports/codex-init-deep.json',
79
+ blockers: blockersOf(report)
80
+ });
81
+ }
82
+ const blockers = rows.flatMap((row) => row.blockers);
83
+ const mutationWarnings = await recordRepairMutations(root, rows);
84
+ const report = {
85
+ schema: 'sks.codex-native-repair-transaction.v1',
86
+ ok: rows.every((row) => row.ok) && blockers.length === 0,
87
+ generated_at: nowIso(),
88
+ requested_by: input.requestedBy,
89
+ repaired: rows,
90
+ confirmed: true,
91
+ mutation_ledger_path: path.relative(root, mutationLedgerPath(root)).split(path.sep).join('/'),
92
+ blockers: [...new Set(blockers)],
93
+ warnings: mutationWarnings
94
+ };
95
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-repair-transaction.json'), report).catch(() => undefined);
96
+ return report;
97
+ }
98
+ async function recordRepairMutations(root, rows) {
99
+ const contract = createRequestedScopeContract({
100
+ route: 'codex-native-repair-transaction',
101
+ userRequest: 'Repair SKS-managed Codex Native assets after explicit --yes confirmation.',
102
+ projectRoot: root,
103
+ overrides: { global_codex_config: true }
104
+ });
105
+ const warnings = [];
106
+ for (const row of rows) {
107
+ const kind = row.asset === 'project_memory' ? 'file_write' : 'global_config_write';
108
+ const entry = evaluateMutation(contract, kind, {
109
+ target: row.asset,
110
+ confirmed: true,
111
+ applied: row.changed,
112
+ noOpReason: row.changed ? 'sks_managed_asset_transaction_recorded' : 'repair_noop'
113
+ });
114
+ await recordMutation(root, entry).catch((err) => warnings.push(`mutation_ledger_write_failed:${messageOf(err)}`));
115
+ }
116
+ return warnings;
117
+ }
118
+ function artifactPathFor(asset) {
119
+ return asset === 'skills'
120
+ ? '.sneakoscope/reports/codex-skill-sync.json'
121
+ : asset === 'agent_roles'
122
+ ? '.sneakoscope/reports/codex-agent-role-sync.json'
123
+ : asset === 'hooks'
124
+ ? '.sneakoscope/reports/codex-hook-lifecycle.json'
125
+ : '.sneakoscope/reports/codex-init-deep.json';
126
+ }
127
+ function recordOk(value) {
128
+ return Boolean(value) && typeof value === 'object' && typeof value.ok === 'boolean'
129
+ ? value.ok
130
+ : undefined;
131
+ }
132
+ function blockersOf(value) {
133
+ return Boolean(value) && typeof value === 'object' && Array.isArray(value.blockers)
134
+ ? value.blockers.map((item) => String(item)).filter(Boolean)
135
+ : [];
136
+ }
137
+ function listLength(value, key) {
138
+ const parts = key.split('.');
139
+ let current = value;
140
+ for (const part of parts) {
141
+ if (!current || typeof current !== 'object')
142
+ return 0;
143
+ current = current[part];
144
+ }
145
+ return Array.isArray(current) ? current.length : 0;
146
+ }
147
+ function messageOf(err) {
148
+ return err instanceof Error ? err.message : String(err);
149
+ }
150
+ //# sourceMappingURL=codex-native-repair-transaction.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
  }
@@ -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
  };
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '3.1.6';
8
+ export const PACKAGE_VERSION = '3.1.7';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -59,7 +59,7 @@ export async function planLoopsFromRequest(input) {
59
59
  return {
60
60
  ...node,
61
61
  ...(hints.length ? { memory_hints: hints } : {}),
62
- memory_hints_used: hints.length,
62
+ memory_hints_used: hints,
63
63
  memory_did_not_expand_scope: true
64
64
  };
65
65
  });
@@ -10,6 +10,7 @@ export function buildLoopMakerPrompt(input) {
10
10
  `Owner directories: ${node.owner_scope.directories.join(', ') || '-'}`,
11
11
  `Allowed mutation scope: ${ownerScopeText(node)}`,
12
12
  'Do not mutate outside the owner scope.',
13
+ 'Memory hints are guidance only; memory never grants write permission or expands owner scope.',
13
14
  `Selected local gates: ${allGateIds(node.gates).join(', ') || '-'}`,
14
15
  `Budget: ${JSON.stringify(node.budget)}`,
15
16
  `Worktree path: ${input.worktreePath || '-'}`,
@@ -30,6 +31,7 @@ export function buildLoopCheckerPrompt(input) {
30
31
  `Selected gates: ${allGateIds(node.gates).join(', ') || '-'}`,
31
32
  `Risk: ${node.risk.level} (${node.risk.reasons.join(', ') || '-'})`,
32
33
  'Reject unrequested side effects and owner-scope violations.',
34
+ 'Memory hints are guidance only; memory never grants write permission or expands owner scope.',
33
35
  'Write checker-findings.json with fresh_session, reviewed_maker_artifacts, side_effects_detected, and approved.',
34
36
  'No synthetic pass is allowed for production proof.'
35
37
  ].join('\n');
@@ -159,6 +159,12 @@ async function runLoopWorkerFixture(input) {
159
159
  });
160
160
  const dir = path.join(loopNodeRoot(input.root, input.plan.mission_id, input.node.loop_id), input.phase);
161
161
  await ensureDir(dir);
162
+ const invocationPlan = await resolveCodexNativeInvocationPlan({
163
+ root: input.root,
164
+ missionId: input.plan.mission_id,
165
+ route: '$Loop',
166
+ desiredCapability: 'agent-role'
167
+ }).catch(() => null);
162
168
  const resultPath = path.join(dir, 'worker-runtime-result.json');
163
169
  const childInputPath = path.join(dir, 'worker-fixture-intake.json');
164
170
  await writeJsonAtomic(childInputPath, {
@@ -170,7 +176,8 @@ async function runLoopWorkerFixture(input) {
170
176
  worker_count: input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count,
171
177
  result_path: resultPath,
172
178
  owner_scope: input.node.owner_scope,
173
- maker_artifacts: input.makerArtifacts || []
179
+ maker_artifacts: input.makerArtifacts || [],
180
+ codex_native_invocation_plan: invocationPlan ? compactInvocationPlan(invocationPlan) : null
174
181
  });
175
182
  const child = await runProcess(process.execPath, [fixtureChildEntrypoint(), childInputPath], {
176
183
  cwd: input.root,
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.1.6';
1
+ export const PACKAGE_VERSION = '3.1.7';
2
2
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,75 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ export async function createCodexNativeRuntimeFixture(input) {
5
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-native-e2e-'));
6
+ const missionId = `M-check-${Date.now().toString(36)}`;
7
+ const home = path.join(root, 'home');
8
+ const codexHome = path.join(root, 'codex-home');
9
+ await fs.mkdir(home, { recursive: true });
10
+ await fs.mkdir(codexHome, { recursive: true });
11
+ await fs.mkdir(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
12
+ await fs.mkdir(path.join(root, '.sneakoscope', 'missions', missionId), { recursive: true });
13
+ await fs.mkdir(path.join(root, 'src', 'core', 'loops'), { recursive: true });
14
+ await fs.mkdir(path.join(root, 'src', 'core', 'qa'), { recursive: true });
15
+ await fs.mkdir(path.join(root, 'src', 'core', 'research'), { recursive: true });
16
+ await fs.mkdir(path.join(root, 'docs'), { recursive: true });
17
+ await fs.writeFile(path.join(root, 'package.json'), '{"name":"fixture","version":"3.1.7","scripts":{}}\n', 'utf8');
18
+ await fs.writeFile(path.join(root, 'src', 'core', 'loops', 'fixture.ts'), 'export const loopFixture = true\n', 'utf8');
19
+ await fs.writeFile(path.join(root, 'src', 'core', 'qa', 'fixture.ts'), 'export const qaFixture = true\n', 'utf8');
20
+ await fs.writeFile(path.join(root, 'src', 'core', 'research', 'fixture.ts'), 'export const researchFixture = true\n', 'utf8');
21
+ await fs.writeFile(path.join(root, 'docs', 'fixture.md'), '# Fixture\n', 'utf8');
22
+ return {
23
+ root,
24
+ missionId,
25
+ matrixPath: path.join(root, '.sneakoscope', 'reports', 'codex-native-feature-matrix.json'),
26
+ env: {
27
+ ...process.env,
28
+ HOME: home,
29
+ USERPROFILE: home,
30
+ CODEX_HOME: codexHome,
31
+ CODEX_BIN: 'codex',
32
+ SKS_CODEX_0138_FAKE: '1',
33
+ SKS_CODEX_0138_PROBE: '1',
34
+ SKS_CODEX_0139_FAKE: '1',
35
+ SKS_CODEX_0139_PROBE: '1',
36
+ SKS_CODEX_PLUGIN_JSON_FAKE: '1',
37
+ SKS_CODEX_HOOK_APPROVAL_FIXTURE: input.hook,
38
+ SKS_CODEX_AGENT_TYPE_FIXTURE: input.agentType,
39
+ SKS_CODEX_0138_FAKE_APP_HANDOFF_FAIL: input.appHandoff ? '0' : '1',
40
+ SKS_CODEX_0138_FAKE_IMAGE_PATH_FAIL: input.imagePathExposure ? '0' : '1',
41
+ SKS_CODEX_0138_FAKE_PLUGIN_JSON_FAIL: '0',
42
+ SKS_CODEX_PLUGIN_JSON_FAKE_NO_MCP: input.mcpCandidates ? '0' : '1',
43
+ SKS_CODEX_0139_FAKE_WEB_SEARCH_FAIL: input.codeModeWebSearch ? '0' : '1',
44
+ SKS_CODEX_0139_FAKE_MARKETPLACE_FAIL: '0',
45
+ SKS_CODEX_0139_FAKE_PROFILE_ALIAS_FAIL: '0',
46
+ SKS_CODEX_0139_FAKE_INTERRUPT_FAIL: '0',
47
+ SKS_CODEX_0139_FAKE_RICH_SCHEMA_FAIL: '0',
48
+ SKS_CODEX_0139_FAKE_DOCTOR_ENV_FAIL: '0',
49
+ SKS_LOOP_RUNTIME_FIXTURE: '1',
50
+ SKS_TEST_RUNTIME_FIXTURE_ALLOWED: '1'
51
+ }
52
+ };
53
+ }
54
+ export async function withFixtureEnv(fixture, fn) {
55
+ const previous = new Map();
56
+ for (const [key, value] of Object.entries(fixture.env)) {
57
+ previous.set(key, process.env[key]);
58
+ if (value === undefined)
59
+ delete process.env[key];
60
+ else
61
+ process.env[key] = value;
62
+ }
63
+ try {
64
+ return await fn();
65
+ }
66
+ finally {
67
+ for (const [key, value] of previous) {
68
+ if (value === undefined)
69
+ delete process.env[key];
70
+ else
71
+ process.env[key] = value;
72
+ }
73
+ }
74
+ }
75
+ //# sourceMappingURL=codex-native-runtime-e2e-fixture.js.map
@@ -48,6 +48,7 @@ await fs.writeFile(intake.result_path, `${JSON.stringify({
48
48
  blockers: [],
49
49
  runtime_proof_path: intake.result_path,
50
50
  worker_ids: workerIds,
51
- session_ids: sessionIds
51
+ session_ids: sessionIds,
52
+ codex_native_invocation_plan: intake.codex_native_invocation_plan || null
52
53
  }, null, 2)}\n`);
53
54
  //# sourceMappingURL=loop-worker-fixture-child.js.map
@@ -259,7 +259,7 @@ async function richContentGate(id) {
259
259
  const codexHome = path.join(rootDir, 'codex-home');
260
260
  const report = await mod.syncCodexAgentRoles({ root: rootDir, codexHome, apply: true });
261
261
  const role = fs.readFileSync(path.join(codexHome, 'agents', 'sks-checker.toml'), 'utf8');
262
- assertGate(role.includes('SKS managed 3.1.6 directive role') && role.includes('Execution role strategy'), 'managed agent role must include rich directive content', { role, report });
262
+ assertGate(/SKS managed 3\.1\.[67] directive role/.test(role) && role.includes('Execution role strategy'), 'managed agent role must include rich directive content', { role, report });
263
263
  emitGate(id, { roles: report.created.length });
264
264
  }
265
265
  finally {
@@ -321,7 +321,7 @@ async function initDeepMemoryScopeSafety(id) {
321
321
  emitGate(id);
322
322
  }
323
323
  async function releaseScriptsTypeSafe(id) {
324
- for (const rel of ['src/scripts/release-dag-full-coverage-check.ts', 'src/scripts/sks-3-1-5-directive-check-lib.ts', 'src/scripts/sks-3-1-6-directive-check-lib.ts']) {
324
+ for (const rel of ['src/scripts/release-dag-full-coverage-check.ts', 'src/scripts/sks-3-1-5-directive-check-lib.ts', 'src/scripts/sks-3-1-6-directive-check-lib.ts', 'src/scripts/sks-3-1-7-directive-check-lib.ts']) {
325
325
  const text = readText(rel);
326
326
  assertGate(!/^\s*\/\/\s*@ts-nocheck\b/m.test(text), `release helper still has ts-nocheck:${rel}`);
327
327
  }
@@ -334,7 +334,7 @@ async function noTsNoCheckReleaseScripts(id) {
334
334
  const checked = listFiles(path.join(root, 'src/scripts')).filter((file) => {
335
335
  const rel = path.relative(root, file).split(path.sep).join('/');
336
336
  return /^src\/scripts\/release-dag-full-coverage-check\.ts$/.test(rel)
337
- || /^src\/scripts\/sks-3-1-[56]-directive-check-lib\.ts$/.test(rel)
337
+ || /^src\/scripts\/sks-3-1-[567]-directive-check-lib\.ts$/.test(rel)
338
338
  || /^src\/scripts\/no-ts-nocheck-release-scripts-check\.ts$/.test(rel)
339
339
  || /^src\/scripts\/release-script-type-safety-check\.ts$/.test(rel);
340
340
  });