@skaile/workspaces 0.22.0-beta.0 → 0.22.0-beta.1

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 (260) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/{asset-feeds-PJDJ3QYI.js → asset-feeds-QXCSAJRN.js} +9 -9
  3. package/dist/{asset-feeds-PJDJ3QYI.js.map → asset-feeds-QXCSAJRN.js.map} +1 -1
  4. package/dist/asset-manager/catalog-deployer.d.ts +2 -0
  5. package/dist/asset-manager/contrib.d.ts +2 -0
  6. package/dist/asset-manager/fragments.d.ts +2 -0
  7. package/dist/asset-manager/history.d.ts +2 -0
  8. package/dist/asset-manager/index.d.ts +2 -0
  9. package/dist/asset-manager/index.js +7 -7
  10. package/dist/asset-manager/installer.d.ts +2 -0
  11. package/dist/asset-manager/installer.js +6 -6
  12. package/dist/asset-manager/renderers.d.ts +2 -0
  13. package/dist/asset-manager/src/index.d.ts +18 -7
  14. package/dist/asset-manager/src/index.d.ts.map +1 -1
  15. package/dist/asset-manager/src/installer.d.ts +3 -3
  16. package/dist/asset-manager/src/installer.d.ts.map +1 -1
  17. package/dist/base-assets/connectors/deploy.d.ts +2 -0
  18. package/dist/base-assets/connectors/deploy.js +8 -8
  19. package/dist/base-assets/connectors/devserver.d.ts +2 -0
  20. package/dist/base-assets/connectors/devserver.js +8 -8
  21. package/dist/base-assets/connectors/flow/adapter.js +8 -8
  22. package/dist/base-assets/connectors/flow/engine.d.ts +2 -0
  23. package/dist/base-assets/connectors/flow/run-flow.js +9 -9
  24. package/dist/base-assets/connectors/flow.d.ts +2 -0
  25. package/dist/base-assets/connectors/flow.js +8 -8
  26. package/dist/base-assets/connectors/git.d.ts +2 -0
  27. package/dist/base-assets/connectors/git.js +8 -8
  28. package/dist/base-assets/connectors/gmail.d.ts +2 -0
  29. package/dist/base-assets/connectors/gmail.js +8 -8
  30. package/dist/base-assets/connectors/googledrive.d.ts +2 -0
  31. package/dist/base-assets/connectors/googledrive.js +8 -8
  32. package/dist/base-assets/connectors/local.d.ts +2 -0
  33. package/dist/base-assets/connectors/local.js +8 -8
  34. package/dist/base-assets/connectors/mattermost.d.ts +2 -0
  35. package/dist/base-assets/connectors/mattermost.js +8 -8
  36. package/dist/base-assets/connectors/memory.d.ts +2 -0
  37. package/dist/base-assets/connectors/memory.js +8 -8
  38. package/dist/base-assets/connectors/minio.d.ts +2 -0
  39. package/dist/base-assets/connectors/minio.js +8 -8
  40. package/dist/base-assets/connectors/postgres.d.ts +2 -0
  41. package/dist/base-assets/connectors/postgres.js +8 -8
  42. package/dist/base-assets/connectors/s3.d.ts +2 -0
  43. package/dist/base-assets/connectors/s3.js +8 -8
  44. package/dist/base-assets/connectors/sharepoint.d.ts +2 -0
  45. package/dist/base-assets/connectors/sharepoint.js +8 -8
  46. package/dist/base-assets/connectors/sqlite.d.ts +2 -0
  47. package/dist/base-assets/connectors/sqlite.js +8 -8
  48. package/dist/base-assets/connectors/static-server.d.ts +2 -0
  49. package/dist/base-assets/connectors/static-server.js +8 -8
  50. package/dist/base-assets/connectors/tunnel.d.ts +2 -0
  51. package/dist/base-assets/connectors/tunnel.js +8 -8
  52. package/dist/base-assets/connectors/webdav.d.ts +2 -0
  53. package/dist/base-assets/connectors/webdav.js +8 -8
  54. package/dist/base-assets/connectors/xstate-store.d.ts +2 -0
  55. package/dist/base-assets/connectors/xstate-store.js +8 -8
  56. package/dist/base-assets/connectors/xstate.d.ts +2 -0
  57. package/dist/base-assets/connectors/xstate.js +8 -8
  58. package/dist/bridge/drivers/claude-sdk.d.ts +2 -0
  59. package/dist/bridge/drivers/claude-sdk.js +2 -2
  60. package/dist/bridge/drivers/codex.d.ts +2 -0
  61. package/dist/bridge/drivers/codex.js +2 -2
  62. package/dist/bridge/drivers/echo.d.ts +2 -0
  63. package/dist/bridge/drivers/echo.js +2 -2
  64. package/dist/bridge/drivers/omp.d.ts +2 -0
  65. package/dist/bridge/drivers/omp.js +2 -2
  66. package/dist/bridge/index.d.ts +2 -0
  67. package/dist/bridge/index.js +3 -3
  68. package/dist/bridge/src/registry.d.ts +4 -2
  69. package/dist/bridge/src/registry.d.ts.map +1 -1
  70. package/dist/{chunk-UZVHJ7LX.js → chunk-3ECS5PFD.js} +4 -4
  71. package/dist/{chunk-UZVHJ7LX.js.map → chunk-3ECS5PFD.js.map} +1 -1
  72. package/dist/{chunk-WIR34WMU.js → chunk-4AZKT2BU.js} +13 -13
  73. package/dist/chunk-4AZKT2BU.js.map +1 -0
  74. package/dist/{chunk-UZRY5UI2.js → chunk-6E6PKKAD.js} +68 -3
  75. package/dist/chunk-6E6PKKAD.js.map +1 -0
  76. package/dist/{chunk-IGQEXBBG.js → chunk-6VTG73UY.js} +13 -9
  77. package/dist/chunk-6VTG73UY.js.map +1 -0
  78. package/dist/{chunk-GKM6MDUC.js → chunk-APAOQLPT.js} +3 -3
  79. package/dist/{chunk-GKM6MDUC.js.map → chunk-APAOQLPT.js.map} +1 -1
  80. package/dist/{chunk-SL6JVGRD.js → chunk-D7K72XEY.js} +3 -3
  81. package/dist/{chunk-SL6JVGRD.js.map → chunk-D7K72XEY.js.map} +1 -1
  82. package/dist/{chunk-CSDQBWE6.js → chunk-DKGDOALM.js} +5 -5
  83. package/dist/{chunk-CSDQBWE6.js.map → chunk-DKGDOALM.js.map} +1 -1
  84. package/dist/{chunk-TTY56FQQ.js → chunk-GFNW72LW.js} +17 -5
  85. package/dist/chunk-GFNW72LW.js.map +1 -0
  86. package/dist/{chunk-X5Y4EGZB.js → chunk-I3UEM3FX.js} +36 -8
  87. package/dist/chunk-I3UEM3FX.js.map +1 -0
  88. package/dist/{chunk-TKOLD2O7.js → chunk-J3VKAEQP.js} +497 -143
  89. package/dist/chunk-J3VKAEQP.js.map +1 -0
  90. package/dist/{chunk-KA46DUM4.js → chunk-JHF66MCK.js} +49 -3
  91. package/dist/chunk-JHF66MCK.js.map +1 -0
  92. package/dist/{chunk-G6GKWGOW.js → chunk-LT4DLEYE.js} +6 -6
  93. package/dist/chunk-LT4DLEYE.js.map +1 -0
  94. package/dist/{chunk-XHFMUGDD.js → chunk-M5TE6YI5.js} +3 -3
  95. package/dist/{chunk-XHFMUGDD.js.map → chunk-M5TE6YI5.js.map} +1 -1
  96. package/dist/{chunk-J2FCO6TM.js → chunk-NJLHHZIW.js} +2 -2
  97. package/dist/{chunk-J2FCO6TM.js.map → chunk-NJLHHZIW.js.map} +1 -1
  98. package/dist/{chunk-MO4JPTRD.js → chunk-PTIHB2TV.js} +5 -5
  99. package/dist/{chunk-MO4JPTRD.js.map → chunk-PTIHB2TV.js.map} +1 -1
  100. package/dist/{chunk-RENHNO4J.js → chunk-UMOENHVH.js} +206 -137
  101. package/dist/chunk-UMOENHVH.js.map +1 -0
  102. package/dist/{chunk-NGC7ZQI4.js → chunk-V3QMSM5I.js} +38 -43
  103. package/dist/chunk-V3QMSM5I.js.map +1 -0
  104. package/dist/{chunk-2DNSSQ22.js → chunk-VCYXVP2S.js} +263 -177
  105. package/dist/chunk-VCYXVP2S.js.map +1 -0
  106. package/dist/{chunk-F3MGZ5E6.js → chunk-XIHFJVOD.js} +3 -3
  107. package/dist/{chunk-F3MGZ5E6.js.map → chunk-XIHFJVOD.js.map} +1 -1
  108. package/dist/cli/index.d.ts +2 -0
  109. package/dist/cli/index.js +102 -100
  110. package/dist/cli/index.js.map +1 -1
  111. package/dist/cli/src/commands/deploy.d.ts.map +1 -1
  112. package/dist/cli/src/commands/manage.d.ts +1 -1
  113. package/dist/cli/src/commands/project.d.ts.map +1 -1
  114. package/dist/cli/src/commands/source.d.ts.map +1 -1
  115. package/dist/cli/src/commands/update.d.ts.map +1 -1
  116. package/dist/cli/src/ensure-sources.d.ts.map +1 -1
  117. package/dist/client/index.d.ts +2 -0
  118. package/dist/connectors/config.d.ts +2 -0
  119. package/dist/connectors/config.js +6 -6
  120. package/dist/connectors/index.d.ts +2 -0
  121. package/dist/connectors/index.js +8 -8
  122. package/dist/connectors/rclone-config.d.ts +2 -0
  123. package/dist/connectors/rclone.d.ts +2 -0
  124. package/dist/connectors-shared/index.d.ts +2 -0
  125. package/dist/core/discovery.d.ts +2 -0
  126. package/dist/core/driver-targets.d.ts +2 -0
  127. package/dist/core/framework.d.ts +2 -0
  128. package/dist/core/index.d.ts +2 -0
  129. package/dist/core/index.js +5 -5
  130. package/dist/core/logging.d.ts +2 -0
  131. package/dist/core/manifest.d.ts +2 -0
  132. package/dist/core/manifest.js +2 -2
  133. package/dist/core/models.d.ts +2 -0
  134. package/dist/core/models.js +1 -1
  135. package/dist/core/runtime-assets.d.ts +2 -0
  136. package/dist/core/runtime-assets.js +4 -4
  137. package/dist/core/src/index.d.ts +7 -5
  138. package/dist/core/src/index.d.ts.map +1 -1
  139. package/dist/core/src/lock.d.ts +47 -26
  140. package/dist/core/src/lock.d.ts.map +1 -1
  141. package/dist/core/src/models.d.ts +53 -47
  142. package/dist/core/src/models.d.ts.map +1 -1
  143. package/dist/core/src/repo-manager.d.ts +62 -34
  144. package/dist/core/src/repo-manager.d.ts.map +1 -1
  145. package/dist/core/src/runtime-assets.d.ts.map +1 -1
  146. package/dist/core/src/walker.d.ts +52 -0
  147. package/dist/core/src/walker.d.ts.map +1 -0
  148. package/dist/core/src/workspace-config.d.ts +160 -45
  149. package/dist/core/src/workspace-config.d.ts.map +1 -1
  150. package/dist/core/src/workspace-yaml-editor.d.ts +33 -16
  151. package/dist/core/src/workspace-yaml-editor.d.ts.map +1 -1
  152. package/dist/core/store.d.ts +2 -0
  153. package/dist/core/workspace-config.d.ts +2 -0
  154. package/dist/core/workspace-config.js +3 -3
  155. package/dist/deploy/index.d.ts +2 -0
  156. package/dist/deploy/index.js +34 -15
  157. package/dist/deploy/index.js.map +1 -1
  158. package/dist/deploy/src/targets/container-runtime.d.ts +1 -0
  159. package/dist/deploy/src/targets/container-runtime.d.ts.map +1 -1
  160. package/dist/deploy/src/targets/docker.d.ts +1 -0
  161. package/dist/deploy/src/targets/docker.d.ts.map +1 -1
  162. package/dist/deploy/src/targets/local.d.ts.map +1 -1
  163. package/dist/deploy/src/targets/podman.d.ts +1 -0
  164. package/dist/deploy/src/targets/podman.d.ts.map +1 -1
  165. package/dist/discovery/index.d.ts +2 -0
  166. package/dist/discovery/index.js +3 -3
  167. package/dist/discovery/src/source-config.d.ts +2 -2
  168. package/dist/{ensure-sources-COGVKY44.js → ensure-sources-SL2S4UEX.js} +20 -16
  169. package/dist/ensure-sources-SL2S4UEX.js.map +1 -0
  170. package/dist/library/index.d.ts +2 -0
  171. package/dist/library/index.js +4 -4
  172. package/dist/library/src/remote/remote-catalog-source.d.ts +17 -0
  173. package/dist/library/src/remote/remote-catalog-source.d.ts.map +1 -1
  174. package/dist/open-library-M4DB3D3J.js +13 -0
  175. package/dist/{open-library-DWAQFUSQ.js.map → open-library-M4DB3D3J.js.map} +1 -1
  176. package/dist/plugin-registry/index.d.ts +2 -0
  177. package/dist/plugin-registry/index.js +1 -1
  178. package/dist/plugin-registry/src/deploy-handle.d.ts +17 -1
  179. package/dist/plugin-registry/src/deploy-handle.d.ts.map +1 -1
  180. package/dist/plugin-registry/src/deploy-helpers.d.ts +69 -0
  181. package/dist/plugin-registry/src/deploy-helpers.d.ts.map +1 -0
  182. package/dist/plugin-registry/src/index.d.ts +6 -4
  183. package/dist/plugin-registry/src/index.d.ts.map +1 -1
  184. package/dist/plugin-registry/src/internal.d.ts.map +1 -1
  185. package/dist/plugin-registry/src/registry.d.ts +1 -0
  186. package/dist/plugin-registry/src/registry.d.ts.map +1 -1
  187. package/dist/plugin-registry/src/targets.d.ts +4 -0
  188. package/dist/plugin-registry/src/targets.d.ts.map +1 -1
  189. package/dist/{plugin-store-6OENKNFW.js → plugin-store-AJ3FGXIC.js} +8 -8
  190. package/dist/{plugin-store-6OENKNFW.js.map → plugin-store-AJ3FGXIC.js.map} +1 -1
  191. package/dist/plugins/index.d.ts +2 -0
  192. package/dist/resolver/index.d.ts +2 -0
  193. package/dist/runner/index.d.ts +2 -0
  194. package/dist/runner/index.js +13 -13
  195. package/dist/runner/prompt-assembly.d.ts +2 -0
  196. package/dist/runner/src/resources.d.ts.map +1 -1
  197. package/dist/runner/src/serve.d.ts.map +1 -1
  198. package/dist/sdk/asset-manager.d.ts +2 -0
  199. package/dist/sdk/asset-manager.js +7 -7
  200. package/dist/sdk/bridge.d.ts +2 -0
  201. package/dist/sdk/bridge.js +3 -3
  202. package/dist/sdk/client.d.ts +2 -0
  203. package/dist/sdk/core.d.ts +2 -0
  204. package/dist/sdk/core.js +5 -5
  205. package/dist/sdk/flow.d.ts +2 -0
  206. package/dist/sdk/index.d.ts +2 -0
  207. package/dist/sdk/index.js +49 -16
  208. package/dist/sdk/index.js.map +1 -1
  209. package/dist/sdk/resolver.d.ts +2 -0
  210. package/dist/sdk/runner.d.ts +2 -0
  211. package/dist/sdk/runner.js +13 -13
  212. package/dist/sdk/session.d.ts +2 -0
  213. package/dist/sdk/src/local-runtime.d.ts +8 -0
  214. package/dist/sdk/src/local-runtime.d.ts.map +1 -1
  215. package/dist/sdk/src/transport.d.ts +7 -1
  216. package/dist/sdk/src/transport.d.ts.map +1 -1
  217. package/dist/sdk/store.d.ts +2 -0
  218. package/dist/sdk/telemetry.d.ts +2 -0
  219. package/dist/sdk/transport/ws/client.d.ts +2 -0
  220. package/dist/sdk/transport/ws/server.d.ts +2 -0
  221. package/dist/sdk/transport/ws.d.ts +2 -0
  222. package/dist/sdk/transport.d.ts +2 -0
  223. package/dist/sdk/types.d.ts +2 -0
  224. package/dist/secrets/index.d.ts +2 -0
  225. package/dist/session/index.d.ts +2 -0
  226. package/dist/{setup-ACMP3QZC.js → setup-GBSQX7JF.js} +10 -10
  227. package/dist/{setup-ACMP3QZC.js.map → setup-GBSQX7JF.js.map} +1 -1
  228. package/dist/store/index.d.ts +2 -0
  229. package/dist/store/react.d.ts +2 -0
  230. package/dist/store/vue.d.ts +2 -0
  231. package/dist/store-client-5WBRUC5U.js +14 -0
  232. package/dist/{store-client-ZSLNOOQG.js.map → store-client-5WBRUC5U.js.map} +1 -1
  233. package/dist/telemetry/index.d.ts +2 -0
  234. package/dist/transport/index.d.ts +2 -0
  235. package/dist/transport/ws/client.d.ts +2 -0
  236. package/dist/transport/ws/server.d.ts +2 -0
  237. package/dist/transport/ws.d.ts +2 -0
  238. package/dist/tui/index.d.ts +2 -0
  239. package/dist/tui/index.js +13 -13
  240. package/dist/types/index.d.ts +2 -0
  241. package/dist/types/manifests.d.ts +2 -0
  242. package/dist/workspace-plugin/adapters/mcp.d.ts +2 -0
  243. package/dist/workspace-plugin/adapters/omp.d.ts +2 -0
  244. package/dist/workspace-plugin/index.d.ts +2 -0
  245. package/dist/workspace-plugin/index.js +1 -1
  246. package/package.json +4 -2
  247. package/dist/chunk-2DNSSQ22.js.map +0 -1
  248. package/dist/chunk-G6GKWGOW.js.map +0 -1
  249. package/dist/chunk-IGQEXBBG.js.map +0 -1
  250. package/dist/chunk-KA46DUM4.js.map +0 -1
  251. package/dist/chunk-NGC7ZQI4.js.map +0 -1
  252. package/dist/chunk-RENHNO4J.js.map +0 -1
  253. package/dist/chunk-TKOLD2O7.js.map +0 -1
  254. package/dist/chunk-TTY56FQQ.js.map +0 -1
  255. package/dist/chunk-UZRY5UI2.js.map +0 -1
  256. package/dist/chunk-WIR34WMU.js.map +0 -1
  257. package/dist/chunk-X5Y4EGZB.js.map +0 -1
  258. package/dist/ensure-sources-COGVKY44.js.map +0 -1
  259. package/dist/open-library-DWAQFUSQ.js +0 -13
  260. package/dist/store-client-ZSLNOOQG.js +0 -14
@@ -1,10 +1,13 @@
1
- import { scanDirectory, fromMcpServerMd } from './chunk-WIR34WMU.js';
2
- import { parseAssetRef } from './chunk-X5Y4EGZB.js';
3
- import { mkdirSync, existsSync, readFileSync, writeFileSync, lstatSync, symlinkSync, rmSync, readdirSync } from 'fs';
1
+ import { scanDirectory, parseFrontmatter, fromMcpServerMd } from './chunk-4AZKT2BU.js';
2
+ import { parseAssetRef } from './chunk-I3UEM3FX.js';
3
+ import { mkdirSync, existsSync, readFileSync, writeFileSync, lstatSync, symlinkSync, rmSync, readdirSync, statSync } from 'fs';
4
4
  import { homedir } from 'os';
5
- import { join, resolve, parse as parse$1, dirname } from 'path';
5
+ import { join, resolve, relative, parse as parse$1, dirname } from 'path';
6
6
  import { parse, stringify } from 'yaml';
7
7
  import { spawnSync } from 'child_process';
8
+ import semver from 'semver';
9
+ import { createHash } from 'crypto';
10
+ import fg from 'fast-glob';
8
11
 
9
12
  function getGlobalCacheDir() {
10
13
  return process.env.SKAILE_CACHE_DIR ?? join(homedir(), ".skaile", "repos");
@@ -162,8 +165,8 @@ function scanRepo(repoDir, repoName) {
162
165
  }
163
166
  function resolveAsset(ref, repositories, reposDir, opts) {
164
167
  let repoNames;
165
- if (ref.repository) {
166
- repoNames = [ref.repository];
168
+ if (ref.publisher) {
169
+ repoNames = [ref.publisher];
167
170
  } else if (opts?.preferRepo && opts.preferRepo in repositories) {
168
171
  const rest = Object.keys(repositories).filter((n) => n !== opts.preferRepo);
169
172
  repoNames = [opts.preferRepo, ...rest];
@@ -199,67 +202,149 @@ function resolveRepoDir(decl, name, reposDir, projectDir) {
199
202
  if (existsSync(join(globalDest, ".git"))) return globalDest;
200
203
  return null;
201
204
  }
202
- function resolveAll(deps, repositories, reposDir, opts) {
205
+ var CanonicalRefConflictError = class extends Error {
206
+ constructor(ref, candidates, depChain) {
207
+ super(
208
+ [
209
+ `error: divergent sha256 for ${ref}`,
210
+ "",
211
+ " pulled in via:",
212
+ ...depChain.map((s, i) => ` ${" ".repeat(i * 2)}${s}`),
213
+ "",
214
+ " candidates:",
215
+ ...candidates.map(
216
+ (c) => ` ${c.sourceUrl} @ ${c.commit.slice(0, 8)}
217
+ sha256: ${c.sha256}`
218
+ ),
219
+ "",
220
+ "resolve by:",
221
+ " 1. removing one source from skaile.yaml,",
222
+ " 2. configuring a store and using its canonical digest, or",
223
+ " 3. adding to overrides: (with a non-empty reason:)"
224
+ ].join("\n")
225
+ );
226
+ this.ref = ref;
227
+ this.candidates = candidates;
228
+ this.depChain = depChain;
229
+ this.name = "CanonicalRefConflictError";
230
+ }
231
+ ref;
232
+ candidates;
233
+ depChain;
234
+ };
235
+ var SHA_PIN_RE = /^[0-9a-f]{40}$/i;
236
+ async function resolveAll(deps, opts) {
203
237
  const resolved = [];
204
238
  const seen = /* @__PURE__ */ new Set();
205
239
  const missing = [];
206
240
  const resolvedBy = /* @__PURE__ */ new Map();
207
- function visit(refStr, parent, parentRepo) {
241
+ const overridesApplied = /* @__PURE__ */ new Set();
242
+ const overridesByRef = new Map(opts.overrides.map((o) => [o.ref, o]));
243
+ async function visit(refStr, parent, depChain) {
208
244
  const ref = parseAssetRef(refStr);
209
- const key = `${ref.kind}:${ref.name}`;
245
+ if (!ref.publisher) throw new Error(`dep "${refStr}" missing publisher`);
246
+ const key = `${ref.publisher}/${ref.kind}:${ref.name}`;
210
247
  if (seen.has(key)) return;
211
248
  seen.add(key);
212
- const preferRepo = ref.repository ?? parentRepo;
213
- const entry = resolveAsset(ref, repositories, reposDir, {
214
- ...opts,
215
- preferRepo
216
- });
217
- if (!entry) {
249
+ const sourceCandidates = opts.provenanceIndex.get(key) ?? [];
250
+ const storeCandidates = [];
251
+ if (opts.stores.length > 0 && opts.storeFetcher) {
252
+ const candidateVersions = enumerateVersionsForPin(
253
+ ref.pin,
254
+ sourceCandidates.map((c) => c.version)
255
+ );
256
+ for (const store of opts.stores) {
257
+ for (const v of candidateVersions) {
258
+ if (!v) continue;
259
+ const m = await opts.storeFetcher.getInstallManifest(
260
+ store.url,
261
+ `${ref.publisher}/${ref.kind}:${ref.name}@${v}`
262
+ );
263
+ if (m) {
264
+ storeCandidates.push({
265
+ publisher: ref.publisher,
266
+ kind: ref.kind,
267
+ name: ref.name,
268
+ version: v,
269
+ sourceUrl: m.sourceUrl,
270
+ commit: m.commit,
271
+ files: m.files,
272
+ sha256: m.sha256
273
+ });
274
+ }
275
+ }
276
+ }
277
+ }
278
+ const all = [...sourceCandidates, ...storeCandidates];
279
+ if (all.length === 0) {
218
280
  missing.push(refStr);
219
281
  return;
220
282
  }
221
- resolvedBy.set(key, parent);
222
- const entryRepo = entry.repository;
223
- for (const req of entry.requires) {
224
- visit(`${req.kind}:${req.name}`, key, entryRepo);
283
+ const filtered = all.filter((c) => matchPin(ref.pin, c.version));
284
+ if (filtered.length === 0) {
285
+ missing.push(refStr);
286
+ return;
287
+ }
288
+ const versions = Array.from(new Set(filtered.map((c) => c.version)));
289
+ const best = versions.sort((a, b) => semver.rcompare(coerceVersion(a), coerceVersion(b)))[0];
290
+ const finalists = filtered.filter((c) => c.version === best);
291
+ const canonicalRef = `${ref.publisher}/${ref.kind}:${ref.name}@${best}`;
292
+ const uniqueShas = Array.from(new Set(finalists.map((c) => c.sha256)));
293
+ let chosen;
294
+ if (uniqueShas.length > 1) {
295
+ const override = overridesByRef.get(canonicalRef);
296
+ if (!override) {
297
+ throw new CanonicalRefConflictError(canonicalRef, finalists, [...depChain, refStr]);
298
+ }
299
+ const pick = finalists.find((c) => c.sourceUrl === override.source);
300
+ if (!pick) {
301
+ throw new Error(
302
+ `override for ${canonicalRef} points at ${override.source}, which is not among the resolved candidates`
303
+ );
304
+ }
305
+ chosen = pick;
306
+ overridesApplied.add(canonicalRef);
307
+ } else {
308
+ chosen = [...finalists].sort((a, b) => a.sourceUrl.localeCompare(b.sourceUrl))[0];
225
309
  }
226
- for (const dep of entry.dependencies) {
227
- visit(dep, key, entryRepo);
310
+ if (opts.storeFetcher && opts.stores.length > 0 && sourceCandidates.some((c) => c.version === best) && storeCandidates.some((c) => c.version === best)) {
311
+ for (const store of opts.stores) {
312
+ const d = await opts.storeFetcher.getCanonicalDigest(store.url, canonicalRef);
313
+ if (d && d.sha256 !== chosen.sha256) {
314
+ throw new CanonicalRefConflictError(
315
+ canonicalRef,
316
+ [...finalists, { ...chosen, sourceUrl: store.url, sha256: d.sha256 }],
317
+ [...depChain, refStr]
318
+ );
319
+ }
320
+ }
228
321
  }
229
- resolved.push(entry);
322
+ resolved.push(chosen);
323
+ resolvedBy.set(key, parent);
230
324
  }
231
325
  for (const dep of deps) {
232
- visit(dep, "direct");
326
+ await visit(dep, "direct", []);
233
327
  }
234
- const collisions = detectCollisions(resolved, repositories, reposDir, opts?.projectDir);
235
- return { resolved, missing, resolvedBy, collisions };
328
+ return { resolved, missing, resolvedBy, overridesApplied };
236
329
  }
237
- function detectCollisions(resolved, repositories, reposDir, projectDir) {
238
- const repoNames = Object.keys(repositories);
239
- if (repoNames.length < 2) return [];
240
- const repoEntries = /* @__PURE__ */ new Map();
241
- for (const repoName of repoNames) {
242
- const decl = repositories[repoName];
243
- if (!decl) continue;
244
- const repoDir = resolveRepoDir(decl, repoName, reposDir, projectDir);
245
- if (!repoDir || !existsSync(repoDir)) continue;
246
- repoEntries.set(repoName, scanRepo(repoDir, repoName));
247
- }
248
- const collisions = [];
249
- for (const entry of resolved) {
250
- const key = `${entry.kind}:${entry.name}`;
251
- const shadowedIn = [];
252
- for (const [repoName, entries] of repoEntries) {
253
- if (repoName === entry.repository) continue;
254
- if (entries.some((e) => e.kind === entry.kind && e.name === entry.name)) {
255
- shadowedIn.push(repoName);
256
- }
257
- }
258
- if (shadowedIn.length > 0 && entry.repository) {
259
- collisions.push({ key, resolvedFrom: entry.repository, shadowedIn });
260
- }
330
+ function coerceVersion(v) {
331
+ return semver.valid(v) ? v : semver.coerce(v)?.version ?? "0.0.0";
332
+ }
333
+ function matchPin(pin, version) {
334
+ if (!pin) return true;
335
+ if (SHA_PIN_RE.test(pin)) {
336
+ return version === `0.0.0-sha.${pin.slice(0, 7)}`;
261
337
  }
262
- return collisions;
338
+ if (version.startsWith("0.0.0-sha.")) {
339
+ return version === pin;
340
+ }
341
+ if (!semver.valid(version)) return version === pin;
342
+ return semver.satisfies(version, pin, { includePrerelease: false });
343
+ }
344
+ function enumerateVersionsForPin(pin, sourceVersionsHint) {
345
+ if (!pin) return sourceVersionsHint.length > 0 ? sourceVersionsHint : [];
346
+ if (semver.valid(pin)) return [pin];
347
+ return sourceVersionsHint;
263
348
  }
264
349
  function checkRepoStatus(decl, name, reposDir, projectDir) {
265
350
  if (projectDir) {
@@ -320,8 +405,185 @@ function checkRepoStatus(decl, name, reposDir, projectDir) {
320
405
  base.upToDate = base.behind === 0;
321
406
  return base;
322
407
  }
408
+ function fileSha256(p) {
409
+ return createHash("sha256").update(readFileSync(p)).digest("hex");
410
+ }
411
+ function compositeSha256(files) {
412
+ const lines = files.slice().sort((a, b) => a.path.localeCompare(b.path)).map((f) => `${f.path}:${f.sha256}
413
+ `).join("");
414
+ return createHash("sha256").update(lines).digest("hex");
415
+ }
416
+ function publisherFromGithubUrl(url) {
417
+ const m = url.match(/github\.com[/:]([^/]+)\/[^/]+/);
418
+ return m?.[1];
419
+ }
420
+ function syntheticVersion(commit) {
421
+ return `0.0.0-sha.${commit.slice(0, 7)}`;
422
+ }
423
+ var KIND_DIRS = [
424
+ { dir: "skills", kind: "skill", mdName: "SKILL.md" },
425
+ { dir: "agents", kind: "agent", mdName: "AGENT.md" },
426
+ { dir: "bundles", kind: "bundle", mdName: "BUNDLE.md" },
427
+ { dir: "mcp-servers", kind: "mcp-server", mdName: "MCP.md" },
428
+ { dir: "connectors", kind: "connector", mdName: "CONNECTOR.md" },
429
+ { dir: "prompts", kind: "prompt", mdName: "PROMPT.md" },
430
+ { dir: "contracts", kind: "contract", mdName: "CONTRACT.md" }
431
+ ];
432
+ var MD_FILE_BY_KIND = Object.fromEntries(
433
+ KIND_DIRS.map((k) => [k.kind, k.mdName])
434
+ );
435
+ function buildProvenanceIndex(clones, _opts) {
436
+ const index = /* @__PURE__ */ new Map();
437
+ for (const clone of clones) walkOne(clone, index);
438
+ return index;
439
+ }
440
+ function walkOne(clone, index) {
441
+ const yamlPath = join(clone.localPath, "skaile.yaml");
442
+ if (existsSync(yamlPath)) {
443
+ walkWithManifest(clone, yamlPath, index);
444
+ } else {
445
+ walkFilenameConvention(clone, index);
446
+ }
447
+ }
448
+ function walkWithManifest(clone, yamlPath, index) {
449
+ const raw = parse(readFileSync(yamlPath, "utf8")) ?? {};
450
+ const publisher = typeof raw.publisher === "string" && raw.publisher.length > 0 ? raw.publisher : publisherFromGithubUrl(clone.sourceUrl);
451
+ if (!publisher) {
452
+ throw new Error(
453
+ `${clone.sourceUrl}: publisher must be declared in skaile.yaml (non-GitHub source URL \u2014 auto-derivation not available).`
454
+ );
455
+ }
456
+ const sourceVersion = (typeof raw.version === "string" ? raw.version : void 0) ?? (clone.tag ? clone.tag.replace(/^v/, "") : void 0) ?? syntheticVersion(clone.commit);
457
+ const assets = Array.isArray(raw.assets) ? raw.assets : [];
458
+ if (assets.length === 0) {
459
+ walkFilenameConvention(clone, index, publisher, sourceVersion);
460
+ return;
461
+ }
462
+ for (const a of assets) {
463
+ const assetPublisher = a.publisher ?? publisher;
464
+ const version = a.version ?? sourceVersion;
465
+ const files = expandAssetFiles(clone.localPath, a);
466
+ verifyNameMatch(clone.localPath, files, a.name, a.kind);
467
+ const metadata = readMetadata(clone.localPath, files, a.kind);
468
+ const candidate = {
469
+ publisher: assetPublisher,
470
+ kind: a.kind,
471
+ name: a.name,
472
+ version,
473
+ sourceUrl: clone.sourceUrl,
474
+ commit: clone.commit,
475
+ files,
476
+ sha256: compositeSha256(files),
477
+ metadata
478
+ };
479
+ push(index, `${assetPublisher}/${a.kind}:${a.name}`, candidate);
480
+ }
481
+ }
482
+ function expandAssetFiles(repoRoot, a) {
483
+ const out = [];
484
+ if (a.root) {
485
+ walkDirRecursive(join(repoRoot, a.root), repoRoot, out);
486
+ }
487
+ if (a.files) {
488
+ const matches = fg.sync(a.files, { cwd: repoRoot, onlyFiles: true, dot: false });
489
+ for (const rel of matches) {
490
+ out.push({ path: rel, sha256: fileSha256(join(repoRoot, rel)) });
491
+ }
492
+ }
493
+ const seen = /* @__PURE__ */ new Set();
494
+ return out.filter((f) => seen.has(f.path) ? false : (seen.add(f.path), true));
495
+ }
496
+ function walkDirRecursive(abs, root, out) {
497
+ if (!existsSync(abs)) return;
498
+ for (const entry of readdirSync(abs, { withFileTypes: true })) {
499
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
500
+ const childAbs = join(abs, entry.name);
501
+ if (entry.isDirectory()) {
502
+ walkDirRecursive(childAbs, root, out);
503
+ } else if (entry.isFile()) {
504
+ out.push({ path: relative(root, childAbs), sha256: fileSha256(childAbs) });
505
+ }
506
+ }
507
+ }
508
+ function walkFilenameConvention(clone, index, publisherOverride, versionOverride) {
509
+ const publisher = publisherOverride ?? publisherFromGithubUrl(clone.sourceUrl);
510
+ if (!publisher) {
511
+ throw new Error(
512
+ `${clone.sourceUrl}: publisher must be declared in skaile.yaml (non-GitHub source URL).`
513
+ );
514
+ }
515
+ const version = versionOverride ?? (clone.tag ? clone.tag.replace(/^v/, "") : void 0) ?? syntheticVersion(clone.commit);
516
+ for (const { dir, kind, mdName } of KIND_DIRS) {
517
+ const base = join(clone.localPath, dir);
518
+ if (!existsSync(base) || !statSync(base).isDirectory()) continue;
519
+ for (const entry of readdirSync(base, { withFileTypes: true })) {
520
+ if (!entry.isDirectory()) continue;
521
+ const assetDir = join(base, entry.name);
522
+ if (!existsSync(join(assetDir, mdName))) continue;
523
+ const files = [];
524
+ walkDirRecursive(assetDir, clone.localPath, files);
525
+ const metadata = readMetadata(clone.localPath, files, kind);
526
+ const candidate = {
527
+ publisher,
528
+ kind,
529
+ name: entry.name,
530
+ version,
531
+ sourceUrl: clone.sourceUrl,
532
+ commit: clone.commit,
533
+ files,
534
+ sha256: compositeSha256(files),
535
+ metadata
536
+ };
537
+ push(index, `${publisher}/${kind}:${entry.name}`, candidate);
538
+ }
539
+ }
540
+ }
541
+ function verifyNameMatch(repoRoot, files, expectedName, kind) {
542
+ const mdName = MD_FILE_BY_KIND[kind];
543
+ if (!mdName) return;
544
+ const md = files.find((f) => f.path.endsWith(mdName));
545
+ if (!md) return;
546
+ const { data } = parseFrontmatter(readFileSync(join(repoRoot, md.path), "utf8"));
547
+ const declared = typeof data.name === "string" ? data.name : void 0;
548
+ if (declared && declared !== expectedName) {
549
+ throw new Error(
550
+ `name mismatch: ${mdName} says "${declared}", skaile.yaml says "${expectedName}" \u2014 index-time hard error.`
551
+ );
552
+ }
553
+ }
554
+ function readMetadata(repoRoot, files, kind) {
555
+ if (kind !== "mcp-server") return void 0;
556
+ const mcpMd = files.find((f) => f.path.endsWith("MCP.md"));
557
+ if (!mcpMd) return void 0;
558
+ const { data } = parseFrontmatter(readFileSync(join(repoRoot, mcpMd.path), "utf8"));
559
+ return data;
560
+ }
561
+ function push(index, key, candidate) {
562
+ const arr = index.get(key) ?? [];
563
+ arr.push(candidate);
564
+ index.set(key, arr);
565
+ }
323
566
 
324
567
  // core/src/workspace-config.ts
568
+ var DEFAULT_RECIPE_ATTR = "default";
569
+ function validateAssetRecipeFlake(flake) {
570
+ if (typeof flake !== "string" || flake.length === 0) {
571
+ throw new Error("AssetRecipe.flake must be a non-empty string");
572
+ }
573
+ if (flake.length > 500) {
574
+ throw new Error(`AssetRecipe.flake too long (${flake.length} chars, max 500)`);
575
+ }
576
+ if (flake === ".") return;
577
+ const ALLOWED_SCHEME = /^(github:|git\+https:\/\/|git\+ssh:\/\/|path:\/)/;
578
+ if (!ALLOWED_SCHEME.test(flake)) {
579
+ throw new Error(
580
+ `AssetRecipe.flake "${flake}" has an unsupported source. Allowed: "." or a flake URL (github:, git+https://, git+ssh://, path:/).`
581
+ );
582
+ }
583
+ if (/[\s`$;|&<>(){}#\n\r]/.test(flake)) {
584
+ throw new Error(`AssetRecipe.flake "${flake}" contains forbidden characters.`);
585
+ }
586
+ }
325
587
  function validateAssetRecipeAttr(attr) {
326
588
  if (typeof attr !== "string" || attr.length === 0) {
327
589
  throw new Error("AssetRecipe.attr must be a non-empty string");
@@ -401,13 +663,20 @@ function listSkWorkspaceConfigs(dir) {
401
663
  function resolveSkWorkspaceConfig(projectDir, opts) {
402
664
  const name = opts?.name ?? SK_WORKSPACE_DEFAULT_NAME;
403
665
  const configs = [];
666
+ const assertNoLegacyKey = (file) => {
667
+ const d = file?.diagnostics?.find((x) => x.code === "legacy_key_rejected");
668
+ if (d) throw new Error(d.message);
669
+ };
404
670
  const userConfig = loadSkWorkspaceConfig(join(homedir(), ".skaile"), name);
671
+ assertNoLegacyKey(userConfig);
405
672
  if (userConfig) configs.push(userConfig.config);
406
673
  if (opts?.appDir) {
407
674
  const appConfig = loadSkWorkspaceConfig(opts.appDir, name);
675
+ assertNoLegacyKey(appConfig);
408
676
  if (appConfig) configs.push(appConfig.config);
409
677
  }
410
678
  const projectConfig = loadSkWorkspaceConfig(projectDir, name);
679
+ assertNoLegacyKey(projectConfig);
411
680
  if (projectConfig) configs.push(projectConfig.config);
412
681
  if (configs.length > 0) return configs.reduce(mergeSkWorkspaceConfigs);
413
682
  return {};
@@ -443,10 +712,12 @@ function mergeSkWorkspaceConfigs(base, overlay) {
443
712
  },
444
713
  // startup: overlay wins (project-level only)
445
714
  startup: overlay.startup ?? base.startup,
446
- // ai_resources: dedupe by source name overlay entry wins for same name
447
- ai_resources: dedupeByKey(
448
- [...base.ai_resources ?? [], ...overlay.ai_resources ?? []],
449
- "name"
715
+ // publication half scalars: overlay wins when defined; assets: by kind+name
716
+ publisher: overlay.publisher ?? base.publisher,
717
+ version: overlay.version ?? base.version,
718
+ assets: dedupeByKey(
719
+ [...base.assets ?? [], ...overlay.assets ?? []],
720
+ (a) => `${a.kind}:${a.name}`
450
721
  ),
451
722
  // mounts: by id — overlay wins
452
723
  mounts: mergeById(base.mounts ?? [], overlay.mounts ?? []),
@@ -511,14 +782,14 @@ function mergeSkWorkspaceConfigs(base, overlay) {
511
782
  secrets: overlay.secrets ?? base.secrets,
512
783
  // telemetry: overlay wins entirely (raw pass-through)
513
784
  telemetry: overlay.telemetry ?? base.telemetry,
514
- // repositories: always empty in merged output; the schema no longer
515
- // accepts this key. The internal install pipeline builds its own map
516
- // from `ai_resources[]` entries.
517
- repositories: {},
518
785
  // dependencies: concatenate and dedupe
519
786
  dependencies: dedupe([...base.dependencies ?? [], ...overlay.dependencies ?? []]),
520
- // sources: dedupe by name — overlay entry wins for the same name
521
- sources: dedupeByKey([...base.sources ?? [], ...overlay.sources ?? []], "name"),
787
+ // sources: dedupe by url — overlay entry wins for the same url
788
+ sources: dedupeByKey([...base.sources ?? [], ...overlay.sources ?? []], "url"),
789
+ // stores: dedupe by url — overlay wins
790
+ stores: dedupeByKey([...base.stores ?? [], ...overlay.stores ?? []], "url"),
791
+ // overrides: dedupe by ref — overlay wins
792
+ overrides: dedupeByKey([...base.overrides ?? [], ...overlay.overrides ?? []], "ref"),
522
793
  // plugins: concatenate + dedupe (like dependencies)
523
794
  plugins: dedupe([...base.plugins ?? [], ...overlay.plugins ?? []]),
524
795
  // deploy: overlay wins entirely (scalar-like selection)
@@ -600,46 +871,75 @@ function normalizeConfigInternal(raw) {
600
871
  if (Array.isArray(raw.startup)) {
601
872
  config.startup = raw.startup;
602
873
  }
603
- let aiResRaw;
604
- let aiResKey;
605
- if (raw.ai_resources !== void 0) {
606
- aiResRaw = raw.ai_resources;
607
- aiResKey = "ai_resources";
608
- } else if (raw.aiResources !== void 0) {
609
- aiResRaw = raw.aiResources;
610
- aiResKey = "aiResources";
611
- }
612
- if (aiResKey === "aiResources") {
613
- diagnostics.push({
614
- code: "legacy_key_camelcase",
615
- severity: "warning",
616
- message: 'Non-canonical key "aiResources" \u2014 use "ai_resources".',
617
- path: "aiResources"
618
- });
874
+ if (raw.repositories !== void 0) {
875
+ throw new Error(
876
+ "skaile.yaml: unknown top-level key `repositories:`. The schema changed in @skaile/workspaces 4.x \u2014 see docs/concepts/manifest-schema.md, or ask your AI assistant to apply the `migrate-skaile-manifest` skill."
877
+ );
619
878
  }
620
- if (Array.isArray(aiResRaw)) {
621
- config.ai_resources = aiResRaw;
622
- } else if (aiResRaw && typeof aiResRaw === "object") {
623
- diagnostics.push({
624
- code: "legacy_ai_resources_object",
625
- severity: "warning",
626
- message: "Legacy ai_resources object form ({sources, requires}) \u2014 use an array of {name, path, dependencies}.",
627
- path: aiResKey
628
- });
629
- const old = aiResRaw;
630
- const sources = Array.isArray(old.sources) ? old.sources : [];
631
- const requires = Array.isArray(old.requires) ? old.requires.filter(Boolean) : [];
632
- const autoDeploy = Boolean(old.auto_deploy ?? false);
633
- config.ai_resources = sources.map((s, i) => {
634
- const entry = {
635
- name: String(s.name ?? ""),
636
- path: String(s.path ?? s.url ?? "")
637
- };
638
- if (s.branch) entry.branch = String(s.branch);
639
- if (autoDeploy) entry.auto_deploy = true;
640
- if (i === 0 && requires.length > 0) entry.dependencies = requires;
641
- return entry;
642
- });
879
+ if (raw.ai_resources !== void 0 || raw.aiResources !== void 0) {
880
+ throw new Error(
881
+ "skaile.yaml: unknown top-level key `ai_resources:`. The schema changed in @skaile/workspaces 4.x \u2014 see docs/concepts/manifest-schema.md, or ask your AI assistant to apply the `migrate-skaile-manifest` skill."
882
+ );
883
+ }
884
+ if (typeof raw.publisher === "string" && raw.publisher.length > 0) {
885
+ config.publisher = raw.publisher;
886
+ }
887
+ if (typeof raw.version === "string" && raw.version.length > 0) {
888
+ config.version = raw.version;
889
+ }
890
+ if (Array.isArray(raw.assets)) {
891
+ const entries = [];
892
+ for (const item of raw.assets) {
893
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
894
+ const a = item;
895
+ const kind = typeof a.kind === "string" ? a.kind : "";
896
+ const name = typeof a.name === "string" ? a.name : "";
897
+ if (!kind || !name) continue;
898
+ const entry = { kind, name };
899
+ if (typeof a.root === "string") entry.root = a.root;
900
+ if (Array.isArray(a.files)) {
901
+ entry.files = a.files.filter(
902
+ (f) => typeof f === "string"
903
+ );
904
+ }
905
+ if (typeof a.version === "string") entry.version = a.version;
906
+ if (typeof a.publisher === "string") entry.publisher = a.publisher;
907
+ entries.push(entry);
908
+ }
909
+ if (entries.length > 0) config.assets = entries;
910
+ }
911
+ if (Array.isArray(raw.stores)) {
912
+ const entries = [];
913
+ for (const item of raw.stores) {
914
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
915
+ const s = item;
916
+ const url = typeof s.url === "string" ? s.url : "";
917
+ if (!url) continue;
918
+ entries.push({ url });
919
+ }
920
+ if (entries.length > 0) config.stores = entries;
921
+ }
922
+ if (Array.isArray(raw.overrides)) {
923
+ const entries = [];
924
+ for (const item of raw.overrides) {
925
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
926
+ const o = item;
927
+ const ref = typeof o.ref === "string" ? o.ref : "";
928
+ const source = typeof o.source === "string" ? o.source : "";
929
+ if (!ref || !source) continue;
930
+ if (typeof o.reason !== "string") {
931
+ throw new Error(
932
+ `skaile.yaml: overrides[] entry for ${ref}: reason is required and must be a non-empty string.`
933
+ );
934
+ }
935
+ if (o.reason.trim().length === 0) {
936
+ throw new Error(
937
+ `skaile.yaml: overrides[] entry for ${ref}: reason must not be empty.`
938
+ );
939
+ }
940
+ entries.push({ ref, source, reason: o.reason });
941
+ }
942
+ if (entries.length > 0) config.overrides = entries;
643
943
  }
644
944
  if (Array.isArray(raw.mounts)) {
645
945
  config.mounts = raw.mounts;
@@ -678,13 +978,12 @@ function normalizeConfigInternal(raw) {
678
978
  if (Array.isArray(raw.sources)) {
679
979
  const entries = [];
680
980
  for (const item of raw.sources) {
681
- if (!item || typeof item !== "object") continue;
981
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
682
982
  const s = item;
683
- const name = typeof s.name === "string" ? s.name : "";
684
983
  const url = typeof s.url === "string" ? s.url : "";
685
- if (!name || !url) continue;
686
- const entry = { name, url };
687
- if (typeof s.branch === "string" && s.branch.length > 0) entry.branch = s.branch;
984
+ if (!url) continue;
985
+ const entry = { url };
986
+ if (typeof s.pin === "string" && s.pin.length > 0) entry.pin = s.pin;
688
987
  entries.push(entry);
689
988
  }
690
989
  if (entries.length > 0) config.sources = entries;
@@ -722,7 +1021,21 @@ function decodeSkaileYaml(text) {
722
1021
  ]
723
1022
  };
724
1023
  }
725
- const result = normalizeConfigInternal(parsed);
1024
+ let result;
1025
+ try {
1026
+ result = normalizeConfigInternal(parsed);
1027
+ } catch (err) {
1028
+ return {
1029
+ config: {},
1030
+ diagnostics: [
1031
+ {
1032
+ code: "legacy_key_rejected",
1033
+ severity: "error",
1034
+ message: err instanceof Error ? err.message : String(err)
1035
+ }
1036
+ ]
1037
+ };
1038
+ }
726
1039
  validateConfigValues(result.config, result.diagnostics);
727
1040
  return result;
728
1041
  }
@@ -764,12 +1077,16 @@ function validateConfigValues(config, diagnostics) {
764
1077
  var CANONICAL_KEY_ORDER = [
765
1078
  "name",
766
1079
  "description",
1080
+ "publisher",
1081
+ "version",
1082
+ "assets",
767
1083
  "agent_config",
768
1084
  "dependencies",
769
1085
  "plugins",
770
1086
  "sources",
1087
+ "stores",
1088
+ "overrides",
771
1089
  "startup",
772
- "ai_resources",
773
1090
  "connectors",
774
1091
  "mcp_servers",
775
1092
  "deploy",
@@ -779,8 +1096,7 @@ var CANONICAL_KEY_ORDER = [
779
1096
  "secrets",
780
1097
  "telemetry",
781
1098
  "compaction",
782
- "patches",
783
- "repositories"
1099
+ "patches"
784
1100
  ];
785
1101
  var CONFIG_KEY_TO_YAML = {
786
1102
  agent_config: "agent-config"
@@ -814,10 +1130,11 @@ function dedupe(arr) {
814
1130
  return [...new Set(arr)];
815
1131
  }
816
1132
  function dedupeByKey(arr, key) {
1133
+ const keyOf = typeof key === "function" ? key : (item) => String(item[key] ?? "");
817
1134
  const seen = /* @__PURE__ */ new Set();
818
1135
  const result = [];
819
1136
  for (let i = arr.length - 1; i >= 0; i--) {
820
- const val = String(arr[i][key] ?? "");
1137
+ const val = keyOf(arr[i]);
821
1138
  if (!seen.has(val)) {
822
1139
  seen.add(val);
823
1140
  result.unshift(arr[i]);
@@ -854,13 +1171,22 @@ function mcpDeclFromCatalogEntry(entry) {
854
1171
  if (entry.description) decl.description = entry.description;
855
1172
  if (m.recipe && typeof m.recipe === "object" && !Array.isArray(m.recipe)) {
856
1173
  const r = m.recipe;
857
- if (typeof r.attr === "string") {
1174
+ const hasFlake = typeof r.flake === "string";
1175
+ const rawAttr = typeof r.attr === "string" ? r.attr : void 0;
1176
+ const effectiveAttr = rawAttr ?? (hasFlake ? DEFAULT_RECIPE_ATTR : void 0);
1177
+ if (effectiveAttr !== void 0) {
858
1178
  try {
859
- validateAssetRecipeAttr(r.attr);
860
- decl.recipe = { attr: r.attr };
1179
+ validateAssetRecipeAttr(effectiveAttr);
1180
+ if (hasFlake) {
1181
+ validateAssetRecipeFlake(r.flake);
1182
+ decl.recipe = { attr: effectiveAttr, flake: r.flake };
1183
+ if (typeof r.publisher === "string") decl.recipe.publisher = r.publisher;
1184
+ } else {
1185
+ decl.recipe = { attr: effectiveAttr };
1186
+ }
861
1187
  } catch (err) {
862
1188
  console.warn(
863
- `[workspace-config] mcpDeclFromCatalogEntry: invalid recipe.attr for entry "${entry.name}": ${err instanceof Error ? err.message : String(err)}`
1189
+ `[workspace-config] mcpDeclFromCatalogEntry: invalid recipe for entry "${entry.name}": ${err instanceof Error ? err.message : String(err)}`
864
1190
  );
865
1191
  }
866
1192
  }
@@ -932,33 +1258,49 @@ function loadMaterializedMcpDeclarations(projectDir) {
932
1258
  }
933
1259
  return result;
934
1260
  }
1261
+ function sourceSlug(url) {
1262
+ return url.replace(/\.git$/, "").split(/[/:]/).pop() ?? "source";
1263
+ }
1264
+ function buildSourceClones(sources) {
1265
+ const home = process.env.SKAILE_HOME ?? join(homedir(), ".skaile");
1266
+ const sourcesDir = join(home, "sources");
1267
+ const clones = [];
1268
+ for (const s of sources) {
1269
+ const localPath = join(sourcesDir, sourceSlug(s.url));
1270
+ if (!existsSync(localPath)) continue;
1271
+ const commit = getRepoCommit(localPath) ?? "0".repeat(40);
1272
+ clones.push({ localPath, sourceUrl: s.url, commit, tag: s.pin });
1273
+ }
1274
+ return clones;
1275
+ }
1276
+ function catalogEntryFromCandidate(c) {
1277
+ const md = c.metadata ?? {};
1278
+ return {
1279
+ name: c.name,
1280
+ kind: c.kind,
1281
+ description: typeof md.description === "string" ? md.description : "",
1282
+ source: c.files[0]?.path ?? "",
1283
+ publisher: c.publisher,
1284
+ version: c.version,
1285
+ requires: [],
1286
+ dependencies: [],
1287
+ metadata: c.metadata
1288
+ };
1289
+ }
935
1290
  function loadMcpServerDeclarations(projectDir) {
936
1291
  const config = resolveSkWorkspaceConfig(projectDir);
937
1292
  const explicit = config.mcp_servers ?? [];
938
1293
  const materialized = loadMaterializedMcpDeclarations(projectDir);
939
1294
  const mcpRefStrings = [];
940
1295
  for (const dep of config.dependencies ?? []) {
941
- const ref = parseAssetRef(dep);
942
- if (ref.kind === "mcp-server") mcpRefStrings.push(dep);
943
- }
944
- for (const res of config.ai_resources ?? []) {
945
- for (const dep of res.dependencies ?? []) {
1296
+ try {
946
1297
  const ref = parseAssetRef(dep);
947
1298
  if (ref.kind === "mcp-server") mcpRefStrings.push(dep);
1299
+ } catch {
948
1300
  }
949
1301
  }
950
- const repositories = config.repositories ?? {};
951
- const allRepos = { ...repositories };
952
- for (const res of config.ai_resources ?? []) {
953
- if (res.name && res.path && !(res.name in allRepos)) {
954
- allRepos[res.name] = {
955
- url: res.path.startsWith("/") ? void 0 : res.path,
956
- path: res.path.startsWith("/") ? res.path : void 0,
957
- branch: res.branch
958
- };
959
- }
960
- }
961
- const reposDir = getGlobalCacheDir();
1302
+ const sources = config.sources ?? [];
1303
+ const provenanceIndex = sources.length > 0 ? buildProvenanceIndex(buildSourceClones(sources)) : /* @__PURE__ */ new Map();
962
1304
  const byId = /* @__PURE__ */ new Map();
963
1305
  const order = [];
964
1306
  const upsert = (decl) => {
@@ -972,12 +1314,29 @@ function loadMcpServerDeclarations(projectDir) {
972
1314
  };
973
1315
  const seen = /* @__PURE__ */ new Set();
974
1316
  for (const refStr of mcpRefStrings) {
975
- const ref = parseAssetRef(refStr);
1317
+ let ref;
1318
+ try {
1319
+ ref = parseAssetRef(refStr);
1320
+ } catch {
1321
+ continue;
1322
+ }
976
1323
  if (seen.has(ref.name)) continue;
977
1324
  seen.add(ref.name);
978
- const entry = resolveAsset(ref, allRepos, reposDir, { projectDir });
979
- if (!entry) continue;
980
- const catalogDecl = mcpDeclFromCatalogEntry(entry);
1325
+ let candidates;
1326
+ if (ref.publisher) {
1327
+ candidates = provenanceIndex.get(`${ref.publisher}/${ref.kind}:${ref.name}`);
1328
+ }
1329
+ if (!candidates) {
1330
+ for (const [key, arr] of provenanceIndex) {
1331
+ if (key.endsWith(`/${ref.kind}:${ref.name}`)) {
1332
+ candidates = arr;
1333
+ break;
1334
+ }
1335
+ }
1336
+ }
1337
+ const candidate = candidates?.[0];
1338
+ if (!candidate) continue;
1339
+ const catalogDecl = mcpDeclFromCatalogEntry(catalogEntryFromCandidate(candidate));
981
1340
  if (!catalogDecl) continue;
982
1341
  upsert(catalogDecl);
983
1342
  }
@@ -1011,16 +1370,11 @@ function resolveAgentDir(projectDir) {
1011
1370
  return join(aiAssetsDir, relPath);
1012
1371
  }
1013
1372
  if (definition.startsWith("agent:")) {
1014
- const ref = parseAssetRef(definition);
1015
- const repositories = config.repositories ?? {};
1016
- const reposDir = getGlobalCacheDir();
1017
- const entry = resolveAsset(ref, repositories, reposDir, { projectDir });
1018
- if (!entry) return void 0;
1019
- return dirname(entry.source);
1373
+ return void 0;
1020
1374
  }
1021
1375
  return resolve(projectDir, definition);
1022
1376
  }
1023
1377
 
1024
- export { COMPACTION_DEFAULTS, SKAILE_YAML_DEFAULT, SKAILE_YAML_SUFFIX, SK_WORKSPACE_DEFAULT_NAME, SK_WORKSPACE_SUFFIX, checkRepoStatus, checkoutPin, cloneRepo, decodeSkaileYaml, encodeSkaileYaml, ensureRepo, findWorkspaceRoot, getGlobalCacheDir, getRepoCommit, isWorkspaceConfigFilename, linkRepo, listSkWorkspaceConfigs, loadMcpServerDeclarations, loadSkWorkspaceConfig, mergeSkWorkspaceConfigs, normalizeConfig, pullRepo, readLinks, resolveAgentDir, resolveAll, resolveAsset, resolveSkWorkspaceConfig, saveSkWorkspaceConfig, scanRepo, unlinkRepo, validateAssetRecipeAttr, workspaceConfigFilename, workspaceNameFromFilename, writeLinks };
1025
- //# sourceMappingURL=chunk-TKOLD2O7.js.map
1026
- //# sourceMappingURL=chunk-TKOLD2O7.js.map
1378
+ export { COMPACTION_DEFAULTS, CanonicalRefConflictError, DEFAULT_RECIPE_ATTR, SKAILE_YAML_DEFAULT, SKAILE_YAML_SUFFIX, SK_WORKSPACE_DEFAULT_NAME, SK_WORKSPACE_SUFFIX, buildProvenanceIndex, checkRepoStatus, checkoutPin, cloneRepo, decodeSkaileYaml, encodeSkaileYaml, ensureRepo, findWorkspaceRoot, getGlobalCacheDir, getRepoCommit, isWorkspaceConfigFilename, linkRepo, listSkWorkspaceConfigs, loadMcpServerDeclarations, loadSkWorkspaceConfig, mergeSkWorkspaceConfigs, normalizeConfig, pullRepo, readLinks, resolveAgentDir, resolveAll, resolveAsset, resolveSkWorkspaceConfig, saveSkWorkspaceConfig, scanRepo, unlinkRepo, validateAssetRecipeAttr, validateAssetRecipeFlake, workspaceConfigFilename, workspaceNameFromFilename, writeLinks };
1379
+ //# sourceMappingURL=chunk-J3VKAEQP.js.map
1380
+ //# sourceMappingURL=chunk-J3VKAEQP.js.map