sneakoscope 2.0.5 → 2.0.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 (70) hide show
  1. package/README.md +12 -4
  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/build-manifest.json +37 -8
  8. package/dist/cli/install-helpers.js +23 -0
  9. package/dist/commands/codex-app.js +25 -3
  10. package/dist/commands/doctor.js +19 -4
  11. package/dist/commands/mad-sks.js +2 -2
  12. package/dist/core/agents/agent-orchestrator.js +160 -6
  13. package/dist/core/agents/agent-patch-schema.js +16 -2
  14. package/dist/core/agents/agent-proof-evidence.js +27 -2
  15. package/dist/core/agents/agent-worker-pipeline.js +9 -1
  16. package/dist/core/agents/native-cli-session-swarm.js +25 -4
  17. package/dist/core/agents/native-cli-worker.js +28 -1
  18. package/dist/core/agents/native-worker-backend-router.js +19 -1
  19. package/dist/core/codex-app.js +124 -2
  20. package/dist/core/codex-control/python-codex-sdk-adapter.js +28 -4
  21. package/dist/core/commands/naruto-command.js +48 -14
  22. package/dist/core/feature-registry.js +2 -0
  23. package/dist/core/fsx.js +1 -1
  24. package/dist/core/git/git-integration-worktree.js +15 -0
  25. package/dist/core/git/git-repo-detection.js +72 -0
  26. package/dist/core/git/git-worktree-cache-policy.js +36 -0
  27. package/dist/core/git/git-worktree-capability.js +54 -0
  28. package/dist/core/git/git-worktree-cleanup.js +51 -0
  29. package/dist/core/git/git-worktree-conflict-resolver.js +13 -0
  30. package/dist/core/git/git-worktree-diff.js +50 -0
  31. package/dist/core/git/git-worktree-manager.js +86 -0
  32. package/dist/core/git/git-worktree-merge-queue.js +55 -0
  33. package/dist/core/git/git-worktree-patch-envelope.js +35 -0
  34. package/dist/core/git/git-worktree-pool.js +23 -0
  35. package/dist/core/git/git-worktree-root.js +52 -0
  36. package/dist/core/git/git-worktree-runner.js +40 -0
  37. package/dist/core/hooks-runtime.js +2 -233
  38. package/dist/core/init.js +8 -8
  39. package/dist/core/naruto/naruto-active-pool.js +55 -4
  40. package/dist/core/naruto/naruto-gpt-final-pack.js +2 -0
  41. package/dist/core/naruto/naruto-work-graph.js +16 -1
  42. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  43. package/dist/core/ppt.js +31 -8
  44. package/dist/core/product-design-app-server.js +410 -0
  45. package/dist/core/product-design-plugin.js +139 -0
  46. package/dist/core/routes.js +8 -8
  47. package/dist/core/version.js +1 -1
  48. package/dist/core/zellij/zellij-naruto-dashboard.js +10 -1
  49. package/dist/core/zellij/zellij-worker-pane-manager.js +6 -4
  50. package/dist/scripts/git-worktree-cache-performance-check.js +25 -0
  51. package/dist/scripts/git-worktree-capability-check.js +27 -0
  52. package/dist/scripts/git-worktree-cleanup-check.js +27 -0
  53. package/dist/scripts/git-worktree-diff-export-check.js +43 -0
  54. package/dist/scripts/git-worktree-manager-check.js +37 -0
  55. package/dist/scripts/git-worktree-merge-queue-check.js +30 -0
  56. package/dist/scripts/git-worktree-pool-performance-check.js +20 -0
  57. package/dist/scripts/lib/git-worktree-fixture.js +33 -0
  58. package/dist/scripts/naruto-active-pool-check.js +13 -1
  59. package/dist/scripts/naruto-readonly-routing-check.js +116 -0
  60. package/dist/scripts/naruto-shadow-clone-swarm-check.js +16 -5
  61. package/dist/scripts/naruto-worktree-coding-check.js +44 -0
  62. package/dist/scripts/naruto-worktree-gpt-final-check.js +45 -0
  63. package/dist/scripts/naruto-worktree-zellij-ui-check.js +28 -0
  64. package/dist/scripts/product-design-auto-install-check.js +119 -0
  65. package/dist/scripts/product-design-plugin-routing-check.js +101 -0
  66. package/dist/scripts/release-parallel-check.js +16 -2
  67. package/dist/scripts/release-provenance-check.js +21 -0
  68. package/package.json +17 -3
  69. package/schemas/git/git-worktree-capability.schema.json +19 -0
  70. package/schemas/git/git-worktree-manifest.schema.json +36 -0
@@ -27,6 +27,7 @@ export function normalizeAgentPatchEnvelope(input) {
27
27
  ...(input?.verification_node_id === undefined ? {} : { verification_node_id: String(input.verification_node_id) }),
28
28
  ...(input?.rollback_node_id === undefined ? {} : { rollback_node_id: String(input.rollback_node_id) }),
29
29
  ...(input?.lease_proof ? { lease_proof: normalizeLeaseProof(input.lease_proof) } : {}),
30
+ ...(input?.git_worktree ? { git_worktree: normalizeGitWorktreeMetadata(input.git_worktree) } : {}),
30
31
  ...(input?.rationale ? { rationale: String(input.rationale) } : {}),
31
32
  ...(input?.verification_hint ? { verification_hint: normalizeHint(input.verification_hint) } : {}),
32
33
  ...(input?.rollback_hint ? { rollback_hint: normalizeHint(input.rollback_hint) } : {}),
@@ -49,12 +50,14 @@ export function validateAgentPatchEnvelope(envelope) {
49
50
  violations.push('lease_id_missing');
50
51
  if (!envelope.operations.length)
51
52
  violations.push('operations_missing');
52
- if (envelope.source && !['fixture', 'model_authored', 'process_generated', 'zellij_generated'].includes(envelope.source))
53
+ if (envelope.source && !['fixture', 'model_authored', 'process_generated', 'zellij_generated', 'git-worktree-diff'].includes(envelope.source))
53
54
  violations.push('source_invalid');
54
55
  if (envelope.source === 'model_authored' && !hasFiniteNumber(envelope.backend_child_process_id) && !envelope.backend_sdk_thread_id && !envelope.backend_ollama_request_id)
55
56
  violations.push('model_authored_backend_proof_missing');
56
57
  if (envelope.source === 'fixture' && envelope.backend_child_process_id !== undefined)
57
58
  violations.push('fixture_backend_child_process_id_present');
59
+ if (envelope.source === 'git-worktree-diff' && !envelope.git_worktree?.worktree_path)
60
+ violations.push('git_worktree_metadata_missing');
58
61
  for (const operation of envelope.operations) {
59
62
  if (!operation.path || operation.path.includes('\0') || operation.path.startsWith('/') || operation.path.split(/[\\/]/).includes('..')) {
60
63
  violations.push(`invalid_path:${operation.path || 'missing'}`);
@@ -74,7 +77,7 @@ export function validateAgentPatchEnvelope(envelope) {
74
77
  }
75
78
  function normalizeEnvelopeSource(value) {
76
79
  const text = String(value || '');
77
- return text === 'fixture' || text === 'model_authored' || text === 'process_generated' || text === 'zellij_generated' ? text : null;
80
+ return text === 'fixture' || text === 'model_authored' || text === 'process_generated' || text === 'zellij_generated' || text === 'git-worktree-diff' ? text : null;
78
81
  }
79
82
  function hasFiniteNumber(value) {
80
83
  return value !== null && value !== undefined && value !== '' && Number.isFinite(Number(value));
@@ -104,6 +107,17 @@ function normalizeLeaseProof(input) {
104
107
  ...(input?.rollback_node_id === undefined ? {} : { rollback_node_id: String(input.rollback_node_id) })
105
108
  };
106
109
  }
110
+ function normalizeGitWorktreeMetadata(input) {
111
+ return {
112
+ main_repo_root: String(input?.main_repo_root || ''),
113
+ worktree_path: String(input?.worktree_path || ''),
114
+ branch: input?.branch == null ? null : String(input.branch),
115
+ base_head: input?.base_head == null ? null : String(input.base_head),
116
+ worktree_head: input?.worktree_head == null ? null : String(input.worktree_head),
117
+ changed_files: Array.isArray(input?.changed_files) ? input.changed_files.map(String) : [],
118
+ diff_bytes: Number(input?.diff_bytes || 0)
119
+ };
120
+ }
107
121
  function normalizeHint(input) {
108
122
  return {
109
123
  ...(input?.command === undefined ? {} : { command: String(input.command) }),
@@ -21,6 +21,7 @@ export async function writeAgentProofEvidence(root, input) {
21
21
  const scheduler = input.scheduler || await readJson(path.join(root, 'agent-scheduler-state.json'), null);
22
22
  const taskGraph = input.partition?.task_graph || await readJson(path.join(root, 'agent-task-graph.json'), null);
23
23
  const parallelWritePolicy = input.parallelWritePolicy || await readJson(path.join(root, 'agent-parallel-write-policy.json'), null);
24
+ const gitWorktreeRuntime = input.gitWorktreeRuntime || await readJson(path.join(root, 'agent-git-worktree-runtime.json'), null);
24
25
  const patchQueue = await readJson(path.join(root, 'agent-patch-queue.json'), null);
25
26
  const patchQueueEvents = await readTextSafe(path.join(root, 'agent-patch-queue-events.jsonl'));
26
27
  const patchMerge = await readJson(path.join(root, 'agent-merge-coordinator-report.json'), null);
@@ -119,6 +120,14 @@ export async function writeAgentProofEvidence(root, input) {
119
120
  return !changed || Boolean(row.rollback_digest);
120
121
  }) : true;
121
122
  const parallelPatchApplyVerified = patchSwarm ? Array.isArray(patchProof?.wall_clock_parallel_evidence) && patchProof.wall_clock_parallel_evidence.length > 0 || Number(patchSwarm?.parallel_apply_count || 0) > 1 : false;
123
+ const readOnlyNoWriteLeaseMode = isReadOnlyNoWriteLeaseMode({
124
+ results: input.results || [],
125
+ leases: input.partition?.leases || [],
126
+ parallelWritePolicy,
127
+ taskGraph,
128
+ narutoWorkGraph
129
+ });
130
+ const changedFileLeaseBlockers = readOnlyNoWriteLeaseMode ? [] : agentChangedFileLeaseViolations(input.results || [], input.partition?.leases || []);
122
131
  const blockers = [
123
132
  ...(lifecycle.ok ? [] : ['agent_lifecycle_not_all_closed']),
124
133
  ...(lifecycle.ok ? [] : lifecycle.open_sessions.map((id) => 'session_open:' + id)),
@@ -172,6 +181,7 @@ export async function writeAgentProofEvidence(root, input) {
172
181
  ...(noSubagentScalingPolicy?.ok === false ? noSubagentScalingPolicy.blockers || ['no_subagent_scaling_policy_not_ok'] : []),
173
182
  ...(fastModePropagation?.ok === false ? fastModePropagation.blockers || ['fast_mode_propagation_not_ok'] : []),
174
183
  ...(patchSwarm?.ok === false ? patchSwarm.blockers || ['patch_swarm_not_ok'] : []),
184
+ ...(gitWorktreeRuntime?.required === true && gitWorktreeRuntime?.ok === false ? gitWorktreeRuntime.blockers || ['git_worktree_runtime_not_ok'] : []),
175
185
  ...(patchSwarm && !patchQueue ? ['patch_queue_missing'] : []),
176
186
  ...(patchSwarm && !patchMerge ? ['patch_merge_report_missing'] : []),
177
187
  ...(patchSwarm && !patchApplyResults ? ['patch_apply_results_missing'] : []),
@@ -195,7 +205,7 @@ export async function writeAgentProofEvidence(root, input) {
195
205
  ...(isNarutoRoute && !narutoVerificationDag ? ['naruto_verification_dag_missing'] : []),
196
206
  ...(isNarutoRoute && !narutoGptFinalPack ? ['naruto_gpt_final_pack_missing'] : []),
197
207
  ...(isNarutoRoute && !narutoZellijDashboard ? ['naruto_zellij_dashboard_missing'] : []),
198
- ...agentChangedFileLeaseViolations(input.results || [], input.partition?.leases || [])
208
+ ...changedFileLeaseBlockers
199
209
  ];
200
210
  const evidence = {
201
211
  schema: AGENT_PROOF_EVIDENCE_SCHEMA,
@@ -209,6 +219,7 @@ export async function writeAgentProofEvidence(root, input) {
209
219
  // Deployed Core Skill snapshot consulted at route start (read-only; never
210
220
  // confers mutation rights). Null when no snapshot is deployed for this route.
211
221
  selected_core_skill: input.selectedCoreSkill || null,
222
+ git_worktree_runtime: gitWorktreeRuntime,
212
223
  route_blackbox_kind: input.routeBlackboxKind || (realRouteCommandUsed ? 'actual_route_command' : 'generic_agent_route_standin'),
213
224
  real_route_command_used: realRouteCommandUsed,
214
225
  native_cli_session_proof: nativeCliSessionProof ? 'native-cli-session-proof.json' : null,
@@ -369,7 +380,7 @@ export async function writeAgentProofEvidence(root, input) {
369
380
  triwiki_use_first_count: Number(input.triwikiContext?.use_first?.length || 0),
370
381
  triwiki_hydrate_first_count: Number(input.triwikiContext?.hydrate_first?.length || 0),
371
382
  triwiki_claim_count: Number(input.triwikiContext?.claim_count || 0),
372
- changed_files_lease_checked: true,
383
+ changed_files_lease_checked: !readOnlyNoWriteLeaseMode,
373
384
  dependency_collision_risk: input.partition?.no_overlap_proof?.dependency_collision_risk || [],
374
385
  blockers
375
386
  };
@@ -448,6 +459,20 @@ function agentChangedFileLeaseViolations(results, leases) {
448
459
  }
449
460
  return violations;
450
461
  }
462
+ function isReadOnlyNoWriteLeaseMode(input) {
463
+ const writeLeaseCount = input.leases.filter((lease) => lease.kind === 'write').length;
464
+ if (writeLeaseCount > 0)
465
+ return false;
466
+ const resultWriteSignals = input.results.some((result) => (Array.isArray(result?.writes) && result.writes.length > 0)
467
+ || (Array.isArray(result?.patch_envelopes) && result.patch_envelopes.length > 0));
468
+ if (resultWriteSignals)
469
+ return false;
470
+ const policyReadonly = input.parallelWritePolicy?.readonly === true;
471
+ const policyWriteOff = String(input.parallelWritePolicy?.write_mode || 'off') === 'off';
472
+ const narutoReadOnly = input.narutoWorkGraph?.readonly === true || Number(input.narutoWorkGraph?.write_allowed_count || 0) === 0;
473
+ const taskGraphNoWrites = Number(input.taskGraph?.write_allowed_count || 0) === 0;
474
+ return policyReadonly || policyWriteOff || narutoReadOnly || taskGraphNoWrites;
475
+ }
451
476
  function pathWithin(file, leasePath) {
452
477
  const left = String(file || '').replace(/\\/g, '/').replace(/^\.\//, '');
453
478
  const right = String(leasePath || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '');
@@ -67,7 +67,8 @@ export function validateAgentWorkerResult(result) {
67
67
  normalized.blockers.push(...patchEnvelopeValidation.blockers.map((issue) => 'patch_envelope_invalid:' + issue));
68
68
  normalized.verification = { status: 'failed', checks: [...normalized.verification.checks, 'agent-patch-envelope-schema'] };
69
69
  }
70
- if (patchEnvelopeValidation.envelopes.length === 0 && (normalized.changed_files.length > 0 || normalized.writes.length > 0)) {
70
+ const readOnlyOrNoopWithoutPatch = acceptsNoPatchReadOnlyOrNoop(result?.no_patch_reason);
71
+ if (patchEnvelopeValidation.envelopes.length === 0 && (normalized.writes.length > 0 || (!readOnlyOrNoopWithoutPatch && normalized.changed_files.length > 0))) {
71
72
  normalized.status = 'blocked';
72
73
  normalized.blockers.push('no_patch_generated');
73
74
  normalized.verification = { status: 'failed', checks: [...normalized.verification.checks, 'agent-patch-envelope-required-for-write'] };
@@ -132,4 +133,11 @@ function normalizeVerification(value) {
132
133
  checks: Array.isArray(value?.checks) ? value.checks : []
133
134
  };
134
135
  }
136
+ function acceptsNoPatchReadOnlyOrNoop(value) {
137
+ if (!value || typeof value !== 'object')
138
+ return false;
139
+ return value.ok === true
140
+ && value.read_only_or_noop_evidence === true
141
+ && String(value.reason || '') === 'read_only_or_no_write_paths';
142
+ }
135
143
  //# sourceMappingURL=agent-worker-pipeline.js.map
@@ -26,6 +26,8 @@ class NativeCliSessionSwarmRecorder {
26
26
  await this.persist();
27
27
  }
28
28
  async launchWorker(ctx) {
29
+ const worktree = normalizeWorkerWorktree(ctx.agent?.worktree || ctx.slice?.worktree || ctx.opts?.worktree || null);
30
+ const workerCwd = worktree?.path || ctx.opts.cwd || packageRoot();
29
31
  const workerDirRel = path.join(ctx.agent.session_artifact_dir || path.join('sessions', ctx.agent.id), 'worker');
30
32
  const workerDir = path.join(this.root, workerDirRel);
31
33
  await ensureDir(workerDir);
@@ -45,6 +47,9 @@ class NativeCliSessionSwarmRecorder {
45
47
  backend_explicit: this.input.backendExplicit === true,
46
48
  no_ollama: this.input.noOllama === true || ctx.opts.noOllama === true,
47
49
  agent_root: this.root,
50
+ main_repo_root: worktree?.main_repo_root || ctx.opts.cwd || packageRoot(),
51
+ cwd: workerCwd,
52
+ worktree,
48
53
  agent: ctx.agent,
49
54
  slice: ctx.slice,
50
55
  worker_artifact_dir: workerDirRel,
@@ -89,6 +94,7 @@ class NativeCliSessionSwarmRecorder {
89
94
  patch_envelope_path: patchRel,
90
95
  fast_mode: this.input.fastModePolicy.fast_mode,
91
96
  service_tier: this.input.fastModePolicy.service_tier,
97
+ cwd: workerCwd,
92
98
  status: 'launching',
93
99
  exit_code: null,
94
100
  blockers: []
@@ -110,7 +116,7 @@ class NativeCliSessionSwarmRecorder {
110
116
  });
111
117
  }
112
118
  const child = spawn(process.execPath, args, {
113
- cwd: ctx.opts.cwd || packageRoot(),
119
+ cwd: workerCwd,
114
120
  env: {
115
121
  ...process.env,
116
122
  ...(ctx.opts.env || {}),
@@ -179,6 +185,8 @@ class NativeCliSessionSwarmRecorder {
179
185
  async launchWorkerInZellijPane(input) {
180
186
  const sessionName = String(input.ctx.opts.zellijSessionName || (this.input.missionId ? `sks-${this.input.missionId}` : 'sks-agent-runtime'));
181
187
  const slotId = String(input.ctx.agent.slot_id || input.ctx.agent.id || 'slot-001');
188
+ const worktree = normalizeWorkerWorktree(input.ctx.agent?.worktree || input.ctx.slice?.worktree || input.ctx.opts?.worktree || null);
189
+ const workerCwd = worktree?.path || input.ctx.opts.cwd || packageRoot();
182
190
  const activeToken = this.nextPaneToken--;
183
191
  this.active.add(activeToken);
184
192
  this.maxObserved = Math.max(this.maxObserved, this.active.size);
@@ -222,9 +230,10 @@ class NativeCliSessionSwarmRecorder {
222
230
  patchEnvelopePath: input.record.patch_envelope_path,
223
231
  stdoutLog: input.stdoutRel,
224
232
  stderrLog: input.stderrRel,
225
- cwd: input.ctx.opts.cwd || packageRoot(),
233
+ cwd: workerCwd,
226
234
  providerContext,
227
- serviceTier: this.input.fastModePolicy.service_tier
235
+ serviceTier: this.input.fastModePolicy.service_tier,
236
+ worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null
228
237
  });
229
238
  const launchBlockers = paneRecord.blockers || [];
230
239
  input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', '<native-cli-worker-command>'];
@@ -239,6 +248,7 @@ class NativeCliSessionSwarmRecorder {
239
248
  input.record.provider = paneRecord.provider;
240
249
  input.record.service_tier = paneRecord.service_tier;
241
250
  input.record.provider_context = paneRecord.provider_context;
251
+ input.record.worktree = worktree;
242
252
  input.record.status = launchBlockers.length ? 'failed' : 'running';
243
253
  input.record.blockers = launchBlockers;
244
254
  await this.record(input.record);
@@ -323,7 +333,7 @@ class NativeCliSessionSwarmRecorder {
323
333
  paneRecord = await closeWorkerPane({
324
334
  root: this.root,
325
335
  paneRecord,
326
- cwd: input.ctx.opts.cwd || packageRoot(),
336
+ cwd: workerCwd,
327
337
  status: input.record.status === 'closed' ? 'closed' : 'failed',
328
338
  blockers: input.record.blockers,
329
339
  sdkThreadId,
@@ -462,4 +472,15 @@ function redactWorkerArgs(args) {
462
472
  function shellQuote(value) {
463
473
  return `'${String(value).replace(/'/g, `'\\''`)}'`;
464
474
  }
475
+ function normalizeWorkerWorktree(value) {
476
+ const pathValue = value?.path || value?.worktree_path;
477
+ if (!pathValue)
478
+ return null;
479
+ return {
480
+ id: String(value?.id || value?.worktree_id || value?.slot_id || 'worktree'),
481
+ path: String(pathValue),
482
+ branch: String(value?.branch || 'unknown'),
483
+ main_repo_root: value?.main_repo_root == null ? null : String(value.main_repo_root)
484
+ };
485
+ }
465
486
  //# sourceMappingURL=native-cli-session-swarm.js.map
@@ -27,6 +27,8 @@ export async function runNativeCliWorker(input = {}) {
27
27
  };
28
28
  const slice = intake.slice || {};
29
29
  const backend = String(input.backend || intake.backend || 'fake');
30
+ const workerCwd = path.resolve(String(input.cwd || intake.cwd || process.cwd()));
31
+ const worktree = normalizeWorkerWorktree(input.worktree || intake.worktree || null);
30
32
  const policy = resolveFastModePolicy({
31
33
  fastMode: intake.fast_mode ?? input.fastMode,
32
34
  serviceTier: intake.service_tier ?? input.serviceTier
@@ -37,6 +39,10 @@ export async function runNativeCliWorker(input = {}) {
37
39
  const heartbeatRel = String(input.heartbeatPath || intake.heartbeat_path || path.join(workerDirRel, 'worker-heartbeat.jsonl'));
38
40
  const patchRel = String(input.patchEnvelopePath || intake.patch_envelope_path || path.join(workerDirRel, 'worker-patch-envelope.json'));
39
41
  await ensureDir(workerDir);
42
+ try {
43
+ process.chdir(workerCwd);
44
+ }
45
+ catch { }
40
46
  const recursion = scanAgentTextForRecursion(JSON.stringify({ agent, slice, backend }));
41
47
  const guard = {
42
48
  schema: 'sks.native-cli-worker-recursion-guard.v1',
@@ -56,6 +62,9 @@ export async function runNativeCliWorker(input = {}) {
56
62
  persona_id: String(agent.persona_id || input.personaId || ''),
57
63
  lease_id: String(intake.lease_id || input.leaseId || ''),
58
64
  agent_root: agentRoot,
65
+ main_repo_root: String(input.mainRepoRoot || intake.main_repo_root || worktree?.main_repo_root || agentRoot),
66
+ cwd: workerCwd,
67
+ worktree,
59
68
  worker_artifact_dir: workerDirRel,
60
69
  result_path: resultRel,
61
70
  heartbeat_path: heartbeatRel,
@@ -65,7 +74,9 @@ export async function runNativeCliWorker(input = {}) {
65
74
  backend: backend === 'zellij' ? 'codex-sdk' : backend,
66
75
  output_schema_id: 'sks.agent-worker-result.v1',
67
76
  sandbox_policy: Array.isArray(slice.write_paths) && slice.write_paths.length > 0 ? 'workspace-write' : 'read-only',
68
- thread_policy: 'new_thread_per_generation'
77
+ thread_policy: 'new_thread_per_generation',
78
+ worktree,
79
+ cwd: workerCwd
69
80
  },
70
81
  service_tier: policy.service_tier,
71
82
  fast_mode: policy.fast_mode,
@@ -143,6 +154,8 @@ export async function runNativeCliWorker(input = {}) {
143
154
  generated_at: nowIso(),
144
155
  ok: guard.ok,
145
156
  backend,
157
+ cwd: workerCwd,
158
+ worktree,
146
159
  agent_id: agent.id,
147
160
  session_id: agent.session_id,
148
161
  slot_id: agent.slot_id || null,
@@ -221,6 +234,9 @@ export async function runNativeCliWorker(input = {}) {
221
234
  slot_id: agent.slot_id || null,
222
235
  generation_index: agent.generation_index || null,
223
236
  process_id: process.pid,
237
+ main_repo_root: String(input.mainRepoRoot || intake.main_repo_root || worktree?.main_repo_root || agentRoot),
238
+ cwd: workerCwd,
239
+ worktree,
224
240
  artifact_dir: workerDirRel,
225
241
  patch_envelope: patchEnvelopes.length ? patchRel : null,
226
242
  no_patch_reason: patchEnvelopes.length ? null : path.join(workerDirRel, 'worker-no-patch-reason.json'),
@@ -290,4 +306,15 @@ function redactCommandLine(argv) {
290
306
  return part;
291
307
  });
292
308
  }
309
+ function normalizeWorkerWorktree(value) {
310
+ const pathValue = value?.path || value?.worktree_path;
311
+ if (!pathValue)
312
+ return null;
313
+ return {
314
+ id: String(value?.id || value?.worktree_id || value?.slot_id || 'worktree'),
315
+ path: String(pathValue),
316
+ branch: String(value?.branch || 'unknown'),
317
+ main_repo_root: value?.main_repo_root == null ? null : String(value.main_repo_root)
318
+ };
319
+ }
293
320
  //# sourceMappingURL=native-cli-worker.js.map
@@ -57,6 +57,7 @@ export async function runNativeWorkerBackendRouter(input) {
57
57
  result = validateAgentWorkerResult({
58
58
  ...processRun,
59
59
  patch_envelopes: patchEnvelopes,
60
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
60
61
  artifacts: [...new Set([...(processRun.artifacts || []), ...(patchEnvelopes.length ? [input.patchRel] : [])])],
61
62
  process_child_report: processReport,
62
63
  model_authored_patch_envelopes: false,
@@ -81,6 +82,7 @@ export async function runNativeWorkerBackendRouter(input) {
81
82
  ...ollamaRun,
82
83
  backend: 'ollama',
83
84
  patch_envelopes: patchEnvelopes,
85
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
84
86
  model_authored_patch_envelopes: patchEnvelopes.length > 0,
85
87
  fixture_patch_envelopes: false,
86
88
  verification: { status: ollamaRun.status === 'done' ? 'passed' : 'failed', checks: [...(ollamaRun.verification?.checks || []), 'native-worker-backend-router', 'ollama-api-generate'] }
@@ -147,6 +149,7 @@ export async function runNativeWorkerBackendRouter(input) {
147
149
  ...sdkWorkerResult,
148
150
  backend: sdkTask.backend === 'local-llm' ? 'local-llm' : 'codex-sdk',
149
151
  patch_envelopes: patchEnvelopes,
152
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, sdkTask.backend || backend) }),
150
153
  codex_child_report: sdkReport,
151
154
  codex_sdk_thread: sdkReport,
152
155
  model_authored_patch_envelopes: patchEnvelopes.length > 0,
@@ -189,6 +192,7 @@ export async function runNativeWorkerBackendRouter(input) {
189
192
  result = validateAgentWorkerResult({
190
193
  ...zellijRun,
191
194
  patch_envelopes: patchEnvelopes,
195
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
192
196
  zellij_child_report: zellijReport,
193
197
  model_authored_patch_envelopes: false,
194
198
  fixture_patch_envelopes: false,
@@ -279,10 +283,24 @@ function buildWorkerPrompt(slice) {
279
283
  '',
280
284
  write.length
281
285
  ? `Write-capable slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; include patch_envelopes for write_paths=${JSON.stringify(write)}.`
282
- : `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}.`,
286
+ : `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; do not report pre-existing repository dirtiness as changed_files.`,
283
287
  'Required JSON fields: status, summary, findings, changed_files, patch_envelopes, verification, rollback_notes, blockers.'
284
288
  ].join('\n');
285
289
  }
290
+ function buildNoPatchReason(input, backend) {
291
+ const writePathCount = writePaths(input.slice, input.intake).length;
292
+ return {
293
+ schema: 'sks.native-cli-worker-no-patch-reason.v1',
294
+ generated_at: nowIso(),
295
+ ok: writePathCount === 0,
296
+ reason: writePathCount ? 'write_capable_task_without_backend_patch_envelope' : 'read_only_or_no_write_paths',
297
+ route_justification: writePathCount ? 'backend returned no patch envelopes for a write-capable task' : 'task has no write paths',
298
+ read_only_or_noop_evidence: writePathCount === 0,
299
+ task_slice_id: input.slice?.id || null,
300
+ backend,
301
+ blockers: writePathCount && backend !== 'fake' ? ['write_capable_no_patch_envelope'] : []
302
+ };
303
+ }
286
304
  function hasWriteLease(slice, intake) {
287
305
  return writePaths(slice, intake).length > 0;
288
306
  }
@@ -4,6 +4,8 @@ import fsp from 'node:fs/promises';
4
4
  import { exists, runProcess } from './fsx.js';
5
5
  import { EMPTY_CODEX_INFO, getCodexInfo } from './codex-adapter.js';
6
6
  import { CODEX_CHROME_EXTENSION_DOC_URL, DEFAULT_CODEX_APP_PLUGINS as DEFAULT_CODEX_APP_PLUGIN_TUPLES, RESERVED_CODEX_PLUGIN_SKILL_NAMES } from './routes.js';
7
+ import { PRODUCT_DESIGN_PLUGIN, normalizeProductDesignPluginEvidence } from './product-design-plugin.js';
8
+ import { PRODUCT_DESIGN_AUTO_INSTALL_ENV, ensureProductDesignPluginInstalled, productDesignAutoInstallRequested } from './product-design-app-server.js';
7
9
  export const CODEX_APP_DOCS_URL = 'https://developers.openai.com/codex/app/features';
8
10
  export const CODEX_CHANGELOG_URL = 'https://developers.openai.com/codex/changelog';
9
11
  export const CODEX_ACCESS_TOKENS_DOCS_URL = 'https://developers.openai.com/codex/enterprise/access-tokens';
@@ -128,6 +130,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
128
130
  const chromePath = await findPluginCache('chrome', opts);
129
131
  const computerUsePath = await findPluginCache('computer-use', opts);
130
132
  const defaultPlugins = await codexDefaultPluginStatus(opts);
133
+ const productDesignPlugin = await codexProductDesignPluginStatus(opts);
131
134
  const pluginSkillShadows = await codexPluginSkillShadowStatus(opts);
132
135
  const fastModeConfig = await codexFastModeConfigStatus(opts);
133
136
  const computerUseMcpListed = /computer[-_ ]?use/i.test(mcpText);
@@ -209,6 +212,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
209
212
  browser_use_cache: browserUsePath,
210
213
  chrome_cache: chromePath,
211
214
  default_plugins: defaultPlugins,
215
+ design_product: productDesignPlugin,
212
216
  skill_shadows: pluginSkillShadows,
213
217
  picker: {
214
218
  ok: pluginPickerReady,
@@ -219,7 +223,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
219
223
  }
220
224
  },
221
225
  chrome_extension: chromeExtension,
222
- guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, pluginSkillShadows, fastModeConfig, gitActions, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl })
226
+ guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, productDesignPlugin, pluginSkillShadows, fastModeConfig, gitActions, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl })
223
227
  };
224
228
  }
225
229
  export async function codexChromeExtensionStatus(opts = {}) {
@@ -438,7 +442,7 @@ export function codexAccessTokenStatus(env = process.env) {
438
442
  : ['No CODEX_ACCESS_TOKEN detected in the current process environment. This is fine for interactive ChatGPT login or API-key auth.']
439
443
  };
440
444
  }
441
- export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, pluginSkillShadows = { ok: true, blocking: [] }, fastModeConfig = { ok: true, blockers: [] }, gitActions = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl }) {
445
+ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, productDesignPlugin = null, pluginSkillShadows = { ok: true, blocking: [] }, fastModeConfig = { ok: true, blockers: [] }, gitActions = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl }) {
442
446
  const lines = [];
443
447
  if (!appInstalled) {
444
448
  lines.push('Install and open Codex App for first-party MCP/plugin tools. SKS Zellij launch can still run with Codex CLI alone, but Codex Computer Use and imagegen/gpt-image-2 evidence will be unavailable until Codex App is ready.');
@@ -474,6 +478,18 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
474
478
  lines.push(`Codex default plugin source(s) missing: ${defaultPlugins.missing_installed.join(', ')}. The @ plugin picker can hide built-in surfaces when plugin files are absent even if config says enabled.`);
475
479
  lines.push('Run: sks doctor --fix, then restart Codex App if the plugin cache was just restored.');
476
480
  }
481
+ if (productDesignPlugin?.ok) {
482
+ lines.push(`Product Design plugin is ready for design routes via ${productDesignPlugin.source || 'verified evidence'} (${productDesignPlugin.id}).`);
483
+ }
484
+ else if (productDesignPlugin?.auto_install?.attempted) {
485
+ lines.push(`Product Design auto-install was attempted but did not produce ready evidence: ${(productDesignPlugin.blockers || []).join(', ') || 'unverified'}. Recheck with: ${productDesignPlugin.auto_install.command}`);
486
+ }
487
+ else if (productDesignPlugin?.app_server?.checked && !productDesignPlugin.app_server.ok) {
488
+ lines.push(`Product Design app-server lookup ran but is not ready: ${(productDesignPlugin.blockers || []).join(', ') || 'unverified'}. Run: ${productDesignPlugin.auto_install?.command || 'sks codex-app product-design --json'}`);
489
+ }
490
+ else if (productDesignPlugin?.remote_lookup_required) {
491
+ lines.push(`Product Design is a remote vertical marketplace plugin and may not appear in \`codex plugin list\`; design routes should run ${productDesignPlugin.auto_install?.command || 'sks codex-app product-design --json'} or set ${PRODUCT_DESIGN_AUTO_INSTALL_ENV}=1 before falling back to legacy design.md skills.`);
492
+ }
477
493
  if (pluginSkillShadows?.generated?.length) {
478
494
  const names = pluginSkillShadows.generated.map((entry) => `${entry.name}:${entry.scope}`).join(', ');
479
495
  lines.push(`Codex plugin picker generated skill shadow(s) detected: ${names}. Generated SKS skills with first-party plugin names can hide @ plugin entries after upgrades.`);
@@ -536,6 +552,7 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
536
552
  `App Flags: ${status.features?.required_flags_ok ? 'ok' : `missing ${missingRequiredFeatureFlags(status.features?.required_flags).join(', ') || 'required flags'}`}`,
537
553
  `Fast UI: ${status.features?.fast_mode_config?.ok ? 'ok' : `locked ${(status.features?.fast_mode_config?.blockers || []).join(', ') || 'config'}`}`,
538
554
  `Default Plugins:${status.plugins?.default_plugins?.ok ? ' ok' : ` missing ${defaultPluginMissingSummary(status.plugins?.default_plugins) || 'plugin install/config'}`}`,
555
+ `Product Design:${productDesignStatusSummary(status.plugins?.design_product)}`,
539
556
  `Plugin Picker:${status.plugins?.picker?.ok ? ' ok' : ` blocked ${pluginPickerBlockers(status).join(', ') || 'config'}`}`,
540
557
  `Git Actions:${status.features?.git_actions?.ok ? ' ok' : ` blocked ${(status.features?.git_actions?.blockers || []).join(', ') || 'config'}`}`,
541
558
  `Chrome Ext: ${status.chrome_extension?.ok ? 'ok' : `setup ${(status.chrome_extension?.blockers || []).join(', ') || 'required'}`}`,
@@ -552,6 +569,30 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
552
569
  lines.push('', status.features.stdout.trim());
553
570
  return lines.join('\n');
554
571
  }
572
+ export function formatCodexProductDesignPluginStatus(status) {
573
+ const lines = [
574
+ 'Codex App Product Design Plugin',
575
+ '',
576
+ `Ready: ${status.ok ? 'yes' : 'no'}`,
577
+ `Installed: ${status.installed ? 'yes' : 'no'}`,
578
+ `Enabled: ${status.enabled === true ? 'yes' : status.enabled === false ? 'no' : 'unknown'}`,
579
+ `Source: ${status.source || 'unverified'}`,
580
+ `Marketplace: ${status.marketplace}`,
581
+ `Remote ID: ${status.remote_plugin_id}`,
582
+ `Auto Install: ${status.auto_install?.requested ? 'requested' : 'not requested'}${status.auto_install?.attempted ? ' (attempted)' : ''}`,
583
+ `Command: ${status.auto_install?.command || 'sks codex-app product-design --json'}`,
584
+ '',
585
+ ...(status.blockers || []).map((blocker) => `- ${blocker}`)
586
+ ];
587
+ if (status.remote_evidence?.missing_skills?.length) {
588
+ lines.push(`- missing skills: ${status.remote_evidence.missing_skills.join(', ')}`);
589
+ }
590
+ if (status.ok)
591
+ lines.push('- Product Design is ready for design routes.');
592
+ else if (!status.auto_install?.requested)
593
+ lines.push('- Run `sks codex-app product-design --json`, or set SKS_PRODUCT_DESIGN_AUTO_INSTALL=1 for design-route auto-ensure.');
594
+ return lines.join('\n');
595
+ }
555
596
  function summarizeCodexMcpError(text) {
556
597
  const cleanLines = String(text || '')
557
598
  .split(/\r?\n/)
@@ -629,6 +670,76 @@ async function codexDefaultPluginStatus(opts = {}) {
629
670
  missing_enabled: missingEnabled
630
671
  };
631
672
  }
673
+ export async function codexProductDesignPluginStatus(opts = {}) {
674
+ const home = opts.home || os.homedir();
675
+ const cwd = opts.cwd || process.cwd();
676
+ const globalConfigPath = path.join(home || '', '.codex', 'config.toml');
677
+ const projectConfigPath = path.join(cwd || '', '.codex', 'config.toml');
678
+ const globalConfig = await readTextIfExists(globalConfigPath);
679
+ const projectConfig = path.resolve(projectConfigPath) === path.resolve(globalConfigPath)
680
+ ? ''
681
+ : await readTextIfExists(projectConfigPath);
682
+ const configText = `${globalConfig}\n${projectConfig}`;
683
+ const plugin = { name: PRODUCT_DESIGN_PLUGIN.name, marketplace: PRODUCT_DESIGN_PLUGIN.marketplace };
684
+ const autoInstallProductDesign = productDesignAutoInstallRequested(opts);
685
+ const appServerStatus = opts.productDesignAppServerStatus || (autoInstallProductDesign
686
+ ? await ensureProductDesignPluginInstalled({
687
+ ...opts,
688
+ autoInstallProductDesign
689
+ })
690
+ : null);
691
+ const injectedRemoteEvidence = opts.productDesignPluginReadResponse
692
+ ? normalizeProductDesignPluginEvidence(opts.productDesignPluginReadResponse)
693
+ : null;
694
+ const appServerEvidence = appServerStatus?.remote_evidence?.schema === 'sks.product-design-plugin-evidence.v1'
695
+ ? appServerStatus.remote_evidence
696
+ : null;
697
+ const remoteEvidence = appServerEvidence || injectedRemoteEvidence;
698
+ const localSource = await findDefaultPluginSource(plugin, { home, configText });
699
+ const configEnabled = codexPluginEnabled(configText, plugin);
700
+ const enabled = remoteEvidence?.enabled === true ? true : configEnabled ? true : null;
701
+ const installed = Boolean(localSource) || remoteEvidence?.installed === true;
702
+ const ok = Boolean(remoteEvidence?.ok || (installed && enabled === true));
703
+ const remoteLookupRequired = !remoteEvidence?.ok && (!localSource || enabled !== true) && !appServerStatus?.checked;
704
+ const blockers = ok ? [] : Array.from(new Set([
705
+ ...(!installed ? ['product_design_plugin_not_installed_or_not_locally_visible'] : []),
706
+ ...(enabled !== true ? ['product_design_plugin_enabled_state_requires_remote_evidence'] : []),
707
+ ...(remoteLookupRequired ? ['product_design_remote_vertical_lookup_required'] : []),
708
+ ...(autoInstallProductDesign && appServerStatus && !appServerStatus.ok ? ['product_design_app_server_install_failed'] : []),
709
+ ...(appServerStatus?.blockers || [])
710
+ ]));
711
+ return {
712
+ schema: 'sks.codex-product-design-plugin-status.v1',
713
+ ok,
714
+ checked: true,
715
+ route_required_only: true,
716
+ id: PRODUCT_DESIGN_PLUGIN.id,
717
+ name: PRODUCT_DESIGN_PLUGIN.name,
718
+ display_name: PRODUCT_DESIGN_PLUGIN.display_name,
719
+ marketplace: PRODUCT_DESIGN_PLUGIN.marketplace,
720
+ marketplace_kind: PRODUCT_DESIGN_PLUGIN.marketplace_kind,
721
+ remote_plugin_id: PRODUCT_DESIGN_PLUGIN.remote_plugin_id,
722
+ installed,
723
+ enabled,
724
+ source: remoteEvidence?.ok
725
+ ? appServerStatus?.install_attempted ? 'app_server_plugin_install' : 'app_server_plugin_read'
726
+ : localSource ? 'local_plugin_cache_or_marketplace_source' : null,
727
+ local_source: localSource,
728
+ remote_evidence: remoteEvidence,
729
+ app_server: appServerStatus,
730
+ auto_install: {
731
+ requested: autoInstallProductDesign,
732
+ attempted: Boolean(appServerStatus?.install_attempted),
733
+ command: 'sks codex-app product-design --json',
734
+ env: PRODUCT_DESIGN_AUTO_INSTALL_ENV
735
+ },
736
+ remote_lookup_required: remoteLookupRequired,
737
+ app_server_read_params: PRODUCT_DESIGN_PLUGIN.app_server.read_params,
738
+ app_server_install_params: PRODUCT_DESIGN_PLUGIN.app_server.install_params,
739
+ app_server_list_params: PRODUCT_DESIGN_PLUGIN.app_server.list_params,
740
+ blockers
741
+ };
742
+ }
632
743
  async function codexPluginSkillShadowStatus(opts = {}) {
633
744
  const home = opts.home || os.homedir();
634
745
  const cwd = opts.cwd || process.cwd();
@@ -754,6 +865,17 @@ function pluginPickerBlockers(status = {}) {
754
865
  out.push('fast_mode_config');
755
866
  return out;
756
867
  }
868
+ function productDesignStatusSummary(status = {}) {
869
+ if (status.ok)
870
+ return ' ok';
871
+ if (status.auto_install?.attempted)
872
+ return ' install unverified';
873
+ if (status.remote_lookup_required)
874
+ return ' remote lookup required';
875
+ if (status.app_server?.checked)
876
+ return ' app-server unverified';
877
+ return ' not checked';
878
+ }
757
879
  function defaultPluginMissingSummary(defaultPlugins = {}) {
758
880
  return [
759
881
  ...(defaultPlugins?.missing_installed || []),