agents-inc 0.154.0 → 0.155.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 (213) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/chunk-2H7UWE2G.js +246 -0
  3. package/dist/chunk-2H7UWE2G.js.map +1 -0
  4. package/dist/{chunk-LA3JPESW.js → chunk-4A4L3YIW.js} +5 -5
  5. package/dist/chunk-4G2YHZIM.js +132 -0
  6. package/dist/chunk-4G2YHZIM.js.map +1 -0
  7. package/dist/{chunk-BZ4CZBOM.js → chunk-5F2YR5XV.js} +3 -3
  8. package/dist/{chunk-CHNA5RVS.js → chunk-5OKLCVM4.js} +3 -3
  9. package/dist/{chunk-ZU6DAR4S.js → chunk-5QZAK6J7.js} +7 -7
  10. package/dist/{chunk-ZU6DAR4S.js.map → chunk-5QZAK6J7.js.map} +1 -1
  11. package/dist/{chunk-NFH6SA62.js → chunk-6RLV45HN.js} +10 -10
  12. package/dist/{chunk-NFH6SA62.js.map → chunk-6RLV45HN.js.map} +1 -1
  13. package/dist/{chunk-GUDGCFSF.js → chunk-CLFHC2DK.js} +15 -106
  14. package/dist/chunk-CLFHC2DK.js.map +1 -0
  15. package/dist/chunk-D2CAV7MG.js +61 -0
  16. package/dist/chunk-D2CAV7MG.js.map +1 -0
  17. package/dist/{chunk-UW44KSYL.js → chunk-F2AMBBIA.js} +6 -6
  18. package/dist/{chunk-UW44KSYL.js.map → chunk-F2AMBBIA.js.map} +1 -1
  19. package/dist/{chunk-QQTYMW7N.js → chunk-F5T6TV4Z.js} +206 -73
  20. package/dist/chunk-F5T6TV4Z.js.map +1 -0
  21. package/dist/{chunk-K3O3YQYG.js → chunk-G47TDUK3.js} +2 -2
  22. package/dist/{chunk-AURY6RT6.js → chunk-GS2NZ3GD.js} +8 -8
  23. package/dist/chunk-GS2NZ3GD.js.map +1 -0
  24. package/dist/{chunk-5MFJ24VM.js → chunk-H6UTKTLK.js} +104 -250
  25. package/dist/chunk-H6UTKTLK.js.map +1 -0
  26. package/dist/{chunk-MJFIO5SX.js → chunk-HG4JPXJT.js} +11 -11
  27. package/dist/chunk-HG4JPXJT.js.map +1 -0
  28. package/dist/{chunk-IXEDSKMH.js → chunk-HTYH7CKN.js} +3 -3
  29. package/dist/{chunk-JC7DBPRD.js → chunk-HXKOIWLU.js} +2 -2
  30. package/dist/chunk-HXKOIWLU.js.map +1 -0
  31. package/dist/{chunk-NSJ2S7R5.js → chunk-ILUVL7WM.js} +2 -2
  32. package/dist/{chunk-R54KIGN2.js → chunk-IO5YBKHV.js} +13 -2
  33. package/dist/chunk-IO5YBKHV.js.map +1 -0
  34. package/dist/chunk-IXRZAHZF.js +53 -0
  35. package/dist/chunk-IXRZAHZF.js.map +1 -0
  36. package/dist/chunk-JCPANDNR.js +35 -0
  37. package/dist/chunk-JCPANDNR.js.map +1 -0
  38. package/dist/{chunk-NKCY2ULI.js → chunk-LJCNUP6M.js} +2 -2
  39. package/dist/{chunk-PRJD5MMQ.js → chunk-MHRC3KRX.js} +5 -5
  40. package/dist/chunk-MHRC3KRX.js.map +1 -0
  41. package/dist/chunk-MO5FGLVS.js +142 -0
  42. package/dist/chunk-MO5FGLVS.js.map +1 -0
  43. package/dist/{chunk-Q5FLW4JG.js → chunk-OKH64JZE.js} +2 -2
  44. package/dist/{chunk-WRRP47PY.js → chunk-PCC4LBKB.js} +2 -2
  45. package/dist/chunk-PMUABBPG.js +258 -0
  46. package/dist/chunk-PMUABBPG.js.map +1 -0
  47. package/dist/{chunk-2IMYK4WN.js → chunk-PNMM5HNX.js} +5 -5
  48. package/dist/{chunk-AKQ5QISO.js → chunk-QWVXOCXO.js} +6 -6
  49. package/dist/{chunk-IPJOMAWC.js → chunk-RWOYPOMR.js} +12 -2
  50. package/dist/{chunk-IPJOMAWC.js.map → chunk-RWOYPOMR.js.map} +1 -1
  51. package/dist/{chunk-ONWEOS77.js → chunk-SY4X2LND.js} +4 -4
  52. package/dist/{chunk-XGMOXMS4.js → chunk-TBOVYLQJ.js} +2 -2
  53. package/dist/{chunk-R7JV22QK.js → chunk-TNGMF4UX.js} +20 -20
  54. package/dist/{chunk-R7JV22QK.js.map → chunk-TNGMF4UX.js.map} +1 -1
  55. package/dist/{chunk-7VZV4O6B.js → chunk-UMH2YZKG.js} +14 -5
  56. package/dist/chunk-UMH2YZKG.js.map +1 -0
  57. package/dist/{chunk-6CNRMCFM.js → chunk-VPASBA7P.js} +2 -2
  58. package/dist/{chunk-CBF4ZYV3.js → chunk-WCAUUBHO.js} +1376 -741
  59. package/dist/chunk-WCAUUBHO.js.map +1 -0
  60. package/dist/{chunk-V3ENMW7G.js → chunk-WFRTHH3A.js} +3 -3
  61. package/dist/{chunk-DRKSYJZ2.js → chunk-WTJ3AY33.js} +1 -1
  62. package/dist/{chunk-DRKSYJZ2.js.map → chunk-WTJ3AY33.js.map} +1 -1
  63. package/dist/{chunk-XMEIVWAS.js → chunk-WUQ3MTPF.js} +11 -5
  64. package/dist/{chunk-XMEIVWAS.js.map → chunk-WUQ3MTPF.js.map} +1 -1
  65. package/dist/{chunk-4VCWL2IG.js → chunk-XAXJWXI4.js} +92 -59
  66. package/dist/chunk-XAXJWXI4.js.map +1 -0
  67. package/dist/{chunk-B66SC72V.js → chunk-Y5Y7XHVU.js} +4 -4
  68. package/dist/{chunk-FMYHWKGG.js → chunk-YO2LTXB4.js} +66 -16
  69. package/dist/chunk-YO2LTXB4.js.map +1 -0
  70. package/dist/{chunk-TUQF577Z.js → chunk-ZUAZHDKP.js} +40 -41
  71. package/dist/chunk-ZUAZHDKP.js.map +1 -0
  72. package/dist/commands/build/marketplace.js +98 -121
  73. package/dist/commands/build/marketplace.js.map +1 -1
  74. package/dist/commands/build/plugins.js +8 -8
  75. package/dist/commands/compile.js +19 -24
  76. package/dist/commands/compile.js.map +1 -1
  77. package/dist/commands/doctor.js +220 -79
  78. package/dist/commands/doctor.js.map +1 -1
  79. package/dist/commands/edit.js +549 -75
  80. package/dist/commands/edit.js.map +1 -1
  81. package/dist/commands/eject.js +26 -21
  82. package/dist/commands/eject.js.map +1 -1
  83. package/dist/commands/init.js +41 -39
  84. package/dist/commands/list.js +11 -11
  85. package/dist/commands/list.js.map +1 -1
  86. package/dist/commands/new/marketplace.js +243 -0
  87. package/dist/commands/new/marketplace.js.map +1 -0
  88. package/dist/commands/search.js +16 -14
  89. package/dist/commands/search.js.map +1 -1
  90. package/dist/commands/share.js +67 -0
  91. package/dist/commands/share.js.map +1 -0
  92. package/dist/commands/uninstall.js +61 -74
  93. package/dist/commands/uninstall.js.map +1 -1
  94. package/dist/commands/update.js +9 -9
  95. package/dist/commands/update.js.map +1 -1
  96. package/dist/components/common/prompt-confirm.js +1 -1
  97. package/dist/components/common/removal-plan-confirm.js +10 -0
  98. package/dist/components/common/select-list.js +2 -2
  99. package/dist/components/wizard/category-grid.js +5 -5
  100. package/dist/components/wizard/checkbox-grid.js +5 -5
  101. package/dist/components/wizard/domain-selection.js +11 -11
  102. package/dist/components/wizard/run-wizard-session.js +29 -28
  103. package/dist/components/wizard/scroll-affordance.js +2 -2
  104. package/dist/components/wizard/skill-agent-summary.js +6 -6
  105. package/dist/components/wizard/source-grid.js +6 -6
  106. package/dist/components/wizard/stack-selection.js +8 -8
  107. package/dist/components/wizard/step-agents.js +10 -11
  108. package/dist/components/wizard/step-build.js +9 -9
  109. package/dist/components/wizard/step-confirm.js +10 -10
  110. package/dist/components/wizard/step-sources.js +10 -10
  111. package/dist/components/wizard/step-stack.js +9 -9
  112. package/dist/components/wizard/summary-panel.js +9 -9
  113. package/dist/components/wizard/toast.js +2 -2
  114. package/dist/components/wizard/wizard-layout.js +14 -14
  115. package/dist/components/wizard/wizard-tabs.js +2 -2
  116. package/dist/components/wizard/wizard.js +27 -27
  117. package/dist/copy-local-skills-NSUTBVLJ.js +14 -0
  118. package/dist/hooks/init.js +42 -62
  119. package/dist/hooks/init.js.map +1 -1
  120. package/dist/load-agent-defs-X4E57I2U.js +15 -0
  121. package/dist/{loader-JIZDTR4G.js → loader-G6BFG2VA.js} +3 -3
  122. package/dist/recompile-project-agents-JE4MM72I.js +20 -0
  123. package/dist/{source-loader-OE6EJFPU.js → source-loader-3DL3NYUX.js} +7 -5
  124. package/dist/source-loader-3DL3NYUX.js.map +1 -0
  125. package/dist/src/agents/developer/ai-developer/critical-reminders.md +1 -1
  126. package/dist/src/agents/developer/ai-developer/critical-requirements.md +1 -1
  127. package/dist/src/agents/developer/ai-developer/identity.md +1 -1
  128. package/dist/src/agents/developer/ai-developer/playbook.md +1 -1
  129. package/dist/src/agents/developer/api-developer/identity.md +1 -1
  130. package/dist/src/agents/developer/cli-developer/identity.md +1 -1
  131. package/dist/src/agents/developer/web-developer/identity.md +1 -1
  132. package/dist/src/agents/meta/codex-keeper/identity.md +1 -1
  133. package/dist/src/agents/meta/convention-keeper/identity.md +1 -1
  134. package/dist/src/agents/meta/skill-summoner/identity.md +1 -1
  135. package/dist/src/agents/researcher/ai-researcher/identity.md +1 -1
  136. package/dist/src/agents/researcher/api-researcher/identity.md +1 -1
  137. package/dist/src/agents/researcher/cli-researcher/identity.md +1 -1
  138. package/dist/src/agents/researcher/web-researcher/identity.md +1 -1
  139. package/dist/src/agents/reviewer/reviewer/playbook.md +1 -1
  140. package/dist/src/agents/tester/ai-tester/identity.md +1 -1
  141. package/dist/src/agents/tester/api-tester/critical-reminders.md +1 -1
  142. package/dist/src/agents/tester/api-tester/critical-requirements.md +1 -1
  143. package/dist/src/agents/tester/api-tester/identity.md +2 -2
  144. package/dist/src/agents/tester/cli-tester/identity.md +1 -1
  145. package/dist/src/agents/tester/web-tester/identity.md +3 -3
  146. package/dist/stores/wizard-store.js +7 -5
  147. package/package.json +10 -7
  148. package/src/agents/developer/ai-developer/critical-reminders.md +1 -1
  149. package/src/agents/developer/ai-developer/critical-requirements.md +1 -1
  150. package/src/agents/developer/ai-developer/identity.md +1 -1
  151. package/src/agents/developer/ai-developer/playbook.md +1 -1
  152. package/src/agents/developer/api-developer/identity.md +1 -1
  153. package/src/agents/developer/cli-developer/identity.md +1 -1
  154. package/src/agents/developer/web-developer/identity.md +1 -1
  155. package/src/agents/meta/codex-keeper/identity.md +1 -1
  156. package/src/agents/meta/convention-keeper/identity.md +1 -1
  157. package/src/agents/meta/skill-summoner/identity.md +1 -1
  158. package/src/agents/researcher/ai-researcher/identity.md +1 -1
  159. package/src/agents/researcher/api-researcher/identity.md +1 -1
  160. package/src/agents/researcher/cli-researcher/identity.md +1 -1
  161. package/src/agents/researcher/web-researcher/identity.md +1 -1
  162. package/src/agents/reviewer/reviewer/playbook.md +1 -1
  163. package/src/agents/tester/ai-tester/identity.md +1 -1
  164. package/src/agents/tester/api-tester/critical-reminders.md +1 -1
  165. package/src/agents/tester/api-tester/critical-requirements.md +1 -1
  166. package/src/agents/tester/api-tester/identity.md +2 -2
  167. package/src/agents/tester/cli-tester/identity.md +1 -1
  168. package/src/agents/tester/web-tester/identity.md +3 -3
  169. package/src/schemas/custom-metadata.schema.json +3 -0
  170. package/src/schemas/metadata.schema.json +3 -0
  171. package/src/schemas/project-config.schema.json +4 -8
  172. package/src/schemas/project-source-config.schema.json +2 -2
  173. package/dist/chunk-4VCWL2IG.js.map +0 -1
  174. package/dist/chunk-5MFJ24VM.js.map +0 -1
  175. package/dist/chunk-7VZV4O6B.js.map +0 -1
  176. package/dist/chunk-AURY6RT6.js.map +0 -1
  177. package/dist/chunk-CBF4ZYV3.js.map +0 -1
  178. package/dist/chunk-FMYHWKGG.js.map +0 -1
  179. package/dist/chunk-GUDGCFSF.js.map +0 -1
  180. package/dist/chunk-JC7DBPRD.js.map +0 -1
  181. package/dist/chunk-MJFIO5SX.js.map +0 -1
  182. package/dist/chunk-OJJLYHM3.js +0 -34
  183. package/dist/chunk-OJJLYHM3.js.map +0 -1
  184. package/dist/chunk-PRJD5MMQ.js.map +0 -1
  185. package/dist/chunk-QQTYMW7N.js.map +0 -1
  186. package/dist/chunk-QUDQTO3O.js +0 -41
  187. package/dist/chunk-QUDQTO3O.js.map +0 -1
  188. package/dist/chunk-R54KIGN2.js.map +0 -1
  189. package/dist/chunk-TUQF577Z.js.map +0 -1
  190. package/dist/copy-local-skills-WVHJFRPK.js +0 -14
  191. package/dist/load-agent-defs-5OT54J5M.js +0 -15
  192. package/dist/recompile-project-agents-VREEPBDV.js +0 -20
  193. /package/dist/{chunk-LA3JPESW.js.map → chunk-4A4L3YIW.js.map} +0 -0
  194. /package/dist/{chunk-BZ4CZBOM.js.map → chunk-5F2YR5XV.js.map} +0 -0
  195. /package/dist/{chunk-CHNA5RVS.js.map → chunk-5OKLCVM4.js.map} +0 -0
  196. /package/dist/{chunk-K3O3YQYG.js.map → chunk-G47TDUK3.js.map} +0 -0
  197. /package/dist/{chunk-IXEDSKMH.js.map → chunk-HTYH7CKN.js.map} +0 -0
  198. /package/dist/{chunk-NSJ2S7R5.js.map → chunk-ILUVL7WM.js.map} +0 -0
  199. /package/dist/{chunk-NKCY2ULI.js.map → chunk-LJCNUP6M.js.map} +0 -0
  200. /package/dist/{chunk-Q5FLW4JG.js.map → chunk-OKH64JZE.js.map} +0 -0
  201. /package/dist/{chunk-WRRP47PY.js.map → chunk-PCC4LBKB.js.map} +0 -0
  202. /package/dist/{chunk-2IMYK4WN.js.map → chunk-PNMM5HNX.js.map} +0 -0
  203. /package/dist/{chunk-AKQ5QISO.js.map → chunk-QWVXOCXO.js.map} +0 -0
  204. /package/dist/{chunk-ONWEOS77.js.map → chunk-SY4X2LND.js.map} +0 -0
  205. /package/dist/{chunk-XGMOXMS4.js.map → chunk-TBOVYLQJ.js.map} +0 -0
  206. /package/dist/{chunk-6CNRMCFM.js.map → chunk-VPASBA7P.js.map} +0 -0
  207. /package/dist/{chunk-V3ENMW7G.js.map → chunk-WFRTHH3A.js.map} +0 -0
  208. /package/dist/{chunk-B66SC72V.js.map → chunk-Y5Y7XHVU.js.map} +0 -0
  209. /package/dist/{copy-local-skills-WVHJFRPK.js.map → components/common/removal-plan-confirm.js.map} +0 -0
  210. /package/dist/{load-agent-defs-5OT54J5M.js.map → copy-local-skills-NSUTBVLJ.js.map} +0 -0
  211. /package/dist/{loader-JIZDTR4G.js.map → load-agent-defs-X4E57I2U.js.map} +0 -0
  212. /package/dist/{recompile-project-agents-VREEPBDV.js.map → loader-G6BFG2VA.js.map} +0 -0
  213. /package/dist/{source-loader-OE6EJFPU.js.map → recompile-project-agents-JE4MM72I.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/lib/exit-codes.ts","../src/cli/base-command.ts","../src/cli/lib/operations/source/ensure-marketplace.ts","../src/cli/lib/operations/source/require-marketplace.ts"],"sourcesContent":["export const EXIT_CODES = {\n SUCCESS: 0,\n ERROR: 1,\n INVALID_ARGS: 2,\n NETWORK_ERROR: 3,\n CANCELLED: 4,\n} as const;\n","import { Command } from \"@oclif/core\";\n\nimport chalk from \"chalk\";\nimport { unique } from \"remeda\";\n\nimport { CLI_COLORS, MIN_TERMINAL_SIZE } from \"./consts.js\";\nimport { getErrorMessage } from \"./utils/errors.js\";\nimport {\n configUnreadableError,\n pluginsInstalled,\n propagatedRecompileSummary,\n savedSkillMetadataUnusableError,\n scopeBlockedStackAssignment,\n skillAssignedToNoAgent,\n skillMetadataUnusableDetail,\n STATUS_MESSAGES,\n} from \"./utils/messages.js\";\nimport {\n clearTerminalScreen,\n formatTerminalTooSmallMessage,\n isTerminalLargeEnough,\n} from \"./utils/terminal.js\";\nimport { EXIT_CODES } from \"./lib/exit-codes.js\";\nimport type { ResolvedConfig } from \"./lib/configuration/index.js\";\nimport { isActiveAt } from \"./lib/configuration/scope-predicates.js\";\nimport { getStackSkillIds } from \"./lib/stacks/index.js\";\nimport type {\n AgentName,\n MergedSkillsMatrix,\n ProjectConfig,\n SelectionValidation,\n SkillConfig,\n SkillId,\n} from \"./types/index.js\";\nimport { findConfigLoadFailures } from \"./lib/configuration/project-config.js\";\nimport { findUnusableSavedSkillMetadata } from \"./lib/skills/index.js\";\nimport { requireMarketplace } from \"./lib/operations/source/require-marketplace.js\";\nimport {\n installPluginSkills,\n pluginInstallFailureError,\n type PluginInstallResult,\n} from \"./lib/operations/skills/install-plugin-skills.js\";\nimport type { GateReport } from \"./lib/config-gate/index.js\";\nimport type { SourceLoadResult } from \"./lib/loading/source-loader.js\";\n\n/** Narrow interface for the sourceConfig we attach to oclif's Config in the init hook. */\nexport interface ConfigWithSource {\n sourceConfig?: ResolvedConfig;\n}\n\n/**\n * How often the size gate re-reads the terminal while it is blocked. `resize` is what normally\n * ends the wait; the poll is the belt for the terminals and multiplexers that resize without\n * ever emitting one, and it is the only reason a blocked command is not a hung one there.\n */\nconst TERMINAL_RESIZE_POLL_MS = 500;\n\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- @types/node declares this as `number`; it is undefined whenever stdout is not a TTY\nconst terminalColumns = (): number => process.stdout.columns ?? MIN_TERMINAL_SIZE.COLS;\n\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- @types/node declares this as `number`; it is undefined whenever stdout is not a TTY\nconst terminalRows = (): number => process.stdout.rows ?? MIN_TERMINAL_SIZE.ROWS;\n\nfunction isTerminalBigEnough(): boolean {\n return isTerminalLargeEnough(terminalColumns(), terminalRows());\n}\n\n/**\n * Resolves the moment the terminal satisfies `isSatisfied` — on the resize event where there is\n * one, on the poll where there is not. Both listeners are torn down before resolving, so a\n * command that got past the gate leaves nothing behind holding the process open.\n */\nfunction waitForTerminal(isSatisfied: () => boolean): Promise<void> {\n return new Promise<void>((resolve) => {\n const check = () => {\n if (!isSatisfied()) return;\n clearInterval(interval);\n process.stdout.off(\"resize\", check);\n resolve();\n };\n const interval = setInterval(check, TERMINAL_RESIZE_POLL_MS);\n process.stdout.on(\"resize\", check);\n });\n}\n\n/** A selected skill no sub-agent's stack carries, and the sub-agents the scope rule blocked. */\ntype UnassignedSkill = { skillId: SkillId; blockedBy: AgentName[] };\n\n/**\n * Every active skill of a saved config that no agent's stack references, paired with the\n * global-scoped sub-agents that could not have carried it.\n *\n * `blockedBy` is empty for a global-scoped skill: the scope rule had no say there, and the\n * skill reached nothing for a different reason (no selected sub-agent the resolver\n * considers it relevant to). Naming sub-agents there would blame the wrong rule.\n */\nfunction findUnassignedSkills(config: ProjectConfig): UnassignedSkill[] {\n const assignedIds = new Set(config.stack ? getStackSkillIds(config.stack) : []);\n const globalAgentNames = config.agents\n .filter((agent) => isActiveAt(agent, \"global\"))\n .map((agent) => agent.name);\n\n const unassignedIds = unique(\n config.skills\n .filter((skill) => !skill.excluded)\n .map((skill) => skill.id)\n .filter((skillId) => !assignedIds.has(skillId)),\n );\n\n return unassignedIds.map((skillId) => ({\n skillId,\n blockedBy: hasActiveProjectEntry(config.skills, skillId) ? globalAgentNames : [],\n }));\n}\n\n/** True when an ACTIVE entry for this id sits at project scope — the only case the rule explains. */\nfunction hasActiveProjectEntry(skills: readonly SkillConfig[], skillId: SkillId): boolean {\n return skills.some((skill) => skill.id === skillId && isActiveAt(skill, \"project\"));\n}\n\n/**\n * Nothing is inherited by every command, so this class declares no `baseFlags`.\n *\n * There was one — `--source` — until naming a source became `init`'s decision alone\n * (owner ruling 2026-08-09). Six commands were already opting out of it through an\n * exported empty-object cast; a flag one command wants is that command's flag, so it\n * is declared in `init` and the six opt-outs went with it.\n */\nexport abstract class BaseCommand extends Command {\n async init(): Promise<void> {\n await super.init();\n await this.ensureTerminalSize();\n }\n\n public get sourceConfig(): ResolvedConfig | undefined {\n // Boundary cast: oclif Config is a class (not augmentable); we attach sourceConfig in the init hook\n return (this.config as unknown as ConfigWithSource).sourceConfig;\n }\n\n /**\n * Blocks the command from launching in a terminal below\n * {@link MIN_TERMINAL_SIZE}. This runs once, before Ink mounts; the terminal\n * shrinking mid-session is caught by the matching guard in `WizardLayout`.\n */\n protected async ensureTerminalSize(): Promise<void> {\n if (isTerminalBigEnough()) return;\n\n this.clearTerminal();\n this.log(chalk.hex(CLI_COLORS.WARNING)(formatTerminalTooSmallMessage(terminalColumns())));\n\n await waitForTerminal(isTerminalBigEnough);\n }\n\n protected handleError(error: unknown): never {\n const message = getErrorMessage(error);\n this.error(message, { exit: EXIT_CODES.ERROR });\n }\n\n protected logSuccess(message: string): void {\n this.log(`✓ ${message}`);\n }\n\n protected logWarning(message: string): void {\n this.warn(message);\n }\n\n protected logInfo(message: string): void {\n this.log(message);\n }\n\n protected clearTerminal(): void {\n clearTerminalScreen();\n }\n\n /**\n * Resolves the marketplace required for a plugin operation, or hard-exits with\n * an actionable error when it cannot be resolved. `onRegistered` is invoked\n * (when provided) if the marketplace had to be registered — init uses it to log.\n */\n protected async requireMarketplaceOrExit(\n sourceResult: SourceLoadResult,\n purpose: string,\n onRegistered?: (marketplace: string) => void,\n ): Promise<string> {\n const required = await requireMarketplace(sourceResult, purpose);\n if (!required.ok) {\n this.error(required.error, { exit: EXIT_CODES.ERROR });\n }\n if (required.registered) {\n onRegistered?.(required.marketplace);\n }\n return required.marketplace;\n }\n\n /**\n * Reports every constraint a completed selection breaks — a conflicting pair, an unmet\n * requirement, an exclusive category with two picks.\n *\n * Shared by `init` and `edit` so one selection cannot be reported two ways. Both commands ask\n * the same question of the same matrix, so a roster that draws a warning through one must draw\n * the same warning through the other; `init` used to compute the answer and drop it, which is\n * the whole reason this lives here rather than on either command.\n *\n * These are advisory (`ValidationError` in types/matrix.ts): the selection is installed either\n * way and neither command's exit code turns on them, so they are warnings and not errors. The\n * loop is its own guard — an accepted selection carries no errors and says nothing.\n */\n protected reportValidationErrors(validation: SelectionValidation): void {\n for (const error of validation.errors) {\n this.warn(error.message);\n }\n }\n\n /**\n * Installs skill plugins and narrates the operation.\n *\n * Shared by `init` and `edit` for the same reason as {@link reportValidationErrors}:\n * both run the SAME install against the same marketplace, so a line one prints about\n * it the other owes. `edit` used to run it in silence — a real `claude plugin install`\n * was indistinguishable in the output from nothing having happened.\n */\n protected async installPluginSkillsReported(\n skills: SkillConfig[],\n marketplace: string,\n projectDir: string,\n ): Promise<PluginInstallResult> {\n this.announcePluginInstall();\n const result = await installPluginSkills(skills, marketplace, projectDir);\n this.reportPluginInstalls(result);\n return result;\n }\n\n /**\n * The opening half of the narration above, exposed for the one caller that cannot\n * use the whole of it: an eject→plugin migration installs each plugin and deletes\n * that skill's working copy in the same step (see `executeMigration`), so the\n * install it performs is not this command's to run.\n */\n protected announcePluginInstall(): void {\n this.log(STATUS_MESSAGES.INSTALLING_PLUGINS);\n }\n\n /**\n * The closing half: what was installed, what was not, and the refusal to continue\n * past a failure.\n *\n * The hard-error is part of the report rather than each caller's own postscript\n * because it is the same rule everywhere — plugin install intent is inviolable, so\n * a run that could not honor it must stop BEFORE any config records a marketplace\n * `source` for a skill with no plugin registration.\n */\n protected reportPluginInstalls(result: PluginInstallResult): void {\n for (const item of result.installed) {\n this.log(` Installed ${item.ref}`);\n }\n for (const item of result.failed) {\n this.warn(`Failed to install plugin ${item.id}: ${item.error}`);\n }\n if (result.failed.length > 0) {\n this.error(pluginInstallFailureError(result.failed.length), { exit: EXIT_CODES.ERROR });\n }\n this.log(`${pluginsInstalled(result.installed.length)}\\n`);\n }\n\n /**\n * Renders the recompile a gated write already performed on every OTHER registered\n * project this run's global change was propagated into.\n *\n * Shared by all four commands that fan out — the count and its wording are one\n * statement about one operation, and four copies of it had already drifted into two\n * spellings of the same sentence.\n */\n protected reportPropagatedRecompile(report: GateReport): void {\n if (report.propagated.updated.length === 0) return;\n\n const { rewrittenCount, unchangedCount, failedCount, warnings } = report.recompile;\n for (const warning of warnings) {\n this.warn(warning);\n }\n this.log(propagatedRecompileSummary(rewrittenCount, unchangedCount, failedCount));\n }\n\n /**\n * Names every selected skill this save left in no sub-agent's stack, and — where the\n * scope rule is why — every sub-agent it was kept away from.\n *\n * Shared by `init` and `edit` for the reason {@link reportValidationErrors} is: both\n * build the stack from the same rules over the same selection, so a skill that reaches\n * nothing through one reaches nothing through the other. The rule being reported is\n * CORRECT and nothing here changes it — a project-scoped skill installs, is written to\n * `config.ts`, and is loaded by nobody, which every other surface reports as a clean\n * install.\n *\n * `compile` says the same thing about the same shape one layer over, for a config\n * hand-edited into it — see `Compile.warnScopeDroppedStackPairs`.\n */\n protected reportUnassignedSkills(config: ProjectConfig): void {\n for (const { skillId, blockedBy } of findUnassignedSkills(config)) {\n this.warn(skillAssignedToNoAgent(skillId));\n if (blockedBy.length > 0) {\n this.warn(scopeBlockedStackAssignment(blockedBy, skillId));\n }\n }\n }\n\n /**\n * Refuses to run when a config file exists but cannot be loaded.\n *\n * Both configs this run would read are checked, because both are read on the way through: a\n * project's own `config.ts` and the global one every project write inlines. Checking up\n * front is the whole point — an unreadable GLOBAL config otherwise surfaced only at the\n * config write, after the wizard had copied skills and installed plugins, as a warning that\n * the write never happened.\n *\n * A MISSING config is not a corrupt one and passes here untouched: it is the legitimate\n * state `init` exists for, and the state `edit` reports as \"no installation\".\n */\n protected async ensureConfigReadable(projectDir: string): Promise<void> {\n const [failure] = await findConfigLoadFailures(projectDir);\n if (failure) {\n this.error(configUnreadableError(failure.message), { exit: EXIT_CODES.ERROR });\n }\n }\n\n /**\n * Refuses to run when a saved skill entry names a local skill that IS installed and whose\n * metadata.yaml describes no skill.\n *\n * One layer below {@link ensureConfigReadable} and for the same reason: the config parses,\n * and a file it points at does not. Both wizards resolve their saved roster against the\n * loaded catalogue, and an id nothing can be loaded for reaches no screen — so without this\n * the entry is dropped and reported as a skill the marketplace no longer carries, which is a\n * removal the user never asked for and a sentence blaming the wrong thing.\n *\n * Only entries the catalogue does not carry are looked at, so a healthy install pays a\n * filesystem read for nothing. An entry whose files are simply GONE is not refused — there\n * is no file to repair, so it is removed with a reason that says so\n * (`unresolvedSkillRemovalReasons`).\n *\n * Runs before the wizard mounts, which is the whole point: past that, a refusal costs the\n * user the session they had already spent, and everything this protects has been rewritten.\n */\n protected async ensureSavedSkillsReadable(\n savedSkills: readonly SkillConfig[],\n matrix: MergedSkillsMatrix,\n projectDir: string,\n ): Promise<void> {\n const unresolved = savedSkills\n .filter((saved) => !saved.excluded && !matrix.skills[saved.id])\n .map((saved) => saved.id);\n if (unresolved.length === 0) return;\n\n const unusable = await findUnusableSavedSkillMetadata(unresolved, savedSkills, projectDir);\n if (unusable.length === 0) return;\n\n // The detail is LOGGED and only the refusal is raised — oclif hard-wraps error text at the\n // terminal width, and a path broken across two lines is one nobody can copy. Same split\n // `compile` makes over the same verdict.\n for (const entry of unusable) {\n this.log(skillMetadataUnusableDetail(entry));\n }\n this.error(savedSkillMetadataUnusableError(unusable), { exit: EXIT_CODES.ERROR });\n }\n}\n","import {\n claudePluginMarketplaceExists,\n claudePluginMarketplaceAdd,\n claudePluginMarketplaceUpdate,\n} from \"../../../utils/exec.js\";\nimport { fetchMarketplace } from \"../../loading/index.js\";\nimport { warn } from \"../../../utils/logger.js\";\nimport type { SourceLoadResult } from \"../../loading/source-loader.js\";\n\nexport type MarketplaceResult = {\n /** The resolved marketplace name, or null if no marketplace is configured. */\n marketplace: string | null;\n /** Whether a new marketplace was registered (vs. updated or already existed). */\n registered: boolean;\n};\n\n/**\n * Ensures the marketplace is registered with the Claude CLI.\n *\n * If the marketplace does not exist, registers it. If it exists, updates it.\n * Handles lazy marketplace name resolution when sourceResult.marketplace is undefined.\n *\n * Operation is intentionally SILENT — commands decide what to log based on the\n * `registered` flag.\n */\nexport async function ensureMarketplace(\n sourceResult: SourceLoadResult,\n): Promise<MarketplaceResult> {\n if (!sourceResult.marketplace) {\n try {\n const marketplaceResult = await fetchMarketplace(sourceResult.sourceConfig.source);\n sourceResult.marketplace = marketplaceResult.marketplace.name;\n } catch {\n return { marketplace: null, registered: false };\n }\n }\n\n const marketplace = sourceResult.marketplace;\n const exists = await claudePluginMarketplaceExists(marketplace);\n\n if (!exists) {\n const marketplaceSource = sourceResult.sourceConfig.source.replace(/^github:/, \"\");\n await claudePluginMarketplaceAdd(marketplaceSource);\n return { marketplace, registered: true };\n }\n\n try {\n await claudePluginMarketplaceUpdate(marketplace);\n } catch {\n warn(\"Could not update marketplace — continuing with cached version\");\n }\n\n return { marketplace, registered: false };\n}\n","import { ensureMarketplace } from \"./ensure-marketplace.js\";\nimport type { SourceLoadResult } from \"../../loading/source-loader.js\";\n\nexport type MarketplaceRequirement =\n { ok: true; marketplace: string; registered: boolean } | { ok: false; error: string };\n\n/**\n * Resolves the marketplace required for plugin operations. Plugin install\n * intent is inviolable — callers must hard-error on `ok: false` rather than\n * silently falling back to eject or skipping, and must do so BEFORE any\n * filesystem mutation so a failure leaves no partial state on disk.\n */\nexport async function requireMarketplace(\n sourceResult: SourceLoadResult,\n purpose: string,\n): Promise<MarketplaceRequirement> {\n const mpResult = await ensureMarketplace(sourceResult);\n if (!mpResult.marketplace) {\n return {\n ok: false,\n error:\n `Cannot ${purpose}: marketplace could not be resolved from source '${sourceResult.sourceConfig.source}'. ` +\n `Plugin install mode requires a marketplace — fix the source or switch the affected skills to eject mode.`,\n };\n }\n return { ok: true, marketplace: mpResult.marketplace, registered: mpResult.registered };\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/step-agents.tsx"],"sourcesContent":["import { groupBy, unique } from \"remeda\";\nimport { Box, Text, useInput } from \"ink\";\nimport { deriveScopeBadges, formatScopeTag } from \"../../lib/wizard/index.js\";\nimport React, { useMemo, useState } from \"react\";\nimport { CLI_COLORS, UI_SYMBOLS } from \"../../consts.js\";\nimport { matrix } from \"../../lib/matrix/matrix-provider.js\";\nimport { useWizardStore } from \"../../stores/wizard-store.js\";\nimport type { AgentName, MergedSkillsMatrix } from \"../../types/index.js\";\nimport { isAgentName } from \"../../utils/type-guards.js\";\nimport { typedKeys } from \"../../utils/typed-object.js\";\nimport { toTitleCase } from \"../../utils/string.js\";\nimport { useMeasuredHeight } from \"../hooks/use-measured-height.js\";\nimport { useRowScroll } from \"../hooks/use-row-scroll.js\";\nimport { type CheckboxItem } from \"./checkbox-grid.js\";\nimport { KEY_SPACE } from \"./hotkeys.js\";\nimport { getDomainDisplayName } from \"./utils.js\";\n\ntype AgentItem = CheckboxItem<AgentName>;\n\ntype AgentGroup = {\n label: string;\n items: AgentItem[];\n};\n\nconst BUILT_IN_AGENT_GROUPS: AgentGroup[] = [\n {\n label: \"Web\",\n items: [\n {\n id: \"web-developer\",\n label: \"Web Developer\",\n description: \"Frontend features, components, TypeScript\",\n },\n { id: \"web-researcher\", label: \"Web Researcher\", description: \"Frontend pattern discovery\" },\n {\n id: \"web-tester\",\n label: \"Web Tester\",\n description: \"Frontend tests, E2E, component tests\",\n },\n ],\n },\n {\n label: \"API\",\n items: [\n {\n id: \"api-developer\",\n label: \"API Developer\",\n description: \"Backend routes, database, middleware\",\n },\n { id: \"api-researcher\", label: \"API Researcher\", description: \"Backend pattern discovery\" },\n {\n id: \"api-tester\",\n label: \"API Tester\",\n description: \"Endpoint, database, and auth flow tests\",\n },\n ],\n },\n {\n label: \"AI\",\n items: [\n {\n id: \"ai-developer\",\n label: \"AI Developer\",\n description: \"RAG pipelines, agent loops, tool calling\",\n },\n {\n id: \"ai-researcher\",\n label: \"AI Researcher\",\n description: \"Prompt, model, and RAG pipeline discovery\",\n },\n {\n id: \"ai-tester\",\n label: \"AI Tester\",\n description: \"LLM mocking, prompt regression, eval harnesses\",\n },\n ],\n },\n {\n label: \"CLI\",\n items: [\n {\n id: \"cli-developer\",\n label: \"CLI Developer\",\n description: \"CLI commands, interactive prompts\",\n },\n { id: \"cli-tester\", label: \"CLI Tester\", description: \"CLI application tests\" },\n {\n id: \"cli-researcher\",\n label: \"CLI Researcher\",\n description: \"CLI command and config pattern discovery\",\n },\n ],\n },\n {\n label: \"Meta\",\n items: [\n {\n id: \"pm\",\n label: \"PM\",\n description: \"Cross-domain implementation specs; domain frameworks via skills\",\n },\n {\n id: \"reviewer\",\n label: \"Reviewer\",\n description: \"Cross-domain code review; domain knowledge via skills\",\n },\n { id: \"agent-summoner\", label: \"Agent Summoner\", description: \"Create and improve agents\" },\n {\n id: \"skill-summoner\",\n label: \"Skill Summoner\",\n description: \"Create technology-specific skills\",\n },\n { id: \"codex-keeper\", label: \"Codex Keeper\", description: \"AI-focused documentation\" },\n ],\n },\n];\n\n/** IDs of all built-in agents for fast lookup. */\nconst BUILT_IN_AGENT_IDS = new Set<string>(\n BUILT_IN_AGENT_GROUPS.flatMap((group) => group.items.map((a) => a.id)),\n);\n\ntype FocusId = AgentName | \"continue\";\n\ntype ListRow =\n { type: \"header\"; label: string } | { type: \"spacer\" } | { type: \"agent\"; agent: AgentItem };\n\nfunction buildAgentGroups(matrix: MergedSkillsMatrix): AgentGroup[] {\n const customAgentIds: string[] = unique(\n matrix.suggestedStacks.flatMap((stack) => typedKeys(stack.skills)),\n ).filter((agentName) => !BUILT_IN_AGENT_IDS.has(agentName));\n\n if (customAgentIds.length === 0) return BUILT_IN_AGENT_GROUPS;\n\n // Group custom agents by explicit domain (from metadata.yaml) or kebab prefix fallback\n const customItems = customAgentIds.map((agentId) => {\n const explicitDomain = isAgentName(agentId) ? matrix.agentDefinedDomains?.[agentId] : undefined;\n const domainKey = explicitDomain ?? (agentId.split(\"-\")[0] || \"custom\");\n return {\n groupLabel: getDomainDisplayName(domainKey),\n item: {\n // Boundary cast: custom agent names from marketplace stacks are not in the AgentName union\n id: agentId as AgentName,\n label: toTitleCase(agentId),\n description: \"Custom agent\",\n },\n };\n });\n const customGroups: AgentGroup[] = Object.entries(groupBy(customItems, (c) => c.groupLabel)).map(\n ([label, entries]) => ({ label, items: entries.map((entry) => entry.item) }),\n );\n\n return [...BUILT_IN_AGENT_GROUPS, ...customGroups];\n}\n\nfunction buildFlatRows(groups: AgentGroup[]): ListRow[] {\n return groups.flatMap((group, groupIndex): ListRow[] => [\n ...(groupIndex > 0 ? [{ type: \"spacer\" as const }] : []),\n { type: \"header\" as const, label: group.label },\n ...group.items.map((agent): ListRow => ({ type: \"agent\", agent })),\n ]);\n}\n\nfunction buildFocusableIds(groups: AgentGroup[]): FocusId[] {\n return [...groups.flatMap((group) => group.items.map((a) => a.id)), \"continue\"];\n}\n\n/**\n * Asserting row lookup. `buildFocusableIds` always appends \"continue\", so the\n * list is never empty and every caller below wraps within it — a miss means the\n * wrap arithmetic is wrong, which is worth a diagnostic rather than a focus that\n * silently stops moving.\n */\nfunction focusableIdAt(ids: FocusId[], index: number): FocusId {\n const id = ids[index];\n if (id === undefined) throw new Error(`No focusable row at index ${index} of ${ids.length}`);\n return id;\n}\n\nexport const StepAgents: React.FC = () => {\n const selectedAgents = useWizardStore((s) => s.selectedAgents);\n const agentConfigs = useWizardStore((s) => s.agentConfigs);\n\n const agentGroups = useMemo(() => buildAgentGroups(matrix), []);\n const flatRows = useMemo(() => buildFlatRows(agentGroups), [agentGroups]);\n const focusableIds = useMemo(() => buildFocusableIds(agentGroups), [agentGroups]);\n\n const [focusedId, setFocusedId] = useState<FocusId>(() => {\n const stored = useWizardStore.getState().focusedAgentId;\n return stored && focusableIds.includes(stored) ? stored : focusableIdAt(focusableIds, 0);\n });\n const { ref: listRef, measuredHeight: listHeight } = useMeasuredHeight();\n\n const focusedRowIndex =\n focusedId !== \"continue\"\n ? flatRows.findIndex((row) => row.type === \"agent\" && row.agent.id === focusedId)\n : -1;\n // The Agents grid clips silently: `useRowScroll` exposes no hidden-line counts\n // and this view renders no `ScrollAffordance`, by owner decision — see the\n // hook's doc comment.\n const { scrollEnabled, scrollTop } = useRowScroll({\n focusedIndex: Math.max(0, focusedRowIndex),\n itemCount: flatRows.length,\n availableHeight: listHeight,\n });\n\n useInput((input, key) => {\n if (key.escape) {\n useWizardStore.getState().goBack();\n return;\n }\n\n const currentIdx = focusableIds.indexOf(focusedId);\n\n if (key.upArrow || input === \"k\") {\n const nextIdx = currentIdx <= 0 ? focusableIds.length - 1 : currentIdx - 1;\n setFocusedId(focusableIdAt(focusableIds, nextIdx));\n return;\n }\n\n if (key.downArrow || input === \"j\") {\n const nextIdx = currentIdx >= focusableIds.length - 1 ? 0 : currentIdx + 1;\n setFocusedId(focusableIdAt(focusableIds, nextIdx));\n return;\n }\n\n if (key.return) {\n useWizardStore.getState().setStep(\"confirm\");\n return;\n }\n\n if (input === KEY_SPACE && focusedId !== \"continue\") {\n useWizardStore.getState().toggleAgent(focusedId);\n }\n });\n\n // Sync focusedAgentId so the wizard-level S key handler can toggle agent scope\n React.useEffect(() => {\n useWizardStore.getState().setFocusedAgentId(focusedId === \"continue\" ? null : focusedId);\n }, [focusedId]);\n\n const focusedGroupLabel = agentGroups.find((g) => g.items.some((a) => a.id === focusedId))?.label;\n\n const selectedCount = selectedAgents.length;\n const continueLabel =\n selectedCount > 0 ? `Continue with ${selectedCount} agent(s)` : \"Continue without agents\";\n\n const isContinueFocused = focusedId === \"continue\";\n\n const rowElements = flatRows.map((row, index) => {\n switch (row.type) {\n case \"header\":\n return (\n <Box key={`header-${row.label}`} flexShrink={0}>\n {row.label === focusedGroupLabel ? (\n <Text\n color={CLI_COLORS.WHITE}\n backgroundColor={CLI_COLORS.LABEL_BG}\n >{` ${row.label} `}</Text>\n ) : (\n <Text dimColor bold>\n {\" \"}\n {row.label}\n </Text>\n )}\n </Box>\n );\n case \"spacer\":\n return (\n <Box key={`spacer-${index}`} flexShrink={0}>\n <Text> </Text>\n </Box>\n );\n case \"agent\": {\n const isFocused = row.agent.id === focusedId;\n const isSelected = selectedAgents.includes(row.agent.id);\n const checkbox = isSelected ? \"[\\u2713]\" : \"[ ]\";\n const pointer = isFocused ? UI_SYMBOLS.CHEVRON : UI_SYMBOLS.CHEVRON_SPACER;\n const agentConfig = agentConfigs.find((ac) => ac.name === row.agent.id && !ac.excluded);\n const excludedConfig = agentConfigs.find((ac) => ac.name === row.agent.id && ac.excluded);\n const badges = deriveScopeBadges(agentConfig, excludedConfig);\n const scope = badges.scope ?? \"global\";\n const secondaryScope = badges.secondaryScope;\n return (\n <Box key={row.agent.id} flexShrink={0}>\n <Text>\n <Text {...(isFocused && { color: CLI_COLORS.PRIMARY })}>{pointer}</Text>\n <Text\n {...((isSelected || isFocused) && { color: CLI_COLORS.PRIMARY })}\n bold={isFocused}\n >\n {\" \"}\n {checkbox}{\" \"}\n </Text>\n <Text color={scope === \"global\" ? CLI_COLORS.WARNING : CLI_COLORS.TOAST_BG}>\n {formatScopeTag(scope)}\n </Text>\n {secondaryScope && (\n <Text\n color={secondaryScope === \"global\" ? CLI_COLORS.WARNING : CLI_COLORS.TOAST_BG}\n >\n {formatScopeTag(secondaryScope)}\n </Text>\n )}\n <Text\n {...((isSelected || isFocused) && { color: CLI_COLORS.PRIMARY })}\n bold={isFocused}\n >\n {\" \"}\n {row.agent.label}\n </Text>\n <Text dimColor> {row.agent.description}</Text>\n </Text>\n </Box>\n );\n }\n default: {\n const _exhaustive: never = row;\n return _exhaustive;\n }\n }\n });\n\n return (\n <Box flexDirection=\"column\" width=\"100%\" flexGrow={1} flexBasis={0}>\n <Box ref={listRef} flexDirection=\"column\" flexGrow={1} flexBasis={0}>\n <Box\n flexDirection=\"column\"\n flexGrow={1}\n {...(scrollEnabled && { overflow: \"hidden\" as const })}\n >\n <Box\n flexDirection=\"column\"\n marginTop={scrollTop > 0 ? -scrollTop : 0}\n {...(scrollEnabled && { flexShrink: 0 })}\n >\n {rowElements}\n </Box>\n </Box>\n </Box>\n\n <Text {...(isContinueFocused && { color: CLI_COLORS.PRIMARY })} bold={isContinueFocused}>\n {isContinueFocused ? UI_SYMBOLS.CHEVRON : UI_SYMBOLS.CHEVRON_SPACER} {\"\\u2192\"}{\" \"}\n {continueLabel}\n </Text>\n </Box>\n );\n};\n"],"mappings":"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","names":["matrix"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/wizard-tabs.tsx"],"sourcesContent":["import { Box, Text } from \"ink\";\nimport React from \"react\";\nimport { CLI_COLORS } from \"../../consts.js\";\nimport type { WizardStep } from \"../../stores/wizard-store.js\";\nimport type { Domain } from \"../../types/index.js\";\n\ntype WizardTabStep = {\n id: WizardStep;\n label: string;\n};\n\nexport type TabDropdownItem = {\n id: string;\n label: string;\n};\n\nexport type TabDropdownProps = {\n items: TabDropdownItem[];\n activeId?: string | undefined;\n isSubNav?: boolean | undefined;\n inline?: boolean | undefined;\n};\n\nexport type DomainNavProps = {\n domains: Domain[];\n activeDomain: Domain;\n getDomainLabel: (domain: Domain) => string;\n};\n\nexport type WizardTabsProps = {\n steps: WizardTabStep[];\n currentStep: WizardStep;\n completedSteps: WizardStep[];\n skippedSteps?: WizardStep[] | undefined;\n version?: string | undefined;\n domainNav?: DomainNavProps | undefined;\n dropdowns?: Partial<Record<WizardStep, TabDropdownProps>>;\n};\n\n/** Display label per wizard step. Step ORDER has a single source: WIZARD_STEP_ORDER. */\nconst WIZARD_STEP_LABELS = {\n stack: \"Stack\",\n domains: \"Domains\",\n build: \"Skills\",\n sources: \"Sources\",\n agents: \"Agents\",\n confirm: \"Confirm\",\n} as const satisfies Record<WizardStep, string>;\n\n/**\n * The tabs for a step flow — one per step that flow runs, in its order.\n *\n * A function of the flow rather than a constant over `WIZARD_STEP_ORDER`: a\n * source that ships no stacks gives the wizard no stack step (see\n * `getActiveStepFlow`), and a bar drawn from every step the wizard HAS would\n * paint a Stack tab for a step this run never opens.\n */\nexport function wizardTabsFor(flow: WizardStep[]): WizardTabStep[] {\n return flow.map((id) => ({ id, label: WIZARD_STEP_LABELS[id] }));\n}\n\n/** Tab label for a wizard step. */\nexport function formatStepLabel(stepId: WizardStep): string {\n return WIZARD_STEP_LABELS[stepId];\n}\n\ntype StepState = \"completed\" | \"current\" | \"pending\" | \"skipped\";\n\nconst getStepState = (\n stepId: WizardStep,\n currentStep: WizardStep,\n completedSteps: WizardStep[],\n skippedSteps: WizardStep[],\n): StepState => {\n if (completedSteps.includes(stepId)) return \"completed\";\n if (stepId === currentStep) return \"current\";\n if (skippedSteps.includes(stepId)) return \"skipped\";\n return \"pending\";\n};\n\ntype TabProps = {\n step: WizardTabStep;\n state: StepState;\n};\n\nconst Tab: React.FC<TabProps> = ({ step, state }) => {\n const label = step.label;\n\n switch (state) {\n case \"current\":\n return (\n <Text color={CLI_COLORS.BLACK} backgroundColor={CLI_COLORS.WARNING} bold>\n {\" \"}\n {label}{\" \"}\n </Text>\n );\n case \"completed\":\n return <Text>{label}</Text>;\n case \"skipped\":\n return <Text dimColor>{label}</Text>;\n case \"pending\":\n return <Text color={CLI_COLORS.UNFOCUSED}>{label}</Text>;\n default: {\n const _exhaustive: never = state;\n return _exhaustive;\n }\n }\n};\n\nconst TabDropdown: React.FC<TabDropdownProps> = ({ items, activeId, isSubNav = false, inline }) => (\n <Box\n {...(inline ? {} : { position: \"absolute\" as const, marginTop: 2 })}\n flexDirection=\"row\"\n columnGap={0}\n paddingLeft={1}\n paddingRight={1}\n borderStyle=\"single\"\n borderColor=\"blackBright\"\n flexWrap=\"wrap\"\n >\n {items.map((item) => {\n const isActive = item.id === activeId;\n return (\n <Text\n key={item.id}\n dimColor={!isSubNav}\n bold={isSubNav && isActive}\n color={isActive ? CLI_COLORS.WARNING : CLI_COLORS.UNFOCUSED}\n >\n {\" \"}\n {item.label}{\" \"}\n </Text>\n );\n })}\n </Box>\n);\n\nconst DomainNav: React.FC<DomainNavProps & { inline?: boolean }> = ({\n domains,\n activeDomain,\n getDomainLabel,\n inline,\n}) => (\n <TabDropdown\n items={domains.map((d) => ({ id: d, label: getDomainLabel(d) }))}\n activeId={activeDomain}\n isSubNav\n inline={inline}\n />\n);\n\nconst DOMAIN_NAV_CHAR_THRESHOLD = 25;\n\nconst getStepJustifyContent = (stepId: WizardStep): \"flex-start\" | \"flex-end\" | \"center\" => {\n if (stepId === \"stack\") return \"flex-start\";\n if (stepId === \"confirm\") return \"flex-end\";\n return \"center\";\n};\n\nconst isDomainNavOverThreshold = (domainNav: DomainNavProps): boolean =>\n domainNav.domains.reduce((sum, d) => sum + domainNav.getDomainLabel(d).length, 0) >\n DOMAIN_NAV_CHAR_THRESHOLD;\n\nexport const WizardTabs: React.FC<WizardTabsProps> = ({\n steps,\n currentStep,\n completedSteps,\n skippedSteps = [],\n version,\n domainNav,\n dropdowns,\n}) => {\n const shouldHoistDomainNav = domainNav ? isDomainNavOverThreshold(domainNav) : false;\n\n return (\n <>\n <Box\n flexDirection=\"row\"\n columnGap={2}\n borderColor=\"blackBright\"\n borderStyle=\"single\"\n paddingX={1}\n alignItems=\"center\"\n marginBottom={shouldHoistDomainNav ? 0 : 3}\n >\n {steps.map((step) => {\n const state = getStepState(step.id, currentStep, completedSteps, skippedSteps);\n\n if (step.id === \"build\" && domainNav && !shouldHoistDomainNav) {\n return (\n <Box\n key={step.id}\n position=\"relative\"\n display=\"flex\"\n justifyContent={getStepJustifyContent(step.id)}\n >\n <Tab step={step} state={state} />\n <DomainNav {...domainNav} />\n </Box>\n );\n }\n\n // Generic dropdown for any step\n const dropdown = dropdowns?.[step.id];\n if (dropdown) {\n return (\n <Box\n key={step.id}\n position=\"relative\"\n display=\"flex\"\n justifyContent={getStepJustifyContent(step.id)}\n >\n <Tab step={step} state={state} />\n <TabDropdown {...dropdown} />\n </Box>\n );\n }\n\n return <Tab key={step.id} step={step} state={state} />;\n })}\n <Box flexGrow={1} justifyContent=\"flex-end\">\n <Text dimColor>{`v${version}`}</Text>\n </Box>\n </Box>\n {domainNav && shouldHoistDomainNav && (\n <Box>\n <DomainNav {...domainNav} inline />\n </Box>\n )}\n </>\n );\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/wizard-layout.tsx","../src/cli/components/hooks/use-terminal-dimensions.ts"],"sourcesContent":["import { Box, Text } from \"ink\";\nimport React, { Fragment } from \"react\";\nimport { CLI_COLORS, FALLBACK_DOMAIN, LOGO_MIN_TERMINAL_ROWS } from \"../../consts.js\";\nimport type { StartupMessage } from \"../../utils/logger.js\";\nimport { formatTerminalTooSmallMessage, isTerminalLargeEnough } from \"../../utils/terminal.js\";\nimport { getActiveStepFlow, useWizardStore, type WizardStep } from \"../../stores/wizard-store.js\";\nimport { useTerminalDimensions } from \"../hooks/use-terminal-dimensions.js\";\nimport { SummaryPanel } from \"./summary-panel.js\";\nimport { Toast } from \"./toast.js\";\nimport {\n HOTKEY_INFO,\n HOTKEY_SCOPE,\n HOTKEY_TOGGLE_LABELS,\n KEY_LABEL_ENTER,\n KEY_LABEL_ESC,\n KEY_LABEL_SPACE,\n isInfoPanelAvailable,\n} from \"./hotkeys.js\";\nimport {\n WizardTabs,\n wizardTabsFor,\n type DomainNavProps,\n type TabDropdownProps,\n} from \"./wizard-tabs.js\";\nimport { getDomainDisplayName, getStackName, orderDomains } from \"./utils.js\";\n\ntype KeyHintProps = {\n isVisible?: boolean;\n label: string;\n values: readonly string[];\n};\n\nconst DefinitionItem: React.FC<KeyHintProps> = ({ isVisible = true, label, values }) => {\n if (!isVisible) {\n return null;\n }\n\n return (\n <Text>\n {values.map((value) => (\n <Fragment key={value}>\n <Text backgroundColor={CLI_COLORS.LABEL_BG} color={CLI_COLORS.UNFOCUSED}>\n {\" \"}\n {value}{\" \"}\n </Text>{\" \"}\n </Fragment>\n ))}\n <Text>{label}</Text>\n </Text>\n );\n};\n\nconst HOT_KEYS = [\n { label: \"select\", values: [KEY_LABEL_SPACE] },\n { label: \"continue\", values: [KEY_LABEL_ENTER] },\n { label: \"back\", values: [KEY_LABEL_ESC] },\n] as const satisfies readonly { label: string; values: readonly string[] }[];\n\nconst WizardFooter = () => {\n return (\n <Box\n columnGap={2}\n borderTop\n borderRight={false}\n borderBottom\n borderLeft={false}\n borderColor=\"blackBright\"\n borderStyle=\"single\"\n paddingLeft={1}\n paddingRight={1}\n >\n {HOT_KEYS.map((hotkey) => (\n <DefinitionItem {...hotkey} key={hotkey.label} />\n ))}\n </Box>\n );\n};\n\ntype WizardLayoutProps = {\n version?: string | undefined;\n logo?: string | undefined;\n startupMessages?: StartupMessage[] | undefined;\n children: React.ReactNode;\n};\n\n/**\n * How many buffered messages the band paints before it counts the rest instead.\n * A source that cannot resolve its own relationships warns once per unresolved\n * name — thousands, for a source with a broken `skill-rules.ts` — and every line\n * is a row taken from the step below it.\n *\n * Two budgets, because a row is worth more at some heights than others: a\n * terminal short enough that the logo is already dropped for starving the\n * content cannot spare four rows for news either, and there it paints the first\n * message and counts the rest. The first is the one that matters — the fetch\n * speaks before anything is parsed, so an unreachable source is message one.\n */\nconst MAX_PAINTED_STARTUP_MESSAGES = 3;\nconst MAX_PAINTED_STARTUP_MESSAGES_CRAMPED = 1;\n\nfunction paintedStartupMessageCount(terminalHeight: number): number {\n return terminalHeight >= LOGO_MIN_TERMINAL_ROWS\n ? MAX_PAINTED_STARTUP_MESSAGES\n : MAX_PAINTED_STARTUP_MESSAGES_CRAMPED;\n}\n\nconst STARTUP_MESSAGE_COLOR = {\n info: CLI_COLORS.NEUTRAL,\n warn: CLI_COLORS.WARNING,\n error: CLI_COLORS.ERROR,\n} as const satisfies Record<StartupMessage[\"level\"], string>;\n\n/**\n * What the load said before Ink took the terminal.\n *\n * `warn()` writes to stderr, which the wizard's own `clearTerminal` would wipe,\n * so a load that opens a wizard buffers instead (`lib/operations/source/load-source.ts`)\n * and hands the buffer here. This band is the only place those lines are ever\n * seen — the source-unreachable warning among them.\n *\n * It sits above the step rather than over it, and does not shrink: a warning\n * compressed by Yoga overprints into an unreadable row, which is worse than the\n * row it would have cost the step.\n */\nconst StartupMessages: React.FC<{ messages: StartupMessage[]; terminalHeight: number }> = ({\n messages,\n terminalHeight,\n}) => {\n if (messages.length === 0) return null;\n\n const painted = messages.slice(0, paintedStartupMessageCount(terminalHeight));\n const counted = messages.length - painted.length;\n\n return (\n <Box flexDirection=\"column\" flexShrink={0} paddingX={1}>\n {painted.map((message, index) => (\n <Text key={index} color={STARTUP_MESSAGE_COLOR[message.level]}>\n {message.text}\n </Text>\n ))}\n {counted > 0 && <Text color={CLI_COLORS.NEUTRAL}>{`... and ${counted} more`}</Text>}\n </Box>\n );\n};\n\nconst STEP_DROPDOWN_LABEL: Partial<Record<WizardStep, string>> = {\n stack: \"Choose a stack\",\n domains: \"Select domains\",\n sources: \"Customize skill sources\",\n agents: \"Select agents\",\n};\n\n/**\n * What the wizard paints instead of itself while the terminal is below\n * `MIN_TERMINAL_SIZE` (consts.ts). It REPLACES the wizard tree rather than\n * covering it: Ink lays a still-mounted tree out at the small size regardless of\n * what is drawn on top, so an overlay leaves the squeezed content bleeding\n * underneath.\n */\nconst TerminalTooSmall: React.FC<{ columns: number }> = ({ columns }) => (\n <Box paddingX={1} paddingY={1}>\n <Text color={CLI_COLORS.WARNING}>{formatTerminalTooSmallMessage(columns)}</Text>\n </Box>\n);\n\nfunction resolveDropdownLabel(\n step: WizardStep,\n selectedStackId: string | null,\n): string | undefined {\n if (step === \"confirm\") {\n const stackName = getStackName(selectedStackId);\n return stackName ? `Ready to install ${stackName}` : \"Ready to install your custom stack\";\n }\n return STEP_DROPDOWN_LABEL[step];\n}\n\nexport const WizardLayout: React.FC<WizardLayoutProps> = ({\n version,\n logo,\n startupMessages = [],\n children,\n}) => {\n const store = useWizardStore();\n const { columns: terminalWidth, rows: terminalHeight } = useTerminalDimensions();\n\n // The startup gate cannot catch a terminal that shrinks after launch, and\n // `useTerminalDimensions` already re-renders on resize — so the same check\n // lives here, on the value that hook returns.\n if (!isTerminalLargeEnough(terminalWidth, terminalHeight)) {\n return <TerminalTooSmall columns={terminalWidth} />;\n }\n\n const { completedSteps, skippedSteps } = store.getStepProgress();\n\n const domainNav: DomainNavProps | undefined =\n store.step === \"build\" && store.selectedDomains.length > 0\n ? {\n domains: orderDomains(store.selectedDomains),\n activeDomain: store.getCurrentDomain() || store.selectedDomains[0] || FALLBACK_DOMAIN,\n getDomainLabel: getDomainDisplayName,\n }\n : undefined;\n\n // The logo is decoration; the stack list is the content. Below the threshold\n // its six rows starve that list's viewport until the shared scroll gate stops\n // clipping and the rows bleed over the footer.\n const terminalHasRoomForLogo = terminalHeight >= LOGO_MIN_TERMINAL_ROWS;\n const shouldRenderLogo = !!logo && store.step === \"stack\" && terminalHasRoomForLogo;\n\n const dropdownLabel = resolveDropdownLabel(store.step, store.selectedStackId);\n const dropdowns: Partial<Record<WizardStep, TabDropdownProps>> = dropdownLabel\n ? { [store.step]: { items: [{ id: dropdownLabel, label: dropdownLabel }] } }\n : {};\n\n return (\n <Box flexDirection=\"column\" paddingX={1} height={terminalHeight}>\n {shouldRenderLogo && (\n <Box flexDirection=\"row\" marginTop={1} columnGap={1}>\n <Text>{logo}</Text>\n </Box>\n )}\n <WizardTabs\n steps={wizardTabsFor(getActiveStepFlow())}\n currentStep={store.step}\n completedSteps={completedSteps}\n skippedSteps={skippedSteps}\n version={version}\n domainNav={domainNav}\n dropdowns={dropdowns}\n />\n <StartupMessages messages={startupMessages} terminalHeight={terminalHeight} />\n {store.showInfo ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} flexBasis={0} marginTop={1}>\n <SummaryPanel />\n </Box>\n <WizardFooter />\n </>\n ) : (\n <>\n <Box flexDirection=\"column\" flexGrow={1} flexBasis={0} marginTop={1}>\n {children}\n </Box>\n <Box paddingX={1} columnGap={2} marginTop={2}>\n <DefinitionItem\n label=\"Labels\"\n values={[HOTKEY_TOGGLE_LABELS.label]}\n isVisible={store.step === \"build\"}\n />\n <DefinitionItem\n label=\"Scope\"\n values={[HOTKEY_SCOPE.label]}\n isVisible={\n (store.step === \"build\" || store.step === \"agents\") &&\n !store.isEditingFromGlobalScope\n }\n />\n <DefinitionItem\n label=\"Info\"\n values={[HOTKEY_INFO.label]}\n isVisible={isInfoPanelAvailable(store.step)}\n />\n </Box>\n <WizardFooter />\n {store.toastMessage && (\n <Box position=\"absolute\" marginTop={terminalHeight - 4}>\n <Toast>{store.toastMessage}</Toast>\n </Box>\n )}\n </>\n )}\n </Box>\n );\n};\n","import { useState, useEffect } from \"react\";\nimport { useStdout } from \"ink\";\n\nconst DEFAULT_COLUMNS = 80;\nconst DEFAULT_ROWS = 24;\n\nexport type TerminalDimensions = {\n /** Terminal width in columns */\n columns: number;\n /** Terminal height in rows */\n rows: number;\n};\n\n/**\n * Tracks terminal dimensions reactively. Re-renders on resize.\n *\n * Falls back to DEFAULT_COLUMNS x DEFAULT_ROWS when stdout is not a TTY\n * (e.g., piped output, CI environments, tests).\n */\nexport function useTerminalDimensions(): TerminalDimensions {\n const { stdout } = useStdout();\n\n const [dimensions, setDimensions] = useState<TerminalDimensions>(() => ({\n columns: stdout.columns || DEFAULT_COLUMNS,\n rows: stdout.rows || DEFAULT_ROWS,\n }));\n\n useEffect(() => {\n const handleResize = () => {\n setDimensions({\n columns: stdout.columns || DEFAULT_COLUMNS,\n rows: stdout.rows || DEFAULT_ROWS,\n });\n };\n\n stdout.on(\"resize\", handleResize);\n return () => {\n stdout.off(\"resize\", handleResize);\n };\n }, [stdout]);\n\n return dimensions;\n}\n"],"mappings":"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","names":["Fragment"]}
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- render
4
- } from "./chunk-JFLVEFFP.js";
5
-
6
- // src/cli/components/common/prompt-confirm.tsx
7
- import "react";
8
- async function promptValue(build, options) {
9
- return new Promise((resolve) => {
10
- const instance = render(
11
- build((value) => {
12
- if (options.clearOnResolve) instance.clear();
13
- instance.unmount();
14
- resolve(value);
15
- })
16
- );
17
- instance.waitUntilExit().then(
18
- () => resolve(options.onExit),
19
- () => resolve(options.onExit)
20
- );
21
- });
22
- }
23
- async function promptConfirm(build) {
24
- return promptValue(
25
- (resolve) => build({ onConfirm: () => resolve("confirmed"), onCancel: () => resolve("cancelled") }),
26
- { onExit: "cancelled" }
27
- );
28
- }
29
-
30
- export {
31
- promptValue,
32
- promptConfirm
33
- };
34
- //# sourceMappingURL=chunk-OJJLYHM3.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/common/prompt-confirm.tsx"],"sourcesContent":["import React from \"react\";\nimport { render } from \"../render.js\";\n\nexport type ConfirmHandlers = {\n onConfirm: () => void;\n onCancel: () => void;\n};\n\nexport type PromptValueOptions<T> = {\n /** Value resolved when the Ink app exits before any callback fires (Ctrl+C / render failure). */\n onExit: T;\n /** Call instance.clear() before unmount — repaints a clean terminal (used by the dashboard). */\n clearOnResolve?: boolean;\n};\n\n/**\n * Renders an Ink prompt and resolves with the value produced by its callbacks.\n * First-wins: the first resolve (a callback or the app-exit fallback) settles the\n * promise; later resolves are ignored. The element is unmounted at the resolution\n * site (optionally cleared first). Callers own exit policy (exit codes, logging).\n */\nexport async function promptValue<T>(\n build: (resolve: (value: T) => void) => React.ReactElement,\n options: PromptValueOptions<T>,\n): Promise<T> {\n return new Promise<T>((resolve) => {\n const instance = render(\n build((value) => {\n if (options.clearOnResolve) instance.clear();\n instance.unmount();\n resolve(value);\n }),\n );\n // App exit without a callback (e.g. Ctrl+C) or a render failure counts as the\n // fallback; resolve is first-wins, so a prior callback value is unaffected.\n instance.waitUntilExit().then(\n () => resolve(options.onExit),\n () => resolve(options.onExit),\n );\n });\n}\n\n/**\n * Renders a confirm UI and resolves with the user's choice. The element is\n * unmounted as soon as either handler fires; a clean exit or render failure\n * without a choice resolves as \"cancelled\". Callers own exit policy.\n */\nexport async function promptConfirm(\n build: (handlers: ConfirmHandlers) => React.ReactElement,\n): Promise<\"confirmed\" | \"cancelled\"> {\n return promptValue<\"confirmed\" | \"cancelled\">(\n (resolve) =>\n build({ onConfirm: () => resolve(\"confirmed\"), onCancel: () => resolve(\"cancelled\") }),\n { onExit: \"cancelled\" },\n );\n}\n"],"mappings":";;;;;;AAAA,OAAkB;AAqBlB,eAAsB,YACpB,OACA,SACY;AACZ,SAAO,IAAI,QAAW,CAAC,YAAY;AACjC,UAAM,WAAW;AAAA,MACf,MAAM,CAAC,UAAU;AACf,YAAI,QAAQ,eAAgB,UAAS,MAAM;AAC3C,iBAAS,QAAQ;AACjB,gBAAQ,KAAK;AAAA,MACf,CAAC;AAAA,IACH;AAGA,aAAS,cAAc,EAAE;AAAA,MACvB,MAAM,QAAQ,QAAQ,MAAM;AAAA,MAC5B,MAAM,QAAQ,QAAQ,MAAM;AAAA,IAC9B;AAAA,EACF,CAAC;AACH;AAOA,eAAsB,cACpB,OACoC;AACpC,SAAO;AAAA,IACL,CAAC,YACC,MAAM,EAAE,WAAW,MAAM,QAAQ,WAAW,GAAG,UAAU,MAAM,QAAQ,WAAW,EAAE,CAAC;AAAA,IACvF,EAAE,QAAQ,YAAY;AAAA,EACxB;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/lib/operations/project/load-agent-defs.ts"],"sourcesContent":["import { getAgentDefinitions } from \"../../agents/index.js\";\nimport { loadMergedAgents } from \"../../loading/index.js\";\nimport type { AgentDefinition, AgentName, AgentSourcePaths } from \"../../../types/index.js\";\n\nexport type AgentDefs = {\n /** Merged agent definitions (CLI defaults + source overrides). Source takes precedence. */\n agents: Partial<Record<AgentName, AgentDefinition>>;\n /** The sourcePath used to load agent partials (for compilation). */\n sourcePath: string;\n /** Full agent source paths (agentsDir, templatesDir, sourcePath). */\n agentSourcePaths: AgentSourcePaths;\n};\n\n/**\n * Loads agent definitions from the CLI and optionally from a remote source.\n *\n * Merges CLI built-in agents with source repository agents (source overrides CLI).\n * Returns the merged definitions plus the source path for compilation.\n */\nexport async function loadAgentDefs(options?: { projectDir?: string }): Promise<AgentDefs> {\n const agentSourcePaths = await getAgentDefinitions(undefined, options);\n const agents = await loadMergedAgents(agentSourcePaths.sourcePath);\n\n return {\n agents,\n sourcePath: agentSourcePaths.sourcePath,\n agentSourcePaths,\n };\n}\n"],"mappings":";;;;;;;;;AAmBA,eAAsB,cAAc,SAAuD;AACzF,QAAM,mBAAmB,MAAM,oBAAoB,QAAW,OAAO;AACrE,QAAM,SAAS,MAAM,iBAAiB,iBAAiB,UAAU;AAEjE,SAAO;AAAA,IACL;AAAA,IACA,YAAY,iBAAiB;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/stores/wizard-store.ts","../src/cli/lib/wizard/build-step-logic.ts","../src/cli/lib/wizard/scope-diff.ts","../src/cli/lib/wizard/domain-order.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport {\n DEFAULT_PUBLIC_SOURCE_NAME,\n DEFAULT_SCRATCH_DOMAINS,\n EJECT_SOURCE,\n FALLBACK_DOMAIN,\n INSTALL_MODES,\n} from \"../consts.js\";\nimport type { InstallMode } from \"../lib/installation/index.js\";\nimport { deriveInstallMode as sharedDeriveInstallMode } from \"../lib/installation/installation.js\";\nimport type { AgentScopeConfig, SkillConfig, SkillScope } from \"../types/config.js\";\nimport {\n isActiveAt,\n isGlobalTombstone,\n isProjectOwned,\n} from \"../lib/configuration/scope-predicates.js\";\nimport { matrix, getSkillById, getCategoryDomain } from \"../lib/matrix/matrix-provider.js\";\nimport { buildCategoriesForDomain, orderDomains, skillSlotKey } from \"../lib/wizard/index.js\";\nimport type {\n AgentName,\n Domain,\n DomainSelections,\n SkillId,\n Category,\n CategorySelections,\n ResolvedSkill,\n} from \"../types/index.js\";\nimport type { SourceOption, SourceRow } from \"../components/wizard/source-grid.js\";\nimport { warn } from \"../utils/logger.js\";\nimport { typedEntries, typedFromEntries, typedKeys, typedValues } from \"../utils/typed-object.js\";\n\n/** Toast strings surfaced by scope/selection guards (E2E asserts these verbatim). */\nconst TOAST_MESSAGES = {\n GLOBAL_SKILLS_LOCKED: \"Global skills cannot be changed from project scope\",\n ONLY_SKILL_IN_CATEGORY: \"Cannot deselect the only skill in this category\",\n ALREADY_EJECTED_AT_GLOBAL: \"Already exists as ejected skill at global scope\",\n GLOBAL_AGENTS_LOCKED: \"Global agents cannot be changed from project scope\",\n} as const;\n\n/** First defined source among candidates, else the default public source. */\nfunction resolveEffectiveSource(...candidates: Array<string | undefined>): string {\n return candidates.find((source) => source !== undefined) ?? DEFAULT_PUBLIC_SOURCE_NAME;\n}\n\n/** Name of a skill's primary available source, if any. */\nfunction primarySourceName(skill: ResolvedSkill | undefined): string | undefined {\n return skill?.availableSources?.find((s) => s.primary)?.name;\n}\n\nfunction createDefaultSkillConfig(id: SkillId): SkillConfig {\n const skill = matrix.skills[id];\n const primarySource = primarySourceName(skill);\n return { id, scope: \"global\", source: primarySource ?? DEFAULT_PUBLIC_SOURCE_NAME };\n}\n\n/** True when configs hold an active (non-excluded) project-scope entry for the id. */\nfunction hasProjectActive(configs: SkillConfig[], id: SkillId): boolean {\n return configs.some((sc) => sc.id === id && isActiveAt(sc, \"project\"));\n}\n\n/** The active (non-excluded) global-scope entry for the id, when configs hold one. */\nfunction findGlobalActive(configs: SkillConfig[], id: SkillId): SkillConfig | undefined {\n return configs.find((sc) => sc.id === id && isActiveAt(sc, \"global\"));\n}\n\n/** True when configs hold an active (non-excluded) global-scope entry for the id. */\nfunction hasGlobalActive(configs: SkillConfig[], id: SkillId): boolean {\n return findGlobalActive(configs, id) !== undefined;\n}\n\n/** True when configs hold an excluded global-scope tombstone for the id. */\nfunction hasGlobalTombstone(configs: SkillConfig[], id: SkillId): boolean {\n return configs.some((sc) => sc.id === id && isGlobalTombstone(sc));\n}\n\n/**\n * D-233 Scenario B: a dual-scope pair — an active project entry plus a global tombstone for\n * the same skill. The shape the `[P][G]` row renders from; removal collapses it to a single\n * inherited-global entry, re-selection rebuilds it.\n */\nfunction isDualScopePair(configs: SkillConfig[], id: SkillId): boolean {\n return hasProjectActive(configs, id) && hasGlobalTombstone(configs, id);\n}\n\n/**\n * True when a SELECTED skill must not be deselected at project scope.\n *\n * The lock protects GLOBAL-OWNED halves and nothing else. Two arms: an active global entry in\n * the hydration snapshot (genuinely global-only — the long-standing read-only behaviour); and a\n * snapshot tombstone paired with a LIVE plain active global entry (the stale state a persisted\n * `[P][G]` reaches after an in-session collapse, whose deselect would silently tombstone the\n * still-real global install).\n *\n * A persisted `[P][G]` pair matches neither: its live global half is a tombstone, and its project\n * half is the project's own install to drop — `applySkillRemoval` collapses the pair to the\n * inherited global entry, so removing it never touches the global install it masks. A skill\n * freshly added this session (absent from the snapshot) is likewise nobody's install yet and\n * stays freely deselectable.\n */\nfunction isGloballyLockedSkill(\n installed: SkillConfig[],\n liveConfigs: SkillConfig[],\n id: SkillId,\n): boolean {\n return (\n hasGlobalActive(installed, id) ||\n (hasGlobalTombstone(installed, id) && hasGlobalActive(liveConfigs, id))\n );\n}\n\n/**\n * True when an exclusive-category radio swap must not drop this skill at project scope.\n *\n * The deselect lock, plus one case it deliberately allows on its own row: a live `[P][G]` pair.\n * Dropping a pair's project half leaves the global install it masks ACTIVE, so a swap that did it\n * implicitly would seat the new pick beside a still-active sibling in a category that permits one.\n * Removing the pair stays the project's to do — with the selection key on its own row, where\n * nothing takes the freed slot.\n */\nfunction blocksExclusiveSwap(\n installed: SkillConfig[],\n liveConfigs: SkillConfig[],\n id: SkillId,\n): boolean {\n return isGloballyLockedSkill(installed, liveConfigs, id) || isDualScopePair(liveConfigs, id);\n}\n\n/**\n * True when re-scoping this project-eject entry to global would overwrite an ejected global\n * install that the live config does not already override with a tombstone.\n */\nfunction wouldOverwriteGlobalEject(\n config: SkillConfig,\n liveConfigs: SkillConfig[],\n installedSkillConfigs: SkillConfig[] | null,\n): boolean {\n if (config.scope !== \"project\" || config.source !== EJECT_SOURCE) return false;\n const globalEjectInstalled = installedSkillConfigs?.some(\n (sc) =>\n sc.id === config.id && sc.scope === \"global\" && sc.source === EJECT_SOURCE && !sc.excluded,\n );\n if (!globalEjectInstalled) return false;\n return !liveConfigs.some((sc) => sc.id === config.id && sc.excluded);\n}\n\n/**\n * True when the slot a Sources-step call targets is a global install this session INHERITED —\n * an active global entry the hydration snapshot already carried.\n *\n * Keyed on the `(id, scope)` SLOT, never on the id: an id legitimately occupies slots at both\n * scopes at once, and the project half of a `[P][G]` pair is the project's own install to\n * configure. An id-keyed test would freeze the half the Sources grid deliberately leaves\n * editable.\n *\n * Read off the SNAPSHOT, never off the live entry's scope: a skill this session added at global\n * scope — and every skill in a first `init`, where the snapshot is `null` — is nobody's install\n * yet, so nothing is inherited and every row is the session's own.\n */\nfunction isInheritedGlobalSlot(\n installed: SkillConfig[] | null,\n id: SkillId,\n scope: SkillScope | undefined,\n): boolean {\n if (installed === null) return false;\n return scope === \"global\" && hasGlobalActive(installed, id);\n}\n\n/**\n * Rewrites the source of the ACTIVE entry at (id, scope), leaving every other entry untouched —\n * in particular a dual-scope skill's excluded global tombstone, which keeps describing the\n * masked global install (D-262).\n */\nfunction withActiveEntrySource(\n configs: SkillConfig[],\n id: SkillId,\n scope: SkillScope | undefined,\n source: string,\n): SkillConfig[] {\n return configs.map((sc) =>\n sc.id === id && !sc.excluded && sc.scope === scope ? { ...sc, source } : sc,\n );\n}\n\n/**\n * Removes deselected skills, honouring what the project is allowed to remove (D-277).\n *\n * An entry the project OWNS — project-scoped, or the project's own global tombstone — is\n * dropped. An entry the project merely INHERITS (an active global-scope entry the hydration\n * snapshot already carried) is immutable from project scope and survives BYTE-IDENTICAL: a\n * project edit neither uninstalls a global install nor mints a tombstone for it. A skill this\n * session added itself is absent from the snapshot, is nobody's install yet, and is dropped\n * like any other so an accidental add stays undoable.\n *\n * `installedSkillConfigs` is `null` when editing FROM global scope, where the config being\n * edited IS the global one: nothing is inherited, so every removal is a genuine uninstall\n * (D-233 Scenario C).\n *\n * Dual-scope entries (an active project entry paired with a global tombstone) collapse to a\n * single inherited-global entry so the `[G]` badge keeps rendering after the project half is\n * dropped (D-233 Scenario B).\n */\nfunction applySkillRemoval(\n configs: SkillConfig[],\n removedIds: Iterable<SkillId>,\n installedSkillConfigs: SkillConfig[] | null,\n): SkillConfig[] {\n // Annotated because `instanceof Set` narrows an `Iterable<SkillId>` to `Set<any>`,\n // which then spreads out as `any[]` below.\n const removed: ReadonlySet<SkillId> =\n removedIds instanceof Set ? removedIds : new Set(removedIds);\n const installedIds = new Set((installedSkillConfigs ?? []).map((s) => s.id));\n\n /**\n * A removed entry the project may not drop: a global install that existed before this session\n * and that the project does not own. Dual-scope pairs are excluded — both their halves go, and\n * the surviving global install is re-surfaced as a single inherited entry below.\n */\n const survivesRemoval = (entry: SkillConfig): boolean =>\n !isDualScopePair(configs, entry.id) && !isProjectOwned(entry) && installedIds.has(entry.id);\n\n const retained = configs.filter((sc) => !removed.has(sc.id) || survivesRemoval(sc));\n const resurfacedInheritedGlobals = [...removed]\n .filter((id) => isDualScopePair(configs, id))\n .map((id) => ({\n id,\n scope: \"global\" as const,\n source:\n configs.find((sc) => sc.id === id && sc.scope === \"global\")?.source ??\n DEFAULT_PUBLIC_SOURCE_NAME,\n }));\n\n return [...retained, ...resurfacedInheritedGlobals];\n}\n\n/** All skill IDs across a domain's category selections, in category order. */\nfunction flattenCategorySelections(selections: CategorySelections): SkillId[] {\n return typedValues(selections).flat();\n}\n\n/** Collects all skill IDs from a domain's category selections. */\nfunction collectSkillIdsFromSelections(selections: CategorySelections): Set<SkillId> {\n return new Set(flattenCategorySelections(selections));\n}\n\n/** Reconciles skill configs after selection changes: drops project-owned removals, leaves inherited global installs untouched, restores excluded on re-select, adds new defaults. */\nfunction reconcileSkillConfigs(\n configs: SkillConfig[],\n added: SkillId[],\n removed: SkillId[],\n installedSkillConfigs: SkillConfig[] | null,\n isEditingFromGlobalScope: boolean,\n): SkillConfig[] {\n // Editing from global scope has no project overlay, so a removal is a genuine uninstall.\n // Pass null so applySkillRemoval DROPS the skill rather than retaining it as an inherited\n // global install the project may not touch (D-233 Scenario C).\n const effectiveInstalled = isEditingFromGlobalScope ? null : installedSkillConfigs;\n let result = applySkillRemoval(configs, removed, effectiveInstalled);\n\n for (const id of added) {\n // Dual-scope restore: re-selecting a skill that is globally installed (recorded as a\n // tombstone in the project snapshot) with no current project entry re-creates BOTH a\n // fresh project entry and a global tombstone — mirroring toggleSkillScope's G->P path\n // so the row renders `[P][G]` again (D-233 Scenario B second spacebar).\n if (\n !isEditingFromGlobalScope &&\n hasGlobalTombstone(installedSkillConfigs ?? [], id) &&\n !hasProjectActive(result, id)\n ) {\n const globalEntry =\n (installedSkillConfigs ?? []).find((sc) => sc.id === id && sc.scope === \"global\") ??\n result.find((sc) => sc.id === id && sc.scope === \"global\");\n const source = globalEntry?.source ?? DEFAULT_PUBLIC_SOURCE_NAME;\n result = [\n ...result.filter((sc) => sc.id !== id),\n { id, scope: \"project\", source },\n { id, scope: \"global\", excluded: true, source },\n ];\n continue;\n }\n\n const existingExcluded = result.find((sc) => sc.id === id && sc.excluded);\n if (existingExcluded) {\n // Un-excluding drops the tombstone flag rather than setting it to `undefined`:\n // an absent `excluded` is what \"not excluded\" means everywhere else in the config.\n result = result.map((sc) => {\n if (sc.id !== id || !sc.excluded) return sc;\n const { excluded: _excluded, ...active } = sc;\n return active;\n });\n } else if (!result.some((sc) => sc.id === id)) {\n // Re-selecting a skill that the hydration snapshot still holds is a RESTORE, not a fresh\n // add: it must come back with its persisted scope and source rather than the wizard\n // defaults (a project-scoped eject skill would otherwise round-trip to global+marketplace\n // and trigger a bogus scope/source migration). buildSkillConfigForId degrades to exactly\n // createDefaultSkillConfig's output when the snapshot has no entry, so genuinely-new\n // selections are unaffected.\n result = [...result, buildSkillConfigForId(id, installedSkillConfigs)];\n }\n }\n\n return result;\n}\n\n/** True when configs hold an active (non-excluded) project-scope entry for the agent. */\nfunction agentHasProjectActive(configs: AgentScopeConfig[], name: AgentName): boolean {\n return configs.some((ac) => ac.name === name && ac.scope === \"project\" && !ac.excluded);\n}\n\n/** True when configs hold an active (non-excluded) global-scope entry for the agent. */\nfunction agentHasGlobalActive(configs: AgentScopeConfig[], name: AgentName): boolean {\n return configs.some((ac) => ac.name === name && ac.scope === \"global\" && !ac.excluded);\n}\n\n/** True when configs hold an excluded global-scope tombstone for the agent. */\nfunction agentHasGlobalTombstone(configs: AgentScopeConfig[], name: AgentName): boolean {\n return configs.some((ac) => ac.name === name && ac.scope === \"global\" && ac.excluded);\n}\n\n/** Agent side of isDualScopePair (D-233): active project entry + global tombstone for the same agent. */\nfunction isDualScopeAgentPair(configs: AgentScopeConfig[], name: AgentName): boolean {\n return agentHasProjectActive(configs, name) && agentHasGlobalTombstone(configs, name);\n}\n\n/**\n * Excluded tombstone entries. Preserved across reconcile/preselect merges so a dual-scope\n * pair's tombstone half survives (D-223/D-227) and the pair can be restored later.\n */\nfunction collectTombstones<T extends { excluded?: boolean }>(configs: T[]): T[] {\n return configs.filter((entry) => entry.excluded);\n}\n\n/**\n * Applies an agent toggle: deselect removes the agent's entries; select restores an excluded\n * entry or adds a new one.\n *\n * A deselect never masks a globally-installed agent (D-277). Every reachable deselect here is\n * one the project owns — an active global install and a live `[P][G]` pair are both refused\n * upstream in `toggleAgent`, and a global-scope edit owns everything it can see — so the only\n * correct outcome is a clean removal.\n */\nfunction applyAgentToggle(\n configs: AgentScopeConfig[],\n agent: AgentName,\n isSelected: boolean,\n): AgentScopeConfig[] {\n if (isSelected) {\n return configs.filter((ac) => ac.name !== agent);\n }\n\n const existingExcluded = configs.find((ac) => ac.name === agent && ac.excluded);\n if (existingExcluded) {\n return configs.map((ac) => {\n if (ac.name !== agent || !ac.excluded) return ac;\n const { excluded: _excluded, ...active } = ac;\n return active;\n });\n }\n return [...configs, { name: agent, scope: \"global\" as const }];\n}\n\n/**\n * True when an existing agent entry must outlive a domain-roster rebuild, which merges rather than\n * replaces. Two kinds survive:\n *\n * - ALL excluded tombstones, so a dual-scope pair (active entry plus a tombstone at the other\n * scope) survives preselection — the agent-side mirror of the D-223 fix in\n * `populateFromSkillIds`. Deliberately NOT filtered by roster membership (D-227).\n * - Every entry the project does not own, so a globally-installed agent outside the selected\n * domains' roster is never silently uninstalled by a project edit (D-277).\n *\n * Entries the roster itself rebuilds are excluded — `buildAgentConfigForName` re-derives those.\n */\nfunction survivesRosterRebuild(\n agentConfig: AgentScopeConfig,\n roster: ReadonlySet<AgentName>,\n): boolean {\n if (agentConfig.excluded) return true;\n return !isProjectOwned(agentConfig) && !roster.has(agentConfig.name);\n}\n\n/**\n * D-233: restores the `[P][G]` pair when re-selecting an inherited-global agent row whose\n * global install is recorded as a tombstone in the project snapshot — mirrors\n * reconcileSkillConfigs' restore branch.\n */\nfunction restoreDualScopeAgent(\n selectedAgents: AgentName[],\n agentConfigs: AgentScopeConfig[],\n agent: AgentName,\n) {\n return {\n selectedAgents: [...selectedAgents, agent],\n agentConfigs: [\n ...agentConfigs.filter((ac) => ac.name !== agent),\n { name: agent, scope: \"project\" as const },\n { name: agent, scope: \"global\" as const, excluded: true },\n ],\n };\n}\n\n/**\n * Next selectedAgents after a toggle. A deselect drops the agent; re-enabling a tombstoned\n * agent is already listed; a fresh add appends.\n */\nfunction nextSelectedAgents(\n selectedAgents: AgentName[],\n agent: AgentName,\n flags: { isSelected: boolean; isInList: boolean },\n): AgentName[] {\n if (flags.isSelected) return selectedAgents.filter((a) => a !== agent);\n if (flags.isInList) return selectedAgents;\n return [...selectedAgents, agent];\n}\n\n/** Builds a SkillConfig for a resolved skill ID, preferring saved config values. */\nfunction buildSkillConfigForId(id: SkillId, savedConfigs?: SkillConfig[] | null): SkillConfig {\n // Prefer project-scoped entry over global when duplicates exist (D-198 defensive fix)\n const saved =\n savedConfigs?.find((sc) => sc.id === id && !sc.excluded && sc.scope === \"project\") ??\n savedConfigs?.find((sc) => sc.id === id && !sc.excluded);\n const skill = matrix.skills[id];\n const primarySource = primarySourceName(skill);\n return {\n id,\n scope: saved?.scope ?? \"global\",\n source: resolveEffectiveSource(saved?.source, primarySource),\n };\n}\n\n/**\n * Builds an active AgentScopeConfig for a name, preferring a saved project-scoped\n * active entry. Mirrors buildSkillConfigForId on the agent path so preselection can\n * separate active-entry construction from tombstone preservation (D-227).\n */\nfunction buildAgentConfigForName(\n name: AgentName,\n savedConfigs?: AgentScopeConfig[],\n): AgentScopeConfig {\n // Prefer project-scoped active entry over global when duplicates exist.\n const saved =\n savedConfigs?.find((ac) => ac.name === name && !ac.excluded && ac.scope === \"project\") ??\n savedConfigs?.find((ac) => ac.name === name && !ac.excluded);\n return {\n name,\n scope: saved?.scope ?? \"global\",\n // Model and effort are the user's deliberate choice, not something the roster re-derives —\n // they survive the rebuild on the same terms as scope.\n ...(saved?.model !== undefined && { model: saved.model }),\n ...(saved?.effort !== undefined && { effort: saved.effort }),\n };\n}\n\n/** Restores skill configs for a domain: clears excluded flags on restored skills, adds new defaults for unknown skills. */\nfunction restoreSkillConfigs(\n existingConfigs: SkillConfig[],\n restoredIds: SkillId[],\n): SkillConfig[] {\n const restoredSet = new Set(restoredIds);\n const existingIds = new Set(existingConfigs.map((sc) => sc.id));\n\n const updated = existingConfigs.map((sc) => {\n if (!restoredSet.has(sc.id) || !sc.excluded) return sc;\n const { excluded: _excluded, ...active } = sc;\n return active;\n });\n\n const newConfigs = restoredIds.filter((id) => !existingIds.has(id)).map(createDefaultSkillConfig);\n\n return [...updated, ...newConfigs];\n}\n\n/**\n * Built-in agent names grouped by domain prefix. Custom domains return no preselected agents.\n * The consolidated `pm` and `reviewer` are cross-domain by role, so every domain rosters both;\n * the preselection union dedupes when several selected domains bring them.\n */\nconst DOMAIN_AGENTS: Partial<Record<Domain, AgentName[]>> = {\n web: [\"web-developer\", \"web-researcher\", \"web-tester\", \"pm\", \"reviewer\"],\n api: [\"api-developer\", \"api-researcher\", \"api-tester\", \"pm\", \"reviewer\"],\n cli: [\"cli-developer\", \"cli-tester\", \"cli-researcher\", \"pm\", \"reviewer\"],\n ai: [\"ai-developer\", \"ai-researcher\", \"ai-tester\", \"pm\", \"reviewer\"],\n};\n\nfunction resolveSkillForPopulation(\n skillId: SkillId,\n): { domain: Domain; subcat: Category; techId: SkillId } | null {\n const { skills } = matrix;\n const skill = skills[skillId];\n if (!skill?.category) {\n warn(\n `Installed skill '${skillId}' is not present in the loaded source — it may have been removed or renamed`,\n );\n return null;\n }\n\n const domain = getCategoryDomain(skill.category);\n if (!domain) {\n warn(`Installed skill '${skillId}' has unknown category '${skill.category}' — skipping`);\n return null;\n }\n\n // Boundary cast: domain lookup confirmed category exists in matrix\n const subcat = skill.category as Category;\n return { domain, subcat, techId: skillId };\n}\n\n/** Adds a skill to selections[domain][category] if absent; true when newly added. */\nfunction addToDomainSelections(\n selections: DomainSelections,\n domain: Domain,\n category: Category,\n skillId: SkillId,\n): boolean {\n const categorySelections = (selections[domain] ??= {});\n const skillList = (categorySelections[category] ??= []);\n if (skillList.includes(skillId)) return false;\n skillList.push(skillId);\n return true;\n}\n\n/** The one marketplace a skill can be installed from as a plugin. */\nfunction marketplaceSourceName(skill: ResolvedSkill): string {\n return (\n skill.availableSources?.find((source) => source.type !== \"local\")?.name ??\n DEFAULT_PUBLIC_SOURCE_NAME\n );\n}\n\n/** The `SkillConfig.source` value an install mode writes for this skill. */\nfunction sourceForInstallMode(skill: ResolvedSkill, mode: Exclude<InstallMode, \"mixed\">): string {\n return mode === \"eject\" ? EJECT_SOURCE : marketplaceSourceName(skill);\n}\n\n/**\n * The install mode a persisted `source` value stands for. The per-skill half of\n * `deriveInstallMode`, which asks the same question of a whole selection: `eject` names the\n * project's own copy and every other value names the marketplace the plugin comes from.\n */\nfunction installModeOfSource(source: string | undefined): Exclude<InstallMode, \"mixed\"> {\n return source === EJECT_SOURCE ? \"eject\" : \"plugin\";\n}\n\n/** The two cells of one skill's install-mode control, with the current mode selected. */\nfunction buildInstallModeOptions(selectedMode: Exclude<InstallMode, \"mixed\">): SourceOption[] {\n return INSTALL_MODES.map((mode) => ({ mode, selected: mode === selectedMode }));\n}\n\n/**\n * D-257: the snapshot slots this session has emptied — an `(id, scope)` the hydration snapshot\n * holds ACTIVE that no live config entry occupies any more. The emptying leaves nothing behind to\n * render from: a PROJECT skill is dropped outright (applySkillRemoval leaves no tombstone, unlike\n * the global case), and so is ANY skill deselected while editing from global scope, where\n * `reconcileSkillConfigs` passes `null` so `applySkillRemoval` drops rather than tombstones. In\n * both the snapshot is the only surviving record, so without this set the row would vanish and the\n * user would lose sight of what saving is about to remove. Deliberately ungated on edit context:\n * `computeScopeDiff`, which the confirm step classifies removals with, has no global-scope gate\n * either, so neither surface may have one.\n *\n * Keyed per `(id, scope)` SLOT, mirroring `computeScopeDiff`'s removal match (D-271) — removal is a\n * property of the slot, not of the id. Collapsing a dual-scope `[P][G]` pair to `[G]` empties the\n * PROJECT slot while the global one survives, so the skill renders twice on BOTH surfaces (`-` at\n * Project, `•`/lock at Global) instead of the survivor masking the loss. A tombstone OCCUPIES its\n * slot, so re-activating a masked global install is never a removal. Selection is not consulted:\n * `skillConfigs` is the same input the confirm step diffs, which is what keeps the two agreeing.\n *\n * Snapshot TOMBSTONES are deliberately not removal candidates — narrower than `computeScopeDiff`,\n * whose removal filter counts them. A tombstone is a MASK over a global install, not an install of\n * its own: dropping it deletes nothing (the global copy survives), so a red pending-removal row for\n * one would announce a deletion that never happens. No store transition reaches that shape anyway —\n * every path that drops a tombstone fills the same slot with an active entry (`toggleSkillScope`\n * P→G, `applySkillRemoval`'s dual-scope collapse).\n */\nfunction collectRemovedInstalledEntries(\n installedSkillConfigs: SkillConfig[] | null,\n skillConfigs: SkillConfig[],\n): SkillConfig[] {\n if (!installedSkillConfigs) return [];\n const occupiedSlots = new Set(skillConfigs.map((sc) => skillSlotKey(sc.id, sc.scope)));\n return installedSkillConfigs.filter(\n (sc) =>\n !sc.excluded &&\n !occupiedSlots.has(skillSlotKey(sc.id, sc.scope)) &&\n // A skill the marketplace no longer carries has nothing to render — hydration already\n // skipped it into unresolvableSkillIds, and getSkillById would throw on it downstream.\n !!matrix.skills[sc.id],\n );\n}\n\n/**\n * D-258: every `(id, scope)` slot the hydration snapshot occupies — the baseline a Sources row is\n * \"added\" against, and the direct counterpart of `computeScopeDiff`'s `prevSkillKeySet`. Tombstones\n * count as occupied, exactly as that set does (D-232), so re-reading a stored tombstone never flags\n * a spurious addition.\n *\n * A missing snapshot (a first `init`, nothing installed at either scope) collapses to the EMPTY set\n * rather than to a distinct \"no baseline\" state, because the two are the same answer: an empty\n * baseline occupies no slot, so every row is new — which is what `classifyDiffRow` already tells the\n * confirm step for a null baseline, and what an empty snapshot already told this surface.\n */\nfunction collectInstalledSkillSlots(\n installedSkillConfigs: SkillConfig[] | null,\n): ReadonlySet<string> {\n if (!installedSkillConfigs) return new Set();\n return new Set(installedSkillConfigs.map((sc) => skillSlotKey(sc.id, sc.scope)));\n}\n\n/**\n * D-258: the `{ added: true }` fragment for a row whose SLOT is new this session, `{}` otherwise —\n * spread into the row so the Sources tab flags it with the info panel's added-diff marker (`+`).\n * Keying on the id alone would miss a skill adopted at project scope while it stays installed\n * globally: the id is old, but the project slot is new — which is exactly how `classifyDiffRow`\n * classifies the same change on the confirm step. Never gated on `isEditingFromGlobalScope`: an\n * addition is an addition regardless of edit context.\n */\nfunction addedSlotFlag(\n installedSkillSlots: ReadonlySet<string>,\n id: SkillId,\n scope: SkillScope | undefined,\n): { added?: true } {\n return installedSkillSlots.has(skillSlotKey(id, scope)) ? {} : { added: true };\n}\n\n/** Copy of the options with the persisted source's mode selected — for rows pinned to it. */\nfunction withSelectedMode(options: SourceOption[], source: string | undefined): SourceOption[] {\n const mode = installModeOfSource(source);\n return options.map((option) => ({ ...option, selected: option.mode === mode }));\n}\n\n/**\n * The per-skill inputs every source row shares: the canonical id, the live config entry driving\n * scope/lock classification, and the source options with the effective source preselected. A\n * pending-removal row re-pins that selection to its persisted source (`withSelectedSource`).\n */\nfunction resolveSkillRowInputs(\n id: SkillId,\n skillConfigs: SkillConfig[],\n): { skillId: SkillId; configEntry: SkillConfig | undefined; options: SourceOption[] } {\n const skill = getSkillById(id);\n const configEntry = skillConfigs.find((sc) => sc.id === skill.id);\n const selectedSource = resolveEffectiveSource(\n configEntry?.source,\n skill.activeSource?.name,\n primarySourceName(skill),\n );\n return {\n skillId: skill.id,\n configEntry,\n options: buildInstallModeOptions(installModeOfSource(selectedSource)),\n };\n}\n\ntype SourceRowContext = {\n configEntry: SkillConfig | undefined;\n installedSkillConfigs: SkillConfig[] | null;\n isEditingFromGlobalScope: boolean;\n /** D-258: `(id, scope)` slots the snapshot occupies — the baseline each row's `+` derives from. */\n installedSkillSlots: ReadonlySet<string>;\n};\n\n/**\n * D-257: the inert row for a snapshot slot this session emptied. Carries the PERSISTED scope and\n * source so the row stays visible and shows what saving removes. Deliberately NOT readOnly: that\n * renders a lock, which reads as \"installed globally\" rather than \"about to be removed\". Carries no\n * added flag either — the slot it renders comes FROM the snapshot, so it can never be new this\n * session.\n */\nfunction toPendingRemovalRow(\n skillId: SkillId,\n options: SourceOption[],\n removedInstalledEntry: SkillConfig,\n): SourceRow {\n return {\n skillId,\n options: withSelectedMode(options, removedInstalledEntry.source),\n scope: removedInstalledEntry.scope,\n disabled: true,\n };\n}\n\n/**\n * True when an already-emitted row renders the snapshot entry's `(id, scope)` slot, which makes a\n * pending-removal row for it a duplicate rather than a loss.\n *\n * The one shape that reaches this: a global install adopted at project scope WITHOUT a tombstone\n * (`[G]` snapshot, `[P]` live — the historical no-tombstone dual-scope shape `computeScopeDiff`\n * calls an inherited global). Its global slot is unoccupied in the live config, yet the locked\n * global row already renders it FROM the snapshot, so the install is inherited, not removed.\n */\nfunction isSlotAlreadyRendered(rows: SourceRow[], removedInstalledEntry: SkillConfig): boolean {\n const slot = skillSlotKey(removedInstalledEntry.id, removedInstalledEntry.scope);\n return rows.some((row) => skillSlotKey(row.skillId, row.scope) === slot);\n}\n\n/**\n * The locked row for a global install this project may not change from here — the lock reads\n * \"installed globally, not yours to edit at project scope\". Its source is pinned to the installed\n * one, and its added flag derives from the GLOBAL slot, so an install the project merely inherits\n * stays a plain lock rather than reading as new.\n */\nfunction toLockedGlobalRow(\n skillId: SkillId,\n options: SourceOption[],\n installedSource: string | undefined,\n installedSkillSlots: ReadonlySet<string>,\n): SourceRow {\n return {\n skillId,\n options: withSelectedMode(options, installedSource),\n scope: \"global\" as const,\n readOnly: true,\n ...addedSlotFlag(installedSkillSlots, skillId, \"global\"),\n };\n}\n\n/**\n * Classifies one skill's LIVE config entry into its source-grid rows: a locked global row for\n * excluded-global entries, locked global + editable project rows for skills re-scoped\n * global→project this session, or a single row otherwise. Pending-removal rows are not derived\n * here — they belong to snapshot slots with no live entry left (see `toPendingRemovalRow`).\n */\nfunction classifySkillSourceRows(\n skillId: SkillId,\n options: SourceOption[],\n context: SourceRowContext,\n): SourceRow[] {\n const { configEntry, installedSkillConfigs, isEditingFromGlobalScope, installedSkillSlots } =\n context;\n const installedGlobalConfig = findGlobalActive(installedSkillConfigs ?? [], skillId);\n\n const isExcludedGlobal = configEntry?.excluded && configEntry.scope === \"global\";\n if (isExcludedGlobal && !isEditingFromGlobalScope) {\n return [\n toLockedGlobalRow(\n skillId,\n options,\n installedGlobalConfig?.source ?? configEntry.source,\n installedSkillSlots,\n ),\n ];\n }\n\n if (!isEditingFromGlobalScope && installedGlobalConfig && configEntry?.scope === \"project\") {\n // Skill toggled from global to project — emit locked global copy + editable project copy. Each\n // half derives its own added flag from its own slot, so the `+` lands on the newly occupied\n // PROJECT row while the still-installed global row stays a plain lock.\n return [\n toLockedGlobalRow(skillId, options, installedGlobalConfig.source, installedSkillSlots),\n {\n skillId,\n options,\n scope: \"project\" as const,\n ...addedSlotFlag(installedSkillSlots, skillId, \"project\"),\n },\n ];\n }\n\n const readOnly = !isEditingFromGlobalScope && !!installedGlobalConfig;\n return [\n {\n skillId,\n options,\n ...(configEntry?.scope !== undefined && { scope: configEntry.scope }),\n ...(readOnly ? { readOnly: true as const } : {}),\n ...addedSlotFlag(installedSkillSlots, skillId, configEntry?.scope),\n },\n ];\n}\n\n/**\n * Visual grouping order in source-grid: global readOnly, global editable, then project. A\n * pending-removal (disabled) row is NOT a separate tier — it sorts by its own scope so it renders\n * inline with the other rows of that scope, matching the info panel, which shows removals in place\n * rather than in a dedicated section. Its trailing position within a scope section falls out of the\n * removal rows being appended after every live row, which the stable sort preserves.\n */\nfunction sourceRowSortTier(row: SourceRow): number {\n if (row.scope === \"global\" && row.readOnly) return 0;\n if (row.scope === \"global\") return 1;\n return 2;\n}\n\n/**\n * Wizard step identifiers for the multi-step init/edit flow.\n *\n * Progression: stack -> domains -> build -> sources -> agents -> confirm\n * The \"stack\" step shows all stacks + \"Start from scratch\" in a unified list.\n * The \"domains\" step shows domain selection (web, api, cli, mobile, shared).\n * Navigation is tracked via the `history` stack for goBack() support.\n */\nexport type WizardStep =\n | \"stack\" // Select stack or \"Start from scratch\"\n | \"domains\" // Select domains to configure\n | \"build\" // CategoryGrid for technology selection\n | \"sources\" // Choose skill sources (recommended vs custom)\n | \"agents\" // Select which agents to compile\n | \"confirm\"; // Final confirmation\n\n/** How the user builds their config: from a pre-built stack, or skill-by-skill. */\ntype WizardApproach = \"stack\" | \"scratch\";\n\n/** For the stack approach: use the stack's defaults, or customize its skills. */\ntype StackAction = \"defaults\" | \"customize\";\n\nexport const WIZARD_STEP_ORDER = [\n \"stack\",\n \"domains\",\n \"build\",\n \"sources\",\n \"agents\",\n \"confirm\",\n] as const satisfies readonly WizardStep[];\n\n/** Whether the loaded source offers any stack to choose between. */\nfunction offersStacks(): boolean {\n return matrix.suggestedStacks.length > 0;\n}\n\n/**\n * The steps this session's wizard actually runs, in order.\n *\n * `WIZARD_STEP_ORDER` is every step the wizard has; this is the ones it has\n * THIS time. The stack step exists only where there is a stack to choose, so a\n * source that ships none — the built-in catalogue standing in for the default\n * public marketplace alone — drops it: hydration opens past it (see\n * {@link FIRST_STEP_WITHOUT_STACKS}) and the tab bar is drawn without it. One\n * answer to \"does this run have a stack step\", so the tabs cannot advertise a\n * step the flow does not have.\n */\nexport function getActiveStepFlow(): WizardStep[] {\n if (offersStacks()) return [...WIZARD_STEP_ORDER];\n return WIZARD_STEP_ORDER.filter((step) => step !== \"stack\");\n}\n\n/** The flow's steps before the active one; none when the step is outside the flow. */\nfunction stepsBefore(flow: WizardStep[], step: WizardStep): WizardStep[] {\n const index = flow.indexOf(step);\n return index < 0 ? [] : flow.slice(0, index);\n}\n\n/**\n * Wizard store state and actions.\n *\n * The store uses a composition pattern: small, focused actions that each mutate\n * one or two state fields. Wizard step components compose these actions to build\n * up the full selection state incrementally (domains -> categories -> skills -> sources).\n *\n * State flow: stack/scratch selection -> domain selection -> per-domain skill\n * selection (build step) -> source customization -> agent selection -> confirmation.\n */\nexport type WizardState = {\n step: WizardStep;\n\n approach: WizardApproach | null;\n selectedStackId: string | null;\n stackAction: StackAction | null;\n\n selectedDomains: Domain[];\n\n currentDomainIndex: number;\n domainSelections: DomainSelections;\n /** Snapshot of stack-provided domain selections for restoration on domain re-toggle */\n _stackDomainSelections: DomainSelections | null;\n\n showLabels: boolean;\n\n skillConfigs: SkillConfig[];\n focusedSkillId: SkillId | null;\n\n /**\n * Skill ids from the saved config that could NOT be resolved against the currently-loaded\n * source matrix this session (`populateFromSkillIds` skipped them, warning per skill). The\n * wizard cannot represent them, so they are absent from its result and the merge removes them\n * like any other absent entry — this list is what lets `edit` name each one and say why it went\n * rather than dropping it in silence (CLI-450).\n */\n unresolvableSkillIds: SkillId[];\n\n showInfo: boolean;\n\n selectedAgents: AgentName[];\n agentConfigs: AgentScopeConfig[];\n focusedAgentId: AgentName | null;\n\n /** Snapshot of configs that were installed before the wizard opened, used for diff rendering */\n installedSkillConfigs: SkillConfig[] | null;\n installedAgentConfigs: AgentScopeConfig[] | null;\n\n /** True when running init (first-time setup), false when editing an existing installation */\n isInitMode: boolean;\n\n /** When true, scope toggling is disabled (editing from ~/.claude/ with no project to move items to) */\n isEditingFromGlobalScope: boolean;\n\n /** Temporary toast message shown in the wizard (auto-cleared after timeout) */\n toastMessage: string | null;\n\n /** Global skill configs to pre-select when a stack or scratch is chosen in init. Set by use-wizard-initialization. */\n globalPreselections: SkillConfig[] | null;\n\n /** Global agent preselections to restore after selectStack wipes selectedAgents/agentConfigs. Set by use-wizard-initialization. */\n globalAgentPreselections: { agents: AgentName[]; configs: AgentScopeConfig[] } | null;\n\n history: WizardStep[];\n\n /**\n * Navigate to a wizard step, pushing the current step onto history.\n * @param step - Target step to navigate to\n *\n * Side effects: sets `step`, appends previous step to `history`\n */\n setStep: (step: WizardStep) => void;\n /**\n * Set the wizard approach (stack-based or build-from-scratch).\n * @param approach - \"stack\" to use a pre-built template, \"scratch\" to select skills manually, null to reset\n *\n * Side effects: sets `approach`\n */\n setApproach: (approach: WizardApproach | null) => void;\n /**\n * Select a stack by ID, or null to deselect.\n * @param stackId - Stack identifier from suggestedStacks, or null to clear\n *\n * Side effects: sets `selectedStackId`\n */\n selectStack: (stackId: string | null) => void;\n /**\n * Set how to apply the selected stack.\n * @param action - \"defaults\" to use stack as-is, \"customize\" to enter the build step\n *\n * Side effects: sets `stackAction`\n */\n setStackAction: (action: StackAction) => void;\n /**\n * Prepare a build with no stack behind it — what \"Start from scratch\" means.\n *\n * Two surfaces arrive here and must agree on what it is: the stack step's\n * scratch row, and the hydration that opens the wizard past a stack step the\n * loaded source gave nothing to show on.\n *\n * Side effects: clears every stack-derived selection (via `selectStack(null)`),\n * sets `approach` to \"scratch\", restores `selectedAgents` / `agentConfigs` from\n * `globalAgentPreselections`, populates selections from `globalPreselections`,\n * and selects the scratch domains. Does NOT navigate — the caller owns the step.\n */\n startFromScratch: () => void;\n /**\n * Pre-populate domainSelections from a flat list of installed skill IDs.\n *\n * Used by `npx agents-inc edit` to restore wizard state from existing project config.\n * Looks up each skill's category and domain, warns for unresolvable skills.\n *\n * @param skillIds - Flat array of currently installed skill IDs\n * @param skills - Skill lookup providing category and displayName per skill ID\n * @param categories - Category definitions used to resolve category -> domain mapping\n *\n * Side effects: sets `domainSelections`, sets `selectedDomains` to domains found in the provided skill IDs\n */\n populateFromSkillIds: (skillIds: SkillId[], savedConfigs?: SkillConfig[]) => void;\n /**\n * Toggle a domain on or off in the selectedDomains list.\n * @param domain - Domain to toggle\n *\n * Side effects: adds or removes from `selectedDomains`\n */\n toggleDomain: (domain: Domain) => void;\n /**\n * Toggle a skill selection within a domain's category.\n *\n * When exclusive is true (radio behavior), selecting a new skill replaces any\n * existing selection in that category. When false (checkbox behavior),\n * the skill is added to or removed from the selection array.\n *\n * @param domain - Domain containing the category\n * @param category - Category within the domain\n * @param technology - Skill ID to toggle\n * @param exclusive - If true, only one skill can be selected per category (radio)\n *\n * Side effects: updates `domainSelections[domain][category]`\n */\n toggleTechnology: (\n domain: Domain,\n category: Category,\n technology: SkillId,\n exclusive: boolean,\n ) => void;\n /**\n * Advance to the next domain in the build step.\n * @returns true if advanced, false if already at the last domain\n *\n * Side effects: increments `currentDomainIndex`\n */\n nextDomain: () => boolean;\n /**\n * Go back to the previous domain in the build step.\n * @returns true if moved back, false if already at the first domain\n *\n * Side effects: decrements `currentDomainIndex`\n */\n prevDomain: () => boolean;\n /**\n * Set the current domain index directly.\n * @param index - Index to set (0-based, must be within selectedDomains range)\n *\n * Side effects: sets `currentDomainIndex` if index is valid, otherwise no-op\n */\n setCurrentDomainIndex: (index: number) => void;\n /** Toggle compatibility label visibility on skill tags in the build step grid. */\n toggleShowLabels: () => void;\n /**\n * Derive the install mode from skillConfigs source values.\n * If all skills use \"eject\" source, returns \"eject\". If all use non-eject, returns \"plugin\".\n * If mixed, returns \"mixed\". Returns \"eject\" when no skills are configured.\n */\n deriveInstallMode: () => InstallMode;\n /**\n * Toggle the scope of a specific skill between \"project\" and \"global\".\n * @param skillId - Skill to toggle scope for\n *\n * Side effects: updates `skillConfigs` entry for the skill\n */\n toggleSkillScope: (skillId: SkillId) => void;\n /**\n * Set the currently focused skill ID in the build step (for S hotkey).\n * @param id - Skill ID to focus, or null to clear\n *\n * Side effects: sets `focusedSkillId`\n */\n setFocusedSkillId: (id: SkillId | null) => void;\n /**\n * Seed `focusedSkillId` to the skill the build-step grid focuses first for the\n * active domain (row 0, col 0 of the filtered category rows). Runs synchronously\n * at build-step entry and on every domain change so the `s` scope hotkey — which\n * reads `focusedSkillId` — always resolves the visually-focused skill, with no\n * dependency on CategoryGrid's post-mount effect.\n *\n * Side effects: sets `focusedSkillId` (null when the active domain has no skills)\n */\n seedFocusedSkillForActiveDomain: () => void;\n /**\n * Set how a specific skill is installed: the project's own copy, or the marketplace plugin.\n * @param skillId - Skill to configure the install mode for\n * @param mode - `eject` for a local copy, `plugin` for the one marketplace\n * @param scope - Acting scope from the Sources row: only the active entry at this scope is\n * updated; a masked global tombstone for the same id keeps its source.\n *\n * Refuses a project-context call that targets a global slot the hydration snapshot already\n * owns ({@link isInheritedGlobalSlot}) — a project edit may not change how a global install is\n * installed, the same authority the Sources grid enforces by rendering that row inert.\n *\n * Side effects: updates the active `skillConfigs` entry for the skill at `scope`, writing the\n * `source` value the mode resolves to. No-op with a warning on an empty skill id; silent no-op\n * on a refused global slot.\n */\n setInstallMode: (\n skillId: SkillId,\n mode: Exclude<InstallMode, \"mixed\">,\n scope: SkillScope | undefined,\n ) => void;\n /** Toggle the info overlay (selected skills and agents). */\n toggleInfo: () => void;\n /** Set a temporary toast message, or null to clear it. */\n setToastMessage: (message: string | null) => void;\n /**\n * Navigate to the previous wizard step using the history stack.\n * No-op when history is empty (e.g., edit flow starting at a mid-wizard step).\n *\n * Side effects: pops from `history`, sets `step` to the popped value\n */\n goBack: () => void;\n /**\n * Toggle an agent on or off in the selectedAgents list.\n * @param agent - Agent name to toggle\n *\n * Side effects: adds or removes from `selectedAgents`, syncs `agentConfigs`\n */\n toggleAgent: (agent: AgentName) => void;\n /**\n * Toggle the scope of a specific agent between \"project\" and \"global\".\n * @param agentName - Agent to toggle scope for\n *\n * Side effects: updates `agentConfigs` entry for the agent\n */\n toggleAgentScope: (agentName: AgentName) => void;\n /**\n * Set the currently focused agent ID in the agents step (for S hotkey).\n * @param id - Agent name to focus, or null to clear\n *\n * Side effects: sets `focusedAgentId`\n */\n setFocusedAgentId: (id: AgentName | null) => void;\n /**\n * Preselect agents based on selected domains from the first wizard step.\n * Matches domains against DOMAIN_AGENTS mapping.\n * Optional agents (meta/pattern) are excluded.\n *\n * The from-scratch path only: with a stack chosen, its declared roster stands and\n * this is a no-op. Selecting a stack is the statement of which sub-agents an\n * installation gets, and the user edits that roster in the agents step.\n *\n * Side effects: replaces `selectedAgents` with computed preselection, unless a\n * stack is selected\n */\n preselectAgentsFromDomains: () => void;\n /**\n * Preselect agents for a chosen stack: merges the stack's agent keys with global\n * agent preselections, preserving dual-scope tombstones. Parallels\n * populateFromSkillIds on the skill path.\n *\n * This is the roster the install gets — nothing downstream re-derives it.\n *\n * Side effects: replaces `selectedAgents` and `agentConfigs` with the merged result\n */\n preselectAgentsFromStack: (stackAgents: AgentName[]) => void;\n /** Reset all wizard state to initial values. */\n reset: () => void;\n\n /**\n * Collect all selected skill IDs across all domains and categories.\n * @returns Flat array of every selected SkillId (may contain duplicates if shared across domains)\n */\n getAllSelectedTechnologies: () => SkillId[];\n /**\n * Group selected skill IDs by domain.\n * @returns Partial record mapping each domain with selections to its skill ID array\n */\n getSelectedTechnologiesPerDomain: () => Partial<Record<Domain, SkillId[]>>;\n /**\n * Get the domain currently visible in the build step.\n * @returns The domain at currentDomainIndex, or null if no domains are selected\n */\n getCurrentDomain: () => Domain | null;\n /**\n * Count total selected technologies across all domains.\n * @returns Number of selected skill IDs\n */\n getTechnologyCount: () => number;\n /**\n * Compute which wizard steps are completed and which are skipped.\n * Used by WizardTabs to render step progress indicators. Both lists are read\n * against {@link getActiveStepFlow} — a step this run never has cannot be one\n * it completed.\n * @returns Object with completedSteps and skippedSteps string arrays\n */\n getStepProgress: () => { completedSteps: WizardStep[]; skippedSteps: WizardStep[] };\n /** @returns true if there is a next domain after the current one */\n canGoToNextDomain: () => boolean;\n /** @returns true if there is a previous domain before the current one */\n canGoToPreviousDomain: () => boolean;\n\n /**\n * Build the source selection rows for the sources step UI.\n *\n * For each selected technology, resolves the canonical skill ID and marks which of the two\n * install modes its persisted `source` stands for.\n *\n * @returns Array of row objects, one per selected technology, each containing:\n * - `skillId` - Canonical resolved skill ID\n * - `options` - The two install-mode cells, with the current one selected\n */\n buildSourceRows: () => SourceRow[];\n};\n\n/** State-only fields from WizardState (excludes actions/getters). Used to type createInitialState(). */\ntype WizardStateData = Pick<\n WizardState,\n | \"step\"\n | \"approach\"\n | \"selectedStackId\"\n | \"stackAction\"\n | \"selectedDomains\"\n | \"currentDomainIndex\"\n | \"domainSelections\"\n | \"_stackDomainSelections\"\n | \"showLabels\"\n | \"skillConfigs\"\n | \"focusedSkillId\"\n | \"unresolvableSkillIds\"\n | \"showInfo\"\n | \"selectedAgents\"\n | \"agentConfigs\"\n | \"focusedAgentId\"\n | \"installedSkillConfigs\"\n | \"installedAgentConfigs\"\n | \"isInitMode\"\n | \"isEditingFromGlobalScope\"\n | \"toastMessage\"\n | \"globalPreselections\"\n | \"globalAgentPreselections\"\n | \"history\"\n>;\n\nexport const createInitialState = (overrides?: Partial<WizardStateData>): WizardStateData => ({\n step: \"stack\",\n approach: null,\n selectedStackId: null,\n stackAction: null,\n selectedDomains: [],\n currentDomainIndex: 0,\n domainSelections: {},\n /** Snapshot of domainSelections from populateFromSkillIds, used to restore on domain re-toggle */\n _stackDomainSelections: null,\n showLabels: false,\n skillConfigs: [],\n focusedSkillId: null,\n unresolvableSkillIds: [],\n showInfo: false,\n selectedAgents: [],\n agentConfigs: [],\n focusedAgentId: null,\n installedSkillConfigs: null,\n installedAgentConfigs: null,\n isInitMode: false,\n isEditingFromGlobalScope: false,\n toastMessage: null,\n globalPreselections: null,\n globalAgentPreselections: null,\n history: [],\n ...overrides,\n});\n\nexport const useWizardStore = create<WizardState>((set, get) => ({\n ...createInitialState(),\n\n setStep: (step) => {\n set((state) => ({\n step,\n history: [...state.history, state.step],\n }));\n if (step === \"build\") get().seedFocusedSkillForActiveDomain();\n },\n\n setApproach: (approach) => set({ approach }),\n\n selectStack: (stackId) =>\n set({\n selectedStackId: stackId,\n domainSelections: {},\n _stackDomainSelections: null,\n selectedDomains: [],\n skillConfigs: [],\n selectedAgents: [],\n agentConfigs: [],\n currentDomainIndex: 0,\n stackAction: null,\n }),\n\n setStackAction: (action) => set({ stackAction: action }),\n\n startFromScratch: () => {\n // selectStack(null) wipes the agent selections along with the stack's, so the\n // global ones are put back rather than left as collateral.\n get().selectStack(null);\n get().setApproach(\"scratch\");\n\n const { globalAgentPreselections, globalPreselections } = get();\n if (globalAgentPreselections) {\n set({\n selectedAgents: globalAgentPreselections.agents,\n agentConfigs: globalAgentPreselections.configs,\n });\n }\n if (globalPreselections?.length) {\n get().populateFromSkillIds(\n globalPreselections.map((skill) => skill.id),\n globalPreselections,\n );\n }\n\n // Additive: a scratch domain the preselections already brought stays on.\n for (const domain of DEFAULT_SCRATCH_DOMAINS) {\n if (!get().selectedDomains.includes(domain)) get().toggleDomain(domain);\n }\n },\n\n populateFromSkillIds: (skillIds, savedConfigs) =>\n set(() => {\n const domainSelections: DomainSelections = {};\n const resolvedSkillIds: SkillId[] = [];\n const unresolvableSkillIds: SkillId[] = [];\n\n for (const skillId of skillIds) {\n const resolved = resolveSkillForPopulation(skillId);\n if (!resolved) {\n unresolvableSkillIds.push(skillId);\n continue;\n }\n\n const { domain, subcat, techId } = resolved;\n if (addToDomainSelections(domainSelections, domain, subcat, techId)) {\n resolvedSkillIds.push(techId);\n }\n }\n\n const selectedDomains = orderDomains(typedKeys<Domain>(domainSelections));\n\n const skillConfigs: SkillConfig[] = resolvedSkillIds.map((id) =>\n buildSkillConfigForId(id, savedConfigs),\n );\n\n // Preserve excluded entries so they flow through to wizard result.\n // D-223: allow an excluded tombstone to coexist with an active entry for the\n // same skill id at a different scope (render layer computes secondaryScope\n // from the pair).\n const excludedConfigs = collectTombstones(savedConfigs ?? []);\n\n return {\n domainSelections,\n _stackDomainSelections: structuredClone(domainSelections),\n selectedDomains,\n skillConfigs: [...skillConfigs, ...excludedConfigs],\n unresolvableSkillIds,\n };\n }),\n\n toggleDomain: (domain) =>\n set((state) => {\n const isSelected = state.selectedDomains.includes(domain);\n if (isSelected) {\n const { [domain]: _removed, ...remainingSelections } = state.domainSelections;\n const removedSkillIds = _removed\n ? collectSkillIdsFromSelections(_removed)\n : new Set<SkillId>();\n\n return {\n selectedDomains: state.selectedDomains.filter((d) => d !== domain),\n domainSelections: remainingSelections,\n // Global-scope edit: no overlay, so deselecting a domain uninstalls its skills cleanly\n // rather than tombstoning them in the global config (D-233 Scenario C).\n skillConfigs: applySkillRemoval(\n state.skillConfigs,\n removedSkillIds,\n state.isEditingFromGlobalScope ? null : state.installedSkillConfigs,\n ),\n };\n }\n\n // Restore stack selections for this domain if a stack snapshot exists\n const stackSelections = state._stackDomainSelections?.[domain];\n if (stackSelections) {\n const restoredSkillIds = [...collectSkillIdsFromSelections(stackSelections)];\n\n return {\n selectedDomains: orderDomains([...state.selectedDomains, domain]),\n domainSelections: {\n ...state.domainSelections,\n [domain]: structuredClone(stackSelections),\n },\n skillConfigs: restoreSkillConfigs(state.skillConfigs, restoredSkillIds),\n };\n }\n\n return {\n selectedDomains: orderDomains([...state.selectedDomains, domain]),\n };\n }),\n\n toggleTechnology: (domain, category, technology, exclusive) =>\n set((state) => {\n const installed = state.installedSkillConfigs ?? [];\n const currentSelections = state.domainSelections[domain]?.[category] || [];\n const isSelected = currentSelections.includes(technology);\n\n // Block a GLOBAL-OWNED entry from being changed at project scope. On a SELECT only the\n // active-global arm applies (an inherited-global row is read-only in both directions); the\n // stale-collapse arm is a deselect guard, kept behind isSelected so the re-select restore\n // path (reconcileSkillConfigs rebuilds `[P][G]`) still runs. The project half of a\n // persisted `[P][G]` pair is project-owned and passes both ways.\n const isGlobalLocked = isSelected\n ? isGloballyLockedSkill(installed, state.skillConfigs, technology)\n : hasGlobalActive(installed, technology);\n if (isGlobalLocked && !state.isEditingFromGlobalScope) {\n return { toastMessage: TOAST_MESSAGES.GLOBAL_SKILLS_LOCKED };\n }\n\n if (isSelected) {\n const categoryDef = matrix.categories[category];\n if (categoryDef?.exclusive && categoryDef.required) {\n const categorySkillCount = typedValues(matrix.skills).filter(\n (s) => s.category === category,\n ).length;\n if (categorySkillCount <= 1) {\n return { toastMessage: TOAST_MESSAGES.ONLY_SKILL_IN_CATEGORY };\n }\n }\n }\n\n // In exclusive mode, selecting a new skill replaces the current one. Block if that would\n // implicitly drop a skill this scope may not drop THIS WAY: a radio swap must never\n // tombstone a global install, and never unmask one either.\n if (exclusive && !isSelected) {\n const wouldDropLockedSkill = currentSelections.some((selectedId) =>\n blocksExclusiveSwap(installed, state.skillConfigs, selectedId),\n );\n if (wouldDropLockedSkill && !state.isEditingFromGlobalScope) {\n return { toastMessage: TOAST_MESSAGES.GLOBAL_SKILLS_LOCKED };\n }\n }\n\n let newSelections: SkillId[];\n if (exclusive) {\n newSelections = isSelected ? [] : [technology];\n } else {\n newSelections = isSelected\n ? currentSelections.filter((t) => t !== technology)\n : [...currentSelections, technology];\n }\n\n const removed = currentSelections.filter((id) => !newSelections.includes(id));\n const added = newSelections.filter((id) => !currentSelections.includes(id));\n\n const skillConfigs = reconcileSkillConfigs(\n state.skillConfigs,\n added,\n removed,\n state.installedSkillConfigs,\n state.isEditingFromGlobalScope,\n );\n\n // A dual-scope deselect collapses to a single active inherited-global entry, so the\n // skill is still genuinely active. Keep it in the domain selection (mirroring what a\n // save-and-reopen re-derives via populateFromSkillIds) instead of dropping it purely\n // because it left the pre-removal selection array. A genuine full removal leaves no\n // active entry and still drops normally.\n const stillActiveAfterRemoval =\n isSelected &&\n !newSelections.includes(technology) &&\n (hasProjectActive(skillConfigs, technology) || hasGlobalActive(skillConfigs, technology));\n const resolvedSelections = stillActiveAfterRemoval\n ? [...newSelections, technology]\n : newSelections;\n\n return {\n skillConfigs,\n domainSelections: {\n ...state.domainSelections,\n [domain]: {\n ...state.domainSelections[domain],\n [category]: resolvedSelections,\n },\n },\n };\n }),\n\n nextDomain: () => {\n const state = get();\n if (state.currentDomainIndex < state.selectedDomains.length - 1) {\n set({\n currentDomainIndex: state.currentDomainIndex + 1,\n });\n get().seedFocusedSkillForActiveDomain();\n return true;\n }\n return false;\n },\n\n prevDomain: () => {\n const state = get();\n if (state.currentDomainIndex > 0) {\n set({\n currentDomainIndex: state.currentDomainIndex - 1,\n });\n get().seedFocusedSkillForActiveDomain();\n return true;\n }\n return false;\n },\n\n setCurrentDomainIndex: (index) => {\n const state = get();\n if (index >= 0 && index < state.selectedDomains.length) {\n set({ currentDomainIndex: index });\n get().seedFocusedSkillForActiveDomain();\n }\n },\n\n toggleShowLabels: () => set((state) => ({ showLabels: !state.showLabels })),\n\n deriveInstallMode: (): InstallMode => {\n const { skillConfigs } = get();\n return sharedDeriveInstallMode(skillConfigs);\n },\n\n toggleSkillScope: (skillId) =>\n set((state) => {\n if (state.isEditingFromGlobalScope) return state;\n\n const config = state.skillConfigs.find((sc) => sc.id === skillId && !sc.excluded);\n if (!config) return state;\n\n // Guard: block project eject → global when global eject already exists (would overwrite)\n if (wouldOverwriteGlobalEject(config, state.skillConfigs, state.installedSkillConfigs)) {\n return { toastMessage: TOAST_MESSAGES.ALREADY_EJECTED_AT_GLOBAL };\n }\n\n // A real global install exists when the snapshot carries EITHER an active global entry\n // or a global tombstone: a tombstone means the skill IS installed globally and this\n // project was overriding it. Both must re-create the tombstone on a G→P toggle, so an\n // in-session collapse→`s` restores a genuine `[P][G]` pair rather than a bare `[P]` that\n // loses the still-real global install. (Matches the agent-path derivation, which already\n // counts tombstones.)\n const wasInstalledGlobally =\n state.installedSkillConfigs?.some((sc) => sc.id === skillId && sc.scope === \"global\") ??\n false;\n const newScope = config.scope === \"project\" ? (\"global\" as const) : (\"project\" as const);\n\n const rescoped = state.skillConfigs.map((sc) =>\n sc.id === skillId && !sc.excluded ? { ...sc, scope: newScope } : sc,\n );\n\n if (newScope === \"project\") {\n // Moving global → project: add the excluded global tombstone if not already there.\n // Gated on wasInstalledGlobally so fresh init toggles don't create spurious tombstones.\n const needsTombstone =\n wasInstalledGlobally && !rescoped.some((sc) => sc.id === skillId && sc.excluded);\n return {\n skillConfigs: needsTombstone\n ? [\n ...rescoped,\n { id: skillId, scope: \"global\" as const, excluded: true, source: config.source },\n ]\n : rescoped,\n };\n }\n\n // Moving project → global: always drop any excluded global tombstone for this id.\n // An active entry at global scope supersedes any tombstone at the same scope — the\n // invariant \"no active + tombstone at the same (id, scope)\" must hold. Unconditional\n // removal (not gated on wasInstalledGlobally) heals the D-224 case where the prior\n // G→P produced a tombstone that installedSkillConfigs-derived wasInstalledGlobally\n // cannot see (because its `!sc.excluded` filter ignores the tombstone itself).\n return { skillConfigs: rescoped.filter((sc) => !(sc.id === skillId && sc.excluded)) };\n }),\n\n setFocusedSkillId: (id) => set({ focusedSkillId: id }),\n\n seedFocusedSkillForActiveDomain: () => {\n const state = get();\n // Mirror the build-step renderer's domain fallback (use-build-step-props.ts):\n // when no domain is selected the grid still renders FALLBACK_DOMAIN and\n // highlights a cell, so the seed must resolve that same domain — never null\n // while a cell is visibly focused — or the cold `s` toggle no-ops on a desync.\n const domain = state.getCurrentDomain() ?? FALLBACK_DOMAIN;\n const categories = buildCategoriesForDomain(\n domain,\n state.getAllSelectedTechnologies(),\n undefined,\n state.skillConfigs,\n );\n set({ focusedSkillId: categories[0]?.options[0]?.id ?? null });\n },\n\n setInstallMode: (skillId, mode, scope) =>\n set((state) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- SkillId is a string union with no empty member, so the type rules this out; the guard is for a runtime caller handing over an empty id\n if (!skillId) {\n warn(\"Ignoring setInstallMode call with empty skillId\");\n return state;\n }\n // Block a GLOBAL-OWNED slot from being reconfigured at project scope — the authority the\n // Sources grid already enforces by rendering that row inert, held here so it does not rest\n // on the UI alone. Silent: the grid never offers the row, so a refused call is a caller\n // reaching past it.\n if (\n isInheritedGlobalSlot(state.installedSkillConfigs, skillId, scope) &&\n !state.isEditingFromGlobalScope\n ) {\n return state;\n }\n const source = sourceForInstallMode(getSkillById(skillId), mode);\n return {\n skillConfigs: withActiveEntrySource(state.skillConfigs, skillId, scope, source),\n };\n }),\n\n toggleInfo: () => set((state) => ({ showInfo: !state.showInfo })),\n\n setToastMessage: (message) => set({ toastMessage: message }),\n\n goBack: () =>\n set((state) => {\n if (state.history.length === 0) return state;\n const history = [...state.history];\n const previousStep = history.pop()!;\n return {\n step: previousStep,\n history,\n };\n }),\n\n toggleAgent: (agent) =>\n set((state) => {\n const installed = state.installedAgentConfigs ?? [];\n\n // A live dual-scope `[P][G]` row is locked to `s`: the selection key is inert on it and\n // emits the same global-locked toast an inherited-global row shows. Mirrors the skill-side\n // dual-scope arm in toggleTechnology — `s` alone collapses and restores the pair.\n const isDualScopePairRow =\n !state.isEditingFromGlobalScope && isDualScopeAgentPair(state.agentConfigs, agent);\n if (isDualScopePairRow) {\n return { toastMessage: TOAST_MESSAGES.GLOBAL_AGENTS_LOCKED };\n }\n\n const isInheritedGlobalReselect =\n !state.isEditingFromGlobalScope &&\n agentHasGlobalActive(state.agentConfigs, agent) &&\n !agentHasProjectActive(state.agentConfigs, agent) &&\n !state.selectedAgents.includes(agent) &&\n agentHasGlobalTombstone(installed, agent);\n if (isInheritedGlobalReselect) {\n return restoreDualScopeAgent(state.selectedAgents, state.agentConfigs, agent);\n }\n\n // Block a globally-installed agent from being changed at project scope. Mirrors the\n // skill-path guard in toggleTechnology: fire on an active global entry in the snapshot,\n // or on a snapshot tombstone paired with a LIVE plain active global entry (the stale\n // state a persisted `[P][G]` reaches after an in-session collapse). A freshly-added\n // global agent (absent from the snapshot) stays freely deselectable.\n const isActiveGlobal =\n agentHasGlobalActive(installed, agent) ||\n (agentHasGlobalTombstone(installed, agent) &&\n agentHasGlobalActive(state.agentConfigs, agent));\n if (isActiveGlobal && !state.isEditingFromGlobalScope) {\n return { toastMessage: TOAST_MESSAGES.GLOBAL_AGENTS_LOCKED };\n }\n\n const isInList = state.selectedAgents.includes(agent);\n const hasExcludedTombstone = state.agentConfigs.some(\n (ac) => ac.name === agent && ac.excluded,\n );\n // An agent is effectively selected only if it's in the list WITHOUT an excluded tombstone.\n // When it has an excluded tombstone, it's visually \"off\" and toggling means \"re-enable\".\n const isSelected = isInList && !hasExcludedTombstone;\n\n return {\n selectedAgents: nextSelectedAgents(state.selectedAgents, agent, { isSelected, isInList }),\n agentConfigs: applyAgentToggle(state.agentConfigs, agent, isSelected),\n };\n }),\n\n toggleAgentScope: (agentName) =>\n set((state) => {\n if (state.isEditingFromGlobalScope) return state;\n\n const config = state.agentConfigs.find((ac) => ac.name === agentName && !ac.excluded);\n if (!config) return state;\n\n // Counts a global tombstone as \"installed globally\" (a tombstone means a real global\n // install this project overrides), so an in-session collapse→`s` restores a genuine\n // `[P][G]` pair.\n const wasInstalledGlobally =\n state.installedAgentConfigs?.some((ac) => ac.name === agentName && ac.scope === \"global\") ??\n false;\n const newScope = config.scope === \"project\" ? (\"global\" as const) : (\"project\" as const);\n\n const rescoped = state.agentConfigs.map((ac) =>\n ac.name === agentName && !ac.excluded ? { ...ac, scope: newScope } : ac,\n );\n\n if (newScope === \"project\") {\n // Moving global → project: add the excluded global tombstone if not already there.\n // Gated on wasInstalledGlobally so fresh init toggles don't create spurious tombstones.\n const needsTombstone =\n wasInstalledGlobally && !rescoped.some((ac) => ac.name === agentName && ac.excluded);\n return {\n agentConfigs: needsTombstone\n ? [...rescoped, { name: agentName, scope: \"global\" as const, excluded: true }]\n : rescoped,\n };\n }\n\n // Moving project → global: always drop any excluded global tombstone for this name.\n // Symmetric with toggleSkillScope — see its comment for the invariant.\n return { agentConfigs: rescoped.filter((ac) => !(ac.name === agentName && ac.excluded)) };\n }),\n\n setFocusedAgentId: (id) => set({ focusedAgentId: id }),\n\n preselectAgentsFromDomains: () =>\n set((state) => {\n // A stack declares its own roster, and that declaration is the whole of what\n // choosing it means. Deriving one from the domains would run later in the step\n // order and overwrite it, so the from-scratch path stops here.\n if (state.selectedStackId) return state;\n\n // Deduped: several selected domains each roster the shared `reviewer`.\n const sorted = [\n ...new Set(state.selectedDomains.flatMap((domain) => DOMAIN_AGENTS[domain] ?? [])),\n ].sort();\n const roster = new Set(sorted);\n const merged = sorted.map((name) => buildAgentConfigForName(name, state.agentConfigs));\n const retained = state.agentConfigs.filter((ac) => survivesRosterRebuild(ac, roster));\n return {\n selectedAgents: sorted,\n agentConfigs: [...merged, ...retained],\n };\n }),\n\n preselectAgentsFromStack: (stackAgents) =>\n set((state) => {\n const savedConfigs = state.globalAgentPreselections?.configs ?? [];\n const globalAgents = state.globalAgentPreselections?.agents ?? [];\n const mergedAgents = [...new Set([...stackAgents, ...globalAgents])].sort();\n const merged = mergedAgents.map((name) => buildAgentConfigForName(name, savedConfigs));\n // Preserve dual-scope tombstones, same invariant as preselectAgentsFromDomains (D-227).\n const excludedConfigs = collectTombstones(savedConfigs);\n return {\n selectedAgents: mergedAgents,\n agentConfigs: [...merged, ...excludedConfigs],\n };\n }),\n\n reset: () => set(createInitialState()),\n\n getAllSelectedTechnologies: () => {\n return typedValues(get().domainSelections).flatMap(flattenCategorySelections);\n },\n\n getSelectedTechnologiesPerDomain: () => {\n const entries = typedEntries(get().domainSelections)\n .map(([domain, domainSel]) => [domain, flattenCategorySelections(domainSel)] as const)\n .filter(([, techs]) => techs.length > 0);\n return typedFromEntries(entries);\n },\n\n getCurrentDomain: () => {\n const state = get();\n return state.selectedDomains[state.currentDomainIndex] || null;\n },\n\n getTechnologyCount: () => {\n return get().getAllSelectedTechnologies().length;\n },\n\n getStepProgress: () => {\n const state = get();\n const isStackDefaults =\n state.approach === \"stack\" && !!state.selectedStackId && state.stackAction === \"defaults\";\n const skippedSteps: WizardStep[] = isStackDefaults ? [\"build\", \"sources\", \"agents\"] : [];\n const precedingSteps = stepsBefore(getActiveStepFlow(), state.step);\n const completedSteps = precedingSteps.filter((step) => !skippedSteps.includes(step));\n return { completedSteps, skippedSteps };\n },\n\n canGoToNextDomain: () => {\n const state = get();\n return state.currentDomainIndex < state.selectedDomains.length - 1;\n },\n\n canGoToPreviousDomain: () => {\n const state = get();\n return state.currentDomainIndex > 0;\n },\n\n buildSourceRows: () => {\n const state = get();\n const selectedTechnologies = get().getAllSelectedTechnologies();\n const { skillConfigs } = state;\n\n // Include inherited global skills (in skillConfigs but not in domainSelections)\n const selectedSet = new Set(selectedTechnologies);\n const inheritedSkillIds = skillConfigs\n .filter((sc) => !sc.excluded && !selectedSet.has(sc.id))\n .map((sc) => sc.id);\n const allActiveIds = new Set([...inheritedSkillIds, ...selectedTechnologies]);\n const excludedGlobalIds = skillConfigs\n .filter((sc) => isGlobalTombstone(sc) && !allActiveIds.has(sc.id))\n .map((sc) => sc.id);\n // Every skill with a LIVE config entry or selection. Internally disjoint by construction, so no\n // skill contributes live rows twice.\n const liveSkillIds = [...inheritedSkillIds, ...selectedTechnologies, ...excludedGlobalIds];\n // D-258: the slot baseline additions are measured against. Each emitted row checks its OWN\n // `(id, scope)` slot, so a skill adopted at a second scope registers as added on the newly\n // occupied row alone — the same classification the confirm step's computeScopeDiff makes.\n const installedSkillSlots = collectInstalledSkillSlots(state.installedSkillConfigs);\n const resolveRowInputs = (id: SkillId) => resolveSkillRowInputs(id, skillConfigs);\n\n const liveRows: SourceRow[] = liveSkillIds.flatMap((id) => {\n const { skillId, configEntry, options } = resolveRowInputs(id);\n return classifySkillSourceRows(skillId, options, {\n configEntry,\n installedSkillConfigs: state.installedSkillConfigs,\n isEditingFromGlobalScope: state.isEditingFromGlobalScope,\n installedSkillSlots,\n });\n });\n\n // D-257/D-271: slots the session emptied. A skill may appear in BOTH lists — a collapsed\n // dual-scope pair keeps its surviving global row and gains a project pending-removal row — so\n // rows are no longer disjoint per SKILL. They stay disjoint per `(id, scope)` SLOT: a snapshot\n // slot a live row already renders never gets a removal row on top of it.\n const pendingRemovalRows: SourceRow[] = collectRemovedInstalledEntries(\n state.installedSkillConfigs,\n skillConfigs,\n )\n .filter((entry) => !isSlotAlreadyRendered(liveRows, entry))\n .map((entry) => {\n const { skillId, options } = resolveRowInputs(entry.id);\n return toPendingRemovalRow(skillId, options, entry);\n });\n\n // Stable sort by scope tier: global readOnly, global editable, then project. Pending-removal\n // rows carry no dedicated tier — they sort by their own scope and, being appended last, trail\n // inline within that scope section. Matches source-grid grouping so navigation indices align\n // with render order.\n const rows = [...liveRows, ...pendingRemovalRows];\n rows.sort((a, b) => sourceRowSortTier(a) - sourceRowSortTier(b));\n\n return rows;\n },\n}));\n\nexport type HydrateOptions = {\n initialStep?: WizardStep;\n initialDomains?: Domain[];\n initialAgents?: AgentName[];\n installedSkillIds?: SkillId[];\n installedSkillConfigs?: SkillConfig[];\n installedAgentConfigs?: AgentScopeConfig[];\n isEditingFromGlobalScope?: boolean;\n};\n\n/**\n * Imperatively hydrates the wizard store before the first render.\n *\n * Must be called BEFORE render(<Wizard ... />) so React captures the correct\n * initial snapshot on the first frame. Running this inside a render-phase hook\n * causes a one-frame flash of the default \"stack\" step before the jump to the\n * intended step is committed to stdout.\n */\nexport function hydrateWizardStore(options: HydrateOptions): void {\n useWizardStore.setState(createInitialState());\n\n const { initialStep } = options;\n const isEditFlow = initialStep !== undefined;\n if (isEditFlow) {\n hydrateForEdit(initialStep, options);\n } else {\n hydrateForInit(options);\n }\n}\n\n/** Edit flow: populate from installed skills, then jump straight to the saved step. */\nfunction hydrateForEdit(initialStep: WizardStep, options: HydrateOptions): void {\n const {\n initialDomains,\n initialAgents,\n installedSkillIds,\n installedSkillConfigs,\n installedAgentConfigs,\n isEditingFromGlobalScope,\n } = options;\n\n if (installedSkillIds?.length) {\n useWizardStore.getState().populateFromSkillIds(installedSkillIds, installedSkillConfigs);\n }\n\n useWizardStore.setState({\n isInitMode: false,\n // Jump directly to initialStep with empty history. The user is starting\n // fresh at this step — no prior steps to navigate back through.\n step: initialStep,\n history: [],\n approach: \"scratch\",\n // Saved domains/agents from config override what populateFromSkillIds derived.\n ...(initialDomains?.length ? { selectedDomains: initialDomains, currentDomainIndex: 0 } : {}),\n ...(initialAgents?.length ? { selectedAgents: initialAgents } : {}),\n ...(initialAgents?.length && installedAgentConfigs?.length\n ? { agentConfigs: installedAgentConfigs }\n : {}),\n // Snapshot installed configs for diff rendering in SkillAgentSummary\n ...(installedSkillConfigs?.length || installedAgentConfigs?.length\n ? {\n installedSkillConfigs: installedSkillConfigs ?? null,\n installedAgentConfigs: installedAgentConfigs ?? null,\n }\n : {}),\n ...(isEditingFromGlobalScope ? { isEditingFromGlobalScope: true } : {}),\n });\n\n useWizardStore.getState().seedFocusedSkillForActiveDomain();\n}\n\n/** Where an init opens when the loaded source offers no stack to choose. */\nconst FIRST_STEP_WITHOUT_STACKS: WizardStep = \"domains\";\n\n/**\n * Init flow: skills are not populated yet — the stack step runs first, so saved\n * selections are stored as preselections for stack-selection.tsx to merge after\n * the stack/scratch choice (selectStack wipes agents).\n *\n * Unless the loaded source offers no stacks at all — a custom marketplace that\n * ships none, since the built-in catalogue stands in for the default public\n * marketplace alone. That step would then hold nothing but its own \"Start from\n * scratch\" row, so the wizard opens where that row leads, having done what\n * pressing it does.\n */\nfunction hydrateForInit(options: HydrateOptions): void {\n const {\n initialDomains,\n initialAgents,\n installedSkillConfigs,\n installedAgentConfigs,\n isEditingFromGlobalScope,\n } = options;\n\n useWizardStore.setState({\n isInitMode: true,\n ...(initialDomains?.length ? { selectedDomains: initialDomains, currentDomainIndex: 0 } : {}),\n ...(initialAgents?.length ? { selectedAgents: initialAgents } : {}),\n ...(initialAgents?.length && installedAgentConfigs?.length\n ? { agentConfigs: installedAgentConfigs }\n : {}),\n // Snapshot installed configs for diff rendering in SkillAgentSummary\n ...(installedSkillConfigs?.length || installedAgentConfigs?.length\n ? {\n installedSkillConfigs: installedSkillConfigs ?? null,\n installedAgentConfigs: installedAgentConfigs ?? null,\n }\n : {}),\n ...(isEditingFromGlobalScope ? { isEditingFromGlobalScope: true } : {}),\n ...(installedSkillConfigs?.length ? { globalPreselections: installedSkillConfigs } : {}),\n ...(initialAgents?.length || installedAgentConfigs?.length\n ? {\n globalAgentPreselections: {\n agents: initialAgents ?? [],\n configs: installedAgentConfigs ?? [],\n },\n }\n : {}),\n });\n\n if (!offersStacks()) {\n useWizardStore.getState().startFromScratch();\n // Nothing behind this step: the stack step it would have been pushed onto\n // never rendered, so the history it would have gone into stays empty.\n useWizardStore.setState({ step: FIRST_STEP_WITHOUT_STACKS, history: [] });\n }\n\n useWizardStore.getState().seedFocusedSkillForActiveDomain();\n}\n","import { sortBy } from \"remeda\";\nimport { typedValues } from \"../../utils/typed-object.js\";\nimport type { CategorySelections, Domain, SkillId } from \"../../types/index.js\";\nimport type { SkillConfig } from \"../../types/config.js\";\nimport { getAvailableSkills, getCellState, getUnmetRequiredBy } from \"../matrix/index.js\";\nimport { matrix, getSkillById } from \"../matrix/matrix-provider.js\";\nimport { deriveScopeBadges } from \"./scope-diff.js\";\nimport type { CategoryRow, CategoryOption } from \"../../components/wizard/category-grid.js\";\n\nexport type BuildStepValidation = {\n valid: boolean;\n message?: string;\n};\n\nexport function validateBuildStep(\n categories: CategoryRow[],\n selections: CategorySelections,\n): BuildStepValidation {\n const emptyRequired = categories.find(\n (category) => category.required && !selections[category.id]?.length,\n );\n return emptyRequired\n ? {\n valid: true,\n message: `No skills selected in ${emptyRequired.displayName} (required category)`,\n }\n : { valid: true };\n}\n\n// Build CategoryRow[] from matrix for a domain\nexport function buildCategoriesForDomain(\n domain: Domain,\n allSelections: SkillId[],\n installedSkillIds?: SkillId[],\n skillConfigs?: SkillConfig[],\n): CategoryRow[] {\n const categories = sortBy(\n typedValues(matrix.categories).filter((cat) => cat.domain === domain),\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- CategoryDefinition declares this required, but an auto-synthesized category for a custom skill can arrive without it — the type is stricter than the data\n (cat) => cat.order ?? 0,\n );\n\n const categoryRows: CategoryRow[] = categories.map((cat) => {\n const skillOptions = getAvailableSkills(cat.id, allSelections);\n\n // Sort options by displayName so the grid order is deterministic across\n // machines and source types (readdir/insertion order is otherwise unstable).\n // Lowercased ordinal comparison keeps the order locale-independent.\n const sortedOptions = sortBy(skillOptions, (skill) =>\n getSkillById(skill.id).displayName.toLowerCase(),\n );\n\n const options: CategoryOption[] = sortedOptions.map((skill) => {\n const activeConfig = skillConfigs?.find((sc) => sc.id === skill.id && !sc.excluded);\n const excludedConfig = skillConfigs?.find((sc) => sc.id === skill.id && sc.excluded);\n const { secondaryScope } = deriveScopeBadges(activeConfig, excludedConfig);\n const local = getSkillById(skill.id).local;\n const requiredBy = skill.selected ? undefined : getUnmetRequiredBy(skill.id, allSelections);\n return {\n id: skill.id,\n // The cell verdict, not the raw advisory: inside a pick-one category\n // the shared semantics forgive what the swap resolves.\n state: getCellState(skill.id, allSelections),\n selected: skill.selected,\n ...(local !== undefined && { local }),\n installed: installedSkillIds?.includes(skill.id) || false,\n ...(activeConfig?.scope !== undefined && { scope: activeConfig.scope }),\n ...(secondaryScope !== undefined && { secondaryScope }),\n ...(activeConfig?.source !== undefined && { source: activeConfig.source }),\n hasUnmetRequirements: skill.hasUnmetRequirements,\n ...(skill.unmetRequirementsReason !== undefined && {\n unmetRequirementsReason: skill.unmetRequirementsReason,\n }),\n ...(requiredBy !== undefined && { requiredBy }),\n };\n });\n\n return {\n id: cat.id,\n displayName: cat.displayName,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- CategoryDefinition declares this required, but an auto-synthesized category for a custom skill can arrive without it — the type is stricter than the data\n required: cat.required ?? false,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- CategoryDefinition declares this required, but an auto-synthesized category for a custom skill can arrive without it — the type is stricter than the data\n exclusive: cat.exclusive ?? true,\n options,\n };\n });\n\n return categoryRows.filter((row) => row.options.length > 0);\n}\n","import type { AgentScopeConfig, SkillConfig, SkillScope } from \"../../types/config\";\nimport type { AgentName, SkillId } from \"../../types/index\";\n\nexport type ScopeDiffInput = {\n currentSkills: SkillConfig[];\n currentAgents: AgentScopeConfig[];\n installedSkillConfigs: SkillConfig[] | null;\n installedAgentConfigs: AgentScopeConfig[] | null;\n isInitMode: boolean;\n};\n\nexport type DiffRowStatus = \"added\" | \"mode-changed\" | \"removed\" | \"unchanged\";\n\nexport type SkillDiffRow = {\n id: SkillId;\n source: string;\n status: DiffRowStatus;\n};\n\nexport type AgentDiffRow = {\n name: AgentName;\n status: Exclude<DiffRowStatus, \"mode-changed\">;\n};\n\nexport type ScopeDiff = {\n projectSkillRows: SkillDiffRow[];\n globalSkillRows: SkillDiffRow[];\n projectAgentRows: AgentDiffRow[];\n globalAgentRows: AgentDiffRow[];\n hasContent: boolean;\n};\n\n/**\n * Computes the per-scope diff rows the confirm-step summary renders.\n *\n * The diff baseline keeps tombstones as first-class entries. A tombstone\n * occupies the (id, scope) slot: it means \"the global install still exists but\n * is silenced at project scope\" (D-223 dual-scope indicator). Treating the slot\n * as occupied — for both the previous-key set (isNew detection) and removal\n * (slot-occupancy match) — prevents a dual-scope G→P toggle from rendering a\n * spurious `-` at Global (D-230) or a spurious `+` at Global on the next edit\n * when the stored tombstone is re-read (D-232). See D-225 investigation 09 for\n * the full derivation; mode-change (`~`) tracking filters to active baseline\n * entries because tombstones don't represent a live install.\n */\nexport function computeScopeDiff(input: ScopeDiffInput): ScopeDiff {\n const { currentSkills, currentAgents, installedSkillConfigs, installedAgentConfigs, isInitMode } =\n input;\n\n const projectSkills = currentSkills.filter((s) => s.scope === \"project\" && !s.excluded);\n const globalSkills = currentSkills.filter((s) => s.scope === \"global\" && !s.excluded);\n const projectAgents = currentAgents.filter((a) => a.scope === \"project\" && !a.excluded);\n const globalAgents = currentAgents.filter((a) => a.scope === \"global\" && !a.excluded);\n const excludedGlobalSkills = currentSkills.filter((s) => s.scope === \"global\" && !!s.excluded);\n const excludedGlobalAgents = currentAgents.filter((a) => a.scope === \"global\" && !!a.excluded);\n\n const prevSkillKeySet = installedSkillConfigs\n ? new Set(installedSkillConfigs.map((s) => skillSlotKey(s.id, s.scope)))\n : null;\n const prevSourceMap = installedSkillConfigs\n ? new Map(\n installedSkillConfigs\n .filter((s) => !s.excluded)\n .map((s) => [skillSlotKey(s.id, s.scope), s.source]),\n )\n : null;\n const prevAgentKeySet = installedAgentConfigs\n ? new Set(installedAgentConfigs.map((a) => agentSlotKey(a.name, a.scope)))\n : null;\n\n // Active baseline globals that are overridden at project scope without a\n // tombstone in current state — a historical no-tombstone dual-scope shape.\n // Current tombstones are handled directly via `uniqueExcludedGlobalSkills`.\n const activeSkillBaseline = installedSkillConfigs\n ? installedSkillConfigs.filter((s) => !s.excluded)\n : [];\n const activeAgentBaseline = installedAgentConfigs\n ? installedAgentConfigs.filter((a) => !a.excluded)\n : [];\n const inheritedGlobalSkills = activeSkillBaseline.filter(\n (s) =>\n s.scope === \"global\" &&\n !globalSkills.some((g) => g.id === s.id) &&\n !excludedGlobalSkills.some((e) => e.id === s.id) &&\n projectSkills.some((p) => p.id === s.id),\n );\n const inheritedGlobalAgents = activeAgentBaseline.filter(\n (a) =>\n a.scope === \"global\" &&\n !globalAgents.some((g) => g.name === a.name) &&\n !excludedGlobalAgents.some((e) => e.name === a.name) &&\n projectAgents.some((p) => p.name === a.name),\n );\n\n // Slot-occupancy match: a baseline entry at (id, scope) is considered\n // removed only if nothing — active OR tombstone — occupies that slot in\n // current. A current tombstone at the same key keeps the slot occupied\n // (dual-scope indicator, not a removal). See D-230 / D-232.\n const removedSkills = installedSkillConfigs\n ? installedSkillConfigs.filter(\n (s) => !currentSkills.some((c) => c.id === s.id && c.scope === s.scope),\n )\n : [];\n const removedAgents = installedAgentConfigs\n ? installedAgentConfigs.filter(\n (a) => !currentAgents.some((c) => c.name === a.name && c.scope === a.scope),\n )\n : [];\n\n // `uniqueExcludedGlobalSkills` dedups the current tombstone row against any\n // inherited-global entry for the same id so the Global section never shows\n // two rows for the same skill. Under the slot-occupancy removal match above,\n // a current tombstone at (id, global) keeps the slot occupied and therefore\n // cannot collide with the removed-global rows — no further dedup needed there.\n const inheritedSkillIdSet = new Set(inheritedGlobalSkills.map((s) => s.id));\n const uniqueExcludedGlobalSkills = excludedGlobalSkills.filter(\n (s) => !inheritedSkillIdSet.has(s.id),\n );\n const allGlobalSkills = [\n ...globalSkills,\n ...inheritedGlobalSkills,\n ...uniqueExcludedGlobalSkills,\n ];\n const inheritedAgentNameSet = new Set(inheritedGlobalAgents.map((a) => a.name));\n const uniqueExcludedGlobalAgents = excludedGlobalAgents.filter(\n (a) => !inheritedAgentNameSet.has(a.name),\n );\n const allGlobalAgents = [\n ...globalAgents,\n ...inheritedGlobalAgents,\n ...uniqueExcludedGlobalAgents,\n ];\n\n const removedGlobalSkills = isInitMode ? [] : removedSkills.filter((s) => s.scope === \"global\");\n const removedProjectSkills = removedSkills.filter((s) => s.scope === \"project\");\n const removedGlobalAgents = isInitMode ? [] : removedAgents.filter((a) => a.scope === \"global\");\n const removedProjectAgents = removedAgents.filter((a) => a.scope === \"project\");\n\n const projectSkillRows = [\n ...projectSkills.map((s) => classifyDiffRow(s, prevSkillKeySet, prevSourceMap)),\n ...removedProjectSkills.map(toRemovedSkillRow),\n ];\n const globalSkillRows = [\n ...allGlobalSkills.map((s) => classifyDiffRow(s, prevSkillKeySet, prevSourceMap)),\n ...removedGlobalSkills.map(toRemovedSkillRow),\n ];\n const projectAgentRows = [\n ...projectAgents.map((a) => classifyAgentDiffRow(a, prevAgentKeySet)),\n ...removedProjectAgents.map(toRemovedAgentRow),\n ];\n const globalAgentRows = [\n ...allGlobalAgents.map((a) => classifyAgentDiffRow(a, prevAgentKeySet)),\n ...removedGlobalAgents.map(toRemovedAgentRow),\n ];\n\n const hasContent =\n projectSkillRows.length > 0 ||\n globalSkillRows.length > 0 ||\n projectAgentRows.length > 0 ||\n globalAgentRows.length > 0;\n\n return { projectSkillRows, globalSkillRows, projectAgentRows, globalAgentRows, hasContent };\n}\n\n/**\n * The `(id, scope)` SLOT key this module diffs on. Exported so every surface that computes its own\n * session diff — notably the wizard store's Sources tab — keys on the same slot rather than on the\n * id alone, which is what let one skill read as added on one surface and unchanged on the other\n * (todo/D-271 secondary hardening).\n */\nexport function skillSlotKey(id: SkillId, scope: SkillScope | undefined): string {\n return `${id}:${scope}`;\n}\n\n/**\n * The `(name, scope)` SLOT key for an agent — the agent-side counterpart of {@link skillSlotKey},\n * exported for the same reason. Today only this module diffs agent slots, so there is nothing to\n * disagree with; the helper exists so that a second surface routes through it from the start rather\n * than re-deriving the key on `name` alone, which is precisely how the skill side reached D-278.\n */\nexport function agentSlotKey(name: AgentName, scope: SkillScope | undefined): string {\n return `${name}:${scope}`;\n}\n\nexport type ScopeBadges = {\n scope: SkillScope | undefined;\n secondaryScope: SkillScope | undefined;\n};\n\n/** Bracketed scope badge: `[G]` for global, `[P]` for project. */\nexport function formatScopeTag(scope: SkillScope): \"[G]\" | \"[P]\" {\n return scope === \"global\" ? \"[G]\" : \"[P]\";\n}\n\n/**\n * D-223: derives the primary + secondary scope badges for a row from its active\n * entry and its excluded tombstone. A tombstone at the OTHER scope renders as a\n * secondary badge (`[P][G]`); a same-scope tombstone renders nothing extra.\n */\nexport function deriveScopeBadges(\n activeConfig: { scope: SkillScope } | undefined,\n excludedConfig: { scope: SkillScope } | undefined,\n): ScopeBadges {\n const secondaryScope =\n excludedConfig && activeConfig && excludedConfig.scope !== activeConfig.scope\n ? excludedConfig.scope\n : undefined;\n return { scope: activeConfig?.scope, secondaryScope };\n}\n\n/**\n * Classifies an active skill entry against the baseline: added, mode-changed, or unchanged.\n *\n * The comparison is still on `source`, because that field IS where a skill's install mode is\n * recorded: `eject` means the project's own copy and anything else names the marketplace the\n * plugin comes from. With no second marketplace to move between, a `source` that changed is a\n * mode that changed, which is what the `~` marker has always meant to a reader.\n */\nfunction classifyDiffRow(\n skill: SkillConfig,\n prevKeySet: Set<string> | null,\n prevSourceMap: Map<string, string> | null,\n): SkillDiffRow {\n const key = skillSlotKey(skill.id, skill.scope);\n const isNew = prevKeySet === null || !prevKeySet.has(key);\n const prevSource = prevSourceMap?.get(key);\n const modeChanged = !isNew && prevSource != null && prevSource !== skill.source;\n if (modeChanged) {\n return { id: skill.id, source: skill.source, status: \"mode-changed\" };\n }\n return { id: skill.id, source: skill.source, status: isNew ? \"added\" : \"unchanged\" };\n}\n\nfunction classifyAgentDiffRow(\n agent: AgentScopeConfig,\n prevKeySet: Set<string> | null,\n): AgentDiffRow {\n const isNew = prevKeySet === null || !prevKeySet.has(agentSlotKey(agent.name, agent.scope));\n return { name: agent.name, status: isNew ? \"added\" : \"unchanged\" };\n}\n\nfunction toRemovedSkillRow(skill: SkillConfig): SkillDiffRow {\n return { id: skill.id, source: skill.source, status: \"removed\" };\n}\n\nfunction toRemovedAgentRow(agent: AgentScopeConfig): AgentDiffRow {\n return { name: agent.name, status: \"removed\" };\n}\n","import { DOMAIN_ORDER } from \"@workspace/matrix\";\nimport type { Domain } from \"../../types/index.js\";\n\n/**\n * Sort domains into canonical display order: custom domains first (alphabetically),\n * then built-in domains in the shared DOMAIN_ORDER.\n */\nexport function orderDomains(domains: Domain[]): Domain[] {\n const builtInSet = new Set<Domain>(DOMAIN_ORDER);\n return [\n ...domains.filter((d) => !builtInSet.has(d)).sort(),\n ...DOMAIN_ORDER.filter((d) => domains.includes(d)),\n ];\n}\n"],"mappings":"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","names":[]}
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- Wizard
4
- } from "./chunk-R7JV22QK.js";
5
- import {
6
- render
7
- } from "./chunk-JFLVEFFP.js";
8
- import {
9
- hydrateWizardStore
10
- } from "./chunk-QQTYMW7N.js";
11
-
12
- // src/cli/components/wizard/run-wizard-session.tsx
13
- import "react";
14
- import { jsx } from "react/jsx-runtime";
15
- async function runWizardSession(options) {
16
- hydrateWizardStore(options.hydrate);
17
- let wizardResult = null;
18
- const readResult = () => wizardResult;
19
- const { waitUntilExit, clear } = render(
20
- /* @__PURE__ */ jsx(
21
- Wizard,
22
- {
23
- ...options.props,
24
- onComplete: (result2) => {
25
- wizardResult = result2;
26
- },
27
- onCancel: options.onCancel
28
- }
29
- )
30
- );
31
- await waitUntilExit();
32
- clear();
33
- options.clearTerminal();
34
- const result = readResult();
35
- return !result || result.cancelled ? null : result;
36
- }
37
-
38
- export {
39
- runWizardSession
40
- };
41
- //# sourceMappingURL=chunk-QUDQTO3O.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/run-wizard-session.tsx"],"sourcesContent":["import React from \"react\";\nimport { render } from \"../render.js\";\nimport { Wizard, type WizardProps, type WizardResultV2 } from \"./wizard.js\";\nimport { hydrateWizardStore, type HydrateOptions } from \"../../stores/wizard-store.js\";\n\nexport type WizardSessionOptions = {\n hydrate: HydrateOptions;\n props: Omit<WizardProps, \"onComplete\" | \"onCancel\">;\n onCancel: () => void;\n /** Command-owned terminal reset, run after the Ink render unmounts. */\n clearTerminal: () => void;\n};\n\n/**\n * Runs one full wizard session: hydrate the store, render the wizard, wait\n * for exit, clear the screen, and return the result — or null when cancelled.\n */\nexport async function runWizardSession(\n options: WizardSessionOptions,\n): Promise<WizardResultV2 | null> {\n hydrateWizardStore(options.hydrate);\n\n let wizardResult: WizardResultV2 | null = null;\n // Read through a function boundary: onComplete mutates wizardResult inside the\n // closure before waitUntilExit resolves, which TS's flow narrowing can't track\n const readResult = (): WizardResultV2 | null => wizardResult;\n\n const { waitUntilExit, clear } = render(\n <Wizard\n {...options.props}\n onComplete={(result) => {\n wizardResult = result;\n }}\n onCancel={options.onCancel}\n />,\n );\n\n await waitUntilExit();\n clear();\n options.clearTerminal();\n\n const result = readResult();\n return !result || result.cancelled ? null : result;\n}\n"],"mappings":";;;;;;;;;;;;AAAA,OAAkB;AA4Bd;AAXJ,eAAsB,iBACpB,SACgC;AAChC,qBAAmB,QAAQ,OAAO;AAElC,MAAI,eAAsC;AAG1C,QAAM,aAAa,MAA6B;AAEhD,QAAM,EAAE,eAAe,MAAM,IAAI;AAAA,IAC/B;AAAA,MAAC;AAAA;AAAA,QACE,GAAG,QAAQ;AAAA,QACZ,YAAY,CAACA,YAAW;AACtB,yBAAeA;AAAA,QACjB;AAAA,QACA,UAAU,QAAQ;AAAA;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,cAAc;AACpB,QAAM;AACN,UAAQ,cAAc;AAEtB,QAAM,SAAS,WAAW;AAC1B,SAAO,CAAC,UAAU,OAAO,YAAY,OAAO;AAC9C;","names":["result"]}