thoth-agents 0.2.11 → 0.3.0

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 (149) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +21 -0
  3. package/README.md +301 -311
  4. package/dist/agents/configured-role-prompt.d.ts +9 -0
  5. package/dist/agents/prompt-sections.d.ts +8 -4
  6. package/dist/agents/prompt-utils.d.ts +0 -4
  7. package/dist/{chunk-ASKXWU6E.js → chunk-2MZ5C2RN.js} +1078 -1725
  8. package/dist/chunk-XO74DXBZ.js +1781 -0
  9. package/dist/{chunk-6BNAV534.js → chunk-YUCGIJPP.js} +12 -30
  10. package/dist/cli/claude-code-install.d.ts +24 -26
  11. package/dist/cli/claude-code-paths.d.ts +0 -28
  12. package/dist/cli/codex-install.d.ts +2 -2
  13. package/dist/cli/codex-paths.d.ts +0 -2
  14. package/dist/cli/index.js +279 -119
  15. package/dist/cli/install.d.ts +5 -1
  16. package/dist/cli/model-catalog/cache.d.ts +1 -1
  17. package/dist/cli/operations/claude-code.d.ts +6 -3
  18. package/dist/cli/operations/codex.d.ts +2 -1
  19. package/dist/cli/operations/opencode.d.ts +2 -1
  20. package/dist/cli/operations/types.d.ts +9 -1
  21. package/dist/cli/package-root.d.ts +1 -0
  22. package/dist/cli/providers.d.ts +6 -116
  23. package/dist/cli/skills.d.ts +19 -32
  24. package/dist/cli/thoth-mem-install.d.ts +45 -0
  25. package/dist/cli/tui/components/StatusView.d.ts +12 -1
  26. package/dist/cli/tui/index.js +256 -142
  27. package/dist/cli/tui/operations.d.ts +4 -3
  28. package/dist/cli/types.d.ts +0 -10
  29. package/dist/config/constants.d.ts +20 -16
  30. package/dist/config/index.d.ts +0 -1
  31. package/dist/config/loader.d.ts +1 -1
  32. package/dist/config/schema.d.ts +2 -38
  33. package/dist/config/utils.d.ts +1 -2
  34. package/dist/harness/adapters/codex-surfaces.d.ts +3 -3
  35. package/dist/harness/core/agent-pack.d.ts +37 -29
  36. package/dist/harness/core/memory-governance.d.ts +24 -48
  37. package/dist/harness/core/sdd.d.ts +364 -131
  38. package/dist/harness/generate-integration-packages.d.ts +20 -0
  39. package/dist/harness/opencode-init-command.d.ts +11 -0
  40. package/dist/harness/types.d.ts +14 -1
  41. package/dist/harness/writers/claude-code-subagent.d.ts +3 -1
  42. package/dist/hooks/index.d.ts +0 -5
  43. package/dist/index.js +562 -1314
  44. package/dist/mcp/index.d.ts +1 -2
  45. package/package.json +17 -12
  46. package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +50 -0
  47. package/plugin/.claude-plugin/plugin.json +8 -0
  48. package/plugin/.codex-plugin/.thoth-agents-plugin-assets.json +16 -0
  49. package/plugin/.codex-plugin/plugin.json +7 -0
  50. package/plugin/.mcp.json +19 -0
  51. package/plugin/agents/deep.md +89 -0
  52. package/plugin/agents/designer.md +89 -0
  53. package/plugin/agents/explorer.md +90 -0
  54. package/plugin/agents/librarian.md +90 -0
  55. package/plugin/agents/oracle.md +92 -0
  56. package/plugin/agents/orchestrator.md +128 -0
  57. package/plugin/agents/quick.md +89 -0
  58. package/plugin/codex.mcp.json +17 -0
  59. package/plugin/settings.json +3 -0
  60. package/plugin/skills/thoth-archive/SKILL.md +40 -0
  61. package/plugin/skills/thoth-archive/scripts/archive.mjs +748 -0
  62. package/plugin/skills/thoth-constitution/SKILL.md +45 -0
  63. package/plugin/skills/thoth-constitution/scripts/validate.mjs +167 -0
  64. package/plugin/skills/thoth-constitution/templates/constitution.md +54 -0
  65. package/plugin/skills/thoth-init/SKILL.md +30 -0
  66. package/plugin/skills/thoth-init/scripts/init.mjs +141 -0
  67. package/plugin/skills/thoth-sdd/SKILL.md +83 -0
  68. package/plugin/skills/thoth-sdd/references/phases/analyze.md +21 -0
  69. package/plugin/skills/thoth-sdd/references/phases/checklist.md +20 -0
  70. package/plugin/skills/thoth-sdd/references/phases/clarify.md +15 -0
  71. package/plugin/skills/thoth-sdd/references/phases/converge.md +14 -0
  72. package/plugin/skills/thoth-sdd/references/phases/explore.md +10 -0
  73. package/plugin/skills/thoth-sdd/references/phases/implement.md +19 -0
  74. package/plugin/skills/thoth-sdd/references/phases/plan.md +15 -0
  75. package/plugin/skills/thoth-sdd/references/phases/specify.md +21 -0
  76. package/plugin/skills/thoth-sdd/references/phases/tasks.md +20 -0
  77. package/plugin/skills/thoth-sdd/references/phases/verify.md +22 -0
  78. package/plugin/skills/thoth-sdd/scripts/validate.mjs +1117 -0
  79. package/plugin/skills/thoth-sdd/templates/archive-report.md +25 -0
  80. package/plugin/skills/thoth-sdd/templates/checklist.md +27 -0
  81. package/plugin/skills/thoth-sdd/templates/plan.md +38 -0
  82. package/plugin/skills/thoth-sdd/templates/spec.md +54 -0
  83. package/plugin/skills/thoth-sdd/templates/tasks.md +29 -0
  84. package/plugin/skills/thoth-sdd/templates/verify-report.md +28 -0
  85. package/skills/README.md +12 -0
  86. package/skills/thoth-archive/SKILL.md +40 -0
  87. package/skills/thoth-archive/scripts/archive.mjs +748 -0
  88. package/skills/thoth-constitution/SKILL.md +45 -0
  89. package/skills/thoth-constitution/scripts/validate.mjs +167 -0
  90. package/skills/thoth-constitution/templates/constitution.md +54 -0
  91. package/skills/thoth-init/SKILL.md +30 -0
  92. package/skills/thoth-init/scripts/init.mjs +141 -0
  93. package/skills/thoth-sdd/SKILL.md +83 -0
  94. package/skills/thoth-sdd/references/phases/analyze.md +21 -0
  95. package/skills/thoth-sdd/references/phases/checklist.md +20 -0
  96. package/skills/thoth-sdd/references/phases/clarify.md +15 -0
  97. package/skills/thoth-sdd/references/phases/converge.md +14 -0
  98. package/skills/thoth-sdd/references/phases/explore.md +10 -0
  99. package/skills/thoth-sdd/references/phases/implement.md +19 -0
  100. package/skills/thoth-sdd/references/phases/plan.md +15 -0
  101. package/skills/thoth-sdd/references/phases/specify.md +21 -0
  102. package/skills/thoth-sdd/references/phases/tasks.md +20 -0
  103. package/skills/thoth-sdd/references/phases/verify.md +22 -0
  104. package/skills/thoth-sdd/scripts/validate.mjs +1117 -0
  105. package/skills/thoth-sdd/templates/archive-report.md +25 -0
  106. package/skills/thoth-sdd/templates/checklist.md +27 -0
  107. package/skills/thoth-sdd/templates/plan.md +38 -0
  108. package/skills/thoth-sdd/templates/spec.md +54 -0
  109. package/skills/thoth-sdd/templates/tasks.md +29 -0
  110. package/skills/thoth-sdd/templates/verify-report.md +28 -0
  111. package/thoth-agents.schema.json +0 -39
  112. package/dist/chunk-OZE7JVGS.js +0 -2344
  113. package/dist/cli/custom-skills.d.ts +0 -49
  114. package/dist/cli/skill-manifest.d.ts +0 -32
  115. package/dist/harness/core/skills.d.ts +0 -211
  116. package/dist/harness/generate-codex-plugin.d.ts +0 -9
  117. package/dist/harness/writers/claude-code-skill-layout.d.ts +0 -16
  118. package/dist/harness/writers/fs-skill-collect.d.ts +0 -7
  119. package/dist/harness/writers/skill-layout.d.ts +0 -16
  120. package/dist/hooks/chat-headers.d.ts +0 -16
  121. package/dist/hooks/phase-reminder/index.d.ts +0 -28
  122. package/dist/hooks/post-read-nudge/index.d.ts +0 -18
  123. package/dist/hooks/skill-sync.d.ts +0 -10
  124. package/dist/hooks/thoth-mem/index.d.ts +0 -46
  125. package/dist/hooks/thoth-mem/protocol.d.ts +0 -7
  126. package/dist/mcp/thoth.d.ts +0 -3
  127. package/dist/sdd/artifact-governance/artifact-loader.d.ts +0 -130
  128. package/dist/sdd/artifact-governance/index.d.ts +0 -6
  129. package/dist/sdd/artifact-governance/tasks-validator.d.ts +0 -17
  130. package/dist/sdd/artifact-governance/types.d.ts +0 -52
  131. package/dist/thoth/client.d.ts +0 -14
  132. package/dist/thoth/index.d.ts +0 -2
  133. package/src/skills/_shared/openspec-convention.md +0 -350
  134. package/src/skills/_shared/persistence-contract.md +0 -163
  135. package/src/skills/_shared/thoth-mem-convention.md +0 -123
  136. package/src/skills/executing-plans/SKILL.md +0 -288
  137. package/src/skills/plan-reviewer/SKILL.md +0 -219
  138. package/src/skills/requirements-interview/SKILL.md +0 -233
  139. package/src/skills/sdd-apply/SKILL.md +0 -89
  140. package/src/skills/sdd-archive/SKILL.md +0 -92
  141. package/src/skills/sdd-clarify/SKILL.md +0 -105
  142. package/src/skills/sdd-constitution/SKILL.md +0 -107
  143. package/src/skills/sdd-design/SKILL.md +0 -155
  144. package/src/skills/sdd-init/SKILL.md +0 -233
  145. package/src/skills/sdd-propose/SKILL.md +0 -101
  146. package/src/skills/sdd-spec/SKILL.md +0 -136
  147. package/src/skills/sdd-tasks/SKILL.md +0 -232
  148. package/src/skills/sdd-verify/SKILL.md +0 -131
  149. package/src/skills/thoth-mem-agents/SKILL.md +0 -210
@@ -5,31 +5,25 @@ import {
5
5
  CODEX_PROMPT_DIALECT,
6
6
  CONFIRMED_OPENAI_SUBAGENT_PRESET,
7
7
  CONTEXT7_MCP_URL,
8
- CUSTOM_SKILLS,
9
8
  DEFAULT_MODELS,
10
- DEFAULT_THOTH_COMMAND,
11
9
  GREP_APP_MCP_URL,
12
10
  appendPromptSections,
13
- checkCustomSkillsNeedUpdate,
14
11
  claudeCodeSubagentType,
15
12
  composeAgentPrompt,
16
13
  createModelFamilySection,
17
- createOrchestratorPromptSections,
18
- createReadOnlySpecialistPromptSections,
14
+ createRolePromptSections,
19
15
  createStepBudgetSection,
20
- createWriteCapableSpecialistPromptSections,
21
16
  ensureConfigDir,
22
17
  exa,
23
- findPackageRoot,
24
18
  generateLiteConfig,
25
19
  getAgentOverride,
26
- getCustomSkillsDir,
20
+ getAgentPackContract,
21
+ getDefaultOpenCodeModel,
22
+ getDefaultOpenCodeVariant,
27
23
  getExistingConfigPath,
28
24
  getExistingLiteConfigPath,
29
25
  getOpenCodeManagedModelStatePath,
30
26
  getPrimaryModelId,
31
- getSkillRegistry,
32
- installCustomSkills,
33
27
  loadAgentPrompt,
34
28
  parseConfig,
35
29
  renderPromptSection,
@@ -37,7 +31,7 @@ import {
37
31
  updateOpenCodeMainConfig,
38
32
  writeConfig,
39
33
  writeLiteConfig
40
- } from "./chunk-OZE7JVGS.js";
34
+ } from "./chunk-XO74DXBZ.js";
41
35
 
42
36
  // src/cli/codex-paths.ts
43
37
  import { homedir } from "os";
@@ -72,14 +66,7 @@ function resolveCodexTargets(options) {
72
66
  ".thoth-agents-managed-models.json"
73
67
  ),
74
68
  skillsDir: options.scope === "project" ? join(options.projectRoot, ".agents", "skills") : join(options.homeDir ?? homedir(), ".agents", "skills"),
75
- packageRoot: join(options.projectRoot, ".codex-plugin"),
76
- personalPluginRoot: join(codexHome, "plugins", "thoth-agents"),
77
- personalMarketplacePath: join(
78
- options.homeDir ?? homedir(),
79
- ".agents",
80
- "plugins",
81
- "marketplace.json"
82
- )
69
+ packageRoot: join(options.projectRoot, ".codex-plugin")
83
70
  };
84
71
  }
85
72
 
@@ -96,234 +83,80 @@ function isExplicitEffort(selection) {
96
83
  }
97
84
 
98
85
  // src/cli/codex-install.ts
99
- import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync } from "fs";
100
- import { basename, dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
86
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
87
+ import { basename } from "path";
101
88
  import { fileURLToPath as fileURLToPath2 } from "url";
102
89
 
103
90
  // src/harness/adapters/codex.ts
104
91
  import { fileURLToPath } from "url";
105
92
 
106
- // src/harness/core/agent-pack.ts
107
- var AGENT_ROLES = [
108
- {
109
- name: "orchestrator",
110
- mode: "primary-non-mutating",
111
- dispatch: "root-coordinator",
112
- canMutateWorkspace: false,
113
- scope: "coordination, routing, decisions, progress, and root memory",
114
- responsibility: "Delegate-first coordinator for SDD workflow, specialist dispatch, and root-session memory ownership.",
115
- toolGovernance: [
116
- "delegates inspection, writing, debugging, and verification",
117
- "owns question prompts and root-session memory",
118
- "does not perform inline workspace implementation"
119
- ],
120
- verification: [
121
- "routes verification to specialists",
122
- "summarizes evidence from changed files, diagnostics, and tests"
123
- ]
124
- },
125
- {
126
- name: "explorer",
127
- mode: "read-only",
128
- dispatch: "task",
129
- canMutateWorkspace: false,
130
- scope: "local repository discovery",
131
- responsibility: "Find workspace facts fast and return paths, lines, symbols, constraints, edit targets, and conclusions.",
132
- toolGovernance: [
133
- "read/search/code-navigation tools only",
134
- "no durable memory writes",
135
- "no task delegation or progress ownership"
136
- ],
137
- verification: ["reports confidence, anchors, unchecked areas, and gaps"]
138
- },
139
- {
140
- name: "librarian",
141
- mode: "read-only",
142
- dispatch: "task",
143
- canMutateWorkspace: false,
144
- scope: "external research plus local confirmation when needed",
145
- responsibility: "Gather authoritative external evidence and distinguish official docs from examples.",
146
- toolGovernance: [
147
- "research and read-only local confirmation tools",
148
- "no workspace mutation",
149
- "no durable memory writes"
150
- ],
151
- verification: ["sources every substantive external claim with a URL"]
152
- },
153
- {
154
- name: "oracle",
155
- mode: "read-only",
156
- dispatch: "synchronous-task-only",
157
- canMutateWorkspace: false,
158
- scope: "advice, diagnosis, architecture, code review, and plan review",
159
- responsibility: "Provide strategic technical guidance anchored to evidence and review SDD plans.",
160
- toolGovernance: [
161
- "read-only analysis and review",
162
- "no implementation; may perform read-only SDD plan review and verification review, but does not persist artifacts",
163
- "no task delegation"
164
- ],
165
- verification: ["separates observations, risks, and recommendations"]
166
- },
167
- {
168
- name: "designer",
169
- mode: "write-capable",
170
- dispatch: "synchronous-task-only",
171
- canMutateWorkspace: true,
172
- scope: "UI/UX decisions, implementation, and visual verification",
173
- responsibility: "Own user-facing implementation choices and visual QA for UI work.",
174
- toolGovernance: [
175
- "may edit focused UI/UX files",
176
- "owns screenshots and visual QA",
177
- "does not delegate or own SDD progress"
178
- ],
179
- verification: ["includes visual verification status when applicable"]
180
- },
181
- {
182
- name: "quick",
183
- mode: "write-capable",
184
- dispatch: "synchronous-task-only",
185
- canMutateWorkspace: true,
186
- scope: "fast bounded implementation",
187
- responsibility: "Implement well-defined narrow or mechanical changes with focused verification.",
188
- toolGovernance: [
189
- "may edit bounded targets",
190
- "does not perform broad rediscovery",
191
- "does not delegate or own SDD progress"
192
- ],
193
- verification: ["runs the smallest sufficient focused check"]
194
- },
195
- {
196
- name: "deep",
197
- mode: "write-capable",
198
- dispatch: "synchronous-task-only",
199
- canMutateWorkspace: true,
200
- scope: "thorough implementation and verification",
201
- responsibility: "Handle correctness-critical, multi-file, or edge-case-heavy changes with full local context analysis.",
202
- toolGovernance: [
203
- "may edit implementation and tests",
204
- "validates shared behavior against related code and call sites",
205
- "does not delegate or own SDD progress"
206
- ],
207
- verification: ["does not skip verification and reports edge-case evidence"]
208
- }
209
- ];
210
- var DELEGATE_FIRST_RULES = [
211
- "The orchestrator coordinates, decides, asks blocking questions, and delegates evidence or action.",
212
- "Explorer, librarian, and oracle remain read-only specialists.",
213
- "Designer, quick, and deep are write-capable leaf agents with synchronous task dispatch.",
214
- "Subagents return findings, diffs, verification, and blockers rather than raw file dumps.",
215
- "No leaf agent owns SDD progress checkboxes or orchestrator-only memory."
216
- ];
217
- var VERIFICATION_PROTOCOL = [
218
- "Completion reports include changed files and verification evidence.",
219
- "Behavior changes require the smallest sufficient automated check or an explicitly documented check.",
220
- "Visual changes require designer-owned visual QA when feasible."
221
- ];
222
- var AGENT_PACK_CONTRACT = {
223
- roles: [...AGENT_ROLES],
224
- delegateFirstRules: [...DELEGATE_FIRST_RULES],
225
- verificationProtocol: [...VERIFICATION_PROTOCOL]
226
- };
227
- function getAgentPackContract() {
228
- return {
229
- roles: AGENT_PACK_CONTRACT.roles.map((role) => ({
230
- ...role,
231
- toolGovernance: [...role.toolGovernance],
232
- verification: [...role.verification]
233
- })),
234
- delegateFirstRules: [...AGENT_PACK_CONTRACT.delegateFirstRules],
235
- verificationProtocol: [...AGENT_PACK_CONTRACT.verificationProtocol]
236
- };
93
+ // src/agents/configured-role-prompt.ts
94
+ function renderConfiguredRolePrompt({
95
+ role,
96
+ dialect,
97
+ config,
98
+ model
99
+ }) {
100
+ const promptOverrides = loadAgentPrompt(role, config?.preset);
101
+ const override = getAgentOverride(config, role);
102
+ const modelSection = createModelFamilySection(role, model);
103
+ const stepSection = createStepBudgetSection(override?.steps);
104
+ const basePrompt = renderRolePrompt(createRolePromptSections(role), dialect);
105
+ const prompt = composeAgentPrompt({
106
+ basePrompt,
107
+ customPrompt: promptOverrides.prompt,
108
+ customAppendPrompt: appendPromptSections(
109
+ modelSection ? renderPromptSection(modelSection, dialect) : void 0,
110
+ promptOverrides.appendPrompt
111
+ )
112
+ });
113
+ return appendPromptSections(
114
+ prompt,
115
+ stepSection ? renderPromptSection(stepSection, dialect) : void 0
116
+ );
237
117
  }
238
118
 
239
119
  // src/harness/core/memory-governance.ts
240
- var ROOT_OWNED_OPERATIONS = [
241
- { tool: "mem_session", action: "start" },
242
- { tool: "mem_session", action: "checkpoint" },
243
- { tool: "mem_session", action: "summary" },
244
- { tool: "mem_save", kind: "prompt" },
245
- { tool: "mem_save", kind: "session_summary" }
246
- ];
247
- var PARENT_SCOPED_READ_TOOLS = [
248
- "mem_recall",
249
- "mem_context",
250
- "mem_get",
251
- "mem_project"
252
- ];
253
- var WRITE_CAPABLE_DELEGATED_TOOLS = [
254
- ...PARENT_SCOPED_READ_TOOLS,
255
- "mem_save"
256
- ];
257
- var ALL_MEMORY_TOOLS = [
258
- "mem_save",
259
- "mem_recall",
260
- "mem_context",
261
- "mem_get",
262
- "mem_project",
263
- "mem_session"
264
- ];
265
- function uniqueTools(tools) {
266
- return [...new Set(tools)];
267
- }
268
- function roleAllowedTools(role) {
269
- if (role.name === "orchestrator") {
270
- return [...ALL_MEMORY_TOOLS];
271
- }
272
- if (role.mode === "read-only") {
273
- return [...PARENT_SCOPED_READ_TOOLS];
274
- }
275
- return uniqueTools([...WRITE_CAPABLE_DELEGATED_TOOLS]);
276
- }
277
120
  function roleRules(role) {
278
- const sharedSubagentRules = [
279
- "Every subagent memory call requires the parent session_id and project from dispatch; if either is missing, do not call thoth-mem.",
280
- 'Delegated handoff recovery uses the parent-scoped recall funnel: mem_recall(mode="compact") -> mem_recall(mode="context") -> mem_get(...) before memory content is treated as source material.',
281
- "Use mem_get(include_timeline=true) when chronology around a recovered record matters.",
282
- 'mem_context(recall_query=...) and bounded mem_project(action="graph"|"topics"|"topic") are supplemental project context only and do not replace the recall funnel.',
283
- "Report missing, stale, contradictory, or insufficient recalled context instead of guessing through it.",
284
- 'Never own mem_session(action="start"|"checkpoint"|"summary") or mem_save(kind="prompt"|"session_summary"); those operations are root/orchestrator-owned.',
285
- "Never save generated subagent prompts as user intent.",
286
- "Protect the sdd/* topic namespace; SDD artifacts may use deterministic sdd/{change}/{artifact} topic keys only in persistence modes that include thoth-mem."
121
+ const sharedRules = [
122
+ "thoth-mem remains an external provider; its installed provider guidance is authoritative for memory operations and thoth-agents does not prescribe the mechanism.",
123
+ "Provider-dependent use requires parent-scoped authorization with the stable root session identity or explicit unavailable state and project supplied by dispatch.",
124
+ "The MEMORY authorization is none, recall, or observe: none forbids provider work, recall permits bounded reads, and observe additionally permits one bounded durable observation under the delegated scope.",
125
+ "Only authorized context may be used, and the delegate must report missing, stale, contradictory, or insufficient context instead of guessing.",
126
+ "Memory authorization never changes workspace permissions or grants control of root lifecycle and real-user-intent ownership.",
127
+ "A handoff must keep accepted scope, decisions, permissions, and artifacts plus bounded memory context available to the authorized delegate.",
128
+ "Completion continuity is a provider-confirmed semantic summary outcome owned by the root.",
129
+ "Missing capability evidence is reported as degraded or unsupported and never as successful persistence or recovery.",
130
+ "openspec/ is the canonical SDD store; do not mirror spec.md, plan.md, tasks.md, verification reports, or archive reports into provider memory.",
131
+ "Do not invent a consumer fallback or silently change the selected persistence mode."
287
132
  ];
288
133
  if (role.name === "orchestrator") {
289
134
  return [
290
- 'mem_session(action="start"|"checkpoint"|"summary"), mem_save(kind="prompt"), and mem_save(kind="session_summary") are root/main orchestrator-owned operations and responsibilities.',
291
- "In harnesses without an orchestrator-named agent, root/main orchestrator-owned means the initial/root agent when the harness does not expose an orchestrator-named agent.",
292
- 'Before delegation, save or refresh the handoff body with root-owned mem_session(action="summary") or mem_save(kind="session_summary") when available; otherwise disclose that compaction could not be persisted.',
293
- "Dispatch task instructions plus recovery instructions, not the handoff body, raw transcripts, or generated subagent prompts.",
294
- "Dispatch parent session_id and project when authorizing subagent memory use.",
295
- 'Root recall uses mem_recall(mode="compact") -> mem_recall(mode="context") -> mem_get(...); use mem_context(recall_query=...) and bounded mem_project(action="graph"|"topics"|"topic") for supplemental context.',
296
- "Protect the sdd/* topic namespace and write SDD memory artifacts only in thoth-mem or hybrid persistence modes."
135
+ ...sharedRules,
136
+ "The root coordinator owns authorization, root lifecycle, real-user-intent handling, and semantic completion outcomes.",
137
+ "Dispatch task and authorization context without embedding secrets, raw transcripts, or irrelevant context."
297
138
  ];
298
139
  }
299
140
  if (role.mode === "read-only") {
300
141
  return [
301
- ...sharedSubagentRules,
302
- "Read-only agents may use only parent-scoped mem_recall, mem_context, mem_get, and bounded mem_project reads when authorized.",
303
- "Project-scoped read tools require explicit delegated permission and must stay bounded to the parent session/project scope.",
304
- "Read-only agents must never write durable memory or save prompts."
142
+ ...sharedRules,
143
+ "For a read-only workspace role, observe may authorize a durable provider observation but does not authorize workspace mutation.",
144
+ "Root lifecycle ownership never transfers to this delegate."
305
145
  ];
306
146
  }
307
147
  return [
308
- ...sharedSubagentRules,
309
- "Write-capable agents may use the same parent-scoped reads as read-only agents when authorized.",
310
- 'mem_save(kind="observation") is allowed only for delegated durable observations or assigned deterministic SDD artifacts/apply-progress under the parent session/project.',
311
- "Project-scoped read tools require explicit delegated permission and must stay bounded to the parent session/project scope."
148
+ ...sharedRules,
149
+ "For a write-capable workspace role, provider observations still require observe authorization and the delegated parent scope.",
150
+ "Root lifecycle ownership never transfers to this delegate."
312
151
  ];
313
152
  }
314
153
  function getRoleMemoryGovernance(role) {
315
- const allowedTools = roleAllowedTools(role);
316
154
  return {
317
155
  role: role.name,
318
- rootOwnedOperations: [...ROOT_OWNED_OPERATIONS],
319
- allowedTools,
320
- forbiddenTools: ALL_MEMORY_TOOLS.filter(
321
- (tool) => !allowedTools.includes(tool)
322
- ),
156
+ workspaceMode: role.mode,
323
157
  requiresParentContext: role.name !== "orchestrator",
324
- mayReadProjectMemory: true,
325
- mayWriteDurableObservations: role.mode === "write-capable",
326
- protectsSddNamespace: true,
158
+ availableAuthorizations: ["none", "recall", "observe"],
159
+ ownsRootLifecycle: role.name === "orchestrator",
327
160
  rules: roleRules(role)
328
161
  };
329
162
  }
@@ -334,10 +167,10 @@ function renderMemoryGovernanceInstructions(role, dialect) {
334
167
  `- Progress ownership remains with the coordinator; report memory-governance verification for tracking in ${dialect.tools.progressTool}.`
335
168
  ] : [];
336
169
  return [
337
- "thoth-mem governance:",
170
+ "External thoth-mem provider memory governance:",
338
171
  ...governance.rules.map((rule) => `- ${rule}`),
339
172
  ...harnessRules,
340
- `- Runtime enforcement: ${role.name === "orchestrator" ? "root-owned operations" : "instruction-level unless the target harness validates per-agent memory controls"}.`
173
+ `- Runtime enforcement: ${role.name === "orchestrator" ? "root-owned outcomes" : "instruction-level unless the target harness validates per-agent memory controls"}.`
341
174
  ].join("\n");
342
175
  }
343
176
  function memoryGovernanceDiagnostics(input) {
@@ -346,7 +179,7 @@ function memoryGovernanceDiagnostics(input) {
346
179
  diagnostics.push({
347
180
  severity: "warning",
348
181
  code: `${input.harness}.permission.memory.enforcement_gap`,
349
- message: "Runtime controls for root-owned memory operations are unavailable; governance is rendered as instruction-level guidance.",
182
+ message: "Runtime controls for provider authorization are unavailable; instruction-level guidance must report capability as degraded or unsupported rather than treating instruction text as runtime support.",
350
183
  harness: input.harness,
351
184
  capability: "rolePermissions",
352
185
  fallback: "instruction-only"
@@ -356,7 +189,7 @@ function memoryGovernanceDiagnostics(input) {
356
189
  diagnostics.push({
357
190
  severity: input.parentContextInjection === "unknown" ? "error" : "warning",
358
191
  code: `${input.harness}.context.parent_injection.unvalidated`,
359
- message: "Parent session_id/project injection is not runtime-enforced; subagents must be instructed not to use memory without explicit dispatch context and handoff recovery instructions.",
192
+ message: "Parent-scoped authorization is not runtime-enforced; provider-backed continuity is degraded or unsupported without evidenced authorized context.",
360
193
  harness: input.harness,
361
194
  capability: "parentContextInjection",
362
195
  fallback: "instruction-only"
@@ -366,7 +199,7 @@ function memoryGovernanceDiagnostics(input) {
366
199
  diagnostics.push({
367
200
  severity: "warning",
368
201
  code: `${input.harness}.permission.memory_write.enforcement_gap`,
369
- message: 'Runtime controls for delegated memory writes are unavailable; write-capable agents receive instruction-level mem_save(kind="observation") limits for durable observations and deterministic SDD artifacts only.',
202
+ message: "Runtime controls for delegated provider writes are unavailable; report the outcome as degraded or unsupported unless caller-supplied evidence proves success.",
370
203
  harness: input.harness,
371
204
  capability: "memoryGovernanceEnforcement",
372
205
  fallback: "instruction-only"
@@ -590,10 +423,10 @@ var CODEX_SURFACES = [
590
423
  target: "mcp-config",
591
424
  status: "validated",
592
425
  artifactKind: "mcp-config",
593
- path: ".codex-plugin/.mcp.json",
594
- fields: ["mcpServers", "./.mcp.json"],
426
+ path: ".codex-plugin/codex.mcp.json",
427
+ fields: ["mcpServers", "./codex.mcp.json"],
595
428
  diagnosticCode: "codex.surface.plugin_mcp_json.validated",
596
- summary: "Codex plugin packages can bundle MCP server definitions in plugin-root .mcp.json.",
429
+ summary: "Codex plugin packages can reference bundled MCP server definitions from the plugin root.",
597
430
  evidence: "OpenAI Codex plugin docs describe bundled .mcp.json server definitions referenced from plugin.json using plugin-root relative paths."
598
431
  },
599
432
  {
@@ -843,10 +676,21 @@ var PLUGIN_MANIFEST_SURFACE_ID = "plugin-manifest-json";
843
676
  function normalizePath(value) {
844
677
  return value.replace(/\\/g, "/");
845
678
  }
679
+ function stripProviderMcp(content) {
680
+ if (content === void 0) return void 0;
681
+ try {
682
+ const parsed = JSON.parse(content);
683
+ if (!parsed.mcpServers) return content;
684
+ const { thoth_mem: _provider, ...mcpServers } = parsed.mcpServers;
685
+ return stableJson({ ...parsed, mcpServers });
686
+ } catch {
687
+ return content;
688
+ }
689
+ }
846
690
  function pluginRootReference(pathValue) {
847
691
  const normalized = normalizePath(pathValue);
848
- const relative4 = normalized.slice(".codex-plugin/".length);
849
- return `./${relative4}`;
692
+ const relative = normalized.slice(".codex-plugin/".length);
693
+ return `./${relative}`;
850
694
  }
851
695
  function sha256(content) {
852
696
  return `sha256:${createHash("sha256").update(content).digest("hex")}`;
@@ -886,9 +730,9 @@ function artifactKindForSurface(surfaceId) {
886
730
  if (surfaceId === "plugin-mcp-json") return "mcp-config";
887
731
  return surfaceId === "plugin-hooks-json" ? "hook-config" : "skill";
888
732
  }
889
- function codexHookPackageDiagnostic(code, message) {
733
+ function codexHookPackageDiagnostic(code, message, severity = "warning") {
890
734
  return {
891
- severity: "warning",
735
+ severity,
892
736
  code,
893
737
  message,
894
738
  harness: "codex",
@@ -919,7 +763,8 @@ function renderHookDefinitions(hookDefinitions) {
919
763
  diagnostics.push(
920
764
  codexHookPackageDiagnostic(
921
765
  "codex.plugin.hooks.none_packaged",
922
- "No Codex plugin hooks were packaged because no hook definitions passed existing Codex hook validation."
766
+ "No Codex plugin hooks were packaged because no hook definitions passed existing Codex hook validation.",
767
+ "info"
923
768
  )
924
769
  );
925
770
  return { diagnostics };
@@ -1050,7 +895,8 @@ function renderCodexPluginPackage(input) {
1050
895
  if (asset.surfaceId === "plugin-hooks-json" && !hookContent.content) {
1051
896
  continue;
1052
897
  }
1053
- const assetContent = hookContent.content ?? asset.content;
898
+ const rawAssetContent = hookContent.content ?? asset.content;
899
+ const assetContent = asset.surfaceId === "plugin-mcp-json" ? stripProviderMcp(rawAssetContent) : rawAssetContent;
1054
900
  const reference = pluginRootReference(assetPath);
1055
901
  manifest[asset.manifestField] = reference;
1056
902
  if (assetContent !== void 0 && !assetPath.endsWith("/")) {
@@ -1119,6 +965,13 @@ function renderField(key, value) {
1119
965
  function isRecord2(value) {
1120
966
  return typeof value === "object" && value !== null && !Array.isArray(value);
1121
967
  }
968
+ function stripProviderMcpValues(surfaceId, values) {
969
+ if (surfaceId !== "mcp-server-config") return values;
970
+ const servers = values.mcp_servers;
971
+ if (!isRecord2(servers)) return values;
972
+ const { thoth_mem: _provider, ...mcpServers } = servers;
973
+ return { ...values, mcp_servers: mcpServers };
974
+ }
1122
975
  function fieldOrder(fields, keys) {
1123
976
  const orderedRoots = fields.map((field) => field.split(".")[0]);
1124
977
  return [...keys].sort((left, right) => {
@@ -1161,8 +1014,9 @@ function renderCodexToml(input) {
1161
1014
  const diagnostics = [];
1162
1015
  const lines = [];
1163
1016
  const tables = [];
1164
- for (const key of fieldOrder(fields, Object.keys(input.values))) {
1165
- const value = input.values[key];
1017
+ const values = stripProviderMcpValues(input.surfaceId, input.values);
1018
+ for (const key of fieldOrder(fields, Object.keys(values))) {
1019
+ const value = values[key];
1166
1020
  if (!isAllowedRoot(fields, key)) {
1167
1021
  diagnostics.push({
1168
1022
  severity: "warning",
@@ -1191,144 +1045,6 @@ function renderCodexToml(input) {
1191
1045
  };
1192
1046
  }
1193
1047
 
1194
- // src/harness/writers/skill-layout.ts
1195
- import * as fs2 from "fs";
1196
- import * as path2 from "path";
1197
-
1198
- // src/harness/writers/fs-skill-collect.ts
1199
- import { createHash as createHash2 } from "crypto";
1200
- import * as fs from "fs";
1201
- import * as path from "path";
1202
- function normalizeSkillPath(value) {
1203
- return value.split(path.sep).join("/");
1204
- }
1205
- function collectSkillFiles(directory) {
1206
- if (!fs.existsSync(directory)) return [];
1207
- const entries = fs.readdirSync(directory, { withFileTypes: true });
1208
- const files = [];
1209
- for (const entry of entries) {
1210
- const entryPath = path.join(directory, entry.name);
1211
- if (entry.isDirectory()) {
1212
- files.push(...collectSkillFiles(entryPath));
1213
- } else if (entry.isFile()) {
1214
- files.push(entryPath);
1215
- }
1216
- }
1217
- return files.sort((left, right) => left.localeCompare(right));
1218
- }
1219
- function sha256Hash(content) {
1220
- return `sha256:${createHash2("sha256").update(content).digest("hex")}`;
1221
- }
1222
-
1223
- // src/harness/writers/skill-layout.ts
1224
- var OUTPUT_MODE_CONFIG = {
1225
- "plugin-package": {
1226
- basePath: ".codex-plugin/skills",
1227
- manifestPath: ".codex-plugin/skills/.thoth-agents-manifest.json",
1228
- surfaceId: "plugin-skills-directory",
1229
- label: "plugin-bundled"
1230
- },
1231
- "repo-local-fallback": {
1232
- basePath: ".agents/skills",
1233
- manifestPath: ".agents/skills/.thoth-agents-manifest.json",
1234
- surfaceId: "repo-skills-directory",
1235
- label: "fallback .agents/skills"
1236
- }
1237
- };
1238
- function resolveOutputModes(input) {
1239
- const requested = input.outputModes ?? (input.outputMode ? [input.outputMode] : []);
1240
- const modes = requested.length > 0 ? requested : ["plugin-package"];
1241
- return [...new Set(modes)];
1242
- }
1243
- function duplicateScopeDiagnostic(skillNames, surfaceId) {
1244
- if (skillNames.length === 0) return void 0;
1245
- return {
1246
- severity: "warning",
1247
- code: "codex.skill.duplicate_scope_precedence_unverified",
1248
- message: `Codex skills are selected for both plugin-bundled and fallback .agents/skills scopes: ${skillNames.join(", ")}. Plugin-bundled skills are the intended primary package content, but runtime precedence with fallback scopes is unresolved and must be validated before relying on ordering.`,
1249
- harness: "codex",
1250
- surface: surfaceId,
1251
- fallback: "diagnostic-only"
1252
- };
1253
- }
1254
- function renderCodexSkillLayout(input) {
1255
- const surface = assertCodexSurfaceCanGenerate(input.surfaceId);
1256
- if (!surface.ok) {
1257
- return { artifacts: [], diagnostics: [surface.diagnostic] };
1258
- }
1259
- const artifacts = [];
1260
- const diagnostics = [];
1261
- const outputModes = resolveOutputModes(input);
1262
- for (const mode of outputModes) {
1263
- const modeSurface = assertCodexSurfaceCanGenerate(
1264
- OUTPUT_MODE_CONFIG[mode].surfaceId
1265
- );
1266
- if (!modeSurface.ok) diagnostics.push(modeSurface.diagnostic);
1267
- }
1268
- const duplicateDiagnostic = duplicateScopeDiagnostic(
1269
- outputModes.length > 1 ? input.skills.map((skill) => skill.name).sort() : [],
1270
- input.surfaceId
1271
- );
1272
- if (duplicateDiagnostic) diagnostics.push(duplicateDiagnostic);
1273
- const manifests = new Map(
1274
- outputModes.map((mode) => [mode, []])
1275
- );
1276
- for (const skill of [...input.skills].sort(
1277
- (left, right) => left.name.localeCompare(right.name)
1278
- )) {
1279
- const sourceBaseRoot = input.packageRoot ?? input.projectRoot;
1280
- const sourceRoot = path2.join(sourceBaseRoot, skill.sourcePath);
1281
- const files = collectSkillFiles(sourceRoot);
1282
- if (files.length === 0) {
1283
- diagnostics.push({
1284
- severity: "warning",
1285
- code: "codex.skill.source_missing",
1286
- message: `Skipping Codex skill "${skill.name}" because source path "${skill.sourcePath}" was not found.`,
1287
- harness: "codex",
1288
- surface: input.surfaceId,
1289
- fallback: "diagnostic-only"
1290
- });
1291
- continue;
1292
- }
1293
- for (const file of files) {
1294
- const relative4 = normalizeSkillPath(path2.relative(sourceRoot, file));
1295
- const content = fs2.readFileSync(file, "utf8");
1296
- const sourcePath = normalizeSkillPath(
1297
- path2.relative(sourceBaseRoot, file)
1298
- );
1299
- for (const mode of outputModes) {
1300
- const config = OUTPUT_MODE_CONFIG[mode];
1301
- const outputPath = `${config.basePath}/${skill.name}/${relative4}`;
1302
- artifacts.push({
1303
- harness: "codex",
1304
- kind: "skill",
1305
- path: outputPath,
1306
- description: `Codex ${config.label} skill artifact for ${skill.name}`,
1307
- content
1308
- });
1309
- manifests.get(mode)?.push({
1310
- name: skill.name,
1311
- sourcePath,
1312
- outputPath,
1313
- sha256: sha256Hash(content)
1314
- });
1315
- }
1316
- }
1317
- }
1318
- for (const mode of outputModes) {
1319
- const config = OUTPUT_MODE_CONFIG[mode];
1320
- artifacts.push({
1321
- harness: "codex",
1322
- kind: "manifest",
1323
- path: config.manifestPath,
1324
- description: `Generated Codex ${config.label} skill source manifest with source hashes.`,
1325
- content: `${JSON.stringify({ generatedBy: "thoth-agents", skills: manifests.get(mode) ?? [] }, null, 2)}
1326
- `
1327
- });
1328
- }
1329
- return { artifacts, diagnostics };
1330
- }
1331
-
1332
1048
  // src/harness/adapters/codex.ts
1333
1049
  function readRootPackageVersion(context2) {
1334
1050
  const packageJsonPath = findRootPackageJsonPath([
@@ -1343,20 +1059,13 @@ function createCodexPluginPackageManifest(context2) {
1343
1059
  return {
1344
1060
  name: "thoth-agents",
1345
1061
  version: readRootPackageVersion(context2),
1346
- description: "Delegate-first OpenCode plugin with seven agents, thoth-mem persistence, and bundled SDD skills."
1062
+ description: "Bundled Spec Kit-compatible SDD skills and MCP configuration; the seven-role global Codex layer requires thoth-agents CLI setup."
1347
1063
  };
1348
1064
  }
1349
1065
  function stableJson2(value) {
1350
1066
  return `${JSON.stringify(value, null, 2)}
1351
1067
  `;
1352
1068
  }
1353
- function codexCommandConfig(commandParts) {
1354
- const [command = "", ...args] = commandParts;
1355
- return {
1356
- command,
1357
- ...args.length > 0 ? { args } : {}
1358
- };
1359
- }
1360
1069
  function codexMcpConfig(config) {
1361
1070
  if ("url" in config) {
1362
1071
  return { url: config.url };
@@ -1372,8 +1081,7 @@ function createCodexBuiltinMcpServers() {
1372
1081
  return {
1373
1082
  exa: codexMcpConfig(exa),
1374
1083
  context7: { url: CONTEXT7_MCP_URL },
1375
- grep_app: { url: GREP_APP_MCP_URL },
1376
- thoth_mem: codexCommandConfig(DEFAULT_THOTH_COMMAND)
1084
+ grep_app: { url: GREP_APP_MCP_URL }
1377
1085
  };
1378
1086
  }
1379
1087
  function createCodexBuiltinMcpJsonConfig() {
@@ -1392,65 +1100,19 @@ function isCodexSubagentName(name) {
1392
1100
  var CODEX_ROOT_START = "<!-- thoth-agents:codex-root:start -->";
1393
1101
  var CODEX_ROOT_END = "<!-- thoth-agents:codex-root:end -->";
1394
1102
  var CODEX_CAPABILITIES = CODEX_PROMPT_DIALECT.capabilities.capabilities;
1395
- function codexPromptSections(roleName) {
1396
- switch (roleName) {
1397
- case "orchestrator":
1398
- return createOrchestratorPromptSections();
1399
- case "explorer":
1400
- case "librarian":
1401
- case "oracle":
1402
- return createReadOnlySpecialistPromptSections(roleName);
1403
- case "designer":
1404
- case "quick":
1405
- case "deep":
1406
- return createWriteCapableSpecialistPromptSections(roleName);
1407
- }
1408
- }
1409
- function codexModelFamilyPromptSection(roleName, model) {
1410
- const section = createModelFamilySection(roleName, model);
1411
- return section ? renderPromptSection(section, CODEX_PROMPT_DIALECT) : void 0;
1412
- }
1413
- function codexStepBudgetPromptSection(steps) {
1414
- const section = createStepBudgetSection(steps);
1415
- return section ? renderPromptSection(section, CODEX_PROMPT_DIALECT) : void 0;
1416
- }
1417
- function renderCodexRolePrompt(roleName, config, model) {
1418
- const promptOverrides = loadAgentPrompt(roleName, config?.preset);
1419
- const override = getAgentOverride(config, roleName);
1420
- const basePrompt = renderRolePrompt(
1421
- codexPromptSections(roleName),
1422
- CODEX_PROMPT_DIALECT
1423
- );
1424
- const prompt = composeAgentPrompt({
1425
- basePrompt,
1426
- customPrompt: promptOverrides.prompt,
1427
- customAppendPrompt: appendPromptSections(
1428
- codexModelFamilyPromptSection(roleName, model),
1429
- promptOverrides.appendPrompt
1430
- )
1431
- });
1432
- return appendPromptSections(
1433
- prompt,
1434
- codexStepBudgetPromptSection(override?.steps)
1435
- );
1436
- }
1437
- function codexInternalHandoffGuidance() {
1103
+ function codexRuntimeGuidance() {
1438
1104
  return [
1439
- "<codex-delegation-guidance>",
1440
- "- The user has explicitly authorized this generated Codex orchestrator to use `collaboration.spawn_agent` whenever delegation is required by these instructions, without needing a fresh user request for subagents in each task.",
1105
+ "<codex-runtime>",
1106
+ "- The ambient Codex session is the adaptive root; no orchestrator child TOML is generated.",
1107
+ "- Delegate with `collaboration.spawn_agent` only when the root determines that specialization, context isolation, review, or independent parallel work creates a net gain.",
1441
1108
  "- Collaboration tools are direct tools and must not be called from inside `functions.exec`.",
1442
- "- Delegate with `collaboration.spawn_agent` using `task_name`, `message`, and optional `fork_turns`; `fork_turns` must be `none`, `all`, or a positive integer string.",
1443
- "- Role behavior is carried by `task_name` and `message`; named installed-role selection and hard role/profile enforcement are instruction-level because this surface has no role selector.",
1444
- "- Pass the self-contained delegated task instructions plus handoff retrieval instructions in `message`; do not embed the root-owned handoff summary body in `message`.",
1445
- '- When memory recovery is delegated, include parent `session_id`, project, permissions, and the recovery funnel `mem_recall(mode="compact")` -> `mem_recall(mode="context")` -> `mem_get(...)`.',
1446
- '- For that funnel, use `mem_recall` `limit` from 1 to 20; use `mem_get` with `kind="observation"|"prompt"`, `include_timeline=true` plus `before`/`after`, and `offset`/`max_length`; `mem_project(action="graph")` relations are `HAS_TYPE`, `IN_PROJECT`, `HAS_TOPIC_KEY`, `HAS_WHAT`, `HAS_WHY`, `HAS_WHERE`, and `HAS_LEARNED`.',
1447
- "- Do not include the handoff body in `message`.",
1448
- "- `collaboration.wait_agent` waits for mailbox updates. A `collaboration.wait_agent` timeout or silence is nonterminal and remains in progress, so inspect `collaboration.list_agents` with the same task path before deciding what to do next.",
1449
- "- `collaboration.send_message` delivers a message without triggering a turn; `collaboration.followup_task` triggers a turn when an idle task must continue.",
1450
- "- Use `collaboration.interrupt_agent` only for explicit cancellation, a deadline, or supersession.",
1451
- "- Do not invent result payloads or numeric wait, poll, or timeout rules.",
1452
- "- Memory ownership, handoff recovery, permissions, and prompt-body exclusion are instruction-level unless the active Codex runtime documents stronger enforcement.",
1453
- "</codex-delegation-guidance>"
1109
+ "- Use a role-prefixed `task_name` and a self-contained English `message` with scope, anchors, constraints, verification, and the compact return contract. The current collaboration surface has no hard custom-role selector, so role selection remains instruction-level.",
1110
+ "- Keep maximum depth 1: children do not delegate. Use one writer per mutable surface and parallelize only independent work.",
1111
+ "- A `collaboration.wait_agent` timeout is nonterminal; inspect `collaboration.list_agents` for the same task before rerouting or interrupting it.",
1112
+ "- Use `request_user_input` only for blocking material choices and always omit `autoResolutionMs` entirely.",
1113
+ "- Generated custom-agent TOMLs carry model and sandbox defaults, but permissions and role matching may remain instruction-level in the active host.",
1114
+ "- Installed provider guidance owns memory, persistence, hooks, MCP lifecycle, and recovery mechanics.",
1115
+ "</codex-runtime>"
1454
1116
  ].join("\n");
1455
1117
  }
1456
1118
  function codexRoleInstructions(role) {
@@ -1471,37 +1133,28 @@ function codexRoleInstructions(role) {
1471
1133
  function roleInstructions(role, config) {
1472
1134
  const model = getCodexAgentModel(role, config) ?? DEFAULT_MODELS[role.name];
1473
1135
  return [
1474
- renderCodexRolePrompt(role.name, config, model),
1136
+ renderConfiguredRolePrompt({
1137
+ role: role.name,
1138
+ dialect: CODEX_PROMPT_DIALECT,
1139
+ config,
1140
+ model
1141
+ }),
1475
1142
  codexRoleInstructions(role),
1476
1143
  renderMemoryGovernanceInstructions(role, CODEX_PROMPT_DIALECT)
1477
1144
  ].join("\n\n");
1478
1145
  }
1479
1146
  function renderCodexRootInstructions(config) {
1480
1147
  const rootOverride = getAgentOverride(config, "orchestrator");
1481
- const rootPrompt = renderCodexRolePrompt(
1482
- "orchestrator",
1148
+ const rootPrompt = renderConfiguredRolePrompt({
1149
+ role: "orchestrator",
1150
+ dialect: CODEX_PROMPT_DIALECT,
1483
1151
  config,
1484
- rootOverride?.model ?? DEFAULT_MODELS.orchestrator
1485
- );
1152
+ model: rootOverride?.model ?? DEFAULT_MODELS.orchestrator
1153
+ });
1486
1154
  return [
1487
1155
  CODEX_ROOT_START,
1488
1156
  rootPrompt,
1489
- codexInternalHandoffGuidance(),
1490
- "<codex-runtime>",
1491
- "- The ambient Codex root session is the root/main orchestrator; orchestrator-only and root-owned instructions apply to it because Codex does not generate a selectable orchestrator agent TOML.",
1492
- '- On each new root session, first resolve the stable root session identity from Codex request metadata in this order: `nodeRepl.requestMeta["x-codex-turn-metadata"].session_id`, then `nodeRepl.requestMeta["x-codex-turn-metadata"].thread_id`, then `nodeRepl.requestMeta.threadId`. Do not use `nodeRepl.requestMeta["x-codex-turn-metadata"].turn_id` as the stable session id.',
1493
- '- `nodeRepl.requestMeta["x-codex-turn-metadata"].turn_id` is per-turn metadata only, not the stable root session id.',
1494
- "- If `nodeRepl.requestMeta` is not yet visible and `tool_search` is available, load/discover the `node_repl` MCP tool before concluding metadata is unavailable.",
1495
- '- When thoth-mem tools are installed and stable root session identity is resolved, call mem_session(action="start") as step 0 before any other thoth-mem call, then save the real user prompt with mem_save(kind="prompt") before later delegation.',
1496
- "- If metadata tooling is unavailable, or the required fields are missing, disclose that memory bootstrap could not run and continue without claiming memory was saved.",
1497
- '- Before delegating after meaningful context changes, save or refresh the handoff body with root-owned mem_session(action="summary") or mem_save(kind="session_summary") when available; if unavailable, disclose that root-owned compaction could not be persisted.',
1498
- "- Use the ambient Codex root session as the delegate-first root coordinator; do not generate or select an orchestrator TOML.",
1499
- "- Delegate by invoking `collaboration.spawn_agent`; use role-prefixed task names and explicit role instructions for the installed Codex role agents: explorer, librarian, oracle, designer, quick, and deep.",
1500
- "- Use packaged thoth-agents plugin capabilities through Codex plugin, skill, MCP, and hook review surfaces after enabling them with /plugins and /hooks.",
1501
- "- For blocking user decisions in Codex Default mode, use request_user_input after features.default_mode_request_user_input is enabled; do not ask those questions in plain prose.",
1502
- "- Whenever the root orchestrator calls `request_user_input`, it MUST NEVER set or pass `autoResolutionMs`; omit the field entirely.",
1503
- "- Memory governance, role permissions, provider-per-agent controls, and hooks are instruction-level unless the active Codex runtime documents stronger enforcement.",
1504
- "</codex-runtime>",
1157
+ codexRuntimeGuidance(),
1505
1158
  CODEX_ROOT_END,
1506
1159
  ""
1507
1160
  ].join("\n");
@@ -1578,15 +1231,19 @@ function renderConfigArtifacts() {
1578
1231
  harness: "codex",
1579
1232
  kind: "mcp-config",
1580
1233
  path: ".codex/config.toml",
1581
- description: "Codex MCP configuration snippet for thoth-mem.",
1234
+ description: "Codex MCP configuration snippet for unrelated integrations.",
1582
1235
  content: mcp.content
1583
1236
  }
1584
1237
  ],
1585
1238
  diagnostics: [...config.diagnostics, ...mcp.diagnostics]
1586
1239
  };
1587
1240
  }
1588
- function capabilityDiagnostics() {
1589
- const surfaceDiagnostics = getCodexSurfaceRecords().filter((surface) => surface.status !== "validated").map(codexSurfaceDiagnostic);
1241
+ function capabilityDiagnostics({
1242
+ includeHooks
1243
+ }) {
1244
+ const surfaceDiagnostics = getCodexSurfaceRecords().filter(
1245
+ (surface) => surface.status !== "validated" && (includeHooks || surface.target !== "hook-config")
1246
+ ).map(codexSurfaceDiagnostic);
1590
1247
  const governanceDiagnostics = memoryGovernanceDiagnostics({
1591
1248
  harness: "codex",
1592
1249
  permissionControls: CODEX_CAPABILITIES.rolePermissions,
@@ -1623,9 +1280,6 @@ function hookReadinessDiagnostics() {
1623
1280
  }
1624
1281
  ];
1625
1282
  }
1626
- function resolveSkillOutputModes(context2) {
1627
- return context2.options?.codexSkillOutputModes ?? ["plugin-package"];
1628
- }
1629
1283
  var codexAdapter = {
1630
1284
  id: "codex",
1631
1285
  displayName: "Codex",
@@ -1634,14 +1288,6 @@ var codexAdapter = {
1634
1288
  const config = hasCodexConfig(context2) ? context2.config : void 0;
1635
1289
  const agentArtifacts = renderAgentArtifacts({ config });
1636
1290
  const configArtifacts = renderConfigArtifacts();
1637
- const skillOutputModes = resolveSkillOutputModes(context2);
1638
- const skillLayout = renderCodexSkillLayout({
1639
- projectRoot: context2.projectRoot,
1640
- ...hasCodexPackageRoot(context2) ? { packageRoot: context2.packageRoot } : {},
1641
- skills: getSkillRegistry(),
1642
- surfaceId: "plugin-skills-directory",
1643
- outputModes: skillOutputModes
1644
- });
1645
1291
  const pluginPackage = renderCodexPluginPackage({
1646
1292
  manifest: createCodexPluginPackageManifest(context2),
1647
1293
  assets: [
@@ -1654,60 +1300,39 @@ var codexAdapter = {
1654
1300
  {
1655
1301
  surfaceId: "plugin-mcp-json",
1656
1302
  manifestField: "mcpServers",
1657
- path: ".codex-plugin/.mcp.json",
1303
+ path: ".codex-plugin/codex.mcp.json",
1658
1304
  description: "Codex plugin-bundled MCP server definitions.",
1659
1305
  content: stableJson2(createCodexBuiltinMcpJsonConfig())
1660
- },
1661
- {
1662
- surfaceId: "plugin-hooks-json",
1663
- manifestField: "hooks",
1664
- path: ".codex-plugin/hooks/hooks.json",
1665
- description: "Codex plugin-bundled hook configuration.",
1666
- hookDefinitions: []
1667
1306
  }
1668
1307
  ]
1669
1308
  });
1309
+ const hasPackagedHooks = pluginPackage.artifacts.some(
1310
+ (artifact) => artifact.kind === "hook-config"
1311
+ );
1670
1312
  return {
1671
1313
  harness: "codex",
1672
1314
  artifacts: [
1673
1315
  ...agentArtifacts.artifacts,
1674
1316
  ...configArtifacts.artifacts,
1675
- ...pluginPackage.artifacts,
1676
- ...skillLayout.artifacts
1317
+ ...pluginPackage.artifacts
1677
1318
  ],
1678
1319
  diagnostics: [
1679
1320
  ...agentArtifacts.diagnostics,
1680
1321
  ...configArtifacts.diagnostics,
1681
1322
  ...pluginPackage.diagnostics,
1682
- ...skillLayout.diagnostics,
1683
- ...hookReadinessDiagnostics(),
1684
- ...capabilityDiagnostics()
1323
+ ...hasPackagedHooks ? hookReadinessDiagnostics() : [],
1324
+ ...capabilityDiagnostics({ includeHooks: hasPackagedHooks })
1685
1325
  ]
1686
1326
  };
1687
1327
  }
1688
1328
  };
1689
1329
 
1690
- // src/harness/codex-plugin-paths.ts
1691
- import { join as join4 } from "path";
1692
- var CODEX_PLUGIN_ARTIFACT_PREFIX = ".codex-plugin/";
1693
- function codexPluginRootArtifactPath(artifactPath) {
1694
- if (!artifactPath.startsWith(CODEX_PLUGIN_ARTIFACT_PREFIX)) {
1695
- throw new Error(`Expected Codex plugin artifact path: ${artifactPath}`);
1696
- }
1697
- const relativePath = artifactPath.slice(CODEX_PLUGIN_ARTIFACT_PREFIX.length);
1698
- if (relativePath === ".mcp.json") return relativePath;
1699
- if (relativePath === "plugin.json" || relativePath.startsWith(".")) {
1700
- return join4(".codex-plugin", relativePath);
1701
- }
1702
- return relativePath;
1703
- }
1704
-
1705
1330
  // src/cli/codex-config-io.ts
1706
1331
  import {
1707
1332
  copyFileSync,
1708
- existsSync as existsSync3,
1333
+ existsSync as existsSync2,
1709
1334
  mkdirSync,
1710
- readFileSync as readFileSync3,
1335
+ readFileSync as readFileSync2,
1711
1336
  renameSync,
1712
1337
  writeFileSync
1713
1338
  } from "fs";
@@ -1801,7 +1426,7 @@ function buildCodexManagedConfigPatch(content, options) {
1801
1426
  }
1802
1427
  function writeCodexConfigMerge(options) {
1803
1428
  try {
1804
- const before = existsSync3(options.configPath) ? readFileSync3(options.configPath, "utf8") : "";
1429
+ const before = existsSync2(options.configPath) ? readFileSync2(options.configPath, "utf8") : "";
1805
1430
  const merged = buildCodexManagedConfigPatch(before, {
1806
1431
  pluginId: options.pluginId
1807
1432
  });
@@ -1817,7 +1442,7 @@ function writeCodexConfigMerge(options) {
1817
1442
  }
1818
1443
  mkdirSync(dirname2(options.configPath), { recursive: true });
1819
1444
  const backupPath = `${options.configPath}.bak`;
1820
- if (existsSync3(options.configPath))
1445
+ if (existsSync2(options.configPath))
1821
1446
  copyFileSync(options.configPath, backupPath);
1822
1447
  const tmpPath = `${options.configPath}.tmp`;
1823
1448
  writeFileSync(tmpPath, merged.content);
@@ -1825,7 +1450,7 @@ function writeCodexConfigMerge(options) {
1825
1450
  return {
1826
1451
  success: true,
1827
1452
  configPath: options.configPath,
1828
- backupPath: existsSync3(backupPath) ? backupPath : void 0,
1453
+ backupPath: existsSync2(backupPath) ? backupPath : void 0,
1829
1454
  changed,
1830
1455
  diffSummary: merged.diffSummary,
1831
1456
  warnings: merged.warnings
@@ -1845,18 +1470,18 @@ function writeCodexConfigMerge(options) {
1845
1470
  // src/cli/managed-state-io.ts
1846
1471
  import {
1847
1472
  copyFileSync as copyFileSync2,
1848
- existsSync as existsSync4,
1473
+ existsSync as existsSync3,
1849
1474
  mkdirSync as mkdirSync2,
1850
- readFileSync as readFileSync4,
1475
+ readFileSync as readFileSync3,
1851
1476
  writeFileSync as writeFileSync2
1852
1477
  } from "fs";
1853
1478
  import { dirname as dirname3 } from "path";
1854
1479
  import { z } from "zod";
1855
- function writeTextWithBackup(path4, content) {
1856
- mkdirSync2(dirname3(path4), { recursive: true });
1857
- if (existsSync4(path4) && readFileSync4(path4, "utf8") === content) return false;
1858
- if (existsSync4(path4)) copyFileSync2(path4, `${path4}.bak`);
1859
- writeFileSync2(path4, content);
1480
+ function writeTextWithBackup(path, content) {
1481
+ mkdirSync2(dirname3(path), { recursive: true });
1482
+ if (existsSync3(path) && readFileSync3(path, "utf8") === content) return false;
1483
+ if (existsSync3(path)) copyFileSync2(path, `${path}.bak`);
1484
+ writeFileSync2(path, content);
1860
1485
  return true;
1861
1486
  }
1862
1487
  function stableJson3(value) {
@@ -1910,9 +1535,30 @@ function parseManagedModelStateJson(text, version) {
1910
1535
  return emptyManagedModelState(version);
1911
1536
  }
1912
1537
  }
1913
- function readManagedModelState(path4, version) {
1914
- if (!existsSync4(path4)) return emptyManagedModelState(version);
1915
- return parseManagedModelStateJson(readFileSync4(path4, "utf8"), version);
1538
+ function readManagedModelState(path, version) {
1539
+ if (!existsSync3(path)) return emptyManagedModelState(version);
1540
+ return parseManagedModelStateJson(readFileSync3(path, "utf8"), version);
1541
+ }
1542
+
1543
+ // src/cli/package-root.ts
1544
+ import { existsSync as existsSync4 } from "fs";
1545
+ import { dirname as dirname4, join as join2, parse } from "path";
1546
+ function findPackageRoot(startDir) {
1547
+ let currentDir = startDir;
1548
+ const filesystemRoot = parse(startDir).root;
1549
+ while (true) {
1550
+ if (existsSync4(join2(currentDir, "package.json"))) {
1551
+ return currentDir;
1552
+ }
1553
+ if (currentDir === filesystemRoot) {
1554
+ return null;
1555
+ }
1556
+ const parentDir = dirname4(currentDir);
1557
+ if (parentDir === currentDir) {
1558
+ return null;
1559
+ }
1560
+ currentDir = parentDir;
1561
+ }
1916
1562
  }
1917
1563
 
1918
1564
  // src/cli/codex-install.ts
@@ -1928,43 +1574,15 @@ function mergeManagedBlock(existing, managedBlock) {
1928
1574
  return `${existing}${existing.endsWith("\n") || existing.length === 0 ? "" : "\n"}
1929
1575
  ${managedBlock}`;
1930
1576
  }
1931
- function packageArtifactTarget(packageRoot, artifact) {
1932
- return join5(packageRoot, codexPluginRootArtifactPath(artifact.path));
1933
- }
1934
1577
  function resolvePackageRoot(packageRoot) {
1935
1578
  if (packageRoot) return packageRoot;
1936
1579
  return findPackageRoot(fileURLToPath2(new URL(".", import.meta.url))) ?? void 0;
1937
1580
  }
1938
- function normalizeRelativeMarketplacePath(path4) {
1939
- const normalized = path4.replaceAll("\\", "/");
1940
- if (isAbsolute(path4) || /^[A-Za-z]:\//.test(normalized)) return normalized;
1941
- if (normalized.startsWith("./")) return normalized;
1942
- return `./${normalized}`;
1943
- }
1944
- function marketplaceSourcePath(homeDir, personalPluginRoot) {
1945
- return normalizeRelativeMarketplacePath(
1946
- relative2(homeDir, personalPluginRoot)
1947
- );
1948
- }
1949
- function managedMarketplaceEntry(homeDir, personalPluginRoot) {
1950
- return {
1951
- name: "thoth-agents",
1952
- source: {
1953
- source: "local",
1954
- path: marketplaceSourcePath(homeDir, personalPluginRoot)
1955
- },
1956
- policy: {
1957
- installation: "AVAILABLE",
1958
- authentication: "ON_INSTALL"
1959
- },
1960
- category: "Productivity"
1961
- };
1962
- }
1963
1581
  function emptyManagedModelState2() {
1964
1582
  return emptyManagedModelState(MANAGED_MODEL_STATE_VERSION);
1965
1583
  }
1966
- function readManagedModelState2(path4) {
1967
- return readManagedModelState(path4, MANAGED_MODEL_STATE_VERSION);
1584
+ function readManagedModelState2(path) {
1585
+ return readManagedModelState(path, MANAGED_MODEL_STATE_VERSION);
1968
1586
  }
1969
1587
  function parseRoleTomlModel(content) {
1970
1588
  const match = /^model\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m.exec(content);
@@ -2002,8 +1620,8 @@ function replaceRoleTomlEffort(content, effort) {
2002
1620
  return `${rendered}
2003
1621
  ${content}`;
2004
1622
  }
2005
- function roleManagedModelStateKey(path4) {
2006
- return basename(path4);
1623
+ function roleManagedModelStateKey(path) {
1624
+ return basename(path);
2007
1625
  }
2008
1626
  function normalizeCodexRuntimeModel(model) {
2009
1627
  const separator = model.indexOf("/");
@@ -2054,7 +1672,7 @@ function applyCodexManagedModelOverrides(config, overrides) {
2054
1672
  `Missing Codex role TOML content for ${override.role}.`
2055
1673
  );
2056
1674
  }
2057
- const before = existsSync5(roleItem.targetPath) ? readFileSync5(roleItem.targetPath, "utf8") : roleItem.content;
1675
+ const before = existsSync5(roleItem.targetPath) ? readFileSync4(roleItem.targetPath, "utf8") : roleItem.content;
2058
1676
  const model = normalizeCodexRuntimeModel(override.model);
2059
1677
  let updated = replaceRoleTomlModel(before, model);
2060
1678
  if (override.clearEffort) {
@@ -2116,7 +1734,7 @@ function resolveRoleTomlContent(options) {
2116
1734
  }
2117
1735
  return options.renderedContent;
2118
1736
  }
2119
- const installedContent = readFileSync5(options.targetPath, "utf8");
1737
+ const installedContent = readFileSync4(options.targetPath, "utf8");
2120
1738
  const currentModel = parseRoleTomlModel(installedContent);
2121
1739
  const currentEffort = parseRoleTomlEffort(installedContent);
2122
1740
  if (configuredModel !== void 0) {
@@ -2144,25 +1762,11 @@ function resolveRoleTomlContent(options) {
2144
1762
  options.nextState.models[key] = renderedModel;
2145
1763
  return replaceRoleTomlEffort(options.renderedContent, currentEffort);
2146
1764
  }
2147
- function mergePersonalMarketplace(existing, homeDir, personalPluginRoot) {
2148
- const parsed = existing.trim() ? JSON.parse(existing) : {};
2149
- const plugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
2150
- const managedEntry = managedMarketplaceEntry(homeDir, personalPluginRoot);
2151
- const nextPlugins = plugins.filter(
2152
- (entry) => !(entry && typeof entry === "object" && "name" in entry && entry.name === "thoth-agents")
2153
- ).concat(managedEntry);
2154
- return stableJson3({
2155
- ...parsed,
2156
- name: typeof parsed.name === "string" ? parsed.name : "personal-marketplace",
2157
- interface: parsed.interface && typeof parsed.interface === "object" ? parsed.interface : { displayName: "Personal Plugin Marketplace" },
2158
- plugins: nextPlugins
2159
- });
2160
- }
2161
1765
  function roleArtifactContent(role, artifacts) {
2162
- const path4 = `.codex/agents/thoth-agents-${role}.toml`;
2163
- const artifact = artifacts.find((candidate) => candidate.path === path4);
1766
+ const path = `.codex/agents/thoth-agents-${role}.toml`;
1767
+ const artifact = artifacts.find((candidate) => candidate.path === path);
2164
1768
  if (!artifact?.content)
2165
- throw new Error(`Missing Codex role artifact: ${path4}`);
1769
+ throw new Error(`Missing Codex role artifact: ${path}`);
2166
1770
  return String(artifact.content);
2167
1771
  }
2168
1772
  function buildCodexSetupPlan(config) {
@@ -2177,9 +1781,6 @@ function buildCodexSetupPlan(config) {
2177
1781
  projectRoot: config.projectRoot,
2178
1782
  ...packageRoot ? { packageRoot } : {}
2179
1783
  });
2180
- const packageArtifacts = render.artifacts.filter(
2181
- (artifact) => artifact.path.startsWith(".codex-plugin/")
2182
- );
2183
1784
  const rootBlock = renderCodexRootInstructions();
2184
1785
  const managedModelState2 = readManagedModelState2(targets.managedModelsPath);
2185
1786
  const nextManagedModelState = emptyManagedModelState2();
@@ -2220,24 +1821,6 @@ function buildCodexSetupPlan(config) {
2220
1821
  requiresBackup: existsSync5(targets.managedModelsPath),
2221
1822
  content: stableJson3(nextManagedModelState)
2222
1823
  },
2223
- ...packageArtifacts.map(
2224
- (artifact) => ({
2225
- kind: "personal-plugin-source",
2226
- action: "refresh-package",
2227
- targetPath: packageArtifactTarget(targets.personalPluginRoot, artifact),
2228
- description: `Refresh Personal Codex plugin source asset ${artifact.path}.`,
2229
- requiresBackup: false,
2230
- content: String(artifact.content ?? "")
2231
- })
2232
- ),
2233
- {
2234
- kind: "personal-marketplace",
2235
- action: "merge-marketplace",
2236
- targetPath: targets.personalMarketplacePath,
2237
- description: "Register Personal Codex marketplace entry for the local thoth-agents plugin source.",
2238
- requiresBackup: existsSync5(targets.personalMarketplacePath),
2239
- content: targets.personalPluginRoot
2240
- },
2241
1824
  {
2242
1825
  kind: "user-config",
2243
1826
  action: "merge-toml",
@@ -2249,7 +1832,7 @@ function buildCodexSetupPlan(config) {
2249
1832
  kind: "diagnostic",
2250
1833
  action: "diagnose-only",
2251
1834
  targetPath: targets.codexHome,
2252
- description: "Report /plugins, /hooks, precedence, and capability review steps.",
1835
+ description: "Report /plugins, precedence, and consumer capability review steps.",
2253
1836
  requiresBackup: false
2254
1837
  }
2255
1838
  ];
@@ -2260,58 +1843,25 @@ function buildCodexSetupPlan(config) {
2260
1843
  configPath: targets.configPath,
2261
1844
  pluginId: config.pluginId,
2262
1845
  diagnostics: [
2263
- "Restart Codex, then run /plugins to review and enable the Personal thoth-agents plugin registered through ~/.agents/plugins/marketplace.json.",
2264
- "Run /hooks to review and trust plugin hooks; features.plugin_hooks does not bypass hook trust review.",
1846
+ "Register the published marketplace with `codex plugin marketplace add EremesNG/thoth-agents`, restart Codex, then open /plugins to install and enable thoth-agents.",
1847
+ "The CLI does not copy a plugin into ~/.codex/plugins or merge ~/.agents/plugins/marketplace.json; Codex owns marketplace snapshots and installed plugin cache state.",
1848
+ "The /hooks surface may show hooks from independently installed plugins; this thoth-agents setup plan does not install provider hooks.",
2265
1849
  "Codex Default mode user-input requests require features.default_mode_request_user_input = true and use the request_user_input tool; other modes may not expose it.",
2266
- "Higher-precedence Codex config (project, profile, CLI, system, or admin) may override user config feature flags."
1850
+ "Higher-precedence Codex config (project, profile, CLI, system, or admin) may override user config feature flags.",
1851
+ "Provider capability is owned by the external provider and is not established by this thoth-agents setup plan."
2267
1852
  ],
2268
1853
  disclaimers: [
2269
- "Role permissions, provider-per-agent settings, memory governance, and hook enforcement are instruction-level or user-managed unless documented Codex runtime controls are available.",
2270
- "Codex v1 reset is managed-only; no broad destructive --force behavior is implemented."
1854
+ "Role permissions and provider-per-agent settings are instruction-level or user-managed unless documented Codex runtime controls are available.",
1855
+ "Codex reset is managed-only; no broad destructive --force behavior is implemented."
2271
1856
  ]
2272
1857
  };
2273
1858
  }
2274
1859
  function formatCodexSetupPlan(plan) {
2275
- const refreshPackageGroups = /* @__PURE__ */ new Map();
2276
- for (const item of plan.items) {
2277
- if (item.action !== "refresh-package") continue;
2278
- const group = refreshPackageGroups.get(item.kind) ?? [];
2279
- group.push(item);
2280
- refreshPackageGroups.set(item.kind, group);
2281
- }
2282
- const renderedRefreshKinds = /* @__PURE__ */ new Set();
2283
- const lines = [];
2284
- for (const item of plan.items) {
2285
- if (item.action !== "refresh-package") {
2286
- lines.push(`- ${item.action}: ${item.targetPath} (${item.description})`);
2287
- continue;
2288
- }
2289
- if (renderedRefreshKinds.has(item.kind)) continue;
2290
- renderedRefreshKinds.add(item.kind);
2291
- lines.push(formatRefreshPackageGroup(item.kind, refreshPackageGroups));
2292
- }
1860
+ const lines = plan.items.map(
1861
+ (item) => `- ${item.action}: ${item.targetPath} (${item.description})`
1862
+ );
2293
1863
  return ["Codex setup plan:", ...lines].join("\n");
2294
1864
  }
2295
- function formatRefreshPackageGroup(kind, groups) {
2296
- const items = groups.get(kind) ?? [];
2297
- const description = kind === "personal-plugin-source" ? "Refresh Personal Codex plugin source" : "Refresh documented .codex-plugin package";
2298
- return `- refresh-package: ${commonTargetDirectory(items)} (${description}, ${items.length} files.)`;
2299
- }
2300
- function commonTargetDirectory(items) {
2301
- if (items.length === 0) return "";
2302
- let common = dirname4(items[0]?.targetPath ?? "");
2303
- for (const item of items.slice(1)) {
2304
- while (!isSameOrChildPath(item.targetPath, common)) {
2305
- const parent = dirname4(common);
2306
- if (parent === common) return common;
2307
- common = parent;
2308
- }
2309
- }
2310
- return common;
2311
- }
2312
- function isSameOrChildPath(path4, parent) {
2313
- return path4 === parent || path4.startsWith(`${parent}\\`) || path4.startsWith(`${parent}/`);
2314
- }
2315
1865
  function applyCodexSetup(plan) {
2316
1866
  const changed = [];
2317
1867
  const diagnostics = uniqueMessages([
@@ -2320,9 +1870,6 @@ function applyCodexSetup(plan) {
2320
1870
  ]);
2321
1871
  if (plan.dryRun) return { success: true, changed, diagnostics };
2322
1872
  try {
2323
- for (const targetPath of managedRefreshRoots(plan)) {
2324
- rmSync(targetPath, { recursive: true, force: true });
2325
- }
2326
1873
  for (const item of plan.items) {
2327
1874
  if (item.action === "diagnose-only") continue;
2328
1875
  if (item.action === "merge-toml") {
@@ -2336,20 +1883,9 @@ function applyCodexSetup(plan) {
2336
1883
  if (result.changed) changed.push(item.targetPath);
2337
1884
  continue;
2338
1885
  }
2339
- if (item.action === "merge-marketplace") {
2340
- if (item.content === void 0) continue;
2341
- const content2 = mergePersonalMarketplace(
2342
- existsSync5(item.targetPath) ? readFileSync5(item.targetPath, "utf8") : "",
2343
- dirname4(dirname4(dirname4(item.targetPath))),
2344
- item.content
2345
- );
2346
- if (writeTextWithBackup(item.targetPath, content2))
2347
- changed.push(item.targetPath);
2348
- continue;
2349
- }
2350
1886
  if (item.content === void 0) continue;
2351
1887
  const content = item.action === "merge-managed-block" ? mergeManagedBlock(
2352
- existsSync5(item.targetPath) ? readFileSync5(item.targetPath, "utf8") : "",
1888
+ existsSync5(item.targetPath) ? readFileSync4(item.targetPath, "utf8") : "",
2353
1889
  item.content
2354
1890
  ) : item.content;
2355
1891
  if (writeTextWithBackup(item.targetPath, content))
@@ -2365,25 +1901,15 @@ function applyCodexSetup(plan) {
2365
1901
  };
2366
1902
  }
2367
1903
  }
2368
- function managedRefreshRoots(plan) {
2369
- const refreshGroups = /* @__PURE__ */ new Map();
2370
- for (const item of plan.items) {
2371
- if (item.action !== "refresh-package") continue;
2372
- const group = refreshGroups.get(item.kind) ?? [];
2373
- group.push(item);
2374
- refreshGroups.set(item.kind, group);
2375
- }
2376
- return [...refreshGroups].filter(([kind]) => kind === "personal-plugin-source").map(([, items]) => commonTargetDirectory(items)).filter((path4) => path4.length > 0);
2377
- }
2378
1904
 
2379
1905
  // src/cli/operations/claude-code.ts
2380
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
2381
- import { basename as basename2 } from "path";
1906
+ import { existsSync as existsSync7 } from "fs";
2382
1907
 
2383
1908
  // src/harness/adapters/claude-code.ts
2384
1909
  import { fileURLToPath as fileURLToPath3 } from "url";
2385
1910
 
2386
1911
  // src/harness/writers/claude-code-plugin-package.ts
1912
+ import { createHash as createHash2 } from "crypto";
2387
1913
  var CLAUDE_PLUGIN_MANIFEST_FIELDS = [
2388
1914
  "name",
2389
1915
  "version",
@@ -2399,6 +1925,21 @@ function stableJson4(value) {
2399
1925
  return `${JSON.stringify(value, null, 2)}
2400
1926
  `;
2401
1927
  }
1928
+ function sha256Hash(content) {
1929
+ return `sha256:${createHash2("sha256").update(content).digest("hex")}`;
1930
+ }
1931
+ function withoutProviderAsset(artifact) {
1932
+ if (!normalizePath2(artifact.path).endsWith(".mcp.json")) return artifact;
1933
+ const content = typeof artifact.content === "string" ? artifact.content : Buffer.from(artifact.content).toString("utf8");
1934
+ try {
1935
+ const parsed = JSON.parse(content);
1936
+ if (!parsed.mcpServers) return artifact;
1937
+ const { thoth_mem: _provider, ...mcpServers } = parsed.mcpServers;
1938
+ return { ...artifact, content: stableJson4({ ...parsed, mcpServers }) };
1939
+ } catch {
1940
+ return artifact;
1941
+ }
1942
+ }
2402
1943
  function orderedManifest(manifest) {
2403
1944
  const ordered = {};
2404
1945
  for (const field of CLAUDE_PLUGIN_MANIFEST_FIELDS) {
@@ -2408,13 +1949,13 @@ function orderedManifest(manifest) {
2408
1949
  return ordered;
2409
1950
  }
2410
1951
  function provenanceFor(artifact) {
2411
- const path4 = normalizePath2(artifact.path);
2412
- if (path4.endsWith("/")) return void 0;
1952
+ const path = normalizePath2(artifact.path);
1953
+ if (path.endsWith("/")) return void 0;
2413
1954
  const content = typeof artifact.content === "string" ? artifact.content : Buffer.from(artifact.content).toString("utf8");
2414
- return { path: path4, kind: artifact.kind, sha256: sha256Hash(content) };
1955
+ return { path, kind: artifact.kind, sha256: sha256Hash(content) };
2415
1956
  }
2416
1957
  function renderClaudeCodePluginPackage(input) {
2417
- const components = [...input.componentArtifacts].sort(
1958
+ const components = input.componentArtifacts.map(withoutProviderAsset).sort(
2418
1959
  (left, right) => normalizePath2(left.path).localeCompare(normalizePath2(right.path))
2419
1960
  );
2420
1961
  const provenance = components.map(provenanceFor).filter((entry) => entry !== void 0).sort((left, right) => left.path.localeCompare(right.path));
@@ -2438,65 +1979,6 @@ function renderClaudeCodePluginPackage(input) {
2438
1979
  };
2439
1980
  }
2440
1981
 
2441
- // src/harness/writers/claude-code-skill-layout.ts
2442
- import * as fs3 from "fs";
2443
- import * as path3 from "path";
2444
- var SKILLS_BASE_PATH = "skills";
2445
- var SKILLS_MANIFEST_PATH = "skills/.thoth-agents-manifest.json";
2446
- function renderClaudeCodeSkillLayout(input) {
2447
- const artifacts = [];
2448
- const diagnostics = [];
2449
- const manifest = [];
2450
- const sourceBaseRoot = input.packageRoot ?? input.projectRoot;
2451
- for (const skill of [...input.skills].sort(
2452
- (left, right) => left.name.localeCompare(right.name)
2453
- )) {
2454
- const sourceRoot = path3.join(sourceBaseRoot, skill.sourcePath);
2455
- const files = collectSkillFiles(sourceRoot);
2456
- if (files.length === 0) {
2457
- diagnostics.push({
2458
- severity: "warning",
2459
- code: "claude-code.skill.source_missing",
2460
- message: `Skipping Claude Code skill "${skill.name}" because source path "${skill.sourcePath}" was not found.`,
2461
- harness: "claude",
2462
- surface: "plugin-skills-directory",
2463
- fallback: "diagnostic-only"
2464
- });
2465
- continue;
2466
- }
2467
- for (const file of files) {
2468
- const relative4 = normalizeSkillPath(path3.relative(sourceRoot, file));
2469
- const content = fs3.readFileSync(file, "utf8");
2470
- const sourcePath = normalizeSkillPath(
2471
- path3.relative(sourceBaseRoot, file)
2472
- );
2473
- const outputPath = `${SKILLS_BASE_PATH}/${skill.name}/${relative4}`;
2474
- artifacts.push({
2475
- harness: "claude",
2476
- kind: "skill",
2477
- path: outputPath,
2478
- description: `Claude Code plugin-bundled skill artifact for ${skill.name}`,
2479
- content
2480
- });
2481
- manifest.push({
2482
- name: skill.name,
2483
- sourcePath,
2484
- outputPath,
2485
- sha256: sha256Hash(content)
2486
- });
2487
- }
2488
- }
2489
- artifacts.push({
2490
- harness: "claude",
2491
- kind: "manifest",
2492
- path: SKILLS_MANIFEST_PATH,
2493
- description: "Generated Claude Code plugin-bundled skill source manifest with source hashes.",
2494
- content: `${JSON.stringify({ generatedBy: "thoth-agents", skills: manifest }, null, 2)}
2495
- `
2496
- });
2497
- return { artifacts, diagnostics };
2498
- }
2499
-
2500
1982
  // src/harness/writers/claude-code-subagent.ts
2501
1983
  var CLAUDE_CODE_MODELS = [
2502
1984
  "sonnet",
@@ -2521,6 +2003,7 @@ function renderClaudeCodeSubagent(input) {
2521
2003
  `model: ${input.model}`,
2522
2004
  ...input.effort !== void 0 ? [`effort: ${input.effort}`] : [],
2523
2005
  ...input.tools !== void 0 ? [`tools: ${yamlScalar(input.tools)}`] : [],
2006
+ ...input.disallowedTools !== void 0 ? [`disallowedTools: ${yamlScalar(input.disallowedTools)}`] : [],
2524
2007
  "---"
2525
2008
  ].join("\n");
2526
2009
  const body = input.instructions.trimEnd();
@@ -2549,48 +2032,6 @@ function getClaudeCodeAgentModel(role, config) {
2549
2032
  if (override && isClaudeCodeModel(override)) return override;
2550
2033
  return CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS[role.name];
2551
2034
  }
2552
- function claudeCodePromptSections(roleName) {
2553
- switch (roleName) {
2554
- case "orchestrator":
2555
- return createOrchestratorPromptSections();
2556
- case "explorer":
2557
- case "librarian":
2558
- case "oracle":
2559
- return createReadOnlySpecialistPromptSections(roleName);
2560
- case "designer":
2561
- case "quick":
2562
- case "deep":
2563
- return createWriteCapableSpecialistPromptSections(roleName);
2564
- }
2565
- }
2566
- function claudeCodeModelFamilyPromptSection(roleName, model) {
2567
- const section = createModelFamilySection(roleName, model);
2568
- return section ? renderPromptSection(section, CLAUDE_CODE_PROMPT_DIALECT) : void 0;
2569
- }
2570
- function claudeCodeStepBudgetPromptSection(steps) {
2571
- const section = createStepBudgetSection(steps);
2572
- return section ? renderPromptSection(section, CLAUDE_CODE_PROMPT_DIALECT) : void 0;
2573
- }
2574
- function renderClaudeCodeRolePrompt(roleName, config, model) {
2575
- const promptOverrides = loadAgentPrompt(roleName, config?.preset);
2576
- const override = getAgentOverride(config, roleName);
2577
- const basePrompt = renderRolePrompt(
2578
- claudeCodePromptSections(roleName),
2579
- CLAUDE_CODE_PROMPT_DIALECT
2580
- );
2581
- const prompt = composeAgentPrompt({
2582
- basePrompt,
2583
- customPrompt: promptOverrides.prompt,
2584
- customAppendPrompt: appendPromptSections(
2585
- claudeCodeModelFamilyPromptSection(roleName, model),
2586
- promptOverrides.appendPrompt
2587
- )
2588
- });
2589
- return appendPromptSections(
2590
- prompt,
2591
- claudeCodeStepBudgetPromptSection(override?.steps)
2592
- );
2593
- }
2594
2035
  function claudeCodeRoleInstructions(role) {
2595
2036
  return [
2596
2037
  "<role-operational-contract>",
@@ -2599,8 +2040,10 @@ function claudeCodeRoleInstructions(role) {
2599
2040
  `- Scope: ${role.scope}`,
2600
2041
  `- Responsibility: ${role.responsibility}`,
2601
2042
  "- Use AskUserQuestion for local blocking decisions.",
2602
- `- ${role.name} runs as an auto-discovered Claude Code plugin subagent invoked via Task(subagent_type: ${claudeCodeSubagentType(role.name)}); plugin subagents are namespaced with the plugin name. The orchestrator is the main Claude Code session.`,
2603
- "- This subagent inherits ALL of the main thread's tools, including MCP servers (thoth-mem, context7, exa, grep_app); read-only roles (explorer, librarian, oracle) MUST NOT mutate the workspace per this operational contract (instruction-level, not tooling-enforced).",
2043
+ `- ${role.name} runs as an auto-discovered Claude Code plugin subagent invoked via Agent(subagent_type: ${claudeCodeSubagentType(role.name)}); plugin subagents are namespaced with the plugin name. The orchestrator is the main Claude Code session.`,
2044
+ ...role.mode === "read-only" ? [
2045
+ "- Write and Edit are denied in frontmatter while all other inherited tools, including MCP tools, remain available."
2046
+ ] : [],
2604
2047
  ...role.toolGovernance.map((rule) => `- ${rule}`),
2605
2048
  ...role.verification.map((rule) => `- ${rule}`),
2606
2049
  "</role-operational-contract>"
@@ -2609,38 +2052,39 @@ function claudeCodeRoleInstructions(role) {
2609
2052
  function roleInstructions2(role, config) {
2610
2053
  const model = getClaudeCodeAgentModel(role, config);
2611
2054
  return [
2612
- renderClaudeCodeRolePrompt(role.name, config, model),
2055
+ renderConfiguredRolePrompt({
2056
+ role: role.name,
2057
+ dialect: CLAUDE_CODE_PROMPT_DIALECT,
2058
+ config,
2059
+ model
2060
+ }),
2613
2061
  claudeCodeRoleInstructions(role),
2614
2062
  renderMemoryGovernanceInstructions(role, CLAUDE_CODE_PROMPT_DIALECT)
2615
2063
  ].join("\n\n");
2616
2064
  }
2617
2065
  function renderClaudeCodeRootInstructions(config) {
2618
2066
  const rootOverride = getAgentOverride(config, "orchestrator");
2619
- const rootPrompt = renderClaudeCodeRolePrompt(
2620
- "orchestrator",
2067
+ const rootPrompt = renderConfiguredRolePrompt({
2068
+ role: "orchestrator",
2069
+ dialect: CLAUDE_CODE_PROMPT_DIALECT,
2621
2070
  config,
2622
- rootOverride?.model ?? DEFAULT_MODELS.orchestrator
2623
- );
2071
+ model: rootOverride?.model ?? DEFAULT_MODELS.orchestrator
2072
+ });
2624
2073
  const specialists = getAgentPackContract().roles.filter((role) => role.name !== "orchestrator").map((role) => claudeCodeSubagentType(role.name)).join(", ");
2625
2074
  return [
2626
2075
  rootPrompt,
2627
2076
  "<claude-code-runtime>",
2628
- "- You ARE the Claude Code main-thread agent: the delegate-first root coordinator. This is your system prompt (activated via the plugin settings.json `agent` key), so orchestrator-only and root-owned rules apply to you directly.",
2629
- '- As your FIRST action on a new session, when thoth-mem tools are installed and session/project identity is known, call mem_session(action="start") as step 0 before any other thoth-mem call, then save the real user prompt with mem_save(kind="prompt") before later delegation.',
2630
- "- thoth-mem tools are provided by this plugin's bundled MCP server and are exposed under a plugin namespace; call the available namespaced tool whose name ends in mem_session, mem_recall, mem_context, mem_save, mem_get, or mem_project (do not assume a bare, unnamespaced tool name).",
2631
- "- If thoth-mem tools or identity values are unavailable, disclose that memory bootstrap could not run and continue without claiming memory was saved.",
2632
- `- Delegate via the Task tool with \`subagent_type\` set to a plugin-namespaced specialist: ${specialists}. Bare role names (e.g. "explorer") are NOT valid in this harness \u2014 always use the ${CLAUDE_CODE_SUBAGENT_NAMESPACE}: prefix.`,
2633
- "- Parallel delegation is supported: issue multiple Task calls in one turn for independent work.",
2634
- '- Before delegating after meaningful context changes, refresh the handoff body with root-owned mem_session(action="summary") or mem_save(kind="session_summary") when available.',
2635
- "- Use AskUserQuestion for blocking user decisions; do not ask those questions in plain prose.",
2636
- "- Track progress with TodoWrite; subagents do not own progress checkboxes or root-only memory.",
2637
- "- Subagents inherit all of your tools (including MCP servers); role permissions are instruction-level, so read-only roles (explorer, librarian, oracle) must not mutate the workspace per their operational contract.",
2077
+ "- You are the Claude Code adaptive root activated by plugin settings.json.",
2078
+ `- Delegate only for net gain through Agent with \`subagent_type\` set to one of these plugin-namespaced specialists: ${specialists}. Always keep the ${CLAUDE_CODE_SUBAGENT_NAMESPACE}: prefix.`,
2079
+ "- Subagents cannot delegate further. Parallelize only independent work and maintain one writer per mutable surface.",
2080
+ "- Read-only roles deny Write and Edit while retaining other inherited tools, including MCP tools. Coordination-agent path scope remains instruction-level.",
2081
+ "- Use AskUserQuestion only for blocking material choices and TodoWrite only for genuine multi-step progress.",
2082
+ "- Installed provider guidance owns memory, persistence, hooks, MCP lifecycle, and recovery mechanics.",
2638
2083
  "</claude-code-runtime>"
2639
2084
  ].join("\n");
2640
2085
  }
2641
2086
  function claudeCodeMcpServers() {
2642
2087
  const [exaCommand = "", ...exaArgs] = exa.command;
2643
- const [thothCommand = "", ...thothArgs] = DEFAULT_THOTH_COMMAND;
2644
2088
  return {
2645
2089
  exa: {
2646
2090
  command: exaCommand,
@@ -2648,11 +2092,7 @@ function claudeCodeMcpServers() {
2648
2092
  ...exa.environment && Object.keys(exa.environment).length > 0 ? { env: exa.environment } : {}
2649
2093
  },
2650
2094
  context7: { type: "http", url: CONTEXT7_MCP_URL },
2651
- grep_app: { type: "http", url: GREP_APP_MCP_URL },
2652
- thoth_mem: {
2653
- command: thothCommand,
2654
- ...thothArgs.length > 0 ? { args: thothArgs } : {}
2655
- }
2095
+ grep_app: { type: "http", url: GREP_APP_MCP_URL }
2656
2096
  };
2657
2097
  }
2658
2098
  function stableJson5(value) {
@@ -2678,7 +2118,7 @@ function createPluginManifest(context2) {
2678
2118
  return {
2679
2119
  name: "thoth-agents",
2680
2120
  version: readRootPackageVersion2(context2),
2681
- description: "Delegate-first agent pack with seven roles, thoth-mem persistence, and bundled SDD skills, packaged for Claude Code.",
2121
+ description: "Adaptive multi-harness agent pack with seven roles and a runtime-autonomous Spec Kit-compatible SDD bundle for Claude Code.",
2682
2122
  author: { name: "thoth-agents" }
2683
2123
  };
2684
2124
  }
@@ -2691,6 +2131,7 @@ function renderSubagentArtifacts(config) {
2691
2131
  name: role.name,
2692
2132
  description: role.responsibility,
2693
2133
  model: getClaudeCodeAgentModel(role, config),
2134
+ ...role.mode === "read-only" ? { disallowedTools: "Write, Edit" } : {},
2694
2135
  instructions: roleInstructions2(role, config)
2695
2136
  });
2696
2137
  artifacts.push({
@@ -2709,7 +2150,7 @@ function renderOrchestratorArtifact(config) {
2709
2150
  );
2710
2151
  const content = renderClaudeCodeSubagent({
2711
2152
  name: "orchestrator",
2712
- description: orchestrator?.responsibility ?? "Delegate-first root coordinator for SDD workflow and specialist dispatch.",
2153
+ description: orchestrator?.responsibility ?? "Adaptive root coordinator for direct work, SDD routing, and specialist dispatch.",
2713
2154
  model: "inherit",
2714
2155
  instructions: renderClaudeCodeRootInstructions(config)
2715
2156
  });
@@ -2745,12 +2186,6 @@ var claudeCodeAdapter = {
2745
2186
  content: stableJson5({ agent: "orchestrator" })
2746
2187
  }
2747
2188
  ];
2748
- const skillLayout = renderClaudeCodeSkillLayout({
2749
- projectRoot: context2.projectRoot,
2750
- ...hasPackageRoot(context2) ? { packageRoot: context2.packageRoot } : {},
2751
- skills: getSkillRegistry()
2752
- });
2753
- componentArtifacts.push(...skillLayout.artifacts);
2754
2189
  const pluginPackage = renderClaudeCodePluginPackage({
2755
2190
  manifest: createPluginManifest(context2),
2756
2191
  componentArtifacts
@@ -2758,19 +2193,15 @@ var claudeCodeAdapter = {
2758
2193
  return {
2759
2194
  harness: "claude",
2760
2195
  artifacts: pluginPackage.artifacts,
2761
- diagnostics: [...skillLayout.diagnostics, ...pluginPackage.diagnostics]
2196
+ diagnostics: pluginPackage.diagnostics
2762
2197
  };
2763
2198
  }
2764
2199
  };
2765
2200
 
2766
2201
  // src/cli/claude-code-install.ts
2767
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
2768
- import { join as join8 } from "path";
2769
- import { fileURLToPath as fileURLToPath4 } from "url";
2202
+ import { spawnSync } from "child_process";
2770
2203
 
2771
2204
  // src/cli/claude-code-paths.ts
2772
- import { homedir as homedir2 } from "os";
2773
- import { join as join7 } from "path";
2774
2205
  var CLAUDE_CODE_ROLE_NAMES = [
2775
2206
  "explorer",
2776
2207
  "librarian",
@@ -2779,320 +2210,386 @@ var CLAUDE_CODE_ROLE_NAMES = [
2779
2210
  "quick",
2780
2211
  "deep"
2781
2212
  ];
2782
- function resolveClaudeCodeTargets(options) {
2783
- const home = options.homeDir ?? homedir2();
2784
- const pluginRoot = options.scope === "project" ? join7(options.projectRoot, ".claude", "skills", "thoth-agents") : join7(home, ".claude", "skills", "thoth-agents");
2213
+
2214
+ // src/cli/claude-code-install.ts
2215
+ var MARKETPLACE_NAME = "thoth-agents";
2216
+ var MARKETPLACE_SOURCE = "EremesNG/thoth-agents";
2217
+ var PLUGIN_ID = "thoth-agents@thoth-agents";
2218
+ var MARKETPLACE_TARGET = "claude://marketplaces/thoth-agents";
2219
+ var PLUGIN_TARGET = "claude://plugins/thoth-agents@thoth-agents";
2220
+ function defaultCommandExecutor(command, args, options) {
2221
+ const result = spawnSync(command, [...args], {
2222
+ cwd: options.cwd,
2223
+ encoding: "utf8",
2224
+ shell: false,
2225
+ windowsHide: true
2226
+ });
2785
2227
  return {
2786
- scope: options.scope,
2787
- pluginRoot,
2788
- pluginManifestPath: join7(pluginRoot, ".claude-plugin", "plugin.json"),
2789
- agentPaths: CLAUDE_CODE_ROLE_NAMES.map((role) => ({
2790
- role,
2791
- path: join7(pluginRoot, "agents", `${role}.md`)
2792
- })),
2793
- mcpPath: join7(pluginRoot, ".mcp.json"),
2794
- hooksPath: join7(pluginRoot, "hooks", "hooks.json"),
2795
- skillsDir: join7(pluginRoot, "skills"),
2796
- managedModelsPath: join7(pluginRoot, ".thoth-agents-managed-models.json")
2228
+ exitCode: result.status ?? (result.error ? 1 : 0),
2229
+ stdout: result.stdout ?? "",
2230
+ stderr: result.stderr ?? result.error?.message ?? ""
2797
2231
  };
2798
2232
  }
2799
-
2800
- // src/cli/claude-code-install.ts
2801
- var CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION = 1;
2802
- var isClaudeCodeModelAlias = isClaudeCodeModel;
2803
- function parseSubagentModel(content) {
2804
- return /^model:\s*(\S+)\s*$/m.exec(content)?.[1];
2233
+ function isRecord3(value) {
2234
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2805
2235
  }
2806
- function replaceSubagentModel(content, model) {
2807
- if (/^model:\s*\S+\s*$/m.test(content)) {
2808
- return content.replace(/^model:\s*\S+\s*$/m, `model: ${model}`);
2236
+ function parseJsonRecords(content) {
2237
+ try {
2238
+ const parsed = JSON.parse(content);
2239
+ if (Array.isArray(parsed) && parsed.every(isRecord3)) return parsed;
2240
+ if (isRecord3(parsed)) return [parsed];
2241
+ return null;
2242
+ } catch {
2243
+ return null;
2809
2244
  }
2810
- return content;
2811
2245
  }
2812
- function parseSubagentEffort(content) {
2813
- return /^effort:\s*(\S+)\s*$/m.exec(content)?.[1];
2814
- }
2815
- function replaceSubagentEffort(content, effort) {
2816
- if (effort === void 0) {
2817
- return content.replace(/^effort:\s*\S+\s*\n/m, "");
2246
+ function isCanonicalMarketplace(entry) {
2247
+ if (entry.name !== MARKETPLACE_NAME) return false;
2248
+ if (entry.repo === MARKETPLACE_SOURCE || entry.source === MARKETPLACE_SOURCE) {
2249
+ return true;
2818
2250
  }
2819
- if (/^effort:\s*\S+\s*$/m.test(content)) {
2820
- return content.replace(/^effort:\s*\S+\s*$/m, `effort: ${effort}`);
2821
- }
2822
- return content.replace(/^(model:\s*\S+\s*)$/m, `$1
2823
- effort: ${effort}`);
2824
- }
2825
- function emptyManagedModelState3() {
2826
- return emptyManagedModelState(CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION);
2251
+ const source = entry.source;
2252
+ return isRecord3(source) && (source.repo === MARKETPLACE_SOURCE || source.source === MARKETPLACE_SOURCE);
2827
2253
  }
2828
- function parseManagedModelStateJson2(text) {
2829
- return parseManagedModelStateJson(
2830
- text,
2831
- CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION
2254
+ function inspectClaudeManager(executor, cwd, scope) {
2255
+ const marketplaceResult = executor(
2256
+ "claude",
2257
+ ["plugin", "marketplace", "list", "--json"],
2258
+ { cwd }
2832
2259
  );
2833
- }
2834
- function readManagedModelState3(path4) {
2835
- return readManagedModelState(
2836
- path4,
2837
- CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION
2838
- );
2839
- }
2840
- function resolvePackageRoot2(packageRoot) {
2841
- if (packageRoot) return packageRoot;
2842
- return findPackageRoot(fileURLToPath4(new URL(".", import.meta.url))) ?? void 0;
2843
- }
2844
- function targetKindForArtifact(artifact) {
2845
- const path4 = artifact.path.replaceAll("\\", "/");
2846
- if (path4 === ".claude-plugin/plugin.json") return "plugin-manifest";
2847
- if (path4.startsWith("agents/")) return "subagent";
2848
- if (path4 === ".mcp.json") return "mcp-config";
2849
- if (path4.startsWith("hooks/")) return "hook";
2850
- if (path4.startsWith("skills/")) return "skill";
2851
- return "plugin-asset";
2852
- }
2853
- function roleForArtifact(artifact) {
2854
- const match = /^agents\/([^/]+)\.md$/.exec(
2855
- artifact.path.replaceAll("\\", "/")
2260
+ const pluginResult = executor("claude", ["plugin", "list", "--json"], {
2261
+ cwd
2262
+ });
2263
+ const diagnostics = [];
2264
+ if (marketplaceResult.exitCode !== 0 || pluginResult.exitCode !== 0) {
2265
+ diagnostics.push(
2266
+ "Claude Code native plugin state could not be inspected; no manager mutation will be attempted."
2267
+ );
2268
+ return {
2269
+ ready: false,
2270
+ marketplace: "unknown",
2271
+ plugin: "unknown",
2272
+ diagnostics
2273
+ };
2274
+ }
2275
+ const marketplaces = parseJsonRecords(marketplaceResult.stdout);
2276
+ const plugins = parseJsonRecords(pluginResult.stdout);
2277
+ if (!marketplaces || !plugins) {
2278
+ diagnostics.push(
2279
+ "Claude Code returned unparseable plugin manager JSON; no manager mutation will be attempted."
2280
+ );
2281
+ return {
2282
+ ready: false,
2283
+ marketplace: "unknown",
2284
+ plugin: "unknown",
2285
+ diagnostics
2286
+ };
2287
+ }
2288
+ const namedMarketplaces = marketplaces.filter(
2289
+ (entry) => entry.name === MARKETPLACE_NAME
2856
2290
  );
2857
- const name = match?.[1];
2858
- return name && CLAUDE_CODE_ROLE_NAMES.includes(name) ? name : void 0;
2859
- }
2860
- function applyConfiguredModel(content, targetPath, role, state, nextState, reset) {
2861
- const renderedModel = parseSubagentModel(content);
2862
- if (!role || !renderedModel) return content;
2863
- nextState.models[role] = renderedModel;
2864
- const configured = reset ? void 0 : state.configuredModels?.[role];
2865
- let updated = content;
2866
- if (configured !== void 0) {
2867
- nextState.configuredModels ??= {};
2868
- nextState.configuredModels[role] = configured;
2869
- updated = replaceSubagentModel(updated, configured);
2870
- }
2871
- const configuredEffort = reset ? void 0 : state.configuredEfforts?.[role];
2872
- if (configuredEffort !== void 0) {
2873
- nextState.configuredEfforts ??= {};
2874
- nextState.configuredEfforts[role] = configuredEffort;
2291
+ let marketplace = "absent";
2292
+ if (namedMarketplaces.length > 0) {
2293
+ marketplace = namedMarketplaces.every(isCanonicalMarketplace) ? "installed" : "conflict";
2875
2294
  }
2876
- if (!reset && existsSync6(targetPath)) {
2877
- const installedEffort = parseSubagentEffort(
2878
- readFileSync7(targetPath, "utf8")
2295
+ if (marketplace === "conflict") {
2296
+ diagnostics.push(
2297
+ "A Claude marketplace named thoth-agents is registered from a different source; resolve it through Claude Code before retrying."
2879
2298
  );
2880
- updated = replaceSubagentEffort(updated, installedEffort);
2881
2299
  }
2882
- return updated;
2300
+ const matchingPlugins = plugins.filter(
2301
+ (entry) => entry.id === PLUGIN_ID && entry.scope === scope
2302
+ );
2303
+ let plugin = "absent";
2304
+ if (matchingPlugins.some((entry) => entry.enabled === true)) {
2305
+ plugin = "installed";
2306
+ } else if (matchingPlugins.length > 0) {
2307
+ plugin = "disabled";
2308
+ }
2309
+ return {
2310
+ ready: marketplace !== "conflict",
2311
+ marketplace,
2312
+ plugin,
2313
+ diagnostics
2314
+ };
2883
2315
  }
2884
- function buildClaudeCodeSetupPlan(config) {
2885
- const targets = resolveClaudeCodeTargets({
2886
- scope: config.scope,
2887
- projectRoot: config.projectRoot,
2888
- homeDir: config.homeDir
2889
- });
2890
- const packageRoot = resolvePackageRoot2(config.packageRoot);
2891
- const render = claudeCodeAdapter.render({
2892
- projectRoot: config.projectRoot,
2893
- ...packageRoot ? { packageRoot } : {}
2894
- });
2895
- const state = readManagedModelState3(targets.managedModelsPath);
2896
- const nextState = emptyManagedModelState3();
2897
- const items = render.artifacts.map((artifact) => {
2898
- const role = roleForArtifact(artifact);
2899
- const rendered = String(artifact.content ?? "");
2900
- const targetPath = join8(targets.pluginRoot, artifact.path);
2901
- const content = role !== void 0 ? applyConfiguredModel(
2902
- rendered,
2903
- targetPath,
2904
- role,
2905
- state,
2906
- nextState,
2907
- config.reset
2908
- ) : rendered;
2316
+ function commandItem(action, scope, projectRoot) {
2317
+ if (action === "register-marketplace") {
2909
2318
  return {
2910
- kind: targetKindForArtifact(artifact),
2911
- action: "write-plugin-file",
2912
- targetPath,
2913
- description: `Materialize Claude Code plugin asset ${artifact.path}.`,
2914
- requiresBackup: existsSync6(targetPath),
2915
- content,
2916
- ...role ? { role } : {}
2319
+ kind: "native-marketplace",
2320
+ action,
2321
+ targetPath: MARKETPLACE_TARGET,
2322
+ description: "Register the package-owned thoth-agents Claude marketplace.",
2323
+ requiresBackup: false,
2324
+ command: {
2325
+ executable: "claude",
2326
+ args: [
2327
+ "plugin",
2328
+ "marketplace",
2329
+ "add",
2330
+ MARKETPLACE_SOURCE,
2331
+ "--scope",
2332
+ scope
2333
+ ],
2334
+ cwd: projectRoot
2335
+ }
2917
2336
  };
2918
- });
2919
- items.push({
2920
- kind: "managed-model-state",
2921
- action: "write-managed-model-state",
2922
- targetPath: targets.managedModelsPath,
2923
- description: "Record thoth-agents-managed Claude Code subagent model ownership state.",
2924
- requiresBackup: existsSync6(targets.managedModelsPath),
2925
- content: stableJson3(nextState)
2926
- });
2337
+ }
2338
+ let command = "install";
2339
+ let description = "Install the thoth-agents Claude plugin from its native marketplace.";
2340
+ if (action === "enable-plugin") {
2341
+ command = "enable";
2342
+ description = "Enable the manager-owned thoth-agents Claude plugin.";
2343
+ } else if (action === "update-plugin") {
2344
+ command = "update";
2345
+ description = "Update the manager-owned thoth-agents Claude plugin.";
2346
+ }
2347
+ return {
2348
+ kind: "native-plugin",
2349
+ action,
2350
+ targetPath: PLUGIN_TARGET,
2351
+ description,
2352
+ requiresBackup: false,
2353
+ command: {
2354
+ executable: "claude",
2355
+ args: ["plugin", command, PLUGIN_ID, "--scope", scope],
2356
+ cwd: projectRoot
2357
+ }
2358
+ };
2359
+ }
2360
+ function buildClaudeCodeSetupPlan(config) {
2361
+ const commandExecutor = config.commandExecutor ?? defaultCommandExecutor;
2362
+ const inspection = inspectClaudeManager(
2363
+ commandExecutor,
2364
+ config.projectRoot,
2365
+ config.scope
2366
+ );
2367
+ const items = [];
2368
+ if (inspection.ready) {
2369
+ if (inspection.marketplace === "absent") {
2370
+ items.push(
2371
+ commandItem("register-marketplace", config.scope, config.projectRoot)
2372
+ );
2373
+ }
2374
+ if (inspection.plugin === "absent") {
2375
+ items.push(
2376
+ commandItem("install-plugin", config.scope, config.projectRoot)
2377
+ );
2378
+ } else if (inspection.plugin === "disabled") {
2379
+ items.push(
2380
+ commandItem("enable-plugin", config.scope, config.projectRoot)
2381
+ );
2382
+ if (config.refresh === true || config.reset) {
2383
+ items.push(
2384
+ commandItem("update-plugin", config.scope, config.projectRoot)
2385
+ );
2386
+ }
2387
+ } else if (config.refresh === true || config.reset) {
2388
+ items.push(
2389
+ commandItem("update-plugin", config.scope, config.projectRoot)
2390
+ );
2391
+ }
2392
+ }
2927
2393
  return {
2928
2394
  dryRun: config.dryRun === true,
2929
2395
  reset: config.reset,
2930
2396
  items,
2931
- pluginRoot: targets.pluginRoot,
2397
+ pluginRoot: PLUGIN_TARGET,
2398
+ ready: inspection.ready,
2399
+ scope: config.scope,
2400
+ projectRoot: config.projectRoot,
2401
+ commandExecutor,
2932
2402
  diagnostics: [
2933
- `Installed as a skills-directory plugin at ${targets.pluginRoot}; it auto-loads as thoth-agents@skills-dir on the next Claude Code session.`,
2934
- "Restart Claude Code or run /reload-plugins to activate it; run /plugin (Installed tab) to confirm thoth-agents@skills-dir is loaded.",
2935
- "The plugin settings.json activates the orchestrator agent as the main thread, so the session starts in delegate-first mode and bootstraps thoth-mem on its first turn."
2403
+ ...inspection.diagnostics,
2404
+ "Claude Code installs thoth-agents from EremesNG/thoth-agents through its native marketplace and owns the cached plugin files.",
2405
+ "Restart Claude Code or run /reload-plugins after installation; use /plugin to inspect marketplace and plugin state.",
2406
+ "Provider capability is owned by the external provider and is not established by this thoth-agents setup plan."
2936
2407
  ],
2937
2408
  disclaimers: [
2938
- "The orchestrator agent is the Claude Code main thread (plugin settings.json `agent` key); while enabled it replaces the default system prompt for every session in scope.",
2939
- "Every subagent inherits all main-thread tools, including MCP servers; read-only roles must not mutate the workspace per their operational contract (instruction-level, not tooling-enforced).",
2940
- "Subagent models accept only sonnet, opus, haiku, or inherit.",
2941
- "User-scope skills-directory plugins load hooks and MCP servers without extra approval; project-scope requires accepting the workspace trust dialog."
2409
+ "The native plugin activates its orchestrator agent as the main thread through plugin-root settings.json.",
2410
+ "Read-only subagents remain instruction/tool-denylist constrained; capability parity with other harnesses is not implied.",
2411
+ "Per-role Claude model rewrites are unavailable because native marketplace cache contents are manager-owned and immutable to thoth-agents."
2942
2412
  ]
2943
2413
  };
2944
2414
  }
2415
+ function uniqueMessages2(messages) {
2416
+ return [...new Set(messages)];
2417
+ }
2418
+ function boundedCommandFailure(item, result) {
2419
+ const detail = result.stderr.trim().replace(/\s+/g, " ").slice(0, 240);
2420
+ return `${item.description} Claude exited with code ${result.exitCode}${detail ? `: ${detail}` : "."}`;
2421
+ }
2945
2422
  function applyClaudeCodeSetup(plan) {
2946
2423
  const changed = [];
2947
- const diagnostics = uniqueMessages([
2424
+ const diagnostics = uniqueMessages2([
2948
2425
  ...plan.diagnostics,
2949
2426
  ...plan.disclaimers
2950
2427
  ]);
2951
- if (plan.dryRun) return { success: true, changed, diagnostics };
2952
- try {
2953
- for (const item of plan.items) {
2954
- if (writeTextWithBackup(item.targetPath, item.content)) {
2955
- changed.push(item.targetPath);
2956
- }
2957
- }
2958
- return { success: true, changed, diagnostics };
2959
- } catch (error) {
2428
+ if (!plan.ready) {
2960
2429
  return {
2961
2430
  success: false,
2962
2431
  changed,
2963
2432
  diagnostics,
2964
- error: error instanceof Error ? error.message : String(error)
2433
+ error: "Claude Code native plugin manager state is not safe to mutate."
2965
2434
  };
2966
2435
  }
2967
- }
2968
- function applyClaudeCodeManagedModelOverrides(config, overrides) {
2969
- if (config.dryRun) {
2970
- return {
2971
- success: true,
2972
- changed: [],
2973
- diagnostics: [
2974
- "Dry-run Claude Code model override apply requested; no files were written."
2975
- ]
2976
- };
2436
+ if (plan.dryRun) return { success: true, changed, diagnostics };
2437
+ for (const item of plan.items) {
2438
+ const result = plan.commandExecutor(
2439
+ item.command.executable,
2440
+ item.command.args,
2441
+ { cwd: item.command.cwd }
2442
+ );
2443
+ if (result.exitCode !== 0) {
2444
+ return {
2445
+ success: false,
2446
+ changed,
2447
+ diagnostics,
2448
+ error: boundedCommandFailure(item, result)
2449
+ };
2450
+ }
2451
+ changed.push(item.targetPath);
2977
2452
  }
2978
- const plan = buildClaudeCodeSetupPlan({
2979
- ...config,
2980
- dryRun: true,
2981
- reset: false
2982
- });
2983
- const stateItem = plan.items.find(
2984
- (item) => item.action === "write-managed-model-state"
2453
+ const inspection = inspectClaudeManager(
2454
+ plan.commandExecutor,
2455
+ plan.projectRoot,
2456
+ plan.scope
2985
2457
  );
2986
- const statePath = stateItem?.targetPath;
2987
- if (!statePath) {
2988
- return {
2989
- success: false,
2990
- changed: [],
2991
- diagnostics: plan.diagnostics,
2992
- error: "Claude Code managed model state target was not found."
2993
- };
2994
- }
2995
- const changed = [];
2996
- const diagnostics = uniqueMessages([
2997
- ...plan.diagnostics,
2998
- ...plan.disclaimers
2999
- ]);
3000
- const state = parseManagedModelStateJson2(stateItem?.content);
3001
- const nextState = {
3002
- version: CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION,
3003
- models: { ...state.models },
3004
- ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {},
3005
- ...state.configuredEfforts ? { configuredEfforts: { ...state.configuredEfforts } } : {}
3006
- };
3007
- try {
3008
- for (const override of overrides) {
3009
- if (!isClaudeCodeModelAlias(override.model) && override.catalogId !== override.model) {
3010
- throw new Error(
3011
- `Unsupported Claude Code model "${override.model}" for ${override.role}; use sonnet, opus, haiku, or inherit.`
3012
- );
3013
- }
3014
- const roleItem = plan.items.find(
3015
- (item) => item.kind === "subagent" && item.role === override.role
3016
- );
3017
- if (!roleItem) {
3018
- throw new Error(`Missing Claude Code subagent for ${override.role}.`);
3019
- }
3020
- const before = existsSync6(roleItem.targetPath) ? readFileSync7(roleItem.targetPath, "utf8") : roleItem.content;
3021
- let updated = replaceSubagentModel(before, override.model);
3022
- if (override.clearEffort) {
3023
- updated = replaceSubagentEffort(updated, void 0);
3024
- } else if (override.effort !== void 0) {
3025
- updated = replaceSubagentEffort(updated, override.effort);
3026
- }
3027
- if (writeTextWithBackup(roleItem.targetPath, updated)) {
3028
- changed.push(roleItem.targetPath);
3029
- }
3030
- nextState.configuredModels ??= {};
3031
- nextState.configuredModels[override.role] = override.model;
3032
- if (override.clearEffort) {
3033
- if (nextState.configuredEfforts) {
3034
- delete nextState.configuredEfforts[override.role];
3035
- }
3036
- } else if (override.effort !== void 0) {
3037
- nextState.configuredEfforts ??= {};
3038
- nextState.configuredEfforts[override.role] = override.effort;
3039
- }
3040
- }
3041
- if (writeTextWithBackup(statePath, stableJson3(nextState))) {
3042
- changed.push(statePath);
3043
- }
3044
- return { success: true, changed, diagnostics };
3045
- } catch (error) {
2458
+ diagnostics.push(...inspection.diagnostics);
2459
+ if (inspection.marketplace !== "installed" || inspection.plugin !== "installed") {
3046
2460
  return {
3047
2461
  success: false,
3048
2462
  changed,
3049
- diagnostics,
3050
- error: error instanceof Error ? error.message : String(error)
2463
+ diagnostics: uniqueMessages2(diagnostics),
2464
+ error: "Claude Code did not verify the expected marketplace and enabled plugin after mutation."
3051
2465
  };
3052
2466
  }
2467
+ return { success: true, changed, diagnostics: uniqueMessages2(diagnostics) };
3053
2468
  }
3054
2469
  function formatClaudeCodeSetupPlan(plan) {
3055
2470
  const lines = plan.items.map(
3056
- (item) => `- ${item.action}: ${item.targetPath} (${item.description})`
2471
+ (item) => `- ${item.action}: ${item.command.executable} ${item.command.args.join(" ")}`
3057
2472
  );
3058
2473
  return ["Claude Code setup plan:", ...lines].join("\n");
3059
2474
  }
3060
2475
 
3061
- // src/cli/operations/claude-code.ts
3062
- var CLAUDE_CODE_DISPLAY_NAME = "Claude Code";
3063
- var claudeCodePlanSources = /* @__PURE__ */ new WeakMap();
3064
- var claudeCodeModelSources = /* @__PURE__ */ new WeakMap();
3065
- var CLAUDE_CODE_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
3066
- function resolveClaudeCodeEffort(input) {
3067
- if (!input.effort || input.effort.kind === "inherit") {
3068
- return { ok: true, effort: void 0 };
2476
+ // src/cli/skills.ts
2477
+ import { spawnSync as spawnSync2 } from "child_process";
2478
+ import { existsSync as existsSync6 } from "fs";
2479
+ import { homedir as homedir2 } from "os";
2480
+ import { join as join3 } from "path";
2481
+ var SKILLS_CLI_AGENT = {
2482
+ opencode: "opencode",
2483
+ codex: "codex",
2484
+ claude: "claude-code"
2485
+ };
2486
+ var GLOBAL_SKILL_ROOT = {
2487
+ opencode: [".config", "opencode", "skills"],
2488
+ codex: [".codex", "skills"],
2489
+ claude: [".claude", "skills"]
2490
+ };
2491
+ var REQUIRED_SKILLS = [
2492
+ {
2493
+ name: "simplify",
2494
+ repo: "https://github.com/EremesNG/skills",
2495
+ skillName: "simplify",
2496
+ description: "YAGNI code simplification expert"
2497
+ },
2498
+ {
2499
+ name: "tdd",
2500
+ repo: "https://github.com/mattpocock/skills",
2501
+ skillName: "tdd",
2502
+ description: "Test-driven development workflow"
2503
+ },
2504
+ {
2505
+ name: "progressive-context-router",
2506
+ repo: "https://github.com/EremesNG/skills",
2507
+ skillName: "progressive-context-router",
2508
+ description: "Repository instruction and on-demand context routing"
2509
+ },
2510
+ {
2511
+ name: "architectural-grilling",
2512
+ repo: "https://github.com/EremesNG/skills",
2513
+ skillName: "architectural-grilling",
2514
+ description: "Conditional product and architecture decision interview"
3069
2515
  }
3070
- const effort = input.effort.value;
3071
- if (!CLAUDE_CODE_EFFORTS.has(effort)) {
3072
- return {
3073
- ok: false,
3074
- code: "claude-code-effort-runtime-unsupported",
3075
- message: `Claude Code does not support effort ${effort}.`
3076
- };
2516
+ ];
2517
+ function getRequiredSkillPath(skill, harness, homeDir = homedir2()) {
2518
+ return join3(
2519
+ homeDir,
2520
+ ...GLOBAL_SKILL_ROOT[harness],
2521
+ skill.skillName,
2522
+ "SKILL.md"
2523
+ );
2524
+ }
2525
+ function isRequiredSkillInstalled(skill, harness, options = {}) {
2526
+ return existsSync6(getRequiredSkillPath(skill, harness, options.homeDir));
2527
+ }
2528
+ function getRequiredSkillInstallCommand(skill, harness) {
2529
+ return {
2530
+ command: "npx",
2531
+ args: [
2532
+ "skills",
2533
+ "add",
2534
+ skill.repo,
2535
+ "--skill",
2536
+ skill.skillName,
2537
+ "--global",
2538
+ "--agent",
2539
+ SKILLS_CLI_AGENT[harness],
2540
+ "--yes"
2541
+ ]
2542
+ };
2543
+ }
2544
+ function installRequiredSkill(skill, harness, options = {}) {
2545
+ const skillPath = getRequiredSkillPath(skill, harness, options.homeDir);
2546
+ if (isRequiredSkillInstalled(skill, harness, options)) {
2547
+ return { skill, harness, status: "already-installed", skillPath };
2548
+ }
2549
+ const { command, args } = getRequiredSkillInstallCommand(skill, harness);
2550
+ try {
2551
+ const result = spawnSync2(command, args, { stdio: "inherit" });
2552
+ if (result.status === 0) {
2553
+ return { skill, harness, status: "installed", skillPath };
2554
+ }
2555
+ if (isRequiredSkillInstalled(skill, harness, options)) {
2556
+ return { skill, harness, status: "already-installed", skillPath };
2557
+ }
2558
+ return { skill, harness, status: "failed", skillPath, error: result.error };
2559
+ } catch (error) {
2560
+ return { skill, harness, status: "failed", skillPath, error };
3077
2561
  }
3078
- if (isClaudeCodeModelAlias(input.model) && input.model !== "inherit") {
3079
- return { ok: true, effort };
2562
+ }
2563
+
2564
+ // src/cli/operations/types.ts
2565
+ var UNSUPPORTED_PROVIDER_CAPABILITY = {
2566
+ state: "unsupported",
2567
+ source: "none",
2568
+ basis: []
2569
+ };
2570
+ function classifyProviderCapabilityEvidence(input = {}) {
2571
+ const evidence = input.providerEvidence;
2572
+ if (!evidence) {
2573
+ return { ...UNSUPPORTED_PROVIDER_CAPABILITY, basis: [] };
3080
2574
  }
3081
- if (input.catalogId === input.model && input.availableEfforts?.includes(effort)) {
3082
- return { ok: true, effort };
2575
+ const basis = evidence.basis.filter((item) => item.trim().length > 0);
2576
+ const hasDocumentedSource = evidence.source === "provider" || evidence.source === "harness";
2577
+ if (!hasDocumentedSource || basis.length === 0) {
2578
+ return { ...UNSUPPORTED_PROVIDER_CAPABILITY, basis: [] };
3083
2579
  }
3084
- return {
3085
- ok: false,
3086
- code: "claude-code-effort-catalog-unsupported",
3087
- message: `Effort ${effort} is not supported by ${input.catalogId ?? input.model} in the models.dev catalog.`
3088
- };
2580
+ return { ...evidence, basis };
3089
2581
  }
2582
+
2583
+ // src/cli/operations/claude-code.ts
2584
+ var CLAUDE_CODE_DISPLAY_NAME = "Claude Code";
2585
+ var claudeCodePlanSources = /* @__PURE__ */ new WeakMap();
2586
+ var MODEL_CONFIG_DISABLED_REASON = "Native Claude marketplace cache files are manager-owned; configure packaged agent defaults in the repository and publish a new plugin version.";
3090
2587
  var claudeCodeActions = [
3091
2588
  {
3092
2589
  id: "claude-code-status",
3093
2590
  kind: "status",
3094
2591
  label: "Status",
3095
- description: "Inspect managed Claude Code plugin state",
2592
+ description: "Inspect native Claude Code marketplace and plugin state",
3096
2593
  dryRun: false,
3097
2594
  requiresConfirmation: false,
3098
2595
  supported: true
@@ -3110,7 +2607,7 @@ var claudeCodeActions = [
3110
2607
  id: "claude-code-install",
3111
2608
  kind: "install",
3112
2609
  label: "Install",
3113
- description: "Preview Claude Code plugin package install",
2610
+ description: "Preview native Claude marketplace registration and install",
3114
2611
  dryRun: true,
3115
2612
  requiresConfirmation: true,
3116
2613
  supported: true
@@ -3119,7 +2616,7 @@ var claudeCodeActions = [
3119
2616
  id: "claude-code-update",
3120
2617
  kind: "update",
3121
2618
  label: "Update",
3122
- description: "Preview Claude Code managed plugin refresh",
2619
+ description: "Preview native Claude plugin reconciliation",
3123
2620
  dryRun: true,
3124
2621
  requiresConfirmation: true,
3125
2622
  supported: true
@@ -3128,7 +2625,7 @@ var claudeCodeActions = [
3128
2625
  id: "claude-code-sync",
3129
2626
  kind: "sync",
3130
2627
  label: "Sync",
3131
- description: "Preview Claude Code managed plugin sync",
2628
+ description: "Preview native Claude plugin and required-skill reconciliation",
3132
2629
  dryRun: true,
3133
2630
  requiresConfirmation: true,
3134
2631
  supported: true
@@ -3137,40 +2634,39 @@ var claudeCodeActions = [
3137
2634
  id: "claude-code-model-config",
3138
2635
  kind: "model-config",
3139
2636
  label: "Model",
3140
- description: "Preview supported Claude Code subagent model line changes",
2637
+ description: "Claude agent models are package-owned defaults",
3141
2638
  dryRun: true,
3142
- requiresConfirmation: true,
3143
- supported: true
2639
+ requiresConfirmation: false,
2640
+ supported: false,
2641
+ disabledReason: MODEL_CONFIG_DISABLED_REASON
3144
2642
  }
3145
2643
  ];
3146
2644
  var claudeCodeOperationAdapter = {
3147
2645
  id: "claude",
3148
2646
  displayName: CLAUDE_CODE_DISPLAY_NAME,
3149
2647
  available: true,
3150
- description: "Claude Code plugin package and managed subagent surfaces.",
2648
+ description: "Native Claude Code marketplace plugin and required skills.",
3151
2649
  actions: claudeCodeActions
3152
2650
  };
3153
- function claudeCodeConfig(context2 = { cwd: process.cwd() }, dryRun) {
2651
+ function claudeCodeConfig(context2 = { cwd: process.cwd() }, dryRun, refresh = false) {
3154
2652
  return {
3155
2653
  dryRun,
3156
2654
  reset: false,
3157
- // Match the installer (install.ts uses 'user') so post-install status,
3158
- // update, sync, and model commands resolve the same plugin root.
3159
2655
  scope: context2.scope ?? "user",
3160
2656
  projectRoot: context2.cwd,
3161
- homeDir: context2.homeDir,
3162
- packageRoot: context2.packageRoot
2657
+ commandExecutor: context2.commandExecutor,
2658
+ refresh
3163
2659
  };
3164
2660
  }
3165
2661
  function claudeCodeDisclaimers() {
3166
2662
  return [
3167
2663
  {
3168
- message: "Subagents inherit all main-thread tools (including MCP servers); read-only roles must not mutate the workspace per their operational contract (instruction-level, not tooling-enforced). The orchestrator is the main session.",
3169
- code: "claude-code-first-class"
2664
+ message: "Claude Code owns marketplace snapshots and installed plugin cache files; thoth-agents never edits that cache directly.",
2665
+ code: "claude-code-manager-owned-cache"
3170
2666
  },
3171
2667
  {
3172
- message: "Subagent models accept only sonnet, opus, haiku, or inherit.",
3173
- code: "claude-code-model-aliases"
2668
+ message: "Read-only roles remain instruction/tool-denylist constrained; the orchestrator is activated as the main plugin agent.",
2669
+ code: "claude-code-first-class"
3174
2670
  }
3175
2671
  ];
3176
2672
  }
@@ -3179,9 +2675,9 @@ function warning(message, code) {
3179
2675
  }
3180
2676
  function targetForItem(item, state, observed) {
3181
2677
  return {
3182
- kind: item.kind === "managed-model-state" ? "memory-state" : "generated-artifact",
2678
+ kind: "package",
3183
2679
  path: item.targetPath,
3184
- label: item.role ? `Claude Code ${item.role} subagent` : item.kind.replaceAll("-", " "),
2680
+ label: item.kind === "native-marketplace" ? "Claude Code marketplace" : "Claude Code plugin",
3185
2681
  state,
3186
2682
  expected: item.action,
3187
2683
  observed,
@@ -3190,47 +2686,45 @@ function targetForItem(item, state, observed) {
3190
2686
  }
3191
2687
  function surfaceForItem(item) {
3192
2688
  return {
3193
- id: `${item.kind}:${item.role ?? basename2(item.targetPath)}`,
3194
- label: item.role ? `Claude Code ${item.role} subagent` : item.kind.replaceAll("-", " "),
2689
+ id: `${item.kind}:${item.action}`,
2690
+ label: item.kind === "native-marketplace" ? "Claude Code marketplace" : "Claude Code plugin",
3195
2691
  path: item.targetPath,
3196
2692
  description: item.description
3197
2693
  };
3198
2694
  }
3199
- function backupForItem(item) {
3200
- return {
3201
- required: item.requiresBackup,
3202
- strategy: item.requiresBackup ? "managed-backup-file" : "none",
3203
- destinations: item.requiresBackup ? [{ path: `${item.targetPath}.bak`, label: "managed backup" }] : []
3204
- };
3205
- }
3206
- function manifestState(item) {
3207
- if (!existsSync7(item.targetPath))
3208
- return { state: "missing", observed: "absent" };
3209
- const observed = readFileSync8(item.targetPath, "utf8");
3210
- if (observed === item.content)
3211
- return { state: "installed", observed: "current" };
3212
- try {
3213
- const expectedVersion = JSON.parse(item.content).version;
3214
- const observedVersion = JSON.parse(observed).version;
3215
- if (typeof expectedVersion === "string" && typeof observedVersion === "string" && expectedVersion !== observedVersion) {
3216
- return {
3217
- state: "outdated",
3218
- observed: `version ${observedVersion}; expected ${expectedVersion}`
3219
- };
3220
- }
3221
- } catch {
3222
- return { state: "unknown", observed: "unparseable plugin manifest" };
2695
+ function managerTargets(plan) {
2696
+ if (!plan.ready) {
2697
+ return [
2698
+ {
2699
+ kind: "package",
2700
+ path: plan.pluginRoot,
2701
+ label: "Claude Code native plugin",
2702
+ state: "unknown",
2703
+ observed: "native manager state is unsafe to mutate"
2704
+ }
2705
+ ];
3223
2706
  }
3224
- return { state: "drift", observed: "content differs" };
3225
- }
3226
- function contentState(item) {
3227
- if (!existsSync7(item.targetPath))
3228
- return { state: "missing", observed: "absent" };
3229
- return readFileSync8(item.targetPath, "utf8") === item.content ? { state: "installed", observed: "current" } : { state: "drift", observed: "content differs" };
3230
- }
3231
- function classifyItem(item) {
3232
- if (item.kind === "plugin-manifest") return manifestState(item);
3233
- return contentState(item);
2707
+ if (plan.items.length === 0) {
2708
+ return [
2709
+ {
2710
+ kind: "package",
2711
+ path: plan.pluginRoot,
2712
+ label: "Claude Code native plugin",
2713
+ state: "installed",
2714
+ expected: "registered marketplace and enabled plugin",
2715
+ observed: "registered and enabled"
2716
+ }
2717
+ ];
2718
+ }
2719
+ return plan.items.map((item) => {
2720
+ if (item.action === "enable-plugin") {
2721
+ return targetForItem(item, "drift", "plugin disabled");
2722
+ }
2723
+ if (item.action === "update-plugin") {
2724
+ return targetForItem(item, "installed", "native update requested");
2725
+ }
2726
+ return targetForItem(item, "missing", "absent");
2727
+ });
3234
2728
  }
3235
2729
  function aggregateState(states) {
3236
2730
  if (states.includes("unknown")) return "unknown";
@@ -3242,36 +2736,81 @@ function aggregateState(states) {
3242
2736
  function statusSummary(state) {
3243
2737
  switch (state) {
3244
2738
  case "installed":
3245
- return "Claude Code managed plugin is installed and current.";
2739
+ return "Claude Code native plugin and required skills are installed.";
3246
2740
  case "missing":
3247
- return "Claude Code managed plugin is missing.";
2741
+ return "Claude Code native plugin or required skills are missing.";
3248
2742
  case "drift":
3249
- return "Claude Code managed plugin exists but differs from expected output.";
2743
+ return "Claude Code native plugin is installed but disabled or incomplete.";
3250
2744
  case "outdated":
3251
- return "Claude Code managed plugin includes an older generated manifest.";
2745
+ return "Claude Code native plugin is outdated.";
3252
2746
  case "unknown":
3253
- return "Claude Code managed plugin could not be classified safely.";
2747
+ return "Claude Code native plugin state could not be classified safely.";
3254
2748
  }
3255
2749
  }
3256
- function statusFromSetupPlan(plan) {
3257
- const classified = plan.items.map((item) => ({
3258
- item,
3259
- ...classifyItem(item)
3260
- }));
3261
- const state = aggregateState(classified.map((entry) => entry.state));
2750
+ function claudeCodeRequiredSkillStatus(context2) {
2751
+ const targets = REQUIRED_SKILLS.map((skill) => {
2752
+ const path = getRequiredSkillPath(skill, "claude", context2.homeDir);
2753
+ const installed = existsSync7(path);
2754
+ return {
2755
+ kind: "skill",
2756
+ path,
2757
+ label: `Claude Code required skill: ${skill.name}`,
2758
+ state: installed ? "installed" : "missing",
2759
+ expected: "required global Claude Code skill",
2760
+ observed: installed ? "installed" : "missing"
2761
+ };
2762
+ });
2763
+ return {
2764
+ targets,
2765
+ diagnostics: targets.some((target) => target.state === "missing") ? [
2766
+ {
2767
+ severity: "important",
2768
+ code: "claude-code-required-skills-missing",
2769
+ message: "Required Claude Code skills are missing; run install, update, or sync to restore them."
2770
+ }
2771
+ ] : []
2772
+ };
2773
+ }
2774
+ function claudeCodeRequiredSkillPlanItem() {
2775
+ return {
2776
+ title: "Install required external skills for Claude Code",
2777
+ target: {
2778
+ kind: "skill",
2779
+ label: "Required Claude Code skills",
2780
+ expected: REQUIRED_SKILLS.map(({ name }) => name).join(", ")
2781
+ },
2782
+ preview: JSON.stringify(
2783
+ REQUIRED_SKILLS.map((skill) => ({
2784
+ name: skill.name,
2785
+ ...getRequiredSkillInstallCommand(skill, "claude")
2786
+ })),
2787
+ null,
2788
+ 2
2789
+ )
2790
+ };
2791
+ }
2792
+ function statusFromSetupPlan(plan, context2) {
2793
+ const manager = managerTargets(plan);
2794
+ const requiredSkills = claudeCodeRequiredSkillStatus(context2);
2795
+ const state = aggregateState(
2796
+ [...manager, ...requiredSkills.targets].map(
2797
+ (target) => target.state ?? "unknown"
2798
+ )
2799
+ );
3262
2800
  return {
3263
2801
  harness: "claude",
3264
2802
  displayName: CLAUDE_CODE_DISPLAY_NAME,
3265
2803
  state,
3266
2804
  summary: statusSummary(state),
3267
- targets: classified.map(
3268
- ({ item, state: state2, observed }) => targetForItem(item, state2, observed)
3269
- ),
3270
- diagnostics: plan.diagnostics.map((message) => ({
3271
- severity: "minor",
3272
- message,
3273
- code: "claude-code-diagnostic"
3274
- })),
2805
+ targets: [...manager, ...requiredSkills.targets],
2806
+ diagnostics: [
2807
+ ...plan.diagnostics.map((message) => ({
2808
+ severity: "minor",
2809
+ message,
2810
+ code: "claude-code-diagnostic"
2811
+ })),
2812
+ ...requiredSkills.diagnostics
2813
+ ],
3275
2814
  actions: claudeCodeActions,
3276
2815
  disclaimers: [
3277
2816
  ...claudeCodeDisclaimers(),
@@ -3279,10 +2818,14 @@ function statusFromSetupPlan(plan) {
3279
2818
  ]
3280
2819
  };
3281
2820
  }
3282
- function getClaudeCodeStatus(context2 = { cwd: process.cwd() }) {
3283
- let plan;
2821
+ function getClaudeCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
2822
+ const providerCapability = classifyProviderCapabilityEvidence(evidence);
3284
2823
  try {
3285
- plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true));
2824
+ const plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true));
2825
+ return {
2826
+ ...statusFromSetupPlan(plan, context2),
2827
+ providerCapability
2828
+ };
3286
2829
  } catch (error) {
3287
2830
  const message = error instanceof Error ? error.message : String(error);
3288
2831
  return {
@@ -3299,22 +2842,24 @@ function getClaudeCodeStatus(context2 = { cwd: process.cwd() }) {
3299
2842
  }
3300
2843
  ],
3301
2844
  actions: claudeCodeActions,
2845
+ providerCapability,
3302
2846
  disclaimers: claudeCodeDisclaimers()
3303
2847
  };
3304
2848
  }
3305
- return statusFromSetupPlan(plan);
3306
2849
  }
3307
2850
  function planItemFromSetup(item) {
3308
2851
  return {
3309
2852
  title: item.description,
3310
2853
  target: targetForItem(item),
3311
- preview: item.content,
3312
- backup: backupForItem(item)
2854
+ preview: `${item.command.executable} ${item.command.args.join(" ")}`,
2855
+ backup: { required: false, strategy: "external" }
3313
2856
  };
3314
2857
  }
3315
- function planFromSetup(id, action, title, summary, setupPlan) {
3316
- const status = statusFromSetupPlan(setupPlan);
3317
- const canApply = status.state === "installed" || status.state === "missing" || status.state === "outdated" || status.state === "drift";
2858
+ function planFromSetup(id, action, title, summary, setupPlan, context2) {
2859
+ const status = statusFromSetupPlan(setupPlan, context2);
2860
+ const missingSkills = status.targets.some(
2861
+ (target) => target.kind === "skill" && target.state === "missing"
2862
+ );
3318
2863
  const plan = {
3319
2864
  id,
3320
2865
  harness: "claude",
@@ -3322,161 +2867,100 @@ function planFromSetup(id, action, title, summary, setupPlan) {
3322
2867
  title,
3323
2868
  summary,
3324
2869
  dryRun: true,
3325
- canApply,
2870
+ canApply: setupPlan.ready && (setupPlan.items.length > 0 || missingSkills),
3326
2871
  targets: status.targets,
3327
- surfaces: setupPlan.items.map(surfaceForItem),
2872
+ surfaces: setupPlan.items.length > 0 ? setupPlan.items.map(surfaceForItem) : [
2873
+ {
2874
+ id: "native-plugin:installed",
2875
+ label: "Claude Code native plugin",
2876
+ path: setupPlan.pluginRoot,
2877
+ state: setupPlan.ready ? "installed" : "unknown"
2878
+ }
2879
+ ],
3328
2880
  backup: {
3329
- required: setupPlan.items.some((item) => item.requiresBackup),
3330
- strategy: "managed-backup-file",
3331
- description: "Existing Claude Code plugin files are backed up before being overwritten."
2881
+ required: false,
2882
+ strategy: "external",
2883
+ description: "Claude Code owns its marketplace snapshots and plugin cache."
3332
2884
  },
3333
- items: setupPlan.items.map(planItemFromSetup),
2885
+ items: [
2886
+ ...setupPlan.items.map(planItemFromSetup),
2887
+ claudeCodeRequiredSkillPlanItem()
2888
+ ],
3334
2889
  warnings: status.diagnostics,
3335
2890
  disclaimers: [
3336
2891
  ...claudeCodeDisclaimers(),
3337
2892
  ...setupPlan.disclaimers.map((message) => ({ message }))
3338
2893
  ]
3339
2894
  };
3340
- claudeCodePlanSources.set(plan, setupPlan);
2895
+ claudeCodePlanSources.set(plan, { setupPlan, context: context2 });
3341
2896
  return plan;
3342
2897
  }
3343
2898
  function buildClaudeCodeInstallPlan(context2 = { cwd: process.cwd() }) {
3344
2899
  return planFromSetup(
3345
2900
  "claude-code-install-preview",
3346
2901
  "install",
3347
- "Install Claude Code plugin package",
3348
- "Preview Claude Code plugin package install using buildClaudeCodeSetupPlan().",
3349
- buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
2902
+ "Install Claude Code native plugin",
2903
+ "Preview native marketplace registration, plugin installation, and required skills.",
2904
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true)),
2905
+ context2
3350
2906
  );
3351
2907
  }
3352
2908
  function buildClaudeCodeUpdatePlan(context2 = { cwd: process.cwd() }) {
3353
2909
  return planFromSetup(
3354
2910
  "claude-code-update-preview",
3355
2911
  "update",
3356
- "Update Claude Code plugin package",
3357
- "Preview Claude Code managed plugin refresh using buildClaudeCodeSetupPlan().",
3358
- buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
2912
+ "Reconcile Claude Code native plugin",
2913
+ "Preview native marketplace, enabled-plugin, and required-skill reconciliation.",
2914
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true, true)),
2915
+ context2
3359
2916
  );
3360
2917
  }
3361
2918
  function buildClaudeCodeSyncPlan(context2 = { cwd: process.cwd() }) {
3362
2919
  return planFromSetup(
3363
2920
  "claude-code-sync-preview",
3364
2921
  "sync",
3365
- "Sync Claude Code plugin package",
3366
- "Preview Claude Code managed plugin subagents, MCP, hooks, and skills.",
3367
- buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3368
- );
3369
- }
3370
- function isClaudeCodeRole(role) {
3371
- return CLAUDE_CODE_ROLE_NAMES.includes(role);
3372
- }
3373
- function buildClaudeCodeModelPlan(input, context2 = { cwd: process.cwd() }) {
3374
- const status = getClaudeCodeStatus(context2);
3375
- const resolvedRoles = input.roles.map((role) => ({
3376
- role,
3377
- effort: resolveClaudeCodeEffort(role),
3378
- validRole: isClaudeCodeRole(role.role),
3379
- validModel: isClaudeCodeModelAlias(role.model) || role.catalogId === role.model
3380
- }));
3381
- const supportedRoles = resolvedRoles.filter((entry) => entry.validRole && entry.validModel && entry.effort.ok).map(({ role, effort }) => ({
3382
- role: role.role,
3383
- model: role.model,
3384
- ...role.catalogId ? { catalogId: role.catalogId } : {},
3385
- ...effort.ok && effort.effort !== void 0 ? { effort: effort.effort } : {},
3386
- ...role.effort?.kind === "inherit" ? { clearEffort: true } : {}
3387
- }));
3388
- const rejectedRoles = resolvedRoles.filter(
3389
- (entry) => !entry.validRole || !entry.validModel
3390
- );
3391
- const effortErrors = resolvedRoles.filter(
3392
- (entry) => entry.validRole && entry.validModel && !entry.effort.ok
3393
- );
3394
- const warnings = [
3395
- ...status.diagnostics,
3396
- ...input.warnings ?? [],
3397
- ...rejectedRoles.map(
3398
- ({ role }) => warning(
3399
- `Claude Code does not accept role "${role.role}" with model "${role.model}"; roles must be one of ${CLAUDE_CODE_ROLE_NAMES.join(", ")} and models must be sonnet, opus, haiku, or inherit.`,
3400
- "claude-code-unsupported-model-role"
3401
- )
3402
- ),
3403
- ...effortErrors.map(
3404
- ({ effort }) => warning(
3405
- effort.ok ? "Unknown Claude Code effort error." : effort.message,
3406
- effort.ok ? "claude-code-effort-unknown" : effort.code
3407
- )
3408
- )
3409
- ];
3410
- if (input.harness !== "claude") {
3411
- warnings.push(
3412
- warning(
3413
- "Model plan target harness must be claude.",
3414
- "claude-code-model-harness-mismatch"
3415
- )
3416
- );
3417
- }
3418
- const targets = supportedRoles.map(({ role }) => {
3419
- const target = status.targets.find(
3420
- (candidate) => candidate.path?.endsWith(`agents${pathSep()}${role}.md`)
3421
- );
3422
- return target ?? {
3423
- kind: "generated-artifact",
3424
- label: `Claude Code ${role} subagent`,
3425
- state: "missing"
3426
- };
3427
- });
3428
- const stateTarget = status.targets.find(
3429
- (target) => target.path?.endsWith(".thoth-agents-managed-models.json")
3430
- );
3431
- const plan = {
3432
- id: "claude-code-model-config-preview",
3433
- harness: "claude",
3434
- action: "model-config",
3435
- title: "Configure Claude Code subagent model lines",
3436
- summary: "Preview model changes for generated Claude Code subagent files and managed model state only.",
3437
- dryRun: true,
3438
- canApply: input.harness === "claude" && supportedRoles.length > 0 && effortErrors.length === 0 && status.state !== "unknown",
3439
- targets: [...targets, ...stateTarget ? [stateTarget] : []],
3440
- surfaces: targets.map((target) => ({
3441
- id: `claude-code-model:${target.label}`,
3442
- label: target.label ?? "Claude Code subagent",
3443
- path: target.path,
3444
- state: target.state
3445
- })),
3446
- backup: {
3447
- required: true,
3448
- strategy: "managed-backup-file",
3449
- description: "Existing subagent files and managed model state are backed up by the managed write helper."
3450
- },
3451
- items: supportedRoles.map(({ role, model, effort }) => ({
3452
- title: `Set ${role} Claude Code subagent model line`,
3453
- target: targets.find(
3454
- (target) => target.path?.endsWith(`agents${pathSep()}${role}.md`)
3455
- ) ?? {
3456
- kind: "generated-artifact",
3457
- label: `Claude Code ${role} subagent`
2922
+ "Sync Claude Code native plugin",
2923
+ "Preview native marketplace, enabled-plugin, and required-skill reconciliation.",
2924
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true)),
2925
+ context2
2926
+ );
2927
+ }
2928
+ function buildClaudeCodeModelPlan(input, _context = { cwd: process.cwd() }) {
2929
+ const mismatch = input.harness !== "claude";
2930
+ return {
2931
+ id: "claude-code-model-config-unsupported",
2932
+ harness: "claude",
2933
+ action: "model-config",
2934
+ title: "Claude Code package-owned agent models",
2935
+ summary: MODEL_CONFIG_DISABLED_REASON,
2936
+ dryRun: true,
2937
+ canApply: false,
2938
+ targets: input.target ? [input.target] : [],
2939
+ surfaces: [],
2940
+ backup: { required: false, strategy: "none" },
2941
+ items: input.roles.map((role) => ({
2942
+ title: `Requested ${role.role} Claude Code model`,
2943
+ target: {
2944
+ kind: "package",
2945
+ label: `Claude Code ${role.role} packaged agent`
3458
2946
  },
3459
- preview: JSON.stringify({ role, model, effort: effort ?? null }),
3460
- backup: { required: true, strategy: "managed-backup-file" }
2947
+ preview: JSON.stringify({
2948
+ role: role.role,
2949
+ model: role.model,
2950
+ effort: role.effort ?? null,
2951
+ catalogId: role.catalogId ?? null
2952
+ }),
2953
+ backup: { required: false, strategy: "none" }
3461
2954
  })),
3462
- warnings,
3463
- disclaimers: [
3464
- ...claudeCodeDisclaimers(),
3465
- ...input.disclaimers ?? [],
3466
- {
3467
- message: "Claude Code model configuration writes only generated subagent frontmatter model lines and the managed model state JSON.",
3468
- code: "claude-code-model-supported-surface"
3469
- }
3470
- ]
2955
+ warnings: [
2956
+ ...input.warnings ?? [],
2957
+ warning(
2958
+ mismatch ? "Model plan target harness must be claude." : MODEL_CONFIG_DISABLED_REASON,
2959
+ mismatch ? "claude-code-model-harness-mismatch" : "claude-code-model-cache-owned"
2960
+ )
2961
+ ],
2962
+ disclaimers: [...claudeCodeDisclaimers(), ...input.disclaimers ?? []]
3471
2963
  };
3472
- claudeCodeModelSources.set(plan, {
3473
- config: claudeCodeConfig(context2, false),
3474
- roles: supportedRoles
3475
- });
3476
- return plan;
3477
- }
3478
- function pathSep() {
3479
- return process.platform === "win32" ? "\\" : "/";
3480
2964
  }
3481
2965
  function rejectPlan(plan, message, severity = "critical") {
3482
2966
  return {
@@ -3500,75 +2984,80 @@ function validateClaudeCodePlan(plan) {
3500
2984
  "Claude Code plan cannot be applied because canApply is false."
3501
2985
  );
3502
2986
  }
3503
- if (!["install", "update", "sync", "model-config"].includes(plan.action)) {
2987
+ if (!["install", "update", "sync"].includes(plan.action)) {
3504
2988
  return rejectPlan(
3505
2989
  plan,
3506
2990
  `Unsupported Claude Code apply action: ${plan.action}.`
3507
2991
  );
3508
2992
  }
3509
- if (plan.items.length === 0) {
3510
- return rejectPlan(plan, "Claude Code plan has no items to apply.");
3511
- }
3512
2993
  return null;
3513
2994
  }
3514
2995
  function applyClaudeCodePlan(plan) {
3515
2996
  const rejection = validateClaudeCodePlan(plan);
3516
2997
  if (rejection) return rejection;
3517
- if (plan.action === "model-config") {
3518
- const source = claudeCodeModelSources.get(plan);
3519
- if (!source) {
3520
- return rejectPlan(
3521
- plan,
3522
- "Claude Code model plan was not produced by buildClaudeCodeModelPlan in this process."
3523
- );
3524
- }
3525
- const result2 = applyClaudeCodeManagedModelOverrides(
3526
- source.config,
3527
- source.roles.map((role) => ({
3528
- role: role.role,
3529
- model: role.model,
3530
- ...role.catalogId ? { catalogId: role.catalogId } : {},
3531
- ...role.effort ? { effort: role.effort } : {},
3532
- ...role.clearEffort ? { clearEffort: true } : {}
3533
- }))
3534
- );
3535
- return {
3536
- harness: "claude",
3537
- action: "model-config",
3538
- applied: result2.success,
3539
- summary: result2.success ? "Applied Claude Code subagent model overrides." : result2.error ?? "Failed to apply Claude Code subagent model overrides.",
3540
- changedTargets: result2.changed.map((path4) => ({
3541
- kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
3542
- path: path4,
3543
- label: basename2(path4),
3544
- state: "installed"
3545
- })),
3546
- backups: result2.changed.filter((path4) => existsSync7(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
3547
- warnings: result2.success ? [] : [{ severity: "critical", message: result2.error ?? "apply failed." }],
3548
- disclaimers: claudeCodeDisclaimers()
3549
- };
3550
- }
3551
- const setupPlan = claudeCodePlanSources.get(plan);
3552
- if (!setupPlan) {
2998
+ const source = claudeCodePlanSources.get(plan);
2999
+ if (!source) {
3553
3000
  return rejectPlan(
3554
3001
  plan,
3555
3002
  "Claude Code setup plan was not produced by a Claude Code operation plan builder in this process."
3556
3003
  );
3557
3004
  }
3558
- const result = applyClaudeCodeSetup({ ...setupPlan, dryRun: false });
3005
+ const result = applyClaudeCodeSetup({ ...source.setupPlan, dryRun: false });
3006
+ const requiredSkillWarnings = [];
3007
+ const requiredSkillTargets = [];
3008
+ if (result.success) {
3009
+ for (const skill of REQUIRED_SKILLS) {
3010
+ const installed = installRequiredSkill(skill, "claude", {
3011
+ homeDir: source.context.homeDir
3012
+ });
3013
+ const success2 = installed.status !== "failed";
3014
+ requiredSkillTargets.push({
3015
+ kind: "skill",
3016
+ path: installed.skillPath,
3017
+ label: `Claude Code required skill: ${skill.name}`,
3018
+ state: success2 ? "installed" : "drift",
3019
+ observed: success2 ? installed.status : "installation failed"
3020
+ });
3021
+ if (!success2) {
3022
+ requiredSkillWarnings.push({
3023
+ severity: "critical",
3024
+ code: "claude-code-required-skill-failed",
3025
+ message: `Failed to install required Claude Code skill: ${skill.name}.`
3026
+ });
3027
+ }
3028
+ }
3029
+ }
3030
+ const success = result.success && requiredSkillWarnings.length === 0;
3031
+ let summary = result.error ?? `Failed to apply Claude Code ${plan.action} plan.`;
3032
+ if (success) {
3033
+ summary = `Applied Claude Code native ${plan.action} plan.`;
3034
+ } else if (requiredSkillWarnings.length > 0) {
3035
+ summary = "Claude Code plugin was installed, but required skills failed to install.";
3036
+ }
3559
3037
  return {
3560
3038
  harness: "claude",
3561
3039
  action: plan.action,
3562
- applied: result.success,
3563
- summary: result.success ? `Applied Claude Code managed ${plan.action} plan.` : result.error ?? `Failed to apply Claude Code ${plan.action} plan.`,
3564
- changedTargets: result.changed.map((path4) => ({
3565
- kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
3566
- path: path4,
3567
- label: basename2(path4),
3568
- state: "installed"
3569
- })),
3570
- backups: result.changed.filter((path4) => existsSync7(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
3571
- warnings: result.success ? [] : [{ severity: "critical", message: result.error ?? "apply failed." }],
3040
+ applied: success,
3041
+ summary,
3042
+ changedTargets: [
3043
+ ...result.changed.map((path) => ({
3044
+ kind: "package",
3045
+ path,
3046
+ label: path.includes("/marketplaces/") ? "Claude Code marketplace" : "Claude Code plugin",
3047
+ state: "installed"
3048
+ })),
3049
+ ...requiredSkillTargets
3050
+ ],
3051
+ backups: [],
3052
+ warnings: [
3053
+ ...requiredSkillWarnings,
3054
+ ...result.success ? [] : [
3055
+ {
3056
+ severity: "critical",
3057
+ message: result.error ?? "apply failed."
3058
+ }
3059
+ ]
3060
+ ],
3572
3061
  disclaimers: claudeCodeDisclaimers()
3573
3062
  };
3574
3063
  }
@@ -3580,8 +3069,8 @@ function defaultClaudeCodeModelRoles() {
3580
3069
  }
3581
3070
 
3582
3071
  // src/cli/operations/codex.ts
3583
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
3584
- import { basename as basename3 } from "path";
3072
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
3073
+ import { basename as basename2 } from "path";
3585
3074
  var CODEX_DISPLAY_NAME = "Codex";
3586
3075
  var codexPlanSources = /* @__PURE__ */ new WeakMap();
3587
3076
  var codexModelSources = /* @__PURE__ */ new WeakMap();
@@ -3667,7 +3156,7 @@ function codexDisclaimers() {
3667
3156
  code: "codex-root-guidance-only"
3668
3157
  },
3669
3158
  {
3670
- message: "Provider-per-role behavior, hook trust, permissions, and memory governance are instruction-level or user-managed unless Codex exposes runtime controls.",
3159
+ message: "Provider-per-role behavior and permissions are instruction-level or user-managed unless Codex exposes runtime controls.",
3671
3160
  code: "codex-runtime-limits"
3672
3161
  }
3673
3162
  ];
@@ -3688,13 +3177,13 @@ function targetForItem2(item, state, observed) {
3688
3177
  }
3689
3178
  function surfaceForItem2(item) {
3690
3179
  return {
3691
- id: `${item.kind}:${item.role ?? basename3(item.targetPath)}`,
3180
+ id: `${item.kind}:${item.role ?? basename2(item.targetPath)}`,
3692
3181
  label: item.role ? `Codex ${item.role} subagent TOML` : item.kind.replaceAll("-", " "),
3693
3182
  path: item.targetPath,
3694
3183
  description: item.description
3695
3184
  };
3696
3185
  }
3697
- function backupForItem2(item) {
3186
+ function backupForItem(item) {
3698
3187
  return {
3699
3188
  required: item.requiresBackup,
3700
3189
  strategy: item.requiresBackup ? "managed-backup-file" : "none",
@@ -3704,7 +3193,7 @@ function backupForItem2(item) {
3704
3193
  function rootBlockState(item) {
3705
3194
  if (!existsSync8(item.targetPath))
3706
3195
  return { state: "missing", observed: "absent" };
3707
- const content = readFileSync9(item.targetPath, "utf8");
3196
+ const content = readFileSync5(item.targetPath, "utf8");
3708
3197
  if (item.content && content.includes(item.content)) {
3709
3198
  return { state: "installed", observed: "managed root block present" };
3710
3199
  }
@@ -3717,11 +3206,11 @@ function managedModelState(item) {
3717
3206
  if (!existsSync8(item.targetPath))
3718
3207
  return { state: "missing", observed: "absent" };
3719
3208
  try {
3720
- const parsed = JSON.parse(readFileSync9(item.targetPath, "utf8"));
3209
+ const parsed = JSON.parse(readFileSync5(item.targetPath, "utf8"));
3721
3210
  if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
3722
3211
  return { state: "unknown", observed: "invalid managed model state" };
3723
3212
  }
3724
- return item.content === readFileSync9(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
3213
+ return item.content === readFileSync5(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
3725
3214
  } catch {
3726
3215
  return { state: "unknown", observed: "unparseable managed model state" };
3727
3216
  }
@@ -3729,7 +3218,7 @@ function managedModelState(item) {
3729
3218
  function userConfigState(item) {
3730
3219
  if (!existsSync8(item.targetPath))
3731
3220
  return { state: "missing", observed: "absent" };
3732
- const content = readFileSync9(item.targetPath, "utf8");
3221
+ const content = readFileSync5(item.targetPath, "utf8");
3733
3222
  if (/^\s*default_mode_request_user_input\s*=\s*true\b/m.test(content)) {
3734
3223
  return {
3735
3224
  state: "installed",
@@ -3741,41 +3230,10 @@ function userConfigState(item) {
3741
3230
  }
3742
3231
  return { state: "missing", observed: "managed feature flag absent" };
3743
3232
  }
3744
- function marketplaceState(item) {
3745
- if (!existsSync8(item.targetPath))
3746
- return { state: "missing", observed: "absent" };
3747
- try {
3748
- const parsed = JSON.parse(readFileSync9(item.targetPath, "utf8"));
3749
- const plugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
3750
- const entry = plugins.find(
3751
- (plugin) => plugin && typeof plugin === "object" && "name" in plugin && plugin.name === "thoth-agents"
3752
- );
3753
- if (!entry)
3754
- return { state: "missing", observed: "marketplace entry absent" };
3755
- const sourcePath = typeof entry === "object" && entry && "source" in entry && entry.source && typeof entry.source === "object" && "path" in entry.source ? String(entry.source.path) : "";
3756
- return sourcePath.includes(".codex/plugins/thoth-agents") ? { state: "installed", observed: "marketplace entry present" } : { state: "drift", observed: `marketplace source ${sourcePath}` };
3757
- } catch {
3758
- return { state: "unknown", observed: "unparseable marketplace JSON" };
3759
- }
3760
- }
3761
- function contentState2(item) {
3233
+ function contentState(item) {
3762
3234
  if (!existsSync8(item.targetPath))
3763
3235
  return { state: "missing", observed: "absent" };
3764
- const observed = readFileSync9(item.targetPath, "utf8");
3765
- if (basename3(item.targetPath) === "plugin.json" && item.targetPath.replaceAll("\\", "/").includes("/.codex-plugin/")) {
3766
- try {
3767
- const expectedVersion = JSON.parse(item.content ?? "{}").version;
3768
- const observedVersion = JSON.parse(observed).version;
3769
- if (typeof expectedVersion === "string" && typeof observedVersion === "string" && expectedVersion !== observedVersion) {
3770
- return {
3771
- state: "outdated",
3772
- observed: `version ${observedVersion}; expected ${expectedVersion}`
3773
- };
3774
- }
3775
- } catch {
3776
- return { state: "unknown", observed: "unparseable plugin manifest" };
3777
- }
3778
- }
3236
+ const observed = readFileSync5(item.targetPath, "utf8");
3779
3237
  if (item.content === observed)
3780
3238
  return { state: "installed", observed: "current" };
3781
3239
  if (item.role) {
@@ -3790,7 +3248,7 @@ function contentState2(item) {
3790
3248
  }
3791
3249
  return { state: "drift", observed: "content differs" };
3792
3250
  }
3793
- function classifyItem2(item) {
3251
+ function classifyItem(item) {
3794
3252
  if (item.action === "diagnose-only") {
3795
3253
  return { state: "unknown", observed: "diagnostic guidance only" };
3796
3254
  }
@@ -3799,8 +3257,7 @@ function classifyItem2(item) {
3799
3257
  return managedModelState(item);
3800
3258
  }
3801
3259
  if (item.action === "merge-toml") return userConfigState(item);
3802
- if (item.action === "merge-marketplace") return marketplaceState(item);
3803
- return contentState2(item);
3260
+ return contentState(item);
3804
3261
  }
3805
3262
  function aggregateState2(states) {
3806
3263
  if (states.includes("unknown")) return "unknown";
@@ -3823,7 +3280,50 @@ function statusSummary2(state) {
3823
3280
  return "Codex managed setup could not be classified safely.";
3824
3281
  }
3825
3282
  }
3826
- function getCodexStatus(context2 = { cwd: process.cwd() }) {
3283
+ function codexRequiredSkillStatus(context2) {
3284
+ const targets = REQUIRED_SKILLS.map((skill) => {
3285
+ const path = getRequiredSkillPath(skill, "codex", context2.homeDir);
3286
+ const installed = existsSync8(path);
3287
+ return {
3288
+ kind: "skill",
3289
+ path,
3290
+ label: `Codex required skill: ${skill.name}`,
3291
+ state: installed ? "installed" : "missing",
3292
+ expected: "required global Codex skill",
3293
+ observed: installed ? "installed" : "missing"
3294
+ };
3295
+ });
3296
+ return {
3297
+ targets,
3298
+ diagnostics: targets.some((target) => target.state === "missing") ? [
3299
+ {
3300
+ severity: "important",
3301
+ code: "codex-required-skills-missing",
3302
+ message: "Required Codex skills are missing; run install, update, or sync to restore them."
3303
+ }
3304
+ ] : []
3305
+ };
3306
+ }
3307
+ function codexRequiredSkillPlanItem() {
3308
+ return {
3309
+ title: "Install required external skills for Codex",
3310
+ target: {
3311
+ kind: "skill",
3312
+ label: "Required Codex skills",
3313
+ expected: REQUIRED_SKILLS.map(({ name }) => name).join(", ")
3314
+ },
3315
+ preview: JSON.stringify(
3316
+ REQUIRED_SKILLS.map((skill) => ({
3317
+ name: skill.name,
3318
+ ...getRequiredSkillInstallCommand(skill, "codex")
3319
+ })),
3320
+ null,
3321
+ 2
3322
+ )
3323
+ };
3324
+ }
3325
+ function getCodexStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
3326
+ const providerCapability = classifyProviderCapabilityEvidence(evidence);
3827
3327
  let plan;
3828
3328
  try {
3829
3329
  plan = buildCodexSetupPlan(codexConfig(context2, true));
@@ -3843,26 +3343,36 @@ function getCodexStatus(context2 = { cwd: process.cwd() }) {
3843
3343
  }
3844
3344
  ],
3845
3345
  actions: codexActions,
3346
+ providerCapability,
3846
3347
  disclaimers: codexDisclaimers()
3847
3348
  };
3848
3349
  }
3849
- const classified = plan.items.filter((item) => item.action !== "diagnose-only").map((item) => ({ item, ...classifyItem2(item) }));
3850
- const state = aggregateState2(classified.map((item) => item.state));
3350
+ const classified = plan.items.filter((item) => item.action !== "diagnose-only").map((item) => ({ item, ...classifyItem(item) }));
3351
+ const requiredSkills = codexRequiredSkillStatus(context2);
3352
+ const state = aggregateState2([
3353
+ ...classified.map((item) => item.state),
3354
+ ...requiredSkills.targets.map((target) => target.state ?? "unknown")
3355
+ ]);
3851
3356
  const diagnostics = plan.diagnostics.map((message) => ({
3852
3357
  severity: "minor",
3853
3358
  message,
3854
3359
  code: "codex-diagnostic"
3855
3360
  }));
3361
+ diagnostics.push(...requiredSkills.diagnostics);
3856
3362
  return {
3857
3363
  harness: "codex",
3858
3364
  displayName: CODEX_DISPLAY_NAME,
3859
3365
  state,
3860
3366
  summary: statusSummary2(state),
3861
- targets: classified.map(
3862
- ({ item, state: state2, observed }) => targetForItem2(item, state2, observed)
3863
- ),
3367
+ targets: [
3368
+ ...classified.map(
3369
+ ({ item, state: state2, observed }) => targetForItem2(item, state2, observed)
3370
+ ),
3371
+ ...requiredSkills.targets
3372
+ ],
3864
3373
  diagnostics,
3865
3374
  actions: codexActions,
3375
+ providerCapability,
3866
3376
  disclaimers: [
3867
3377
  ...codexDisclaimers(),
3868
3378
  ...plan.disclaimers.map((message) => ({ message }))
@@ -3874,7 +3384,7 @@ function planItemFromSetup2(item) {
3874
3384
  title: item.description,
3875
3385
  target: targetForItem2(item),
3876
3386
  preview: item.content,
3877
- backup: backupForItem2(item)
3387
+ backup: backupForItem(item)
3878
3388
  };
3879
3389
  }
3880
3390
  function planFromSetup2(id, action, title, summary, setupPlan, context2) {
@@ -3895,7 +3405,10 @@ function planFromSetup2(id, action, title, summary, setupPlan, context2) {
3895
3405
  strategy: "existing-helper",
3896
3406
  description: "Codex setup apply uses the existing installer backup behavior for files that already exist."
3897
3407
  },
3898
- items: setupPlan.items.map(planItemFromSetup2),
3408
+ items: [
3409
+ ...setupPlan.items.map(planItemFromSetup2),
3410
+ codexRequiredSkillPlanItem()
3411
+ ],
3899
3412
  warnings: [
3900
3413
  ...status.diagnostics,
3901
3414
  ...canApply ? [] : [
@@ -3910,7 +3423,7 @@ function planFromSetup2(id, action, title, summary, setupPlan, context2) {
3910
3423
  ...setupPlan.disclaimers.map((message) => ({ message }))
3911
3424
  ]
3912
3425
  };
3913
- codexPlanSources.set(plan, setupPlan);
3426
+ codexPlanSources.set(plan, { setupPlan, context: context2 });
3914
3427
  return plan;
3915
3428
  }
3916
3429
  function buildCodexUpdatePlan(context2 = { cwd: process.cwd() }) {
@@ -3930,7 +3443,7 @@ function buildCodexSyncPlan(context2 = { cwd: process.cwd() }) {
3930
3443
  "codex-sync-preview",
3931
3444
  "sync",
3932
3445
  "Sync Codex managed configuration",
3933
- "Preview Codex managed root instructions, subagents, plugin source, marketplace entry, and feature gates.",
3446
+ "Preview Codex managed root instructions, subagents, feature gates, and native marketplace guidance.",
3934
3447
  setupPlan,
3935
3448
  context2
3936
3449
  );
@@ -4120,26 +3633,26 @@ function applyCodexPlan(plan) {
4120
3633
  const rejection = validateCodexPlan(plan);
4121
3634
  if (rejection) return rejection;
4122
3635
  if (plan.action === "model-config") {
4123
- const source = codexModelSources.get(plan);
4124
- if (!source) {
3636
+ const source2 = codexModelSources.get(plan);
3637
+ if (!source2) {
4125
3638
  return rejectPlan2(
4126
3639
  plan,
4127
3640
  "Codex model plan was not produced by buildCodexModelPlan in this process."
4128
3641
  );
4129
3642
  }
4130
- const result2 = applyCodexManagedModelOverrides(source.config, source.roles);
3643
+ const result2 = applyCodexManagedModelOverrides(source2.config, source2.roles);
4131
3644
  return {
4132
3645
  harness: "codex",
4133
3646
  action: "model-config",
4134
3647
  applied: result2.success,
4135
3648
  summary: result2.success ? "Applied Codex subagent model overrides." : result2.error ?? "Failed to apply Codex subagent model overrides.",
4136
- changedTargets: result2.changed.map((path4) => ({
4137
- kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
4138
- path: path4,
4139
- label: basename3(path4),
3649
+ changedTargets: result2.changed.map((path) => ({
3650
+ kind: path.endsWith(".json") ? "memory-state" : "generated-artifact",
3651
+ path,
3652
+ label: basename2(path),
4140
3653
  state: "installed"
4141
3654
  })),
4142
- backups: result2.changed.filter((path4) => existsSync8(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
3655
+ backups: result2.changed.filter((path) => existsSync8(`${path}.bak`)).map((path) => ({ path: `${path}.bak`, label: "managed backup" })),
4143
3656
  warnings: result2.success ? [] : [
4144
3657
  {
4145
3658
  severity: "critical",
@@ -4149,31 +3662,62 @@ function applyCodexPlan(plan) {
4149
3662
  disclaimers: codexDisclaimers()
4150
3663
  };
4151
3664
  }
4152
- const setupPlan = codexPlanSources.get(plan);
4153
- if (!setupPlan) {
3665
+ const source = codexPlanSources.get(plan);
3666
+ if (!source) {
4154
3667
  return rejectPlan2(
4155
3668
  plan,
4156
3669
  "Codex setup plan was not produced by a Codex operation plan builder in this process."
4157
3670
  );
4158
3671
  }
4159
- const result = applyCodexSetup({ ...setupPlan, dryRun: false });
3672
+ const result = applyCodexSetup({ ...source.setupPlan, dryRun: false });
3673
+ const requiredSkillWarnings = [];
3674
+ const requiredSkillTargets = [];
3675
+ if (result.success) {
3676
+ for (const skill of REQUIRED_SKILLS) {
3677
+ const installed = installRequiredSkill(skill, "codex", {
3678
+ homeDir: source.context.homeDir
3679
+ });
3680
+ const success2 = installed.status !== "failed";
3681
+ requiredSkillTargets.push({
3682
+ kind: "skill",
3683
+ path: installed.skillPath,
3684
+ label: `Codex required skill: ${skill.name}`,
3685
+ state: success2 ? "installed" : "drift",
3686
+ observed: success2 ? installed.status : "installation failed"
3687
+ });
3688
+ if (!success2) {
3689
+ requiredSkillWarnings.push({
3690
+ severity: "critical",
3691
+ code: "codex-required-skill-failed",
3692
+ message: `Failed to install required Codex skill: ${skill.name}.`
3693
+ });
3694
+ }
3695
+ }
3696
+ }
3697
+ const success = result.success && requiredSkillWarnings.length === 0;
4160
3698
  return {
4161
3699
  harness: "codex",
4162
3700
  action: plan.action,
4163
- applied: result.success,
4164
- summary: result.success ? `Applied Codex managed ${plan.action} plan.` : result.error ?? `Failed to apply Codex ${plan.action} plan.`,
4165
- changedTargets: result.changed.map((path4) => ({
4166
- kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
4167
- path: path4,
4168
- label: basename3(path4),
4169
- state: "installed"
4170
- })),
4171
- backups: result.changed.filter((path4) => existsSync8(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
4172
- warnings: result.success ? [] : [
4173
- {
4174
- severity: "critical",
4175
- message: result.error ?? "Codex setup apply failed."
4176
- }
3701
+ applied: success,
3702
+ summary: success ? `Applied Codex managed ${plan.action} plan.` : requiredSkillWarnings.length > 0 ? `Codex setup was written, but required skills failed to install.` : result.error ?? `Failed to apply Codex ${plan.action} plan.`,
3703
+ changedTargets: [
3704
+ ...result.changed.map((path) => ({
3705
+ kind: path.endsWith(".json") ? "memory-state" : "generated-artifact",
3706
+ path,
3707
+ label: basename2(path),
3708
+ state: "installed"
3709
+ })),
3710
+ ...requiredSkillTargets
3711
+ ],
3712
+ backups: result.changed.filter((path) => existsSync8(`${path}.bak`)).map((path) => ({ path: `${path}.bak`, label: "managed backup" })),
3713
+ warnings: [
3714
+ ...requiredSkillWarnings,
3715
+ ...result.success ? [] : [
3716
+ {
3717
+ severity: "critical",
3718
+ message: result.error ?? "Codex setup apply failed."
3719
+ }
3720
+ ]
4177
3721
  ],
4178
3722
  disclaimers: codexDisclaimers()
4179
3723
  };
@@ -4181,8 +3725,8 @@ function applyCodexPlan(plan) {
4181
3725
 
4182
3726
  // src/cli/operations/opencode.ts
4183
3727
  import { createHash as createHash3 } from "crypto";
4184
- import { existsSync as existsSync10 } from "fs";
4185
- import { join as join10 } from "path";
3728
+ import { existsSync as existsSync9 } from "fs";
3729
+ import { join as join4 } from "path";
4186
3730
 
4187
3731
  // src/cli/opencode-effort.ts
4188
3732
  var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
@@ -4214,80 +3758,6 @@ function resolveOpenCodeEffort(input) {
4214
3758
  };
4215
3759
  }
4216
3760
 
4217
- // src/cli/skills.ts
4218
- import { spawnSync } from "child_process";
4219
- import { existsSync as existsSync9 } from "fs";
4220
- import { homedir as homedir3 } from "os";
4221
- import { join as join9 } from "path";
4222
- var RECOMMENDED_SKILLS = [
4223
- {
4224
- name: "simplify",
4225
- repo: "https://github.com/brianlovin/claude-config",
4226
- skillName: "simplify",
4227
- description: "YAGNI code simplification expert"
4228
- },
4229
- {
4230
- name: "playwright-cli",
4231
- repo: "https://github.com/microsoft/playwright-cli",
4232
- skillName: "playwright-cli",
4233
- description: "Browser automation for visual checks and testing"
4234
- }
4235
- ];
4236
- function getRecommendedSkillPath(skill, homeDir = homedir3()) {
4237
- return join9(homeDir, ".agents", "skills", skill.skillName, "SKILL.md");
4238
- }
4239
- function isRecommendedSkillInstalled(skill, options = {}) {
4240
- return existsSync9(getRecommendedSkillPath(skill, options.homeDir));
4241
- }
4242
- function runSkillInstallCommand(skill) {
4243
- const args = [
4244
- "skills",
4245
- "add",
4246
- skill.repo,
4247
- "--skill",
4248
- skill.skillName,
4249
- "-a",
4250
- "opencode",
4251
- "-y",
4252
- "--global"
4253
- ];
4254
- try {
4255
- const result = spawnSync("npx", args, { stdio: "inherit" });
4256
- if (result.status !== 0) {
4257
- return { success: false };
4258
- }
4259
- if (skill.postInstallCommands && skill.postInstallCommands.length > 0) {
4260
- console.log(`Running post-install commands for ${skill.name}...`);
4261
- for (const cmd of skill.postInstallCommands) {
4262
- console.log(`> ${cmd}`);
4263
- const [command, ...cmdArgs] = cmd.split(" ");
4264
- const cmdResult = spawnSync(command, cmdArgs, { stdio: "inherit" });
4265
- if (cmdResult.status !== 0) {
4266
- console.warn(`Post-install command failed: ${cmd}`);
4267
- }
4268
- }
4269
- }
4270
- return { success: true };
4271
- } catch (error) {
4272
- console.error(`Failed to install skill: ${skill.name}`, error);
4273
- return { success: false, error };
4274
- }
4275
- }
4276
- function installRecommendedSkill(skill, options = {}) {
4277
- const skillPath = getRecommendedSkillPath(skill, options.homeDir);
4278
- if (isRecommendedSkillInstalled(skill, options)) {
4279
- return { skill, status: "already-installed", skillPath };
4280
- }
4281
- const result = runSkillInstallCommand(skill);
4282
- if (result.success) {
4283
- return { skill, status: "installed", skillPath };
4284
- }
4285
- if (isRecommendedSkillInstalled(skill, options)) {
4286
- return { skill, status: "already-installed", skillPath };
4287
- }
4288
- return { skill, status: "failed", skillPath, error: result.error };
4289
- }
4290
-
4291
3761
  // src/cli/operations/opencode.ts
4292
3762
  var PACKAGE_NAME = "thoth-agents";
4293
3763
  var EXPECTED_PLUGIN = `${PACKAGE_NAME}@latest`;
@@ -4453,7 +3923,7 @@ var openCodeActions = [
4453
3923
  id: "opencode-install",
4454
3924
  kind: "install",
4455
3925
  label: "Install",
4456
- description: "Preview OpenCode install using --no-tui --tmux=no --skills=yes semantics",
3926
+ description: "Preview OpenCode install with required external skills",
4457
3927
  dryRun: true,
4458
3928
  requiresConfirmation: true,
4459
3929
  supported: true
@@ -4508,7 +3978,7 @@ function targetForLiteConfig(state) {
4508
3978
  path: getExistingLiteConfigPath(),
4509
3979
  label: "thoth-agents config",
4510
3980
  ...state ? { state } : {},
4511
- expected: `seven-agent ${OPENAI_PRESET} roster`
3981
+ expected: `seven-role ${OPENAI_PRESET} roster`
4512
3982
  };
4513
3983
  }
4514
3984
  function titleCaseSkillName(value) {
@@ -4522,89 +3992,32 @@ function titleCaseSkillName(value) {
4522
3992
  }).join("-");
4523
3993
  }
4524
3994
  function homeDirFromContext(context2) {
4525
- return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ? join10(context2.cwd, "home") : void 0);
3995
+ return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ? join4(context2.cwd, "home") : void 0);
4526
3996
  }
4527
3997
  function openCodeSkillTargets(context2) {
4528
3998
  const homeDir = homeDirFromContext(context2);
4529
- const recommendedTargets = RECOMMENDED_SKILLS.map(
4530
- (skill) => {
4531
- const path4 = getRecommendedSkillPath(skill, homeDir);
4532
- const installed = existsSync10(path4);
4533
- return {
4534
- kind: "skill",
4535
- path: path4,
4536
- label: titleCaseSkillName(skill.skillName),
4537
- state: installed ? "installed" : "missing",
4538
- expected: "recommended global OpenCode skill",
4539
- observed: installed ? "recommended global skill installed" : "recommended global skill missing"
4540
- };
4541
- }
4542
- );
4543
- let staleSkillNames = /* @__PURE__ */ new Set();
4544
- let removedSkillNames = [];
4545
- let skillHealthError;
4546
- try {
4547
- const updateCheck = checkCustomSkillsNeedUpdate();
4548
- staleSkillNames = new Set(
4549
- updateCheck.skillsNeedingUpdate.map(({ skill }) => skill.name)
4550
- );
4551
- removedSkillNames = updateCheck.removedSkills;
4552
- } catch (error) {
4553
- skillHealthError = String(error);
4554
- }
4555
- const bundledTargets = CUSTOM_SKILLS.map((skill) => {
4556
- const path4 = join10(getCustomSkillsDir(), skill.name, "SKILL.md");
4557
- const installed = existsSync10(path4);
4558
- const stale = installed && staleSkillNames.has(skill.name);
4559
- let state = "installed";
4560
- let observed = "bundled OpenCode skill installed and current";
4561
- if (!installed) {
4562
- state = "missing";
4563
- observed = "bundled OpenCode skill missing";
4564
- } else if (stale) {
4565
- state = "drift";
4566
- observed = "bundled OpenCode skill is outdated";
4567
- }
3999
+ const requiredTargets = REQUIRED_SKILLS.map((skill) => {
4000
+ const path = getRequiredSkillPath(skill, "opencode", homeDir);
4001
+ const installed = existsSync9(path);
4568
4002
  return {
4569
4003
  kind: "skill",
4570
- path: path4,
4571
- label: titleCaseSkillName(skill.name),
4572
- state,
4573
- expected: "bundled thoth-agents OpenCode skill",
4574
- observed
4004
+ path,
4005
+ label: titleCaseSkillName(skill.skillName),
4006
+ state: installed ? "installed" : "missing",
4007
+ expected: "required global OpenCode skill",
4008
+ observed: installed ? "required global skill installed" : "required global skill missing"
4575
4009
  };
4576
4010
  });
4577
4011
  const diagnostics = [];
4578
- if (recommendedTargets.some((target) => target.state === "missing")) {
4579
- diagnostics.push({
4580
- severity: "minor",
4581
- message: "Recommended OpenCode global skills are missing; run the OpenCode install flow with skills enabled.",
4582
- code: "opencode-recommended-skills-missing"
4583
- });
4584
- }
4585
- if (bundledTargets.some((target) => target.state === "missing")) {
4012
+ if (requiredTargets.some((target) => target.state === "missing")) {
4586
4013
  diagnostics.push({
4587
4014
  severity: "important",
4588
- message: "Bundled thoth-agents OpenCode skills are missing; run OpenCode install or sync to refresh managed skills.",
4589
- code: "opencode-bundled-skills-missing"
4590
- });
4591
- }
4592
- if (bundledTargets.some((target) => target.state === "drift") || removedSkillNames.length > 0) {
4593
- diagnostics.push({
4594
- severity: "important",
4595
- message: "Bundled thoth-agents OpenCode skills are outdated; run OpenCode install or sync to refresh managed skills.",
4596
- code: "opencode-bundled-skills-outdated"
4597
- });
4598
- }
4599
- if (skillHealthError) {
4600
- diagnostics.push({
4601
- severity: "critical",
4602
- message: `Bundled skill health could not be classified: ${skillHealthError}`,
4603
- code: "opencode-bundled-skills-health-unknown"
4015
+ message: "Required OpenCode global skills are missing; run install or sync to restore them.",
4016
+ code: "opencode-required-skills-missing"
4604
4017
  });
4605
4018
  }
4606
4019
  return {
4607
- targets: [...recommendedTargets, ...bundledTargets],
4020
+ targets: requiredTargets,
4608
4021
  diagnostics
4609
4022
  };
4610
4023
  }
@@ -4677,12 +4090,10 @@ var REPAIRABLE_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
4677
4090
  "opencode-lite-config-missing",
4678
4091
  "opencode-roster-drift",
4679
4092
  ACTIVE_PRESET_SELECTED_CODE,
4680
- "opencode-bundled-skills-missing",
4681
- "opencode-bundled-skills-outdated"
4093
+ "opencode-required-skills-missing"
4682
4094
  ]);
4683
4095
  var MODEL_PRECONFIG_SKILL_CODES = /* @__PURE__ */ new Set([
4684
- "opencode-bundled-skills-missing",
4685
- "opencode-bundled-skills-outdated"
4096
+ "opencode-required-skills-missing"
4686
4097
  ]);
4687
4098
  function uniqueTargets(targets) {
4688
4099
  const seen = /* @__PURE__ */ new Set();
@@ -4714,9 +4125,9 @@ function targetsForBlockingDiagnostic(diagnostic, status) {
4714
4125
  if (code === "opencode-roster-drift" || code === ACTIVE_PRESET_SELECTED_CODE || code === "opencode-lite-config-missing" || code === "thoth-config-parse-error" || code === "opencode-roster-unrecognized") {
4715
4126
  return lite ? [lite] : [];
4716
4127
  }
4717
- if (code === "opencode-bundled-skills-missing" || code === "opencode-bundled-skills-outdated" || code === "opencode-bundled-skills-health-unknown") {
4128
+ if (code === "opencode-required-skills-missing") {
4718
4129
  return status.targets.filter(
4719
- (target) => target.expected === "bundled thoth-agents OpenCode skill" && (code === "opencode-bundled-skills-health-unknown" || target.state !== "installed")
4130
+ (target) => target.expected === "required global OpenCode skill" && target.state !== "installed"
4720
4131
  );
4721
4132
  }
4722
4133
  return [
@@ -4759,7 +4170,7 @@ function blockingDetails(action, status, includeRepairable = false) {
4759
4170
  function canApplyToManagedHealth(action, status) {
4760
4171
  return blockingDetails(action, status).diagnostics.length === 0;
4761
4172
  }
4762
- function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
4173
+ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4763
4174
  const mainPath = getExistingConfigPath();
4764
4175
  const litePath = getExistingLiteConfigPath();
4765
4176
  const main = parseConfig(mainPath);
@@ -4801,8 +4212,8 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
4801
4212
  actions: openCodeActions
4802
4213
  };
4803
4214
  }
4804
- const mainExists = existsSync10(mainPath);
4805
- const liteExists = existsSync10(litePath);
4215
+ const mainExists = existsSync9(mainPath);
4216
+ const liteExists = existsSync9(litePath);
4806
4217
  const mainTarget = {
4807
4218
  ...targetForMainConfig(),
4808
4219
  observed: configPluginMarker(main.config)
@@ -5028,7 +4439,7 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
5028
4439
  harness: "opencode",
5029
4440
  displayName: "OpenCode",
5030
4441
  state: "drift",
5031
- summary: selectedNamedPreset ? "A valid named OpenCode preset is active outside the managed seven-agent roster." : "thoth-agents config does not match the expected seven-agent roster.",
4442
+ summary: selectedNamedPreset ? "A valid named OpenCode preset is active outside the managed seven-role roster." : "thoth-agents config does not match the expected seven-role roster.",
5032
4443
  targets: [
5033
4444
  { ...mainTarget, state: "installed" },
5034
4445
  { ...liteTarget, state: "drift" },
@@ -5045,11 +4456,14 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
5045
4456
  actions: openCodeActions
5046
4457
  };
5047
4458
  }
4459
+ const requiredSkillsMissing = skillStatus.targets.some(
4460
+ (target) => target.state === "missing"
4461
+ );
5048
4462
  return {
5049
4463
  harness: "opencode",
5050
4464
  displayName: "OpenCode",
5051
- state: "installed",
5052
- summary: "OpenCode managed thoth-agents configuration is installed.",
4465
+ state: requiredSkillsMissing ? "drift" : "installed",
4466
+ summary: requiredSkillsMissing ? "OpenCode configuration is installed, but required external skills are missing." : "OpenCode managed thoth-agents configuration is installed.",
5053
4467
  targets: [
5054
4468
  { ...mainTarget, state: "installed" },
5055
4469
  { ...liteTarget, state: "installed" },
@@ -5059,11 +4473,17 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
5059
4473
  actions: openCodeActions
5060
4474
  };
5061
4475
  }
5062
- function defaultBackup(path4) {
4476
+ function getOpenCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
4477
+ return {
4478
+ ...getOpenCodeManagedStatus(context2),
4479
+ providerCapability: classifyProviderCapabilityEvidence(evidence)
4480
+ };
4481
+ }
4482
+ function defaultBackup(path) {
5063
4483
  return {
5064
4484
  required: true,
5065
4485
  strategy: "managed-backup-file",
5066
- destinations: [{ path: `${path4}.bak`, label: "managed backup" }]
4486
+ destinations: [{ path: `${path}.bak`, label: "managed backup" }]
5067
4487
  };
5068
4488
  }
5069
4489
  function backupFromItems(items) {
@@ -5085,18 +4505,6 @@ function backupFromItems(items) {
5085
4505
  ...primary?.description ? { description: primary.description } : {}
5086
4506
  };
5087
4507
  }
5088
- function bundledSkillsRefreshItem() {
5089
- const names = CUSTOM_SKILLS.map(({ name }) => name);
5090
- return {
5091
- title: "Refresh bundled thoth-agents OpenCode skills",
5092
- target: {
5093
- kind: "skill",
5094
- label: "Bundled thoth-agents OpenCode skills",
5095
- expected: `${names.length} managed bundled skills`
5096
- },
5097
- preview: JSON.stringify({ count: names.length, names }, null, 2)
5098
- };
5099
- }
5100
4508
  function defaultDisclaimers() {
5101
4509
  return [
5102
4510
  {
@@ -5151,7 +4559,7 @@ function planFromItems(id, action, title, summary, items, context2) {
5151
4559
  };
5152
4560
  }
5153
4561
  function buildOpenCodeUpdatePlan(_context = { cwd: process.cwd() }) {
5154
- const path4 = getExistingConfigPath();
4562
+ const path = getExistingConfigPath();
5155
4563
  const { plan, status } = planFromItems(
5156
4564
  "opencode-update-preview",
5157
4565
  "update",
@@ -5163,7 +4571,7 @@ function buildOpenCodeUpdatePlan(_context = { cwd: process.cwd() }) {
5163
4571
  target: targetForMainConfig(),
5164
4572
  state: getOpenCodeStatus(_context).state,
5165
4573
  preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
5166
- backup: defaultBackup(path4)
4574
+ backup: defaultBackup(path)
5167
4575
  }
5168
4576
  ],
5169
4577
  _context
@@ -5177,8 +4585,6 @@ function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
5177
4585
  const generatedConfig = generateLiteConfig({
5178
4586
  agent: "opencode",
5179
4587
  hasTmux: false,
5180
- installSkills: false,
5181
- installCustomSkills: true,
5182
4588
  dryRun: true,
5183
4589
  reset: false
5184
4590
  });
@@ -5203,12 +4609,27 @@ function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
5203
4609
  backup: defaultBackup(getExistingConfigPath())
5204
4610
  },
5205
4611
  {
5206
- title: "Write thoth-agents seven-agent config",
4612
+ title: "Write thoth-agents seven-role config",
5207
4613
  target: targetForLiteConfig(),
5208
4614
  preview: JSON.stringify(generatedConfig, null, 2),
5209
4615
  backup: defaultBackup(litePath)
5210
4616
  },
5211
- bundledSkillsRefreshItem()
4617
+ {
4618
+ title: "Install required external skills",
4619
+ target: {
4620
+ kind: "skill",
4621
+ label: "Required OpenCode skills",
4622
+ expected: REQUIRED_SKILLS.map(({ name }) => name).join(", ")
4623
+ },
4624
+ preview: JSON.stringify(
4625
+ REQUIRED_SKILLS.map((skill) => ({
4626
+ name: skill.name,
4627
+ ...getRequiredSkillInstallCommand(skill, "opencode")
4628
+ })),
4629
+ null,
4630
+ 2
4631
+ )
4632
+ }
5212
4633
  ],
5213
4634
  _context
5214
4635
  );
@@ -5221,8 +4642,6 @@ function tuiInstallConfig() {
5221
4642
  return {
5222
4643
  agent: "opencode",
5223
4644
  hasTmux: false,
5224
- installSkills: true,
5225
- installCustomSkills: true,
5226
4645
  dryRun: true,
5227
4646
  reset: false
5228
4647
  };
@@ -5232,16 +4651,15 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
5232
4651
  const installPreview = {
5233
4652
  noTui: true,
5234
4653
  hasTmux: false,
5235
- installSkills: true,
5236
- installCustomSkills: true,
5237
- equivalentCommand: "install --agent=opencode --no-tui --tmux=no --skills=yes"
4654
+ requiredSkills: REQUIRED_SKILLS.map((skill) => skill.name),
4655
+ equivalentCommand: "install --agent=opencode --no-tui --tmux=no"
5238
4656
  };
5239
4657
  const litePath = getExistingLiteConfigPath();
5240
4658
  const { plan, status } = planFromItems(
5241
4659
  "opencode-install-preview",
5242
4660
  "install",
5243
4661
  "Preview install",
5244
- "Preview OpenCode install equivalent to install --agent=opencode --no-tui --tmux=no --skills=yes.",
4662
+ "Preview OpenCode install with the required external skills.",
5245
4663
  [
5246
4664
  {
5247
4665
  title: "Apply OpenCode TUI install options",
@@ -5250,7 +4668,7 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
5250
4668
  path: getExistingConfigPath(),
5251
4669
  label: "OpenCode install options",
5252
4670
  state: getOpenCodeStatus(_context).state,
5253
- expected: "--no-tui --tmux=no --skills=yes"
4671
+ expected: "--no-tui --tmux=no plus required external skills"
5254
4672
  },
5255
4673
  preview: JSON.stringify(installPreview, null, 2)
5256
4674
  },
@@ -5268,20 +4686,26 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
5268
4686
  backup: defaultBackup(getExistingConfigPath())
5269
4687
  },
5270
4688
  {
5271
- title: "Write thoth-agents seven-agent config",
4689
+ title: "Write thoth-agents seven-role config",
5272
4690
  target: targetForLiteConfig(),
5273
4691
  preview: JSON.stringify(generatedConfig, null, 2),
5274
4692
  backup: defaultBackup(litePath)
5275
4693
  },
5276
- bundledSkillsRefreshItem(),
5277
4694
  {
5278
- title: "Install recommended external skills",
4695
+ title: "Install required external skills",
5279
4696
  target: {
5280
4697
  kind: "skill",
5281
- label: "Recommended OpenCode skills",
5282
- expected: "skills=yes"
4698
+ label: "Required OpenCode skills",
4699
+ expected: REQUIRED_SKILLS.map(({ name }) => name).join(", ")
5283
4700
  },
5284
- preview: JSON.stringify({ installSkills: true }, null, 2)
4701
+ preview: JSON.stringify(
4702
+ REQUIRED_SKILLS.map((skill) => ({
4703
+ name: skill.name,
4704
+ ...getRequiredSkillInstallCommand(skill, "opencode")
4705
+ })),
4706
+ null,
4707
+ 2
4708
+ )
5285
4709
  }
5286
4710
  ],
5287
4711
  _context
@@ -5528,8 +4952,6 @@ function applyModelPlan(roles) {
5528
4952
  const base = parsed.config ?? generateLiteConfig({
5529
4953
  agent: "opencode",
5530
4954
  hasTmux: false,
5531
- installSkills: false,
5532
- installCustomSkills: true,
5533
4955
  dryRun: false,
5534
4956
  reset: false
5535
4957
  });
@@ -5583,85 +5005,33 @@ function applyModelPlan(roles) {
5583
5005
  observed: "agents role overrides updated"
5584
5006
  }
5585
5007
  ],
5586
- backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
5008
+ backups: existsSync9(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
5587
5009
  warnings,
5588
5010
  disclaimers: defaultDisclaimers()
5589
5011
  };
5590
5012
  }
5591
- function applyRecommendedSkills() {
5013
+ function applyRequiredSkills(context2) {
5592
5014
  const warnings = [];
5593
- for (const skill of RECOMMENDED_SKILLS) {
5015
+ const homeDir = homeDirFromContext(context2);
5016
+ for (const skill of REQUIRED_SKILLS) {
5594
5017
  try {
5595
- const result = installRecommendedSkill(skill);
5018
+ const result = installRequiredSkill(skill, "opencode", { homeDir });
5596
5019
  if (result.status !== "failed") continue;
5597
5020
  warnings.push({
5598
- severity: "important",
5599
- code: "opencode-recommended-skill-failed",
5600
- message: `Failed to install optional recommended OpenCode skill: ${skill.name}.`
5021
+ severity: "critical",
5022
+ code: "opencode-required-skill-failed",
5023
+ message: `Failed to install required OpenCode skill: ${skill.name}.`
5601
5024
  });
5602
5025
  } catch (error) {
5603
5026
  const reason = error instanceof Error ? error.message : String(error);
5604
5027
  warnings.push({
5605
- severity: "important",
5606
- code: "opencode-recommended-skill-failed",
5607
- message: `Failed to install optional recommended OpenCode skill ${skill.name}: ${reason}.`
5028
+ severity: "critical",
5029
+ code: "opencode-required-skill-failed",
5030
+ message: `Failed to install required OpenCode skill ${skill.name}: ${reason}.`
5608
5031
  });
5609
5032
  }
5610
5033
  }
5611
- return warnings;
5612
- }
5613
- function bundledSkillsFailure(plan, changedTargets, backups, warnings, detail) {
5614
- const message = `Failed to install bundled thoth-agents OpenCode skills: ${detail}.`;
5615
- return {
5616
- harness: plan.harness,
5617
- action: plan.action,
5618
- applied: false,
5619
- summary: message,
5620
- changedTargets,
5621
- backups,
5622
- warnings: [
5623
- ...warnings,
5624
- {
5625
- severity: "critical",
5626
- code: "opencode-bundled-skills-failed",
5627
- message
5628
- }
5629
- ],
5630
- disclaimers: defaultDisclaimers()
5631
- };
5632
- }
5633
- function applyBundledSkills(plan, changedTargets, backups, warnings) {
5634
- let bundled;
5635
- try {
5636
- bundled = installCustomSkills();
5637
- } catch (error) {
5638
- const detail = error instanceof Error ? error.message : String(error);
5639
- return bundledSkillsFailure(
5640
- plan,
5641
- changedTargets,
5642
- backups,
5643
- warnings,
5644
- detail
5645
- );
5646
- }
5647
- if (!bundled.success) {
5648
- const failedNames = bundled.failedSkills.map(({ skill }) => skill.name);
5649
- const detail = failedNames.length > 0 ? failedNames.join(", ") : "unknown bundled skills";
5650
- return bundledSkillsFailure(
5651
- plan,
5652
- changedTargets,
5653
- backups,
5654
- warnings,
5655
- detail
5656
- );
5657
- }
5658
- changedTargets.push({
5659
- kind: "skill",
5660
- label: "Bundled thoth-agents OpenCode skills",
5661
- state: "installed",
5662
- observed: "managed bundled skills refreshed"
5663
- });
5664
- return null;
5034
+ return { success: warnings.length === 0, warnings };
5665
5035
  }
5666
5036
  function applyOpenCodePlan(plan) {
5667
5037
  const rejection = validateApplyPlan(plan);
@@ -5718,7 +5088,7 @@ function applyOpenCodePlan(plan) {
5718
5088
  ...targetForMainConfig("installed"),
5719
5089
  observed: `plugin includes ${EXPECTED_PLUGIN}`
5720
5090
  });
5721
- if (existsSync10(`${mainConfigResult.configPath}.bak`)) {
5091
+ if (existsSync9(`${mainConfigResult.configPath}.bak`)) {
5722
5092
  backups.push({
5723
5093
  path: `${mainConfigResult.configPath}.bak`,
5724
5094
  label: "OpenCode config backup"
@@ -5729,8 +5099,6 @@ function applyOpenCodePlan(plan) {
5729
5099
  {
5730
5100
  agent: "opencode",
5731
5101
  hasTmux: false,
5732
- installSkills: issued.payload.action === "install",
5733
- installCustomSkills: true,
5734
5102
  dryRun: false,
5735
5103
  reset: false
5736
5104
  },
@@ -5744,32 +5112,36 @@ function applyOpenCodePlan(plan) {
5744
5112
  }
5745
5113
  changedTargets.push({
5746
5114
  ...targetForLiteConfig("installed"),
5747
- observed: "seven-agent roster written"
5115
+ observed: "seven-role roster written"
5748
5116
  });
5749
- if (existsSync10(`${liteResult.configPath}.bak`)) {
5117
+ if (existsSync9(`${liteResult.configPath}.bak`)) {
5750
5118
  backups.push({
5751
5119
  path: `${liteResult.configPath}.bak`,
5752
5120
  label: "thoth-agents config backup"
5753
5121
  });
5754
5122
  }
5755
- const skillsRejection = applyBundledSkills(
5756
- plan,
5757
- changedTargets,
5758
- backups,
5759
- warnings
5760
- );
5761
- if (skillsRejection) return skillsRejection;
5762
5123
  }
5763
- if (issued.payload.action === "install") {
5764
- warnings.push(...applyRecommendedSkills());
5124
+ if (issued.payload.action === "install" || issued.payload.action === "sync") {
5125
+ const requiredSkills = applyRequiredSkills(issued.context);
5126
+ warnings.push(...requiredSkills.warnings);
5765
5127
  changedTargets.push({
5766
5128
  kind: "skill",
5767
- label: "Recommended OpenCode skills",
5768
- state: warnings.some(
5769
- (warning3) => warning3.code === "opencode-recommended-skill-failed"
5770
- ) ? "drift" : "installed",
5771
- observed: "optional skills=yes processed"
5129
+ label: "Required OpenCode skills",
5130
+ state: requiredSkills.success ? "installed" : "drift",
5131
+ observed: requiredSkills.success ? "required external skills installed" : "required external skill installation failed"
5772
5132
  });
5133
+ if (!requiredSkills.success) {
5134
+ return {
5135
+ harness: "opencode",
5136
+ action: plan.action,
5137
+ applied: false,
5138
+ summary: "OpenCode configuration was written, but required skills failed to install.",
5139
+ changedTargets,
5140
+ backups,
5141
+ warnings,
5142
+ disclaimers: defaultDisclaimers()
5143
+ };
5144
+ }
5773
5145
  }
5774
5146
  return {
5775
5147
  harness: "opencode",
@@ -5806,14 +5178,14 @@ import { execFileSync } from "child_process";
5806
5178
 
5807
5179
  // src/cli/model-catalog/cache.ts
5808
5180
  import {
5809
- existsSync as existsSync11,
5181
+ existsSync as existsSync10,
5810
5182
  mkdirSync as mkdirSync3,
5811
- readFileSync as readFileSync10,
5183
+ readFileSync as readFileSync6,
5812
5184
  renameSync as renameSync2,
5813
5185
  unlinkSync,
5814
5186
  writeFileSync as writeFileSync3
5815
5187
  } from "fs";
5816
- import { homedir as homedir4 } from "os";
5188
+ import { homedir as homedir3 } from "os";
5817
5189
  import { dirname as dirname5, posix, win32 } from "path";
5818
5190
  import { z as z2 } from "zod";
5819
5191
  var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
@@ -5834,19 +5206,19 @@ var modelsDevCacheSchema = z2.object({
5834
5206
  function resolveModelsDevCachePath(options = {}) {
5835
5207
  const platform = options.platform ?? process.platform;
5836
5208
  const env = options.env ?? process.env;
5837
- const home = options.homeDir ?? homedir4();
5209
+ const home = options.homeDir ?? homedir3();
5838
5210
  if (platform === "win32") {
5839
- const path4 = win32;
5840
- const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path4.join(home, "AppData", "Local");
5841
- return path4.join(base2, "thoth-agents", "models-dev-v1.json");
5211
+ const path = win32;
5212
+ const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
5213
+ return path.join(base2, "thoth-agents", "models-dev-v1.json");
5842
5214
  }
5843
5215
  const base = env.XDG_CACHE_HOME ?? posix.join(home, ".cache");
5844
5216
  return posix.join(base, "thoth-agents", "models-dev-v1.json");
5845
5217
  }
5846
5218
  function readModelsDevCache(cachePath) {
5847
- if (!existsSync11(cachePath)) return void 0;
5219
+ if (!existsSync10(cachePath)) return void 0;
5848
5220
  try {
5849
- const input = JSON.parse(readFileSync10(cachePath, "utf8"));
5221
+ const input = JSON.parse(readFileSync6(cachePath, "utf8"));
5850
5222
  const parsed = modelsDevCacheSchema.safeParse(input);
5851
5223
  return parsed.success ? parsed.data : void 0;
5852
5224
  } catch {
@@ -5868,7 +5240,7 @@ function writeModelsDevCacheAtomic(cachePath, cache) {
5868
5240
  );
5869
5241
  renameSync2(temporaryPath, cachePath);
5870
5242
  } catch (error) {
5871
- if (existsSync11(temporaryPath)) unlinkSync(temporaryPath);
5243
+ if (existsSync10(temporaryPath)) unlinkSync(temporaryPath);
5872
5244
  throw error;
5873
5245
  }
5874
5246
  }
@@ -6060,7 +5432,7 @@ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
6060
5432
  "max",
6061
5433
  "ultra"
6062
5434
  ]);
6063
- var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
5435
+ var CLAUDE_CODE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
6064
5436
  function parseOpenCodeModels(output) {
6065
5437
  const seen = /* @__PURE__ */ new Set();
6066
5438
  const options = [];
@@ -6093,7 +5465,7 @@ function writableEfforts(harness, option) {
6093
5465
  }
6094
5466
  if (harness === "claude") {
6095
5467
  return option.efforts.filter(
6096
- (effort) => CLAUDE_CODE_EFFORTS2.includes(effort)
5468
+ (effort) => CLAUDE_CODE_EFFORTS.includes(effort)
6097
5469
  );
6098
5470
  }
6099
5471
  return option.efforts.filter(
@@ -6144,7 +5516,7 @@ var CLAUDE_CODE_MODEL_OPTIONS = [
6144
5516
  }
6145
5517
  ].map((option) => ({
6146
5518
  ...option,
6147
- efforts: option.id === "inherit" ? [] : [...CLAUDE_CODE_EFFORTS2],
5519
+ efforts: option.id === "inherit" ? [] : [...CLAUDE_CODE_EFFORTS],
6148
5520
  source: "manual"
6149
5521
  }));
6150
5522
  async function getModelOptions(harness) {
@@ -6181,7 +5553,7 @@ async function getModelOptions(harness) {
6181
5553
  }
6182
5554
 
6183
5555
  // src/cli/tui/operations.ts
6184
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
5556
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
6185
5557
  var context = { cwd: process.cwd() };
6186
5558
  var codexContext = { cwd: process.cwd() };
6187
5559
  var claudeCodeContext = {
@@ -6191,7 +5563,8 @@ var claudeCodeContext = {
6191
5563
  var opencodeModelRoles = ALL_AGENT_NAMES.map(
6192
5564
  (role) => ({
6193
5565
  role,
6194
- model: DEFAULT_MODELS[role] ?? "openai/gpt-5.4"
5566
+ model: getDefaultOpenCodeModel(role),
5567
+ effort: { kind: "effort", value: getDefaultOpenCodeVariant(role) }
6195
5568
  })
6196
5569
  );
6197
5570
  var codexModelRoles = CODEX_ROLE_NAMES.map(
@@ -6222,7 +5595,7 @@ function getCodexModelRoles(source = codexContext) {
6222
5595
  const item = plan.items.find(
6223
5596
  (candidate) => candidate.action === "write-role-toml" && candidate.role === role
6224
5597
  );
6225
- const content = item && existsSync12(item.targetPath) ? readFileSync11(item.targetPath, "utf8") : item?.content;
5598
+ const content = item && existsSync11(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : item?.content;
6226
5599
  const effort = content ? parseRoleTomlEffort(content) : void 0;
6227
5600
  return {
6228
5601
  role,
@@ -6234,32 +5607,8 @@ function getCodexModelRoles(source = codexContext) {
6234
5607
  return codexModelRoles.map((role) => ({ ...role }));
6235
5608
  }
6236
5609
  }
6237
- function getClaudeCodeModelRoles(source = claudeCodeContext) {
6238
- try {
6239
- const plan = buildClaudeCodeSetupPlan({
6240
- dryRun: true,
6241
- reset: false,
6242
- scope: source.scope ?? "user",
6243
- projectRoot: source.cwd,
6244
- homeDir: source.homeDir,
6245
- packageRoot: source.packageRoot
6246
- });
6247
- return CLAUDE_CODE_ROLE_NAMES.map((role) => {
6248
- const item = plan.items.find(
6249
- (candidate) => candidate.kind === "subagent" && candidate.role === role
6250
- );
6251
- const content = item && existsSync12(item.targetPath) ? readFileSync11(item.targetPath, "utf8") : item?.content;
6252
- const model = content ? parseSubagentModel(content) : void 0;
6253
- const effort = content ? parseSubagentEffort(content) : void 0;
6254
- return {
6255
- role,
6256
- model: model ?? "inherit",
6257
- effort: effort ? { kind: "effort", value: effort } : { kind: "inherit" }
6258
- };
6259
- });
6260
- } catch {
6261
- return defaultClaudeCodeModelRoles();
6262
- }
5610
+ function getClaudeCodeModelRoles(_source = claudeCodeContext) {
5611
+ return defaultClaudeCodeModelRoles();
6263
5612
  }
6264
5613
  function readRoleField(config, role, field) {
6265
5614
  if (!config || typeof config !== "object" || Array.isArray(config)) {
@@ -6281,10 +5630,13 @@ function getOpenCodeModelRoles() {
6281
5630
  const selectedPreset = presetName !== void 0 ? presets[presetName] : void 0;
6282
5631
  const activePreset = selectedPreset && typeof selectedPreset === "object" && !Array.isArray(selectedPreset) ? selectedPreset : void 0;
6283
5632
  return ALL_AGENT_NAMES.map((role) => {
6284
- const variant = readRoleField(agents, role, "variant") ?? readRoleField(activePreset, role, "variant");
5633
+ const defaultModel = getDefaultOpenCodeModel(role);
5634
+ const model = readRoleField(agents, role, "model") ?? readRoleField(activePreset, role, "model") ?? defaultModel;
5635
+ const configuredVariant = readRoleField(agents, role, "variant") ?? readRoleField(activePreset, role, "variant");
5636
+ const variant = configuredVariant ?? (model === defaultModel ? getDefaultOpenCodeVariant(role) : void 0);
6285
5637
  return {
6286
5638
  role,
6287
- model: readRoleField(agents, role, "model") ?? readRoleField(activePreset, role, "model") ?? DEFAULT_MODELS[role] ?? "openai/gpt-5.4",
5639
+ model,
6288
5640
  effort: variant ? { kind: "effort", value: variant } : { kind: "inherit" }
6289
5641
  };
6290
5642
  });
@@ -6302,12 +5654,12 @@ function buildTuiModelPlan(harness, roles) {
6302
5654
  return buildCodexModelPlan({ harness, dryRun: true, roles }, codexContext);
6303
5655
  }
6304
5656
  var defaultTuiOperations = {
6305
- status(harness) {
6306
- if (harness === "opencode") return getOpenCodeStatus(context);
5657
+ status(harness, evidence) {
5658
+ if (harness === "opencode") return getOpenCodeStatus(context, evidence);
6307
5659
  if (harness === "claude") {
6308
- return getClaudeCodeStatus(claudeCodeContext);
5660
+ return getClaudeCodeStatus(claudeCodeContext, evidence);
6309
5661
  }
6310
- return getCodexStatus(codexContext);
5662
+ return getCodexStatus(codexContext, evidence);
6311
5663
  },
6312
5664
  modelRoles(harness) {
6313
5665
  if (harness === "opencode") return getOpenCodeModelRoles();
@@ -6365,8 +5717,9 @@ export {
6365
5717
  buildClaudeCodeSetupPlan,
6366
5718
  applyClaudeCodeSetup,
6367
5719
  formatClaudeCodeSetupPlan,
6368
- RECOMMENDED_SKILLS,
6369
- installRecommendedSkill,
5720
+ REQUIRED_SKILLS,
5721
+ getRequiredSkillInstallCommand,
5722
+ installRequiredSkill,
6370
5723
  getClaudeCodeStatus,
6371
5724
  buildClaudeCodeUpdatePlan,
6372
5725
  buildClaudeCodeSyncPlan,