stageflow 0.32.0 → 0.35.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 (335) hide show
  1. package/README.md +4 -3
  2. package/dist/agent/adapterSpill.d.ts +14 -0
  3. package/dist/agent/adapterSpill.js +83 -0
  4. package/dist/agent/agentBackend.d.ts +2 -2
  5. package/dist/agent/agentBackend.js +2 -2
  6. package/dist/agent/claudeAdapter.d.ts +13 -2
  7. package/dist/agent/claudeAdapter.js +50 -6
  8. package/dist/agent/cursorProvider.d.ts +3 -0
  9. package/dist/agent/cursorProvider.js +23 -6
  10. package/dist/agent/fakeAgent.d.ts +3 -3
  11. package/dist/agent/fakeAgent.js +0 -6
  12. package/dist/agent/piAdapter.d.ts +2 -2
  13. package/dist/agent/piAdapter.js +116 -15
  14. package/dist/agent/piIsolatedMcp.d.ts +1 -0
  15. package/dist/agent/piIsolatedMcp.js +19 -1
  16. package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
  17. package/dist/agent/piIsolatedMcpProbe.js +20 -0
  18. package/dist/agent/port.d.ts +2 -2
  19. package/dist/agent/providerAuth.d.ts +6 -0
  20. package/dist/agent/providerAuth.js +86 -17
  21. package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -6
  22. package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +13 -9
  23. package/dist/agent/stageTurnDriver.d.ts +33 -0
  24. package/dist/agent/stageTurnDriver.js +64 -0
  25. package/dist/agent/toolOutputBudget.d.ts +46 -0
  26. package/dist/agent/toolOutputBudget.js +97 -0
  27. package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
  28. package/dist/agent/toolOutputBudgetExtension.js +141 -0
  29. package/dist/agent/toolOutputSpill.d.ts +34 -0
  30. package/dist/agent/toolOutputSpill.js +85 -0
  31. package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
  32. package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
  33. package/dist/browser/anchor.d.ts +2 -0
  34. package/dist/browser/anchor.js +2 -1
  35. package/dist/browser/auditSink.d.ts +8 -0
  36. package/dist/browser/browserHost.d.ts +29 -0
  37. package/dist/browser/browserSweep.d.ts +4 -1
  38. package/dist/browser/browserSweep.js +7 -0
  39. package/dist/browser/browserTeardown.d.ts +1 -0
  40. package/dist/browser/browserTeardown.js +32 -2
  41. package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
  42. package/dist/browser/containerBrowserEndpoint.js +57 -0
  43. package/dist/browser/containerBrowserHost.d.ts +24 -0
  44. package/dist/browser/containerBrowserHost.js +147 -0
  45. package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
  46. package/dist/browser/dockerSandboxOrchestrator.js +142 -0
  47. package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
  48. package/dist/browser/fakeLiveViewRelay.js +94 -0
  49. package/dist/browser/fakeLiveViewSource.d.ts +6 -0
  50. package/dist/browser/fakeLiveViewSource.js +16 -0
  51. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
  52. package/dist/browser/fakeRemoteBrowserHost.js +2 -1
  53. package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
  54. package/dist/browser/fakeSandboxOrchestrator.js +44 -0
  55. package/dist/browser/gateHandoff.d.ts +0 -2
  56. package/dist/browser/gateHandoff.js +5 -11
  57. package/dist/browser/hostCapabilities.d.ts +23 -0
  58. package/dist/browser/hostCapabilities.js +23 -0
  59. package/dist/browser/humanLogin.d.ts +2 -2
  60. package/dist/browser/humanLogin.js +11 -2
  61. package/dist/browser/liveViewPersisted.d.ts +11 -0
  62. package/dist/browser/liveViewPersisted.js +22 -0
  63. package/dist/browser/liveViewRelay.d.ts +76 -0
  64. package/dist/browser/liveViewRelay.js +1 -0
  65. package/dist/browser/liveViewSessions.d.ts +25 -0
  66. package/dist/browser/liveViewSessions.js +139 -0
  67. package/dist/browser/liveViewSource.d.ts +25 -0
  68. package/dist/browser/liveViewSource.js +7 -0
  69. package/dist/browser/liveViewTickets.d.ts +46 -0
  70. package/dist/browser/liveViewTickets.js +122 -0
  71. package/dist/browser/localBrowserHost.d.ts +10 -0
  72. package/dist/browser/localBrowserHost.js +53 -7
  73. package/dist/browser/ownerScope.d.ts +10 -0
  74. package/dist/browser/ownerScope.js +6 -0
  75. package/dist/browser/profileStore.d.ts +5 -2
  76. package/dist/browser/profileStore.js +3 -5
  77. package/dist/browser/sandboxOrchestrator.d.ts +60 -0
  78. package/dist/browser/sandboxOrchestrator.js +8 -0
  79. package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
  80. package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
  81. package/dist/browser/stageBrowserEnv.d.ts +7 -0
  82. package/dist/browser/stageBrowserEnv.js +53 -15
  83. package/dist/browser/stageHandoff.d.ts +47 -0
  84. package/dist/browser/stageHandoff.js +101 -0
  85. package/dist/browser/stageProfileLock.d.ts +1 -0
  86. package/dist/browser/stageProfileLock.js +1 -3
  87. package/dist/browser/volumeProfileStore.d.ts +7 -0
  88. package/dist/browser/volumeProfileStore.js +35 -0
  89. package/dist/cli/browserCommand.d.ts +3 -1
  90. package/dist/cli/browserCommand.js +25 -20
  91. package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
  92. package/dist/cli/resolveCliProjectRoot.js +27 -0
  93. package/dist/cli/runCommand.d.ts +1 -1
  94. package/dist/cli/runCommand.js +59 -17
  95. package/dist/cli/skillsCommand.d.ts +20 -0
  96. package/dist/cli/skillsCommand.js +74 -31
  97. package/dist/cli/validateCommand.d.ts +1 -1
  98. package/dist/cli/validateCommand.js +22 -4
  99. package/dist/cli.js +4 -4
  100. package/dist/config/browseCatalog.d.ts +2 -0
  101. package/dist/config/browseCatalog.js +6 -0
  102. package/dist/config/catalogRelativePath.d.ts +4 -0
  103. package/dist/config/catalogRelativePath.js +37 -9
  104. package/dist/config/createStage.d.ts +1 -0
  105. package/dist/config/createStage.js +11 -2
  106. package/dist/config/createTask.d.ts +30 -0
  107. package/dist/config/createTask.js +161 -0
  108. package/dist/config/createTrigger.d.ts +15 -0
  109. package/dist/config/createTrigger.js +32 -25
  110. package/dist/config/draftPackage.d.ts +41 -0
  111. package/dist/config/draftPackage.js +216 -23
  112. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  113. package/dist/config/ensureCatalogScanRoot.js +6 -2
  114. package/dist/config/findTaskInCatalog.d.ts +33 -0
  115. package/dist/config/findTaskInCatalog.js +69 -0
  116. package/dist/config/hostConfig.d.ts +21 -0
  117. package/dist/config/hostConfig.js +113 -1
  118. package/dist/config/importTask.d.ts +19 -0
  119. package/dist/config/importTask.js +133 -0
  120. package/dist/config/installSkillRequest.d.ts +40 -0
  121. package/dist/config/installSkillRequest.js +286 -0
  122. package/dist/config/listConfig.d.ts +0 -16
  123. package/dist/config/listConfig.js +0 -3
  124. package/dist/config/loadOutcome.d.ts +11 -6
  125. package/dist/config/loadPipeline.js +0 -1
  126. package/dist/config/loadStage.js +2 -0
  127. package/dist/config/loadStageflowManifest.js +4 -1
  128. package/dist/config/loadTask.js +2 -0
  129. package/dist/config/loadTrigger.js +2 -0
  130. package/dist/config/mergePipelineIncludes.js +3 -0
  131. package/dist/config/normalizePipelineStageEntry.js +10 -1
  132. package/dist/config/printTargetYaml.js +0 -1
  133. package/dist/config/publishDraftPackage.d.ts +5 -1
  134. package/dist/config/publishDraftPackage.js +4 -1
  135. package/dist/config/resolveCatalogRoots.d.ts +4 -1
  136. package/dist/config/resolveCatalogRoots.js +18 -1
  137. package/dist/config/skillUsageIndex.d.ts +6 -0
  138. package/dist/config/skillUsageIndex.js +39 -0
  139. package/dist/config/stageBrowser.js +13 -0
  140. package/dist/config/triggerAdapterStatus.d.ts +11 -0
  141. package/dist/config/triggerAdapterStatus.js +90 -0
  142. package/dist/config/triggerCatalog.d.ts +41 -2
  143. package/dist/config/triggerCatalog.js +107 -15
  144. package/dist/config/updateTask.d.ts +30 -0
  145. package/dist/config/updateTask.js +119 -0
  146. package/dist/config/updateTrigger.d.ts +38 -0
  147. package/dist/config/updateTrigger.js +220 -0
  148. package/dist/config/validateCatalog.d.ts +6 -2
  149. package/dist/config/validateCatalog.js +48 -7
  150. package/dist/config/workshopCatalogFilenames.d.ts +37 -0
  151. package/dist/config/workshopCatalogFilenames.js +125 -0
  152. package/dist/config/workshopCatalogRepair.d.ts +7 -0
  153. package/dist/config/workshopCatalogRepair.js +144 -0
  154. package/dist/config/yamlParsePosition.d.ts +4 -0
  155. package/dist/config/yamlParsePosition.js +9 -0
  156. package/dist/hosted/containment.d.ts +3 -0
  157. package/dist/hosted/containment.js +36 -0
  158. package/dist/hosted/errors.d.ts +21 -0
  159. package/dist/hosted/errors.js +30 -0
  160. package/dist/hosted/packageSkills.d.ts +3 -0
  161. package/dist/hosted/packageSkills.js +80 -0
  162. package/dist/hosted/runtime.d.ts +12 -0
  163. package/dist/hosted/runtime.js +596 -0
  164. package/dist/hosted/sequencer.d.ts +28 -0
  165. package/dist/hosted/sequencer.js +129 -0
  166. package/dist/hosted/translate.d.ts +3 -0
  167. package/dist/hosted/translate.js +43 -0
  168. package/dist/hosted/types.d.ts +108 -0
  169. package/dist/hosted/types.js +10 -0
  170. package/dist/index.d.ts +9 -1
  171. package/dist/index.js +6 -0
  172. package/dist/mcp/getStartedTool.js +15 -6
  173. package/dist/mcp/triggerTools.js +2 -8
  174. package/dist/operatorAgent/agentPort.d.ts +11 -0
  175. package/dist/operatorAgent/agentPort.js +12 -0
  176. package/dist/operatorAgent/index.d.ts +1 -0
  177. package/dist/operatorAgent/index.js +1 -0
  178. package/dist/operatorAgent/piModel.js +26 -6
  179. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  180. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
  181. package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
  182. package/dist/package-meta.d.ts +1 -1
  183. package/dist/package-meta.js +1 -1
  184. package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
  185. package/dist/project/ensureProjectRootsFromEnv.js +22 -0
  186. package/dist/project/globalHome.d.ts +1 -0
  187. package/dist/project/globalHome.js +18 -0
  188. package/dist/project/workspaceCatalog.d.ts +10 -0
  189. package/dist/project/workspaceCatalog.js +91 -0
  190. package/dist/projection/projectRun.d.ts +0 -1
  191. package/dist/projection/projectRun.js +0 -1
  192. package/dist/runstore/backup.js +14 -4
  193. package/dist/runstore/port.d.ts +11 -1
  194. package/dist/runstore/restore.js +5 -2
  195. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
  196. package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
  197. package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
  198. package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
  199. package/dist/runstore/sqlite/migrations/index.js +2 -0
  200. package/dist/runstore/stageSnapshot.d.ts +3 -1
  201. package/dist/runstore/stageSnapshot.js +19 -0
  202. package/dist/runstore/workspaceLayout.js +27 -0
  203. package/dist/runstore/writeContainedArtifact.d.ts +1 -0
  204. package/dist/runstore/writeContainedArtifact.js +52 -0
  205. package/dist/runtime/containerLimits.d.ts +0 -3
  206. package/dist/runtime/containerLimits.js +0 -5
  207. package/dist/runtime/credentialBinding.d.ts +3 -0
  208. package/dist/runtime/credentialBinding.js +23 -6
  209. package/dist/runtime/emailSource.js +20 -28
  210. package/dist/runtime/githubPollSource.js +20 -28
  211. package/dist/runtime/pipelineRunner.d.ts +4 -4
  212. package/dist/runtime/pipelineScheduler.d.ts +2 -2
  213. package/dist/runtime/pipelineScheduler.js +3 -0
  214. package/dist/runtime/resumeReconstruct.d.ts +2 -2
  215. package/dist/runtime/resumeTimedOut.d.ts +2 -2
  216. package/dist/runtime/runManager.d.ts +18 -2
  217. package/dist/runtime/runManager.js +95 -30
  218. package/dist/runtime/runRetentionSweep.d.ts +0 -2
  219. package/dist/runtime/runRetentionSweep.js +0 -8
  220. package/dist/runtime/runRetryCoordinator.d.ts +2 -2
  221. package/dist/runtime/scheduleSource.js +15 -24
  222. package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
  223. package/dist/runtime/stageAttemptBootstrap.js +10 -1
  224. package/dist/runtime/stageProcessLauncher.js +2 -2
  225. package/dist/runtime/stageRoots.js +12 -1
  226. package/dist/runtime/stageRunner.d.ts +2 -2
  227. package/dist/runtime/stageRunner.js +8 -2
  228. package/dist/runtime/stageWorker.js +2 -2
  229. package/dist/runtime/triggerPort.d.ts +5 -3
  230. package/dist/runtime/triggerRunner.d.ts +1 -0
  231. package/dist/runtime/triggerRunner.js +27 -20
  232. package/dist/runtime/webhookAccept.d.ts +25 -0
  233. package/dist/runtime/webhookAccept.js +56 -0
  234. package/dist/server/bootstrap.d.ts +7 -2
  235. package/dist/server/bootstrap.js +15 -1
  236. package/dist/server/controlToken.d.ts +1 -1
  237. package/dist/server/controlToken.js +18 -2
  238. package/dist/server/healthSurfaces.d.ts +12 -0
  239. package/dist/server/healthSurfaces.js +27 -6
  240. package/dist/server/http.d.ts +19 -10
  241. package/dist/server/http.js +605 -85
  242. package/dist/server/liveViewPath.d.ts +9 -0
  243. package/dist/server/liveViewPath.js +8 -0
  244. package/dist/server/liveViewRoutes.d.ts +58 -0
  245. package/dist/server/liveViewRoutes.js +565 -0
  246. package/dist/server/mcpHost.d.ts +4 -2
  247. package/dist/server/mcpHost.js +2 -0
  248. package/dist/server/readCatalogFile.d.ts +29 -0
  249. package/dist/server/readCatalogFile.js +215 -0
  250. package/dist/server/triggerRoutes.d.ts +1 -0
  251. package/dist/server/triggerRoutes.js +200 -73
  252. package/dist/tools/readEmail.d.ts +3 -3
  253. package/dist/tools/writeStageArtifact.d.ts +9 -0
  254. package/dist/tools/writeStageArtifact.js +2 -47
  255. package/dist/types/pipeline.d.ts +1 -1
  256. package/dist/types/stage.d.ts +1 -1
  257. package/dist/types/stageflowManifest.d.ts +1 -1
  258. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  259. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  260. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  261. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  262. package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  263. package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  264. package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  265. package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  266. package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  267. package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  268. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  269. package/dist/ui/assets/index-BoFPkKFg.js +256 -0
  270. package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.css} +1 -1
  271. package/dist/ui/index.html +4 -4
  272. package/dist/workshop/autosave.d.ts +2 -2
  273. package/dist/workshop/autosave.js +4 -6
  274. package/dist/workshop/buildStore.d.ts +2 -0
  275. package/dist/workshop/chatAttachments.d.ts +27 -0
  276. package/dist/workshop/chatAttachments.js +98 -0
  277. package/dist/workshop/chatTurn.d.ts +5 -2
  278. package/dist/workshop/chatTurn.js +22 -18
  279. package/dist/workshop/sessionMutations.d.ts +17 -0
  280. package/dist/workshop/sessionMutations.js +107 -0
  281. package/dist/workshop/sessionStore.d.ts +10 -0
  282. package/dist/workshop/sessionStore.js +52 -0
  283. package/dist/workshop/workshopPin.d.ts +4 -3
  284. package/dist/workshop/workshopPin.js +26 -6
  285. package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
  286. package/dist/workshop/workshopTranscriptTurn.js +46 -0
  287. package/examples/README.md +1 -1
  288. package/examples/archify-on-pr/README.md +1 -1
  289. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
  290. package/examples/archify-on-pr/author-diagrams.yaml +1 -0
  291. package/examples/conditional-fork/branch-a.yaml +1 -1
  292. package/examples/conditional-fork/decide.yaml +2 -1
  293. package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
  294. package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
  295. package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
  296. package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
  297. package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
  298. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
  299. package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
  300. package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
  301. package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
  302. package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
  303. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
  304. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
  305. package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
  306. package/examples/hello-world/hello.pipeline.yaml +2 -2
  307. package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
  308. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
  309. package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
  310. package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
  311. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
  312. package/examples/stageflow.yaml +6 -0
  313. package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
  314. package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
  315. package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
  316. package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
  317. package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
  318. package/examples/triggers-readiness/README.md +48 -0
  319. package/examples/triggers-readiness/readiness-emit.yaml +18 -0
  320. package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
  321. package/examples/triggers-readiness/readiness.task.yaml +6 -0
  322. package/examples/triggers-readiness/stageflow.yaml +13 -0
  323. package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
  324. package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
  325. package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
  326. package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
  327. package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
  328. package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
  329. package/package.json +9 -1
  330. package/skills/stageflow/references/control-surface.md +1 -1
  331. package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
  332. package/skills/stageflow-run/SKILL.md +1 -1
  333. package/skills/stageflow-run/references/mcp-call.md +1 -1
  334. package/skills/stageflow-setup/SKILL.md +1 -1
  335. package/dist/ui/assets/index-59cjF0OA.js +0 -133
@@ -0,0 +1,286 @@
1
+ import { access, mkdtemp, readdir, readFile, rm, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { installProjectSkill, } from "../cli/skillsCommand.js";
6
+ import { runGit } from "../git/exec.js";
7
+ function isPlainObject(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9
+ }
10
+ function tokenizeCommand(command) {
11
+ return command.trim().split(/\s+/).filter((part) => part.length > 0);
12
+ }
13
+ export function parseSkillsAddCommand(command) {
14
+ const trimmed = command.trim();
15
+ if (trimmed.length === 0) {
16
+ return { ok: false, status: 400, error: "command must be a non-empty string" };
17
+ }
18
+ const tokens = tokenizeCommand(trimmed);
19
+ let rest;
20
+ if (tokens[0] === "npx" && tokens[1] === "skills" && tokens[2] === "add") {
21
+ rest = tokens.slice(3);
22
+ }
23
+ else if (tokens[0] === "skills" && tokens[1] === "add") {
24
+ rest = tokens.slice(2);
25
+ }
26
+ else {
27
+ return {
28
+ ok: false,
29
+ status: 400,
30
+ error: "command must be 'npx skills add <source> [--skill <name>]' or 'skills add …'",
31
+ };
32
+ }
33
+ let source;
34
+ let skillName;
35
+ for (let i = 0; i < rest.length; i++) {
36
+ const arg = rest[i];
37
+ if (arg === "--skill") {
38
+ const value = rest[++i];
39
+ if (value === undefined || value.length === 0) {
40
+ return { ok: false, status: 400, error: "missing value for --skill" };
41
+ }
42
+ if (value.startsWith("-")) {
43
+ return { ok: false, status: 400, error: "missing value for --skill" };
44
+ }
45
+ skillName = value;
46
+ }
47
+ else if (arg.startsWith("-")) {
48
+ return { ok: false, status: 400, error: `unknown flag: ${arg}` };
49
+ }
50
+ else if (source === undefined) {
51
+ source = arg;
52
+ }
53
+ else {
54
+ return { ok: false, status: 400, error: `unexpected argument: ${arg}` };
55
+ }
56
+ }
57
+ if (source === undefined) {
58
+ return { ok: false, status: 400, error: "missing skill source after 'add'" };
59
+ }
60
+ return {
61
+ source,
62
+ ...(skillName !== undefined ? { skillName } : {}),
63
+ };
64
+ }
65
+ export function parseInstallSkillBody(body) {
66
+ if (!isPlainObject(body)) {
67
+ return { ok: false, status: 400, error: "Request body must be an object" };
68
+ }
69
+ if (typeof body.command !== "string" || body.command.trim().length === 0) {
70
+ return { ok: false, status: 400, error: "command must be a non-empty string" };
71
+ }
72
+ const parsed = parseSkillsAddCommand(body.command);
73
+ if ("ok" in parsed) {
74
+ return parsed;
75
+ }
76
+ return { command: body.command.trim() };
77
+ }
78
+ async function pathExists(filePath) {
79
+ try {
80
+ await access(filePath);
81
+ return true;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
87
+ async function readFrontmatterName(skillMdPath) {
88
+ try {
89
+ const raw = await readFile(skillMdPath, "utf8");
90
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
91
+ if (!match)
92
+ return undefined;
93
+ const nameLine = /^name:\s*(.+)\s*$/m.exec(match[1] ?? "");
94
+ const value = nameLine?.[1]?.trim();
95
+ if (value === undefined || value.length === 0)
96
+ return undefined;
97
+ return value.replace(/^['"]|['"]$/g, "");
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ export async function locateSkillDirectory(checkoutRoot, skillName) {
104
+ const candidates = [];
105
+ const seen = new Set();
106
+ const pushCandidate = (name, root) => {
107
+ const key = path.resolve(root);
108
+ if (seen.has(key))
109
+ return;
110
+ seen.add(key);
111
+ candidates.push({ name, root: key });
112
+ };
113
+ const rootSkillMd = path.join(checkoutRoot, "SKILL.md");
114
+ if (await pathExists(rootSkillMd)) {
115
+ const name = (await readFrontmatterName(rootSkillMd)) ?? path.basename(path.resolve(checkoutRoot));
116
+ pushCandidate(name, checkoutRoot);
117
+ }
118
+ let entries;
119
+ try {
120
+ entries = await readdir(checkoutRoot, { withFileTypes: true });
121
+ }
122
+ catch {
123
+ throw new Error("skill source directory not found");
124
+ }
125
+ for (const entry of entries) {
126
+ if (!entry.isDirectory())
127
+ continue;
128
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "skills") {
129
+ continue;
130
+ }
131
+ const skillMd = path.join(checkoutRoot, entry.name, "SKILL.md");
132
+ if (await pathExists(skillMd)) {
133
+ pushCandidate(entry.name, path.join(checkoutRoot, entry.name));
134
+ }
135
+ }
136
+ const skillsDir = path.join(checkoutRoot, "skills");
137
+ try {
138
+ const skillsStat = await stat(skillsDir);
139
+ if (skillsStat.isDirectory()) {
140
+ const skillEntries = await readdir(skillsDir, { withFileTypes: true });
141
+ for (const entry of skillEntries) {
142
+ if (!entry.isDirectory())
143
+ continue;
144
+ const skillMd = path.join(skillsDir, entry.name, "SKILL.md");
145
+ if (await pathExists(skillMd)) {
146
+ pushCandidate(entry.name, path.join(skillsDir, entry.name));
147
+ }
148
+ }
149
+ }
150
+ }
151
+ catch {
152
+ // no skills/ directory
153
+ }
154
+ if (skillName !== undefined) {
155
+ const namedPaths = [
156
+ path.join(checkoutRoot, skillName, "SKILL.md"),
157
+ path.join(checkoutRoot, "skills", skillName, "SKILL.md"),
158
+ ];
159
+ for (const skillMd of namedPaths) {
160
+ if (await pathExists(skillMd)) {
161
+ return { name: skillName, root: path.dirname(skillMd) };
162
+ }
163
+ }
164
+ const match = candidates.find((candidate) => candidate.name === skillName);
165
+ if (match !== undefined) {
166
+ return match;
167
+ }
168
+ throw new Error(`skill "${skillName}" not found in source`);
169
+ }
170
+ if (candidates.length === 1) {
171
+ return candidates[0];
172
+ }
173
+ if (candidates.length === 0) {
174
+ throw new Error("no SKILL.md found in source");
175
+ }
176
+ const names = candidates.map((candidate) => candidate.name).join(", ");
177
+ throw new Error(`multiple skills found (${names}); pass --skill <name>`);
178
+ }
179
+ export function resolveGitCloneUrl(source) {
180
+ const githubHttps = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:\/|$)/i.exec(source);
181
+ if (githubHttps) {
182
+ return `https://github.com/${githubHttps[1]}/${githubHttps[2]}.git`;
183
+ }
184
+ if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(source)) {
185
+ return `https://github.com/${source}.git`;
186
+ }
187
+ throw new Error("source must be a GitHub HTTPS URL, owner/repo slug, or file: URL");
188
+ }
189
+ export async function defaultCloneSkillRepo(cloneUrl, destDir) {
190
+ await runGit({
191
+ args: ["clone", "--depth", "1", "--single-branch", cloneUrl, destDir],
192
+ timeoutMs: 120_000,
193
+ });
194
+ }
195
+ function resolveFileSource(source) {
196
+ if (source.startsWith("file:")) {
197
+ return fileURLToPath(source);
198
+ }
199
+ throw new Error("source must be a GitHub HTTPS URL, owner/repo slug, or file: URL");
200
+ }
201
+ export async function installSkillFromCommand(input) {
202
+ const parsed = parseSkillsAddCommand(input.command);
203
+ if ("ok" in parsed) {
204
+ return { ok: false, error: parsed.error, status: parsed.status };
205
+ }
206
+ let cleanup;
207
+ try {
208
+ let checkoutRoot;
209
+ if (parsed.source.startsWith("file:")) {
210
+ checkoutRoot = resolveFileSource(parsed.source);
211
+ try {
212
+ const sourceStat = await stat(checkoutRoot);
213
+ if (!sourceStat.isDirectory()) {
214
+ return { ok: false, error: "skill source path not found", status: 400 };
215
+ }
216
+ }
217
+ catch {
218
+ return { ok: false, error: "skill source path not found", status: 400 };
219
+ }
220
+ }
221
+ else {
222
+ let cloneUrl;
223
+ try {
224
+ cloneUrl = resolveGitCloneUrl(parsed.source);
225
+ }
226
+ catch (err) {
227
+ return {
228
+ ok: false,
229
+ error: err instanceof Error ? err.message : String(err),
230
+ status: 400,
231
+ };
232
+ }
233
+ const tempRoot = await mkdtemp(path.join(tmpdir(), "sf-skill-clone-"));
234
+ cleanup = async () => {
235
+ await rm(tempRoot, { recursive: true, force: true });
236
+ };
237
+ const cloneDest = path.join(tempRoot, "repo");
238
+ const cloneRepo = input.cloneRepo ?? defaultCloneSkillRepo;
239
+ await cloneRepo(cloneUrl, cloneDest);
240
+ checkoutRoot = cloneDest;
241
+ }
242
+ const located = await locateSkillDirectory(checkoutRoot, parsed.skillName);
243
+ const result = await installProjectSkill({
244
+ projectRoot: input.projectRoot,
245
+ fromPath: located.root,
246
+ skillName: located.name,
247
+ io: {
248
+ log: () => undefined,
249
+ error: () => undefined,
250
+ },
251
+ });
252
+ if (!result.ok) {
253
+ return {
254
+ ok: false,
255
+ error: result.error,
256
+ ...(result.doctorFailed ? { doctorFailed: true, status: 422 } : { status: 400 }),
257
+ };
258
+ }
259
+ return {
260
+ ok: true,
261
+ skill_name: result.skillName,
262
+ dest_path: result.destPath,
263
+ project_root: input.projectRoot,
264
+ };
265
+ }
266
+ catch (err) {
267
+ return {
268
+ ok: false,
269
+ error: err instanceof Error ? err.message : String(err),
270
+ status: 400,
271
+ };
272
+ }
273
+ finally {
274
+ if (cleanup !== undefined) {
275
+ await cleanup();
276
+ }
277
+ }
278
+ }
279
+ /** Test helper: build a file: command for a local skill tree. */
280
+ export function skillsAddFileCommand(fromPath, skillName) {
281
+ const source = pathToFileURL(path.resolve(fromPath)).href;
282
+ if (skillName === undefined) {
283
+ return `npx skills add ${source}`;
284
+ }
285
+ return `npx skills add ${source} --skill ${skillName}`;
286
+ }
@@ -1,28 +1,12 @@
1
- import type { StageGateKind } from "../types/stage.js";
2
1
  import type { LoadedManifest } from "../types/stageflowManifest.js";
3
2
  import { type PipelineListing, type TaskListing } from "./browseCatalog.js";
4
3
  import { type StageflowContext } from "../project/resolveStageflowContext.js";
5
4
  import { type CatalogContext } from "./resolveCatalogContext.js";
6
5
  export type { PipelineListing, PipelineStageListing, TaskListing } from "./browseCatalog.js";
7
- export type ValidStageListing = {
8
- path: string;
9
- id: string;
10
- used_by_pipeline_ids: string[];
11
- gate_kinds?: StageGateKind[];
12
- };
13
- export type BrokenStageListing = {
14
- path: string;
15
- error: string;
16
- id?: string;
17
- model?: string;
18
- gate_kinds?: StageGateKind[];
19
- };
20
- export type StageListing = ValidStageListing | BrokenStageListing;
21
6
  export type CatalogListOptions = {
22
7
  projectRoot: string;
23
8
  manifest: LoadedManifest;
24
9
  };
25
10
  export declare function listTasks(options: CatalogListOptions): Promise<TaskListing[]>;
26
11
  export declare function listPipelines(options: CatalogListOptions): Promise<PipelineListing[]>;
27
- export declare function listStages(_options?: unknown): Promise<StageListing[]>;
28
12
  export declare function listModels(cwdOrContext: string | CatalogContext | StageflowContext): Promise<string[]>;
@@ -17,9 +17,6 @@ export async function listPipelines(options) {
17
17
  issues: [],
18
18
  });
19
19
  }
20
- export async function listStages(_options) {
21
- return [];
22
- }
23
20
  export async function listModels(cwdOrContext) {
24
21
  const ctx = typeof cwdOrContext === "string"
25
22
  ? catalogContextFromStageflow(await resolveStageflowContext(cwdOrContext))
@@ -1,32 +1,36 @@
1
1
  import type { ValidationFindingCode } from "./validateCatalog.js";
2
2
  export type TaskLoadCode = "task.invalid_shape" | "task.load_error" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid";
3
3
  export type TriggerLoadCode = "trigger.invalid_shape" | "trigger.load_error" | "trigger.unknown_pipeline" | "trigger.unknown_task";
4
- export type LoadIssue = {
4
+ type LoadIssueLocation = {
5
+ line?: number;
6
+ column?: number;
7
+ };
8
+ export type LoadIssue = ({
5
9
  code: ValidationFindingCode;
6
10
  message: string;
7
11
  category: "pipeline";
8
12
  pipelineId?: string;
9
13
  stageId?: string;
10
- } | {
14
+ } & LoadIssueLocation) | ({
11
15
  code: ValidationFindingCode;
12
16
  message: string;
13
17
  category: "stage";
14
18
  stageId?: string;
15
- } | {
19
+ } & LoadIssueLocation) | ({
16
20
  code: TaskLoadCode;
17
21
  message: string;
18
22
  category: "task";
19
23
  taskId?: string;
20
- } | {
24
+ } & LoadIssueLocation) | ({
21
25
  code: TriggerLoadCode;
22
26
  message: string;
23
27
  category: "trigger";
24
28
  triggerId?: string;
25
- } | {
29
+ } & LoadIssueLocation) | ({
26
30
  code: ValidationFindingCode | string;
27
31
  message: string;
28
32
  category: "catalog";
29
- };
33
+ } & LoadIssueLocation);
30
34
  export type LoadOutcome<T> = {
31
35
  ok: true;
32
36
  value: T;
@@ -37,3 +41,4 @@ export type LoadOutcome<T> = {
37
41
  };
38
42
  export declare function loadFailure<T>(issues: LoadIssue[]): LoadOutcome<T>;
39
43
  export declare function loadSuccess<T>(value: T, issues?: LoadIssue[]): LoadOutcome<T>;
44
+ export {};
@@ -262,7 +262,6 @@ async function buildLoadedPipelineFromMerge(merge, options) {
262
262
  const stage = {
263
263
  ...stageOutcome.value,
264
264
  ...(browser !== undefined ? { browser } : {}),
265
- ...(entry.skill !== undefined ? { skill: entry.skill } : {}),
266
265
  ...(entry.mcp !== undefined ? { mcp: entry.mcp } : {}),
267
266
  ...(entry.secrets !== undefined ? { secrets: entry.secrets } : {}),
268
267
  ...(stageRequires !== undefined ? { requires: stageRequires } : {}),
@@ -7,6 +7,7 @@ import { parseModelField } from "./modelField.js";
7
7
  import { allowLegacyYamlAuthoring, dialectWarningForDocument, legacyAuthoringRejected, presentLegacyKeys, } from "./legacyYaml.js";
8
8
  import { parsePreEmitChecks } from "./parsePreEmitChecks.js";
9
9
  import { readYamlObject } from "./readYamlObject.js";
10
+ import { yamlParsePosition } from "./yamlParsePosition.js";
10
11
  import { z } from "zod";
11
12
  import { parseStageSecrets } from "../runtime/stageSecretDecl.js";
12
13
  import { parseToolRequires } from "./toolRequires.js";
@@ -436,6 +437,7 @@ export async function loadStageOutcome(filePath, options = {}) {
436
437
  code: "stage.load_error",
437
438
  message,
438
439
  category: "stage",
440
+ ...yamlParsePosition(err),
439
441
  },
440
442
  ]);
441
443
  }
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { parseAgentField } from "../agent/agentBackend.js";
5
5
  import { loadFailure, loadSuccess } from "./loadOutcome.js";
6
+ import { yamlParsePosition } from "./yamlParsePosition.js";
6
7
  import { parseModelField } from "./modelField.js";
7
8
  const DEFAULT_PIPELINE_PATTERN = "*.pipeline.yaml";
8
9
  const DEFAULT_TASK_PATTERN = "*.task.yaml";
@@ -87,7 +88,9 @@ export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoo
87
88
  }
88
89
  catch (err) {
89
90
  const message = err instanceof Error ? err.message : String(err);
90
- return loadFailure([catalogIssue("catalog.manifest_load_error", message)]);
91
+ return loadFailure([
92
+ { ...catalogIssue("catalog.manifest_load_error", message), ...yamlParsePosition(err) },
93
+ ]);
91
94
  }
92
95
  const issues = [];
93
96
  const record = raw;
@@ -2,6 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { parse as parseYaml } from "yaml";
3
3
  import { resolveWorkspaceBinding } from "../runtime/workspaceBinding.js";
4
4
  import { loadFailure, loadSuccess } from "./loadOutcome.js";
5
+ import { yamlParsePosition } from "./yamlParsePosition.js";
5
6
  function isPlainObject(value) {
6
7
  return typeof value === "object" && value !== null && !Array.isArray(value);
7
8
  }
@@ -86,6 +87,7 @@ export function loadTaskFromYamlOutcome(yamlText, source = "task yaml") {
86
87
  code: "task.load_error",
87
88
  message,
88
89
  category: "task",
90
+ ...yamlParsePosition(err),
89
91
  },
90
92
  ]);
91
93
  }
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { parse as parseYaml } from "yaml";
3
3
  import { loadFailure, loadSuccess } from "./loadOutcome.js";
4
+ import { yamlParsePosition } from "./yamlParsePosition.js";
4
5
  function isPlainObject(value) {
5
6
  return typeof value === "object" && value !== null && !Array.isArray(value);
6
7
  }
@@ -84,6 +85,7 @@ export function loadTriggerFromYamlOutcome(yamlText, source = "trigger yaml") {
84
85
  code: "trigger.load_error",
85
86
  message,
86
87
  category: "trigger",
88
+ ...yamlParsePosition(err),
87
89
  },
88
90
  ]);
89
91
  }
@@ -4,6 +4,7 @@ import { loadFailure, loadSuccess } from "./loadOutcome.js";
4
4
  import { allowLegacyYamlAuthoring, dialectWarningForDocument, legacyAuthoringRejected, presentLegacyKeys, } from "./legacyYaml.js";
5
5
  import { parseModelField } from "./modelField.js";
6
6
  import { readYamlObject } from "./readYamlObject.js";
7
+ import { yamlParsePosition } from "./yamlParsePosition.js";
7
8
  import { parseToolRequires } from "./toolRequires.js";
8
9
  import { classifyYamlDocument, collectDocumentKeys, mixedDialectIssue, } from "./yamlDialect.js";
9
10
  function isPlainObject(value) {
@@ -39,6 +40,7 @@ async function visitPipelineFile(filePath, stack, idLocations, entries, warnings
39
40
  code: "pipeline.load_error",
40
41
  message,
41
42
  category: "pipeline",
43
+ ...yamlParsePosition(err),
42
44
  },
43
45
  ]);
44
46
  }
@@ -128,6 +130,7 @@ export async function mergePipelineStages(rootPath) {
128
130
  code: "pipeline.load_error",
129
131
  message,
130
132
  category: "pipeline",
133
+ ...yamlParsePosition(err),
131
134
  },
132
135
  ]);
133
136
  }
@@ -43,7 +43,6 @@ function extractBodyRaw(raw) {
43
43
  }
44
44
  function hasBodyKey(raw) {
45
45
  return Object.keys(raw).some((key) => BODY_KEYS.has(key) &&
46
- key !== "skill" &&
47
46
  key !== "mcp" &&
48
47
  key !== "secrets" &&
49
48
  key !== "requires" &&
@@ -116,6 +115,16 @@ export function normalizePipelineStageEntries(rawEntries, ctx) {
116
115
  return skillOutcome;
117
116
  }
118
117
  const skill = skillOutcome?.ok ? skillOutcome.value : undefined;
118
+ if (uses && skill !== undefined) {
119
+ return loadFailure([
120
+ {
121
+ code: "pipeline.stage_wrapper_skill_rejected",
122
+ message: `Pipeline ${ctx.pipelineId} (${ctx.path}): stage entry at index ${index} sets skill: on a uses: wrapper; put skill: on the stage file (${uses}) instead`,
123
+ category: "pipeline",
124
+ pipelineId: ctx.pipelineId,
125
+ },
126
+ ]);
127
+ }
119
128
  const mcpOutcome = parseStageMcp(raw.mcp, `entry at index ${index} in ${declaringPath}`);
120
129
  if (!mcpOutcome.ok)
121
130
  return mcpOutcome;
@@ -163,7 +163,6 @@ const STAGE_BODY_ORDER = [
163
163
  const PIPELINE_WIRING_ORDER = [
164
164
  "id",
165
165
  "uses",
166
- "skill",
167
166
  "mcp",
168
167
  "needs",
169
168
  "fork",
@@ -1,7 +1,11 @@
1
1
  import { type CreateDraftPackageInput, type DraftPackageWriteResult } from "./draftPackage.js";
2
+ import type { CatalogRootKind } from "./resolveCatalogRoots.js";
2
3
  export type PublishDraftPackageMode = "create" | "overwrite" | "auto";
4
+ export type PublishDraftPackageOptions = {
5
+ catalogRootKind?: CatalogRootKind;
6
+ };
3
7
  export type PublishDraftPackageResult = {
4
8
  write: DraftPackageWriteResult;
5
9
  catalogChanged: boolean;
6
10
  };
7
- export declare function publishDraftPackage(projectRoot: string, input: CreateDraftPackageInput, mode: PublishDraftPackageMode): Promise<PublishDraftPackageResult>;
11
+ export declare function publishDraftPackage(projectRoot: string, input: CreateDraftPackageInput, mode: PublishDraftPackageMode, options?: PublishDraftPackageOptions): Promise<PublishDraftPackageResult>;
@@ -1,6 +1,6 @@
1
1
  import { createDraftPackage, overwriteDraftPackage, } from "./draftPackage.js";
2
2
  import { ensureCatalogScanRoot } from "./ensureCatalogScanRoot.js";
3
- export async function publishDraftPackage(projectRoot, input, mode) {
3
+ export async function publishDraftPackage(projectRoot, input, mode, options = {}) {
4
4
  let write;
5
5
  if (mode === "create") {
6
6
  write = await createDraftPackage(projectRoot, input);
@@ -20,6 +20,9 @@ export async function publishDraftPackage(projectRoot, input, mode) {
20
20
  if (!write.ok) {
21
21
  return { write, catalogChanged: false };
22
22
  }
23
+ if (options.catalogRootKind === "workspace") {
24
+ return { write, catalogChanged: false };
25
+ }
23
26
  const catalogChanged = await ensureCatalogScanRoot(projectRoot, input.directory);
24
27
  return { write, catalogChanged };
25
28
  }
@@ -1,5 +1,5 @@
1
1
  import type { RunStore } from "../runstore/port.js";
2
- export type CatalogRootKind = "registered" | "seeded";
2
+ export type CatalogRootKind = "registered" | "seeded" | "workspace";
3
3
  export type CatalogRoot = {
4
4
  /** Wire id: absolute path for registered; symbolic id for seeded (e.g. "examples"). */
5
5
  project_root: string;
@@ -7,6 +7,9 @@ export type CatalogRoot = {
7
7
  path: string;
8
8
  kind: CatalogRootKind;
9
9
  read_only: boolean;
10
+ /** False when workspace provisioning failed at boot. */
11
+ available?: boolean;
12
+ unavailable_reason?: string;
10
13
  };
11
14
  export type ResolveCatalogRootsOptions = {
12
15
  store: RunStore;
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { realpathSync, constants } from "node:fs";
3
3
  import { access } from "node:fs/promises";
4
+ import { WORKSPACE_CATALOG_ID, lastWorkspaceProvisionResult, workspaceCatalogPath, } from "../project/workspaceCatalog.js";
4
5
  function realOrResolve(p) {
5
6
  const resolved = path.resolve(p);
6
7
  try {
@@ -19,6 +20,7 @@ export async function resolveCatalogRoots(options) {
19
20
  const seenPaths = new Set();
20
21
  const seededRoots = options.seededRoots ??
21
22
  (await import("./seededCatalog.js")).defaultSeededRoots();
23
+ const workspaceAbs = realOrResolve(workspaceCatalogPath());
22
24
  let registered = [];
23
25
  try {
24
26
  registered = await options.store.listRegisteredProjects();
@@ -28,7 +30,7 @@ export async function resolveCatalogRoots(options) {
28
30
  }
29
31
  for (const root of registered) {
30
32
  const abs = realOrResolve(root);
31
- if (seenPaths.has(abs))
33
+ if (seenPaths.has(abs) || abs === workspaceAbs)
32
34
  continue;
33
35
  seenPaths.add(abs);
34
36
  roots.push({
@@ -38,6 +40,21 @@ export async function resolveCatalogRoots(options) {
38
40
  read_only: false,
39
41
  });
40
42
  }
43
+ if (!seenPaths.has(workspaceAbs)) {
44
+ seenPaths.add(workspaceAbs);
45
+ const provision = lastWorkspaceProvisionResult();
46
+ const available = provision === undefined || provision.ok;
47
+ roots.push({
48
+ project_root: WORKSPACE_CATALOG_ID,
49
+ path: workspaceCatalogPath(),
50
+ kind: "workspace",
51
+ read_only: false,
52
+ available,
53
+ ...(provision !== undefined && !provision.ok && provision.reason
54
+ ? { unavailable_reason: provision.reason }
55
+ : {}),
56
+ });
57
+ }
41
58
  for (const seeded of seededRoots) {
42
59
  const abs = realOrResolve(seeded.path);
43
60
  if (seenPaths.has(abs))
@@ -0,0 +1,6 @@
1
+ export type SkillUsageEntry = {
2
+ stage_ids: string[];
3
+ pipeline_ids: string[];
4
+ };
5
+ export type SkillUsageIndex = Record<string, SkillUsageEntry>;
6
+ export declare function buildSkillUsageIndex(cwd: string): Promise<SkillUsageIndex | null>;
@@ -0,0 +1,39 @@
1
+ import { loadPipelineOutcome } from "./loadPipeline.js";
2
+ import { getCatalogScanPaths } from "./browseCatalog.js";
3
+ import { catalogContextFromStageflow } from "./resolveCatalogContext.js";
4
+ import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
5
+ function addUsage(index, skillName, pipelineId, stageId) {
6
+ const entry = index[skillName] ?? { stage_ids: [], pipeline_ids: [] };
7
+ if (!entry.pipeline_ids.includes(pipelineId)) {
8
+ entry.pipeline_ids.push(pipelineId);
9
+ entry.pipeline_ids.sort((a, b) => a.localeCompare(b));
10
+ }
11
+ if (!entry.stage_ids.includes(stageId)) {
12
+ entry.stage_ids.push(stageId);
13
+ entry.stage_ids.sort((a, b) => a.localeCompare(b));
14
+ }
15
+ index[skillName] = entry;
16
+ }
17
+ export async function buildSkillUsageIndex(cwd) {
18
+ const ctx = catalogContextFromStageflow(await resolveStageflowContext(cwd));
19
+ const scanPaths = await getCatalogScanPaths(ctx);
20
+ if (!scanPaths) {
21
+ return null;
22
+ }
23
+ const index = {};
24
+ const loadCwd = ctx.projectRoot ?? cwd;
25
+ for (const pipelinePath of scanPaths.pipelinePaths) {
26
+ const outcome = await loadPipelineOutcome(pipelinePath, { cwd: loadCwd });
27
+ if (!outcome.ok) {
28
+ continue;
29
+ }
30
+ const pipelineId = outcome.value.pipeline.id;
31
+ for (const stage of outcome.value.stages) {
32
+ if (stage.skill === undefined) {
33
+ continue;
34
+ }
35
+ addUsage(index, stage.skill, pipelineId, stage.id);
36
+ }
37
+ }
38
+ return index;
39
+ }