sneakoscope 3.1.4 → 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +8 -36
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/command-registry.js +1 -2
  8. package/dist/cli/install-helpers.js +56 -4
  9. package/dist/commands/codex-app.js +1 -11
  10. package/dist/commands/codex-lb.js +12 -9
  11. package/dist/commands/codex-native.js +68 -0
  12. package/dist/commands/doctor.js +64 -0
  13. package/dist/core/codex-app/codex-agent-role-sync.js +69 -11
  14. package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
  15. package/dist/core/codex-app/codex-app-execution-profile.js +43 -14
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
  17. package/dist/core/codex-app/codex-app-harness-matrix.js +4 -65
  18. package/dist/core/codex-app/codex-app-types.js +21 -0
  19. package/dist/core/codex-app/codex-hook-approval-probe.js +188 -0
  20. package/dist/core/codex-app/codex-hook-lifecycle.js +34 -10
  21. package/dist/core/codex-app/codex-init-deep.js +154 -3
  22. package/dist/core/codex-app/codex-skill-sync.js +84 -9
  23. package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
  24. package/dist/core/codex-native/codex-native-feature-broker.js +182 -0
  25. package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
  26. package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
  27. package/dist/core/codex-native/codex-native-interop-policy.js +58 -0
  28. package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
  29. package/dist/core/codex-native/codex-native-pattern-analysis.js +56 -0
  30. package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
  31. package/dist/core/codex-native/codex-native-reference-source.js +110 -0
  32. package/dist/core/codex-native/codex-native-rename-map.js +25 -0
  33. package/dist/core/commands/mad-sks-command.js +37 -2
  34. package/dist/core/commands/qa-loop-command.js +3 -2
  35. package/dist/core/commands/research-command.js +2 -2
  36. package/dist/core/doctor/doctor-zellij-repair.js +5 -1
  37. package/dist/core/feature-fixtures.js +3 -4
  38. package/dist/core/feature-registry.js +5 -2
  39. package/dist/core/fsx.js +1 -1
  40. package/dist/core/image/image-artifact-path-contract.js +18 -1
  41. package/dist/core/init.js +4 -1
  42. package/dist/core/loops/loop-continuation-enforcer.js +11 -3
  43. package/dist/core/loops/loop-owner-inference.js +3 -0
  44. package/dist/core/loops/loop-planner.js +28 -5
  45. package/dist/core/loops/loop-worker-runtime.js +52 -8
  46. package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
  47. package/dist/core/qa-loop.js +62 -4
  48. package/dist/core/research/research-cycle-runner.js +1 -0
  49. package/dist/core/research/research-stage-runner.js +9 -2
  50. package/dist/core/research.js +68 -1
  51. package/dist/core/routes.js +2 -3
  52. package/dist/core/version.js +1 -1
  53. package/dist/core/zellij/homebrew-policy.js +0 -1
  54. package/dist/core/zellij/zellij-self-heal-types.js +45 -0
  55. package/dist/core/zellij/zellij-self-heal.js +69 -8
  56. package/dist/core/zellij/zellij-update.js +9 -1
  57. package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
  58. package/dist/scripts/sks-3-1-5-directive-check-lib.js +318 -0
  59. package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
  60. package/package.json +53 -9
  61. package/dist/cli/hermes-command.js +0 -99
  62. package/dist/cli/openclaw-command.js +0 -83
  63. package/dist/commands/hermes.js +0 -5
  64. package/dist/commands/openclaw.js +0 -3
  65. package/dist/core/codex-app/lazycodex-analysis.js +0 -51
  66. package/dist/core/codex-app/lazycodex-interop-policy.js +0 -49
  67. package/dist/core/hermes.js +0 -192
  68. package/dist/core/openclaw.js +0 -171
@@ -2,6 +2,8 @@ import path from 'node:path';
2
2
  import { exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic, PACKAGE_VERSION } from './fsx.js';
3
3
  import { CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE, CODEX_WEB_VERIFICATION_POLICY, evidenceMentionsForbiddenBrowserAutomation, evidenceMentionsForbiddenWebComputerUseEvidence } from './routes.js';
4
4
  import { appendAgentLedgerEvent, initializeAgentCentralLedger } from './agents/agent-central-ledger.js';
5
+ import { resolveCodexAppExecutionProfile } from './codex-app/codex-app-execution-profile.js';
6
+ import { resolveCodexNativeInvocationPlan } from './codex-native/codex-native-invocation-router.js';
5
7
  export const QA_LOOP_ROUTE = 'QALoop';
6
8
  const QA_REPORT_SUFFIX = 'qa-report.md';
7
9
  const UI_CHROME_EXTENSION_FIRST_ACK = 'use_codex_chrome_extension_first_no_computer_use_for_web_ui_or_mark_unverified';
@@ -320,6 +322,11 @@ export function defaultQaGate(contract = {}, opts = {}) {
320
322
  image_artifact_path_contract_present: false,
321
323
  image_artifact_path_contract_artifact: null,
322
324
  image_artifact_path_contract_blockers: [],
325
+ codex_app_execution_profile: opts.executionProfile ? compactExecutionProfile(opts.executionProfile) : null,
326
+ codex_app_execution_profile_artifact: opts.executionProfile ? 'qa-loop/execution-profile.json' : null,
327
+ codex_app_hooks_approval_required: opts.executionProfile?.hooks_approval_required === true,
328
+ codex_app_agent_role_strategy: opts.executionProfile?.agent_role_strategy || null,
329
+ codex_native_invocation: opts.codexNativeInvocation || null,
323
330
  api_e2e_required: apiRequired,
324
331
  unsafe_external_side_effects: false,
325
332
  corrective_loop_enabled: corrective,
@@ -338,16 +345,25 @@ export async function writeQaLoopArtifacts(dir, mission, contract) {
338
345
  const a = contract.answers || {};
339
346
  const checklist = qaChecklist(a);
340
347
  const reportFile = qaReportFilename();
348
+ const root = missionRootFromDir(dir);
349
+ const executionProfile = root ? await resolveCodexAppExecutionProfile({ root }).catch(() => null) : null;
350
+ const codexNativeInvocation = root ? await resolveQaCodexNativeInvocation(root, mission.id).catch(() => null) : null;
351
+ if (executionProfile)
352
+ await writeJsonAtomic(path.join(dir, 'qa-loop', 'execution-profile.json'), executionProfile).catch(() => undefined);
353
+ if (codexNativeInvocation)
354
+ await writeJsonAtomic(path.join(dir, 'qa-loop', 'codex-native-invocation.json'), codexNativeInvocation).catch(() => undefined);
341
355
  await writeJsonAtomic(path.join(dir, 'qa-ledger.json'), {
342
356
  schema_version: 1,
343
357
  generated_at: nowIso(),
344
358
  mission_id: mission.id,
345
359
  qa_report_file: reportFile,
360
+ codex_app_execution_profile: executionProfile ? compactExecutionProfile(executionProfile) : null,
361
+ codex_native_invocation: codexNativeInvocation,
346
362
  target: { scope: a.QA_SCOPE, environment: a.TARGET_ENVIRONMENT, base_url: a.TARGET_BASE_URL, api_base_url: a.API_BASE_URL },
347
363
  safety: { mutation_policy: a.QA_MUTATION_POLICY, deployed_destructive_tests_allowed: 'never', credentials: 'temp_only_never_saved', ui_evidence: 'codex_chrome_extension_first_required_for_web_ui_e2e' },
348
364
  checklist
349
365
  });
350
- await writeJsonAtomic(path.join(dir, 'qa-gate.json'), defaultQaGate(contract, { reportFile }));
366
+ await writeJsonAtomic(path.join(dir, 'qa-gate.json'), defaultQaGate(contract, { reportFile, executionProfile, codexNativeInvocation }));
351
367
  await writeTextAtomic(path.join(dir, reportFile), qaReportTemplate(mission, contract, checklist));
352
368
  return { checklist_count: checklist.length, report_file: reportFile };
353
369
  }
@@ -432,6 +448,11 @@ export async function writeMockQaResult(dir, mission, contract) {
432
448
  image_artifact_path_contract_present: previousGate.image_artifact_path_contract_present === true,
433
449
  image_artifact_path_contract_artifact: previousGate.image_artifact_path_contract_artifact || null,
434
450
  image_artifact_path_contract_blockers: previousGate.image_artifact_path_contract_blockers || [],
451
+ codex_app_execution_profile: previousGate.codex_app_execution_profile || null,
452
+ codex_app_execution_profile_artifact: previousGate.codex_app_execution_profile_artifact || null,
453
+ codex_app_hooks_approval_required: previousGate.codex_app_hooks_approval_required === true,
454
+ codex_app_agent_role_strategy: previousGate.codex_app_agent_role_strategy || null,
455
+ codex_native_invocation: previousGate.codex_native_invocation || null,
435
456
  blockers: previousGate.blockers || [],
436
457
  passed: !uiRequired,
437
458
  qa_report_written: true,
@@ -453,7 +474,23 @@ export async function writeMockQaResult(dir, mission, contract) {
453
474
  });
454
475
  return evaluateQaGate(dir);
455
476
  }
456
- export function buildQaLoopPrompt({ id, mission, contract, cycle, previous, reportFile, imagePathContract, appHandoff }) {
477
+ async function resolveQaCodexNativeInvocation(root, missionId) {
478
+ const [visualReview, hookEvidence, imageFollowup] = await Promise.all([
479
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$QA-LOOP', desiredCapability: 'visual-review' }),
480
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$QA-LOOP', desiredCapability: 'hook-evidence' }),
481
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Image', desiredCapability: 'image-followup' })
482
+ ]);
483
+ return {
484
+ visual_review: visualReview.selected_strategy,
485
+ visual_review_plan: visualReview,
486
+ hook_evidence_policy: hookEvidence.env.SKS_CODEX_NATIVE_HOOK_EVIDENCE_POLICY,
487
+ hook_evidence_plan: hookEvidence,
488
+ image_path_strategy: imageFollowup.selected_strategy === 'codex-app-native' ? 'model-visible-path' : 'artifact-path',
489
+ image_followup_plan: imageFollowup,
490
+ hook_derived_evidence_counted: hookEvidence.selected_strategy !== 'blocked'
491
+ };
492
+ }
493
+ export function buildQaLoopPrompt({ id, mission, contract, cycle, previous, reportFile, imagePathContract, appHandoff, executionProfile }) {
457
494
  const report = reportFile && isQaReportFilename(reportFile) ? reportFile : 'the date/version-prefixed report named by qa-gate.json.qa_report_file';
458
495
  const imageContractText = imagePathContract
459
496
  ? `\nIMAGE PATH CONTRACT:\n${JSON.stringify(imagePathContract, null, 2)}\nUse model_visible_path values for follow-up image edits; do not invent generated image paths.\n`
@@ -461,6 +498,9 @@ export function buildQaLoopPrompt({ id, mission, contract, cycle, previous, repo
461
498
  const appHandoffText = appHandoff
462
499
  ? `\nCODEX DESKTOP /app HANDOFF:\n${JSON.stringify(appHandoff, null, 2)}\nThis is desktop-app review status only and is not web UI evidence.\n`
463
500
  : '';
501
+ const executionProfileText = executionProfile
502
+ ? `\nCODEX APP EXECUTION PROFILE:\n${JSON.stringify(compactExecutionProfile(executionProfile), null, 2)}\nUse this routing profile for agent role strategy and app/headless assumptions.\n`
503
+ : '';
464
504
  return `SKS QA-LOOP
465
505
  MISSION: ${id}
466
506
  TASK: ${mission.prompt}
@@ -473,7 +513,7 @@ GATE: passed=false while unresolved_findings or unresolved_fixable_findings > 0,
473
513
  ARTIFACTS: update qa-ledger.json, ${report}, qa-gate.json, and qa-loop/cycle-${cycle}/.
474
514
  CONTRACT:
475
515
  ${JSON.stringify(contract, null, 2)}
476
- ${imageContractText}${appHandoffText}
516
+ ${imageContractText}${appHandoffText}${executionProfileText}
477
517
  Previous tail:
478
518
  ${String(previous || '').slice(-2500)}
479
519
  `;
@@ -486,7 +526,8 @@ export async function qaStatus(dir) {
486
526
  const imagePathContract = await readJson(path.join(dir, 'qa-loop', 'image-artifact-path-contract.json'), null);
487
527
  const reportFile = qaReportFileFromGate(gate?.gate || gate || {}) || ledger?.qa_report_file || null;
488
528
  const report = reportFile && isQaReportFilename(reportFile) ? await readText(path.join(dir, reportFile), '') : '';
489
- return { gate, checklist_count: ledger?.checklist?.length ?? null, report_file: reportFile, report_written: Boolean(report.trim()), desktop_app_handoff: appHandoff, desktop_app_confirmation: appConfirmation, desktop_review_complete: appConfirmation?.verdict === 'pass', image_path_contract: imagePathContract };
529
+ const executionProfile = await readJson(path.join(dir, 'qa-loop', 'execution-profile.json'), null);
530
+ return { gate, checklist_count: ledger?.checklist?.length ?? null, report_file: reportFile, report_written: Boolean(report.trim()), desktop_app_handoff: appHandoff, desktop_app_confirmation: appConfirmation, desktop_review_complete: appConfirmation?.verdict === 'pass', image_path_contract: imagePathContract, codex_app_execution_profile: executionProfile };
490
531
  }
491
532
  function qaChecklist(a) {
492
533
  const cases = [
@@ -504,6 +545,23 @@ function qaChecklist(a) {
504
545
  cases.push(['report.evidence', 'Record pass/fail/blocked/skipped with evidence.'], ['report.corrective_loop', 'Record fixes, rechecks, unresolved findings, deferred blockers.'], ['report.honest', 'Run Honest Mode.']);
505
546
  return cases.map(([id, title]) => ({ id, title, status: 'pending', evidence: [] }));
506
547
  }
548
+ function missionRootFromDir(dir) {
549
+ const normalized = path.resolve(String(dir || ''));
550
+ const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;
551
+ const idx = normalized.indexOf(marker);
552
+ return idx > 0 ? normalized.slice(0, idx) : null;
553
+ }
554
+ function compactExecutionProfile(profile) {
555
+ return profile ? {
556
+ mode: profile.mode || 'unknown',
557
+ agent_role_strategy: profile.agent_role_strategy || 'message-role',
558
+ hooks_approval_required: profile.hooks_approval_required === true,
559
+ hook_approval_state: profile.hook_approval_state || 'unknown',
560
+ app_handoff_ready: profile.app_handoff_ready === true,
561
+ plugin_mcp_inventory_ready: profile.plugin_mcp_inventory_ready === true,
562
+ artifact_path: profile.artifact_path || '.sneakoscope/reports/codex-app-execution-profile.json'
563
+ } : null;
564
+ }
507
565
  function qaReportTemplate(mission, contract, checklist) {
508
566
  const a = contract.answers || {};
509
567
  return `# QA-LOOP Report\n\nMission: ${mission.id}\nTarget: ${a.TARGET_BASE_URL || 'unset'}\nScope: ${a.QA_SCOPE || 'unset'}\nEnvironment: ${a.TARGET_ENVIRONMENT || 'unset'}\n\n## Safety\n\n- Deployed destructive tests: never\n- Credentials: temp-only, never saved\n- UI evidence: ${CODEX_WEB_VERIFICATION_POLICY}\n\n## Checklist\n\n${checklist.map((item) => `- [ ] ${item.id}: ${item.title}`).join('\n')}\n\n## Findings\n\nTBD\n\n## Corrections And Rechecks\n\nTBD\n\n## Honest Mode\n\nTBD\n`;
@@ -63,6 +63,7 @@ export async function runResearchCycle(inputOrDir, legacyGraph = null, legacyOpt
63
63
  blockers: [...new Set(blockers)],
64
64
  stages: stageResults.map((stage) => stage.stage_id),
65
65
  stage_results: stageResults,
66
+ codex_app_execution_profile: input.plan?.codex_app_execution_profile || null,
66
67
  parallelism: {
67
68
  max_parallel_stages: maxParallel,
68
69
  max_observed_parallel: maxObservedParallel,
@@ -91,7 +91,7 @@ async function runSourceShardStage(input, startedAt) {
91
91
  const validation = validateResearchSourceShardOutput(output);
92
92
  await writeJsonAtomic(path.join(input.dir, artifact), output);
93
93
  await writeTextAtomic(path.join(input.dir, 'research', `cycle-${input.cycle}`, 'source-notes', `${layer.id}.md`), `# Source shard: ${layer.label}\n\n${output.sources.map((source) => `- ${source.id}: ${source.title}`).join('\n')}\n`);
94
- return baseResult(input, startedAt, 'source_shard', validation.ok ? 'passed' : 'blocked', [artifact, `research/cycle-${input.cycle}/source-notes/${layer.id}.md`], validation.blockers, { layer_id: layer.id, source_count: output.sources.length });
94
+ return baseResult(input, startedAt, 'source_shard', validation.ok ? 'passed' : 'blocked', [artifact, `research/cycle-${input.cycle}/source-notes/${layer.id}.md`], validation.blockers, { layer_id: layer.id, source_count: output.sources.length, source_tool_route: researchSourceToolRoute(input.plan) });
95
95
  }
96
96
  const codex = await runResearchCodexStage({
97
97
  root: input.root,
@@ -333,9 +333,16 @@ function baseResult(input, startedAt, stageKind, status, outputArtifacts, blocke
333
333
  backend: input.backend,
334
334
  worker_result_path: typeof metrics.worker_result_path === 'string' ? metrics.worker_result_path : null,
335
335
  blockers: [...new Set(blockers.map(String).filter(Boolean))],
336
- metrics
336
+ metrics: {
337
+ ...metrics,
338
+ codex_app_execution_profile: input.plan?.codex_app_execution_profile || null,
339
+ source_tool_route: metrics.source_tool_route || researchSourceToolRoute(input.plan)
340
+ }
337
341
  };
338
342
  }
343
+ function researchSourceToolRoute(plan) {
344
+ return plan?.web_research_policy?.source_tool_routing?.mode || (plan?.codex_app_execution_profile?.plugin_mcp_inventory_ready ? 'plugin-mcp-inventory-first' : 'codex-cli-or-web-fallback');
345
+ }
339
346
  async function buildResearchGateSeed(dir, plan) {
340
347
  const sourceLedger = await readJson(path.join(dir, 'source-ledger.json'), null);
341
348
  const claimMatrix = await readJson(path.join(dir, 'claim-evidence-matrix.json'), null);
@@ -17,6 +17,8 @@ import { writeResearchHandoffArtifacts } from './research/research-handoff.js';
17
17
  import { RESEARCH_WORK_GRAPH_ARTIFACT, writeResearchWorkGraph } from './research/research-work-graph.js';
18
18
  import { researchPromptContractText, validateResearchPromptContract } from './research/research-prompt-contract.js';
19
19
  import { buildRealisticResearchPaper, buildRealisticResearchReport } from './research/research-realistic-report.js';
20
+ import { resolveCodexAppExecutionProfile } from './codex-app/codex-app-execution-profile.js';
21
+ import { resolveCodexNativeInvocationPlan } from './codex-native/codex-native-invocation-router.js';
20
22
  export const RESEARCH_PAPER_ARTIFACT = 'research-paper.md';
21
23
  export const RESEARCH_SOURCE_SKILL_ARTIFACT = 'research-source-skill.md';
22
24
  export const RESEARCH_GENIUS_SUMMARY_ARTIFACT = 'genius-opinion-summary.md';
@@ -252,6 +254,15 @@ export function createResearchPlan(prompt, opts = {}) {
252
254
  const createdAt = nowIso();
253
255
  const paperArtifact = researchPaperArtifactName(prompt, createdAt, opts);
254
256
  const nativeAgentPlan = researchNativeAgentPlan(prompt, { paperArtifact, missionId: opts.missionId });
257
+ const executionProfile = opts.executionProfile || null;
258
+ const codexNativeInvocation = opts.codexNativeInvocation || null;
259
+ const sourceStrategy = codexNativeInvocation?.mcp_source?.selected_strategy === 'codex-app-native'
260
+ ? 'mcp-plugin-candidates'
261
+ : codexNativeInvocation?.web_search?.selected_strategy === 'codex-cli-headless'
262
+ ? 'web-sources'
263
+ : executionProfile?.plugin_mcp_inventory_ready
264
+ ? 'mcp-plugin-candidates'
265
+ : 'local-files';
255
266
  return {
256
267
  schema_version: 1,
257
268
  mission_id: opts.missionId || null,
@@ -262,6 +273,8 @@ export function createResearchPlan(prompt, opts = {}) {
262
273
  paper_artifact: paperArtifact,
263
274
  quality_contract: DEFAULT_RESEARCH_QUALITY_CONTRACT,
264
275
  native_agent_plan: nativeAgentPlan,
276
+ codex_app_execution_profile: executionProfile ? compactExecutionProfile(executionProfile) : null,
277
+ codex_native_invocation: codexNativeInvocation,
265
278
  agent_sessions: nativeAgentPlan.personas,
266
279
  agent_batches: nativeAgentPlan.batches,
267
280
  autoresearch_cycle_policy: nativeAgentPlan.autoresearch_cycle_policy,
@@ -318,6 +331,13 @@ export function createResearchPlan(prompt, opts = {}) {
318
331
  web_research_policy: {
319
332
  mode: 'layered_source_retrieval_and_triangulation',
320
333
  requirement: 'Use every safely available public web/source route before synthesis, separated into source layers so the final claim is not dominated by one corpus or platform.',
334
+ source_tool_routing: {
335
+ mode: sourceStrategy,
336
+ plugin_mcp_inventory_ready: executionProfile?.plugin_mcp_inventory_ready === true,
337
+ execution_profile_artifact: executionProfile?.artifact_path || '.sneakoscope/reports/codex-app-execution-profile.json',
338
+ codex_native_invocation_artifact: codexNativeInvocation ? 'research/codex-native-invocation.json' : null,
339
+ rule: 'Prefer verified plugin/MCP candidates when available; otherwise record source-tool blockers instead of assuming live search coverage.'
340
+ },
321
341
  query_sets: [
322
342
  'first-principles and theory sources',
323
343
  'plain-language explanations and empirical examples',
@@ -417,6 +437,9 @@ export function researchPlanMarkdown(plan) {
417
437
  lines.push(`Depth: ${plan.depth}`);
418
438
  lines.push(`Methodology: ${plan.methodology}`);
419
439
  lines.push(`Research paper: ${researchPaperArtifactForPlan(plan)}`);
440
+ if (plan.codex_app_execution_profile) {
441
+ lines.push(`Execution profile: ${plan.codex_app_execution_profile.mode}; agent role strategy ${plan.codex_app_execution_profile.agent_role_strategy}`);
442
+ }
420
443
  if (plan.execution_policy) {
421
444
  lines.push(`Execution: ${plan.execution_policy.normal_run}; default cycle timeout ${plan.execution_policy.default_cycle_timeout_minutes} minutes`);
422
445
  if (plan.execution_policy.default_max_cycles)
@@ -468,6 +491,8 @@ export function researchPlanMarkdown(plan) {
468
491
  lines.push('## Web Research Policy');
469
492
  lines.push(`Mode: ${plan.web_research_policy.mode}`);
470
493
  lines.push(`Requirement: ${plan.web_research_policy.requirement}`);
494
+ if (plan.web_research_policy.source_tool_routing)
495
+ lines.push(`Source tool routing: ${plan.web_research_policy.source_tool_routing.mode}`);
471
496
  for (const querySet of plan.web_research_policy.query_sets || [])
472
497
  lines.push(`- query set: ${querySet}`);
473
498
  if (plan.web_research_policy.skill_creator?.artifact)
@@ -537,7 +562,11 @@ export function countGeniusOpinionSummaries(text = '') {
537
562
  }).length;
538
563
  }
539
564
  export async function writeResearchPlan(dir, prompt, opts = {}) {
540
- const plan = createResearchPlan(prompt, opts);
565
+ const root = opts.root || missionRootFromDir(String(dir || ''));
566
+ const executionProfile = opts.executionProfile || (root ? await resolveCodexAppExecutionProfile({ root }).catch(() => null) : null);
567
+ const missionId = opts.missionId || path.basename(String(dir || ''));
568
+ const codexNativeInvocation = root ? await resolveResearchCodexNativeInvocation(root, missionId).catch(() => null) : null;
569
+ const plan = createResearchPlan(prompt, { ...opts, executionProfile, codexNativeInvocation });
541
570
  const noveltyLedger = {
542
571
  schema_version: 1,
543
572
  entries: [],
@@ -553,6 +582,10 @@ export async function writeResearchPlan(dir, prompt, opts = {}) {
553
582
  const experimentPlan = defaultExperimentPlan(plan);
554
583
  const replicationPack = defaultReplicationPack(plan);
555
584
  await writeJsonAtomic(path.join(dir, 'research-plan.json'), plan);
585
+ if (executionProfile)
586
+ await writeJsonAtomic(path.join(dir, 'research', 'execution-profile.json'), executionProfile).catch(() => undefined);
587
+ if (codexNativeInvocation)
588
+ await writeJsonAtomic(path.join(dir, 'research', 'codex-native-invocation.json'), codexNativeInvocation).catch(() => undefined);
556
589
  await writeTextAtomic(path.join(dir, 'research-plan.md'), researchPlanMarkdown(plan));
557
590
  await writeTextAtomic(path.join(dir, RESEARCH_SOURCE_SKILL_ARTIFACT), researchSourceSkillMarkdown(plan));
558
591
  await writeResearchQualityContract(dir, plan.quality_contract);
@@ -575,6 +608,40 @@ export async function writeResearchPlan(dir, prompt, opts = {}) {
575
608
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.plan.created', depth: plan.depth });
576
609
  return plan;
577
610
  }
611
+ async function resolveResearchCodexNativeInvocation(root, missionId) {
612
+ const [pluginSource, mcpSource, webSearch] = await Promise.all([
613
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Research', desiredCapability: 'plugin-source' }),
614
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Research', desiredCapability: 'mcp-source' }),
615
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Research', desiredCapability: 'web-search' })
616
+ ]);
617
+ return {
618
+ plugin_source: pluginSource,
619
+ mcp_source: mcpSource,
620
+ web_search: webSearch,
621
+ selected_source_strategy: mcpSource.selected_strategy === 'codex-app-native'
622
+ ? 'mcp-plugin-candidates'
623
+ : webSearch.selected_strategy === 'codex-cli-headless'
624
+ ? 'web-sources'
625
+ : 'local-files',
626
+ hook_derived_source_evidence_allowed: false
627
+ };
628
+ }
629
+ function missionRootFromDir(dir) {
630
+ const normalized = path.resolve(String(dir || ''));
631
+ const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;
632
+ const idx = normalized.indexOf(marker);
633
+ return idx > 0 ? normalized.slice(0, idx) : null;
634
+ }
635
+ function compactExecutionProfile(profile) {
636
+ return profile ? {
637
+ mode: profile.mode || 'unknown',
638
+ agent_role_strategy: profile.agent_role_strategy || 'message-role',
639
+ hooks_approval_required: profile.hooks_approval_required === true,
640
+ hook_approval_state: profile.hook_approval_state || 'unknown',
641
+ plugin_mcp_inventory_ready: profile.plugin_mcp_inventory_ready === true,
642
+ artifact_path: profile.artifact_path || '.sneakoscope/reports/codex-app-execution-profile.json'
643
+ } : null;
644
+ }
578
645
  export async function writeResearchNativeAgentLedger(dir, plan, opts = {}) {
579
646
  const missionId = opts.missionId || plan?.mission_id;
580
647
  if (!missionId)
@@ -30,7 +30,7 @@ export const FROM_CHAT_IMG_CHECKLIST_ARTIFACT = 'from-chat-img-checklist.md';
30
30
  export const FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT = 'from-chat-img-temp-triwiki.json';
31
31
  export const FROM_CHAT_IMG_QA_LOOP_ARTIFACT = 'from-chat-img-qa-loop.json';
32
32
  export const FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS = 5;
33
- export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|zellij|tmux|auto-review|team|qa-loop|ppt|image-ux-review|goal|fast-mode|research|db|git|codex|codex-app|hooks|features|all-features|openclaw|hermes|dfix|commit|commit-and-push|design|imagegen|dollar|context7|xai|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|wrongness|code-structure|proof-field|skill-dream|rust';
33
+ export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|zellij|tmux|auto-review|team|qa-loop|ppt|image-ux-review|goal|fast-mode|research|db|git|codex|codex-app|codex-native|hooks|features|all-features|dfix|commit|commit-and-push|design|imagegen|dollar|context7|xai|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|wrongness|code-structure|proof-field|skill-dream|rust';
34
34
  export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
35
35
  export const CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE = 'codex_chrome_extension';
36
36
  export const CODEX_IMAGEGEN_EVIDENCE_SOURCE = 'codex_app_imagegen_gpt_image_2';
@@ -633,11 +633,10 @@ export const COMMAND_CATALOG = [
633
633
  { name: 'deps', usage: 'sks deps check [--json] [--yes]', description: 'Check Node/npm, Codex CLI, and Zellij readiness; pass --yes to repair missing Codex CLI/Zellij tooling when supported.' },
634
634
  { name: 'codex', usage: 'sks codex compatibility|version|doctor|schema [--json]', description: 'Check Codex CLI rust-v0.136.0 compatibility, installed version, 0.136 capabilities, inherited 0.135/0.134/0.133 behavior, and vendored hook schema snapshot freshness.' },
635
635
  { name: 'codex-app', usage: 'sks codex-app [check|product-design|product-design --check-only|ensure-product-design|chrome-extension|pat status|remote-control]', description: 'Check Codex App install, Product Design plugin auto-install readiness, Codex Chrome Extension web verification readiness, PAT-safe status, first-party MCP/plugin readiness, and Codex CLI 0.130.0+ remote-control availability.' },
636
+ { name: 'codex-native', usage: 'sks codex-native status|feature-broker|invocation-plan|init-deep [--json]', description: 'Inspect Codex Native feature broker readiness, invocation routing, pattern evidence, and managed memory setup.' },
636
637
  { name: 'hooks', usage: 'sks hooks explain|status|trust-report|replay|codex-validate|warning-check ... [--json]', description: 'Explain Codex hook events, validate vendored latest 10-event output schemas, replay fixtures, and enforce warning-zero SKS hook policies under the 0.134 compatibility matrix.' },
637
638
  { name: 'codex-lb', usage: 'sks codex-lb status|health|metrics|doctor|circuit|repair|setup ...', description: 'Configure, health-check, repair, and record circuit evidence for codex-lb provider auth without confusing ChatGPT OAuth and proxy keys.' },
638
639
  { name: 'auth', usage: 'sks auth status|health|repair|setup --host <domain> --api-key <key>', description: 'Shortcut for codex-lb provider auth status, health, repair, and setup commands.' },
639
- { name: 'openclaw', usage: 'sks openclaw install|path|print [--dir path] [--force] [--json]', description: 'Generate an OpenClaw skill package so OpenClaw agents can discover and use local SKS workflows.' },
640
- { name: 'hermes', usage: 'sks hermes install|status|path|print [--dir path] [--force] [--json]', description: 'Generate a Hermes Agent skill package so Hermes agents can discover and use local SKS workflows.' },
641
640
  { name: 'zellij', usage: 'sks zellij status|repair [--json] | sks naruto dashboard latest | sks --mad', description: 'Inspect Zellij runtime status, explain repair (no auto-install), and open the SKS Zellij runtime used by MAD and Naruto lane UI.' },
642
641
  { name: 'tmux', usage: 'sks tmux [--json]', description: 'Show the removed-runtime migration notice and point operators to Zellij.' },
643
642
  { name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot Zellij Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.1.4';
1
+ export const PACKAGE_VERSION = '3.1.6';
2
2
  //# sourceMappingURL=version.js.map
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import readline from 'node:readline';
3
2
  export const HOMEBREW_INSTALL_COMMAND = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"';
4
3
  export function resolveHomebrewInstallPolicy(input = {}) {
@@ -0,0 +1,45 @@
1
+ import { nowIso } from '../fsx.js';
2
+ export function isZellijSelfHealResult(value) {
3
+ if (!value || typeof value !== 'object' || Array.isArray(value))
4
+ return false;
5
+ const row = value;
6
+ return row.schema === 'sks.zellij-self-heal.v1'
7
+ && typeof row.ok === 'boolean'
8
+ && typeof row.requested_by === 'string'
9
+ && typeof row.strategy === 'string'
10
+ && row.before !== null
11
+ && typeof row.before === 'object'
12
+ && row.after !== null
13
+ && typeof row.after === 'object'
14
+ && Array.isArray(row.blockers)
15
+ && Array.isArray(row.warnings);
16
+ }
17
+ export function normalizeZellijSelfHealResult(value) {
18
+ if (isZellijSelfHealResult(value)) {
19
+ return {
20
+ ...value,
21
+ dry_run: value.dry_run === true,
22
+ planned_mutations: Array.isArray(value.planned_mutations) ? value.planned_mutations : []
23
+ };
24
+ }
25
+ return {
26
+ schema: 'sks.zellij-self-heal.v1',
27
+ ok: false,
28
+ requested_by: 'setup',
29
+ fix_requested: false,
30
+ auto_approved: false,
31
+ install_homebrew_allowed: false,
32
+ dry_run: false,
33
+ planned_mutations: [],
34
+ before: { status: 'unknown', version: null, bin: null },
35
+ latest_version: null,
36
+ strategy: 'failed',
37
+ command: null,
38
+ after: { status: 'unknown', version: null, bin: null },
39
+ mutation_guard_artifact: null,
40
+ homebrew: { present: false, bin: null, install_attempted: false, install_allowed: false },
41
+ blockers: ['invalid_zellij_self_heal_result'],
42
+ warnings: [`normalized_at:${nowIso()}`]
43
+ };
44
+ }
45
+ //# sourceMappingURL=zellij-self-heal-types.js.map
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import fs from 'node:fs/promises';
3
2
  import path from 'node:path';
4
3
  import { ensureDir, nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
@@ -12,6 +11,7 @@ export async function repairZellijForSks(input) {
12
11
  const root = path.resolve(input.root || process.cwd());
13
12
  const env = input.env || process.env;
14
13
  const autoApproved = input.autoApprove === true;
14
+ const dryRun = input.dryRun === true;
15
15
  const beforeReport = await capabilitySnapshot(root, env, 'before');
16
16
  const before = compactCapability(beforeReport);
17
17
  const latest = await latestZellijVersion(env);
@@ -41,6 +41,9 @@ export async function repairZellijForSks(input) {
41
41
  if (input.fixRequested !== true) {
42
42
  return manualResult(root, input, env, before, latest, brew, 'fix_not_requested');
43
43
  }
44
+ if (dryRun) {
45
+ return dryRunResult(root, input, env, before, latest, brew, mutationArtifact);
46
+ }
44
47
  if (!autoApproved && input.interactive !== true) {
45
48
  return input.allowHeadlessFallback === true
46
49
  ? headlessResult(root, input, before, latest, brew, 'noninteractive_without_auto_approval')
@@ -183,10 +186,10 @@ async function capabilitySnapshot(root, env, phase, fallbackVersion) {
183
186
  require_zellij: false,
184
187
  min_version: ZELLIJ_MIN_VERSION,
185
188
  version: null,
186
- bin: null,
189
+ bin: 'zellij',
187
190
  command: ['zellij', '--version'],
188
191
  docs_evidence: [],
189
- blockers: [`zellij_capability_check_failed:${tail(err?.message || String(err))}`],
192
+ blockers: [`zellij_capability_check_failed:${tail(errorMessage(err))}`],
190
193
  warnings: [],
191
194
  operator_actions: ['Resolve the Zellij capability check failure, then rerun `sks doctor --fix --yes`.']
192
195
  }));
@@ -261,7 +264,7 @@ async function runZellijBrew(root, env, brewBin, args, command) {
261
264
  env,
262
265
  timeoutMs: 180000,
263
266
  maxOutputBytes: 256 * 1024
264
- }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
267
+ }).catch((err) => ({ code: 1, stdout: '', stderr: errorMessage(err) }));
265
268
  }
266
269
  async function runHomebrewInstall(root, env) {
267
270
  if (env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN === '1') {
@@ -281,7 +284,7 @@ async function runHomebrewInstall(root, env) {
281
284
  env,
282
285
  timeoutMs: 600000,
283
286
  maxOutputBytes: 256 * 1024
284
- }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
287
+ }).catch((err) => ({ code: 1, stdout: '', stderr: errorMessage(err) }));
285
288
  }
286
289
  async function appendFakeBrewLog(env, args) {
287
290
  if (!env.SKS_FAKE_BREW_LOG)
@@ -289,6 +292,56 @@ async function appendFakeBrewLog(env, args) {
289
292
  await ensureDir(path.dirname(env.SKS_FAKE_BREW_LOG));
290
293
  await fs.appendFile(env.SKS_FAKE_BREW_LOG, `${args.join(' ')}\n`, 'utf8');
291
294
  }
295
+ async function dryRunResult(root, input, env, before, latest, brew, mutationArtifact) {
296
+ const policy = !brew.present
297
+ ? resolveHomebrewInstallPolicy({
298
+ env,
299
+ installHomebrew: input.installHomebrew === true,
300
+ autoApprove: input.autoApprove === true,
301
+ interactiveAccepted: false
302
+ })
303
+ : null;
304
+ if (!brew.present && policy?.allowed !== true) {
305
+ return input.allowHeadlessFallback === true
306
+ ? headlessResult(root, input, before, latest, brew, policy?.blockers[0] || 'homebrew_missing')
307
+ : manualResult(root, input, env, before, latest, brew, policy?.blockers[0] || 'homebrew_missing');
308
+ }
309
+ const planned = [];
310
+ if (!brew.present) {
311
+ planned.push({
312
+ command: HOMEBREW_INSTALL_COMMAND,
313
+ reason: 'homebrew_missing_for_zellij_repair'
314
+ });
315
+ }
316
+ const install = before.status === 'missing';
317
+ const zellijCommand = install ? 'brew install zellij' : 'brew upgrade zellij';
318
+ planned.push({
319
+ command: zellijCommand,
320
+ reason: install ? 'zellij_missing' : 'zellij_too_old_or_stale'
321
+ });
322
+ const strategy = !brew.present ? 'brew-install-homebrew-then-zellij'
323
+ : install ? 'brew-install-zellij'
324
+ : 'brew-upgrade-zellij';
325
+ return persistSelfHeal(root, input.missionDir, {
326
+ schema: 'sks.zellij-self-heal.v1',
327
+ ok: true,
328
+ requested_by: input.requestedBy,
329
+ fix_requested: true,
330
+ auto_approved: input.autoApprove === true,
331
+ install_homebrew_allowed: policy?.allowed === true,
332
+ dry_run: true,
333
+ planned_mutations: planned,
334
+ before,
335
+ latest_version: latest,
336
+ strategy,
337
+ command: planned.map((row) => row.command).join(' && '),
338
+ after: before,
339
+ mutation_guard_artifact: `${mutationArtifact}#planned`,
340
+ homebrew: { present: brew.present, bin: brew.bin, install_attempted: false, install_allowed: policy?.allowed === true },
341
+ blockers: [],
342
+ warnings: ['dry_run_no_mutation_performed']
343
+ });
344
+ }
292
345
  async function manualResult(root, input, env, before, latest, brew, reason) {
293
346
  const command = brew.present ? 'sks doctor --fix --yes' : 'sks doctor --fix --install-homebrew --yes';
294
347
  return persistSelfHeal(root, input.missionDir, {
@@ -329,10 +382,15 @@ async function headlessResult(root, input, before, latest, brew, reason) {
329
382
  });
330
383
  }
331
384
  async function persistSelfHeal(root, missionDir, result) {
332
- await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'zellij-self-heal.json'), result).catch(() => undefined);
385
+ const normalized = {
386
+ ...result,
387
+ dry_run: result.dry_run === true,
388
+ planned_mutations: result.planned_mutations || []
389
+ };
390
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'zellij-self-heal.json'), normalized).catch(() => undefined);
333
391
  if (missionDir)
334
- await writeJsonAtomic(path.join(missionDir, 'zellij-self-heal.json'), result).catch(() => undefined);
335
- return result;
392
+ await writeJsonAtomic(path.join(missionDir, 'zellij-self-heal.json'), normalized).catch(() => undefined);
393
+ return normalized;
336
394
  }
337
395
  async function askZellijRepairAllowed(question) {
338
396
  if (!(process.stdin.isTTY && process.stdout.isTTY))
@@ -347,6 +405,9 @@ async function askZellijRepairAllowed(question) {
347
405
  rl.close();
348
406
  }
349
407
  }
408
+ function errorMessage(err) {
409
+ return err instanceof Error ? err.message : String(err);
410
+ }
350
411
  function tail(value, limit = 1000) {
351
412
  return String(value || '').replace(/\s+/g, ' ').trim().slice(-limit);
352
413
  }
@@ -232,17 +232,25 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
232
232
  interactive: mode === 'interactive-prompt',
233
233
  installHomebrew: opts.installHomebrew === true || list.includes('--install-homebrew'),
234
234
  allowHeadlessFallback: opts.allowHeadlessFallback === true,
235
+ dryRun: opts.dryRun === true || list.includes('--dry-run'),
235
236
  missionDir: opts.missionDir || null,
236
237
  env
237
238
  });
238
239
  if (repaired.strategy === 'headless-fallback')
239
240
  console.log('Zellij repair: headless fallback selected (live_panes=false).');
241
+ else if (repaired.dry_run)
242
+ console.log(`Zellij repair: dry_run planned ${repaired.command || 'none'}`);
240
243
  else if (repaired.ok && repaired.command)
241
244
  console.log(`Zellij repair: ${repaired.strategy} via ${repaired.command}`);
242
245
  else if (!repaired.ok)
243
246
  console.log(`Zellij repair required. Run: ${repaired.command || notice.upgrade_command}`);
247
+ const repairedStatus = repaired.strategy === 'headless-fallback' ? 'headless_fallback'
248
+ : !repaired.ok || repaired.strategy === 'manual-required' ? 'repair_required'
249
+ : repaired.strategy === 'brew-upgrade-zellij' ? 'upgraded'
250
+ : repaired.strategy === 'none-current' ? 'noop'
251
+ : 'installed';
244
252
  return {
245
- status: repaired.ok ? (repaired.strategy === 'headless-fallback' ? 'missing' : 'installed') : 'manual_required',
253
+ status: repairedStatus,
246
254
  current: repaired.after.version || repaired.before.version,
247
255
  latest: repaired.latest_version,
248
256
  command: repaired.command,
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs';
4
3
  import fsp from 'node:fs/promises';
5
4
  import os from 'node:os';
6
5
  import path from 'node:path';
7
- import { spawnSync } from 'node:child_process';
8
6
  import { assertGate, emitGate, importDist, root } from './sks-1-18-gate-lib.js';
9
7
  export async function runDirective314Gate(id) {
10
8
  if (id.startsWith('zellij:'))
@@ -13,14 +11,10 @@ export async function runDirective314Gate(id) {
13
11
  return doctorZellijGate(id);
14
12
  if (id.startsWith('mad:zellij'))
15
13
  return madZellijGate(id);
16
- if (id === 'lazycodex:analysis')
17
- return lazycodexAnalysisGate(id);
18
14
  if (id.startsWith('codex-app:') || id === 'doctor:codex-app-harness')
19
15
  return codexAppGate(id);
20
16
  if (id.startsWith('loop:'))
21
17
  return loopGate(id);
22
- if (id === 'lazycodex:interop-policy' || id === 'lazycodex:pattern-adoption-blackbox')
23
- return lazycodexInteropGate(id);
24
18
  throw new Error(`unknown_gate:${id}`);
25
19
  }
26
20
  async function zellijGate(id) {
@@ -97,14 +91,6 @@ async function madZellijGate(id) {
97
91
  }
98
92
  emitGate(id, { source_checked: true });
99
93
  }
100
- async function lazycodexAnalysisGate(id) {
101
- const mod = await importDist('core/codex-app/lazycodex-analysis.js');
102
- const report = await mod.writeLazyCodexPatternAnalysis(root);
103
- assertGate(report.patterns.length >= 14, 'LazyCodex analysis must include required patterns', report);
104
- const docs = mod.renderLazyCodexAnalysisMarkdown(report);
105
- await fsp.writeFile(path.join(root, 'docs', 'lazycodex-analysis.md'), `${docs}\n`, 'utf8');
106
- emitGate(id, { patterns: report.patterns.length });
107
- }
108
94
  async function codexAppGate(id) {
109
95
  const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
110
96
  const previous = swapEnv({
@@ -131,7 +117,7 @@ async function codexAppGate(id) {
131
117
  const skillsRoot = path.join(rootDir, 'skills');
132
118
  await fsp.mkdir(path.join(skillsRoot, 'ulw-loop'), { recursive: true });
133
119
  const report = await mod.syncCodexSksSkills({ root: rootDir, skillsRoot, apply: true });
134
- assertGate(report.interop.clobbered_lazycodex === false && report.lazycodex_reserved_present.includes('ulw-loop'), 'skill sync must preserve LazyCodex skills', report);
120
+ assertGate(report.interop.clobbered_external_routes === false && report.external_route_names_preserved.includes('ulw-loop'), 'skill sync must preserve existing external route skills', report);
135
121
  return emitGate(id, { desired: report.desired_skills.length });
136
122
  }
137
123
  if (id === 'codex-app:agent-role-sync') {
@@ -185,21 +171,6 @@ async function loopGate(id) {
185
171
  assertGate(report.should_continue === true, 'loop continuation should request resume when proof missing', report);
186
172
  emitGate(id, { should_continue: report.should_continue });
187
173
  }
188
- async function lazycodexInteropGate(id) {
189
- const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
190
- const previous = swapEnv({ SKS_CODEX_PLUGIN_JSON_FAKE: '1' });
191
- try {
192
- const mod = await importDist('core/codex-app/lazycodex-interop-policy.js');
193
- const skillsRoot = path.join(rootDir, '.codex', 'skills');
194
- await fsp.mkdir(path.join(skillsRoot, 'start-work'), { recursive: true });
195
- const report = await mod.buildLazyCodexInteropPolicy({ root: rootDir, codexHome: path.join(rootDir, '.codex') });
196
- assertGate(report.policy.clobber_lazycodex_skills === false, 'interop policy must not clobber LazyCodex skills', report);
197
- emitGate(id, { detected: report.lazycodex_detected, collisions: report.detection.collisions.length });
198
- }
199
- finally {
200
- restoreEnv(previous);
201
- }
202
- }
203
174
  function fakeZellijEnv(status, opts = {}) {
204
175
  return {
205
176
  ...process.env,