@treeseed/sdk 0.10.28 → 0.12.4

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 (509) hide show
  1. package/README.md +213 -7
  2. package/dist/agent-capacity.d.ts +916 -0
  3. package/dist/agent-capacity.js +1600 -0
  4. package/dist/agent-tools.d.ts +37 -0
  5. package/dist/agent-tools.js +301 -0
  6. package/dist/api/app.d.ts +1 -1
  7. package/dist/api/auth/d1-database.d.ts +2 -2
  8. package/dist/api/auth/d1-provider.d.ts +4 -4
  9. package/dist/api/auth/d1-store.d.ts +2 -2
  10. package/dist/api/auth/memory-provider.d.ts +1 -1
  11. package/dist/api/auth/tokens.d.ts +1 -1
  12. package/dist/api/config.d.ts +3 -1
  13. package/dist/api/config.js +34 -10
  14. package/dist/api/http.d.ts +2 -2
  15. package/dist/api/index.d.ts +10 -10
  16. package/dist/api/index.js +4 -2
  17. package/dist/api/operations-routes.d.ts +3 -3
  18. package/dist/api/operations.d.ts +1 -1
  19. package/dist/api/providers.d.ts +1 -1
  20. package/dist/api/providers.js +21 -4
  21. package/dist/api/railway.d.ts +3 -2
  22. package/dist/api/sdk-dispatch.d.ts +4 -4
  23. package/dist/api/sdk-routes.d.ts +2 -2
  24. package/dist/api/templates.d.ts +2 -2
  25. package/dist/api/types.d.ts +3 -2
  26. package/dist/capacity-provider.d.ts +118 -59
  27. package/dist/capacity-provider.js +235 -43
  28. package/dist/capacity.d.ts +2 -2
  29. package/dist/cli-tools.d.ts +1 -1
  30. package/dist/content-operations.d.ts +97 -0
  31. package/dist/content-operations.js +250 -0
  32. package/dist/content-store.d.ts +4 -4
  33. package/dist/control-plane-client.d.ts +13 -18
  34. package/dist/control-plane-client.js +8 -86
  35. package/dist/control-plane.d.ts +3 -2
  36. package/dist/control-plane.js +38 -13
  37. package/dist/copilot.d.ts +3 -0
  38. package/dist/copilot.js +16 -1
  39. package/dist/d1-http.d.ts +1 -1
  40. package/dist/d1-store.d.ts +32 -57
  41. package/dist/d1-store.js +115 -318
  42. package/dist/db/d1.d.ts +1 -232
  43. package/dist/db/index.d.ts +3 -3
  44. package/dist/db/market-schema.d.ts +50900 -11018
  45. package/dist/db/market-schema.js +2313 -467
  46. package/dist/db/node-sqlite.d.ts +1 -232
  47. package/dist/db/node-sqlite.js +7 -2
  48. package/dist/db/schema.d.ts +0 -462
  49. package/dist/db/schema.js +2 -22
  50. package/dist/dispatch.d.ts +1 -1
  51. package/dist/dispatch.js +0 -11
  52. package/dist/fixture-support.js +52 -4
  53. package/dist/governance.d.ts +133 -0
  54. package/dist/governance.js +285 -0
  55. package/dist/graph/build.d.ts +2 -2
  56. package/dist/graph/context-query-contracts.d.ts +1 -1
  57. package/dist/graph/dsl.d.ts +1 -1
  58. package/dist/graph/query.d.ts +4 -4
  59. package/dist/graph/ranking.d.ts +1 -1
  60. package/dist/graph/schema.d.ts +1 -1
  61. package/dist/graph.d.ts +11 -11
  62. package/dist/guarantees/index.d.ts +425 -0
  63. package/dist/guarantees/index.js +1146 -0
  64. package/dist/hosting/apps.d.ts +12 -0
  65. package/dist/hosting/apps.js +107 -0
  66. package/dist/hosting/builtins.d.ts +25 -0
  67. package/dist/hosting/builtins.js +602 -0
  68. package/dist/hosting/contracts.d.ts +208 -0
  69. package/dist/hosting/contracts.js +0 -0
  70. package/dist/hosting/graph.d.ts +195 -0
  71. package/dist/hosting/graph.js +1092 -0
  72. package/dist/hosting/index.d.ts +4 -0
  73. package/dist/hosting/index.js +4 -0
  74. package/dist/index.d.ts +88 -67
  75. package/dist/index.js +142 -14
  76. package/dist/local-dev/managed-dev.d.ts +85 -0
  77. package/dist/local-dev/managed-dev.js +368 -0
  78. package/dist/managed-dependencies.d.ts +9 -0
  79. package/dist/managed-dependencies.js +10 -9
  80. package/dist/market-client.d.ts +327 -9
  81. package/dist/market-client.js +464 -20
  82. package/dist/model-registry.d.ts +1 -1
  83. package/dist/model-registry.js +19 -19
  84. package/dist/operations/agent-tools.d.ts +2 -11
  85. package/dist/operations/agent-tools.js +2 -14
  86. package/dist/operations/providers/default.d.ts +1 -1
  87. package/dist/operations/providers/default.js +20 -37
  88. package/dist/operations/repository-operations.d.ts +26 -2
  89. package/dist/operations/repository-operations.js +159 -1
  90. package/dist/operations/runtime.d.ts +3 -3
  91. package/dist/operations/services/bootstrap-runner.d.ts +4 -2
  92. package/dist/operations/services/bootstrap-runner.js +22 -2
  93. package/dist/operations/services/build-warning-policy.d.ts +34 -0
  94. package/dist/operations/services/build-warning-policy.js +81 -82
  95. package/dist/operations/services/commit-message-provider.js +4 -4
  96. package/dist/operations/services/config-runtime.d.ts +83 -24
  97. package/dist/operations/services/config-runtime.js +449 -130
  98. package/dist/operations/services/deploy.d.ts +106 -624
  99. package/dist/operations/services/deploy.js +746 -263
  100. package/dist/operations/services/deployment-readiness.d.ts +30 -0
  101. package/dist/operations/services/deployment-readiness.js +206 -0
  102. package/dist/operations/services/git-remote-policy.js +12 -4
  103. package/dist/operations/services/git-runner.d.ts +66 -0
  104. package/dist/operations/services/git-runner.js +479 -0
  105. package/dist/operations/services/git-workflow.d.ts +45 -6
  106. package/dist/operations/services/git-workflow.js +251 -23
  107. package/dist/operations/services/github-actions-verification.d.ts +4 -1
  108. package/dist/operations/services/github-actions-verification.js +12 -3
  109. package/dist/operations/services/github-api.js +22 -6
  110. package/dist/operations/services/github-automation.d.ts +4 -4
  111. package/dist/operations/services/github-automation.js +19 -21
  112. package/dist/operations/services/github-credentials.d.ts +13 -0
  113. package/dist/operations/services/github-credentials.js +59 -0
  114. package/dist/operations/services/hosted-service-checks.d.ts +81 -0
  115. package/dist/operations/services/hosted-service-checks.js +478 -0
  116. package/dist/operations/services/hosting-audit.d.ts +2 -2
  117. package/dist/operations/services/hosting-audit.js +7 -7
  118. package/dist/operations/services/hub-launch.d.ts +1 -1
  119. package/dist/operations/services/hub-provider-launch.d.ts +5 -5
  120. package/dist/operations/services/hub-provider-launch.js +22 -17
  121. package/dist/operations/services/live-hosted-service-checks.d.ts +26 -0
  122. package/dist/operations/services/live-hosted-service-checks.js +463 -0
  123. package/dist/operations/services/local-dev.js +0 -1
  124. package/dist/operations/services/managed-host-security.d.ts +1 -1
  125. package/dist/operations/services/managed-host-security.js +1 -1
  126. package/dist/operations/services/managed-repositories.d.ts +37 -0
  127. package/dist/operations/services/managed-repositories.js +177 -0
  128. package/dist/operations/services/operations-runner-smoke.d.ts +31 -0
  129. package/dist/operations/services/operations-runner-smoke.js +198 -0
  130. package/dist/operations/services/package-adapters.d.ts +170 -0
  131. package/dist/operations/services/package-adapters.js +859 -0
  132. package/dist/operations/services/package-reference-policy.d.ts +13 -88
  133. package/dist/operations/services/package-reference-policy.js +96 -215
  134. package/dist/operations/services/project-host-operations.d.ts +4 -4
  135. package/dist/operations/services/project-platform.d.ts +57 -173
  136. package/dist/operations/services/project-platform.js +80 -111
  137. package/dist/operations/services/project-web-monitor.d.ts +2 -2
  138. package/dist/operations/services/project-web-monitor.js +122 -5
  139. package/dist/operations/services/railway-api.d.ts +117 -5
  140. package/dist/operations/services/railway-api.js +915 -93
  141. package/dist/operations/services/railway-deploy.d.ts +30 -112
  142. package/dist/operations/services/railway-deploy.js +521 -961
  143. package/dist/operations/services/release-history.js +3 -4
  144. package/dist/operations/services/release-proof-ledger.d.ts +13 -0
  145. package/dist/operations/services/release-proof-ledger.js +81 -0
  146. package/dist/operations/services/release-proof-planner.d.ts +48 -0
  147. package/dist/operations/services/release-proof-planner.js +195 -0
  148. package/dist/operations/services/release-proof-runner.d.ts +16 -0
  149. package/dist/operations/services/release-proof-runner.js +169 -0
  150. package/dist/operations/services/release-proof.d.ts +81 -0
  151. package/dist/operations/services/release-proof.js +79 -0
  152. package/dist/operations/services/repository-save-orchestrator.d.ts +8 -7
  153. package/dist/operations/services/repository-save-orchestrator.js +604 -212
  154. package/dist/operations/services/runtime-paths.d.ts +1 -3
  155. package/dist/operations/services/runtime-paths.js +0 -4
  156. package/dist/operations/services/runtime-tools.d.ts +1 -191
  157. package/dist/operations/services/runtime-tools.js +4 -443
  158. package/dist/operations/services/save-deploy-preflight.js +9 -1
  159. package/dist/operations/services/template-host-bindings.d.ts +2 -2
  160. package/dist/operations/services/template-host-bindings.js +3 -2
  161. package/dist/operations/services/template-registry.d.ts +6 -6
  162. package/dist/operations/services/template-registry.js +33 -14
  163. package/dist/operations/services/template-secret-sync.d.ts +3 -3
  164. package/dist/operations/services/verification-cache.d.ts +25 -0
  165. package/dist/operations/services/verification-cache.js +71 -0
  166. package/dist/operations/services/workflow-timing.d.ts +25 -0
  167. package/dist/operations/services/workflow-timing.js +79 -0
  168. package/dist/operations/services/workspace-dependency-mode.js +50 -5
  169. package/dist/operations/services/workspace-preflight.js +8 -8
  170. package/dist/operations/services/workspace-save-script.d.ts +4 -4
  171. package/dist/operations/services/workspace-save.d.ts +1 -0
  172. package/dist/operations/services/workspace-save.js +42 -19
  173. package/dist/operations/services/workspace-tools.js +42 -7
  174. package/dist/operations-registry.d.ts +1 -1
  175. package/dist/operations-registry.js +5 -6
  176. package/dist/operations-types.d.ts +1 -1
  177. package/dist/operations.d.ts +17 -11
  178. package/dist/operations.js +57 -3
  179. package/dist/platform/book-export.js +2 -0
  180. package/dist/platform/books-data.d.ts +1 -1
  181. package/dist/platform/config.d.ts +27 -0
  182. package/dist/platform/config.js +100 -0
  183. package/dist/platform/content-runtime-source.d.ts +54 -0
  184. package/dist/platform/content-runtime-source.js +169 -0
  185. package/dist/platform/contracts.d.ts +38 -2
  186. package/dist/platform/deploy-config.d.ts +1 -1
  187. package/dist/platform/deploy-config.js +80 -9
  188. package/dist/platform/deploy-runtime.d.ts +1 -1
  189. package/dist/platform/desired-state.d.ts +63 -0
  190. package/dist/platform/desired-state.js +1021 -0
  191. package/dist/platform/env.yaml +568 -45
  192. package/dist/platform/environment.d.ts +3 -2
  193. package/dist/platform/environment.js +122 -21
  194. package/dist/platform/local-content-materialization.d.ts +9 -0
  195. package/dist/platform/local-content-materialization.js +212 -0
  196. package/dist/platform/plugin.d.ts +13 -4
  197. package/dist/platform/plugins/runtime.d.ts +3 -3
  198. package/dist/platform/plugins/runtime.js +62 -1
  199. package/dist/platform/plugins.d.ts +6 -6
  200. package/dist/platform/published-content-pipeline.d.ts +3 -3
  201. package/dist/platform/published-content-pipeline.js +5 -1
  202. package/dist/platform/published-content.d.ts +4 -3
  203. package/dist/platform/site-config-schema.d.ts +1 -0
  204. package/dist/platform/site-config-schema.js +4 -1
  205. package/dist/platform/tenant/runtime-config.d.ts +152 -2
  206. package/dist/platform/tenant-config.d.ts +1 -1
  207. package/dist/platform/utils/site-config-schema.d.ts +154 -0
  208. package/dist/platform/utils/site-config-schema.js +400 -461
  209. package/dist/platform-operation-store.d.ts +1 -1
  210. package/dist/platform-operation-store.js +2 -2
  211. package/dist/platform-operations.js +1 -1
  212. package/dist/plugin-default.d.ts +1 -1
  213. package/dist/plugin-default.js +27 -12
  214. package/dist/project-import.d.ts +91 -0
  215. package/dist/project-import.js +257 -0
  216. package/dist/project-workflow.d.ts +1 -1
  217. package/dist/reconcile/bootstrap-systems.d.ts +2 -2
  218. package/dist/reconcile/bootstrap-systems.js +5 -5
  219. package/dist/reconcile/builtin-adapters.d.ts +7 -1
  220. package/dist/reconcile/builtin-adapters.js +3190 -429
  221. package/dist/reconcile/contracts.d.ts +23 -6
  222. package/dist/reconcile/desired-state.d.ts +2 -192
  223. package/dist/reconcile/desired-state.js +29 -38
  224. package/dist/reconcile/engine.d.ts +61 -395
  225. package/dist/reconcile/engine.js +229 -44
  226. package/dist/reconcile/index.d.ts +10 -8
  227. package/dist/reconcile/index.js +2 -0
  228. package/dist/reconcile/live-acceptance.d.ts +100 -0
  229. package/dist/reconcile/live-acceptance.js +2027 -0
  230. package/dist/reconcile/platform.d.ts +104 -0
  231. package/dist/reconcile/platform.js +100 -0
  232. package/dist/reconcile/providers/docker-private.d.ts +54 -0
  233. package/dist/reconcile/providers/docker-private.js +101 -0
  234. package/dist/reconcile/providers/github-private.d.ts +65 -0
  235. package/dist/reconcile/providers/github-private.js +119 -0
  236. package/dist/reconcile/providers/local-private.d.ts +28 -0
  237. package/dist/reconcile/providers/local-private.js +75 -0
  238. package/dist/reconcile/providers/railway-iac.d.ts +66 -0
  239. package/dist/reconcile/providers/railway-iac.js +275 -0
  240. package/dist/reconcile/providers/release-private.d.ts +92 -0
  241. package/dist/reconcile/providers/release-private.js +152 -0
  242. package/dist/reconcile/registry.d.ts +2 -2
  243. package/dist/reconcile/registry.js +16 -1
  244. package/dist/reconcile/state.d.ts +1 -1
  245. package/dist/reconcile/state.js +7 -44
  246. package/dist/reconcile/units.d.ts +1 -1
  247. package/dist/reconcile/units.js +9 -3
  248. package/dist/remote.d.ts +6 -6
  249. package/dist/scenes/artifacts.d.ts +16 -0
  250. package/dist/scenes/artifacts.js +61 -0
  251. package/dist/scenes/auth.d.ts +2 -0
  252. package/dist/scenes/auth.js +28 -0
  253. package/dist/scenes/base-url.d.ts +12 -0
  254. package/dist/scenes/base-url.js +123 -0
  255. package/dist/scenes/builtin-plugins.d.ts +2 -0
  256. package/dist/scenes/builtin-plugins.js +447 -0
  257. package/dist/scenes/checkpoints.d.ts +14 -0
  258. package/dist/scenes/checkpoints.js +33 -0
  259. package/dist/scenes/device-matrix.d.ts +2 -0
  260. package/dist/scenes/device-matrix.js +97 -0
  261. package/dist/scenes/devices.d.ts +11 -0
  262. package/dist/scenes/devices.js +22 -0
  263. package/dist/scenes/diagnostics.d.ts +5 -0
  264. package/dist/scenes/diagnostics.js +22 -0
  265. package/dist/scenes/diagram-providers.d.ts +2 -0
  266. package/dist/scenes/diagram-providers.js +181 -0
  267. package/dist/scenes/diagram-validation.d.ts +22 -0
  268. package/dist/scenes/diagram-validation.js +74 -0
  269. package/dist/scenes/environment.d.ts +2 -0
  270. package/dist/scenes/environment.js +65 -0
  271. package/dist/scenes/evidence-bundle.d.ts +5 -0
  272. package/dist/scenes/evidence-bundle.js +42 -0
  273. package/dist/scenes/evidence-report.d.ts +10 -0
  274. package/dist/scenes/evidence-report.js +73 -0
  275. package/dist/scenes/evidence.d.ts +16 -0
  276. package/dist/scenes/evidence.js +277 -0
  277. package/dist/scenes/index.d.ts +48 -0
  278. package/dist/scenes/index.js +312 -0
  279. package/dist/scenes/inspect.d.ts +9 -0
  280. package/dist/scenes/inspect.js +64 -0
  281. package/dist/scenes/loader.d.ts +3 -0
  282. package/dist/scenes/loader.js +38 -0
  283. package/dist/scenes/logs.d.ts +2 -0
  284. package/dist/scenes/logs.js +47 -0
  285. package/dist/scenes/operations.d.ts +3 -0
  286. package/dist/scenes/operations.js +128 -0
  287. package/dist/scenes/phase0.d.ts +9 -0
  288. package/dist/scenes/phase0.js +637 -0
  289. package/dist/scenes/planner.d.ts +12 -0
  290. package/dist/scenes/planner.js +166 -0
  291. package/dist/scenes/playwright-adapter.d.ts +2 -0
  292. package/dist/scenes/playwright-adapter.js +63 -0
  293. package/dist/scenes/plugins.d.ts +5 -0
  294. package/dist/scenes/plugins.js +115 -0
  295. package/dist/scenes/progress.d.ts +17 -0
  296. package/dist/scenes/progress.js +24 -0
  297. package/dist/scenes/publish-destinations.d.ts +12 -0
  298. package/dist/scenes/publish-destinations.js +99 -0
  299. package/dist/scenes/publish-plan-report.d.ts +10 -0
  300. package/dist/scenes/publish-plan-report.js +70 -0
  301. package/dist/scenes/publish-plan.d.ts +15 -0
  302. package/dist/scenes/publish-plan.js +248 -0
  303. package/dist/scenes/publish-redaction.d.ts +16 -0
  304. package/dist/scenes/publish-redaction.js +148 -0
  305. package/dist/scenes/publish-release-record.d.ts +13 -0
  306. package/dist/scenes/publish-release-record.js +34 -0
  307. package/dist/scenes/publish-report.d.ts +10 -0
  308. package/dist/scenes/publish-report.js +67 -0
  309. package/dist/scenes/publish.d.ts +16 -0
  310. package/dist/scenes/publish.js +298 -0
  311. package/dist/scenes/registry.d.ts +12 -0
  312. package/dist/scenes/registry.js +65 -0
  313. package/dist/scenes/remotion-adapter.d.ts +3 -0
  314. package/dist/scenes/remotion-adapter.js +94 -0
  315. package/dist/scenes/remotion-composition-registry.d.ts +2 -0
  316. package/dist/scenes/remotion-composition-registry.js +12 -0
  317. package/dist/scenes/remotion-compositions.d.ts +15 -0
  318. package/dist/scenes/remotion-compositions.js +464 -0
  319. package/dist/scenes/remotion-diagrams.d.ts +9 -0
  320. package/dist/scenes/remotion-diagrams.js +188 -0
  321. package/dist/scenes/remotion-input.d.ts +13 -0
  322. package/dist/scenes/remotion-input.js +291 -0
  323. package/dist/scenes/remotion-visuals.d.ts +29 -0
  324. package/dist/scenes/remotion-visuals.js +198 -0
  325. package/dist/scenes/render-media-assets.d.ts +9 -0
  326. package/dist/scenes/render-media-assets.js +117 -0
  327. package/dist/scenes/render-report.d.ts +10 -0
  328. package/dist/scenes/render-report.js +27 -0
  329. package/dist/scenes/render.d.ts +2 -0
  330. package/dist/scenes/render.js +218 -0
  331. package/dist/scenes/reporter.d.ts +3 -0
  332. package/dist/scenes/reporter.js +101 -0
  333. package/dist/scenes/resume.d.ts +2 -0
  334. package/dist/scenes/resume.js +79 -0
  335. package/dist/scenes/runner.d.ts +2 -0
  336. package/dist/scenes/runner.js +674 -0
  337. package/dist/scenes/schema.d.ts +6 -0
  338. package/dist/scenes/schema.js +994 -0
  339. package/dist/scenes/seed.d.ts +2 -0
  340. package/dist/scenes/seed.js +83 -0
  341. package/dist/scenes/segments.d.ts +15 -0
  342. package/dist/scenes/segments.js +79 -0
  343. package/dist/scenes/selectors.d.ts +3 -0
  344. package/dist/scenes/selectors.js +21 -0
  345. package/dist/scenes/timeline.d.ts +11 -0
  346. package/dist/scenes/timeline.js +26 -0
  347. package/dist/scenes/timeouts.d.ts +6 -0
  348. package/dist/scenes/timeouts.js +17 -0
  349. package/dist/scenes/training-captions.d.ts +14 -0
  350. package/dist/scenes/training-captions.js +98 -0
  351. package/dist/scenes/training-report.d.ts +10 -0
  352. package/dist/scenes/training-report.js +78 -0
  353. package/dist/scenes/training-transcript.d.ts +24 -0
  354. package/dist/scenes/training-transcript.js +204 -0
  355. package/dist/scenes/training.d.ts +10 -0
  356. package/dist/scenes/training.js +134 -0
  357. package/dist/scenes/types.d.ts +1872 -0
  358. package/dist/scenes/types.js +8 -0
  359. package/dist/scenes/visual-audit-contact-sheets.d.ts +6 -0
  360. package/dist/scenes/visual-audit-contact-sheets.js +119 -0
  361. package/dist/scenes/visual-audit-fixtures.d.ts +25 -0
  362. package/dist/scenes/visual-audit-fixtures.js +210 -0
  363. package/dist/scenes/visual-audit-report.d.ts +11 -0
  364. package/dist/scenes/visual-audit-report.js +85 -0
  365. package/dist/scenes/visual-audit-review.d.ts +23 -0
  366. package/dist/scenes/visual-audit-review.js +829 -0
  367. package/dist/scenes/visual-audit-routes.d.ts +13 -0
  368. package/dist/scenes/visual-audit-routes.js +308 -0
  369. package/dist/scenes/visual-audit.d.ts +3 -0
  370. package/dist/scenes/visual-audit.js +596 -0
  371. package/dist/scripts/build-dist.js +154 -40
  372. package/dist/scripts/check-build-warnings.js +42 -53
  373. package/dist/scripts/cleanup-markdown.js +1 -1
  374. package/dist/scripts/cli-test-fixtures.js +24 -28
  375. package/dist/scripts/deployment-readiness.js +20 -0
  376. package/dist/scripts/generate-treedx-openapi-types.js +186 -0
  377. package/dist/scripts/migrate-journey-csv-to-guarantees.js +140 -0
  378. package/dist/scripts/operations-runner-smoke.js +16 -0
  379. package/dist/scripts/package-tools.js +12 -1
  380. package/dist/scripts/prepare.js +3 -7
  381. package/dist/scripts/release-verify.js +6 -6
  382. package/dist/scripts/run-fixture-astro-command.js +1 -1
  383. package/dist/scripts/template-catalog.test.js +68 -82
  384. package/dist/scripts/tenant-dev.js +0 -10
  385. package/dist/scripts/tenant-workflow-action.js +10 -1
  386. package/dist/scripts/test-cloudflare-local.js +1 -1
  387. package/dist/scripts/test-scaffold.js +0 -1
  388. package/dist/scripts/test-smoke.js +3 -3
  389. package/dist/scripts/treeseed-build-dist.js +4 -7
  390. package/dist/scripts/treeseed-release-verify.js +1 -1
  391. package/dist/scripts/verify-driver.js +30 -21
  392. package/dist/scripts/workflow-commands.test.js +18 -27
  393. package/dist/scripts/workspace-command-e2e.js +1 -1
  394. package/dist/scripts/workspace-lint.js +6 -0
  395. package/dist/scripts/workspace-release-test.js +90 -1
  396. package/dist/scripts/workspace-test.js +6 -0
  397. package/dist/sdk-dispatch.d.ts +1 -1
  398. package/dist/sdk-dispatch.js +0 -16
  399. package/dist/sdk-fields.d.ts +1 -1
  400. package/dist/sdk-filters.d.ts +1 -1
  401. package/dist/sdk-types.d.ts +1352 -116
  402. package/dist/sdk-types.js +413 -2
  403. package/dist/sdk.d.ts +106 -100
  404. package/dist/sdk.js +211 -51
  405. package/dist/secrets-capability.d.ts +421 -0
  406. package/dist/secrets-capability.js +530 -0
  407. package/dist/seeds/normalize.js +1 -0
  408. package/dist/seeds/planner.js +38 -0
  409. package/dist/seeds/schema.js +293 -4
  410. package/dist/seeds/types.d.ts +60 -0
  411. package/dist/seeds/types.js +9 -1
  412. package/dist/service-credentials.d.ts +54 -0
  413. package/dist/service-credentials.js +112 -0
  414. package/dist/stores/cursor-store.d.ts +2 -2
  415. package/dist/stores/envelopes.d.ts +1 -1
  416. package/dist/stores/helpers.d.ts +1 -1
  417. package/dist/stores/lease-store.d.ts +2 -2
  418. package/dist/stores/message-store.d.ts +2 -2
  419. package/dist/stores/operational-store.d.ts +3 -26
  420. package/dist/stores/operational-store.js +0 -225
  421. package/dist/stores/project-workflow-store.d.ts +2 -2
  422. package/dist/stores/run-store.d.ts +2 -3
  423. package/dist/stores/run-store.js +0 -98
  424. package/dist/stores/subscription-store.d.ts +2 -3
  425. package/dist/stores/subscription-store.js +0 -100
  426. package/dist/template-catalog.d.ts +1 -1
  427. package/dist/template-launch-requirements.d.ts +2 -2
  428. package/dist/template-launch-requirements.js +9 -0
  429. package/dist/template-launch-ui.d.ts +2 -2
  430. package/dist/treedx/adapters.d.ts +6 -0
  431. package/dist/treedx/adapters.js +36 -0
  432. package/dist/treedx/client.d.ts +222 -0
  433. package/dist/treedx/client.js +871 -0
  434. package/dist/treedx/errors.d.ts +13 -0
  435. package/dist/treedx/errors.js +17 -0
  436. package/dist/treedx/federated-client.d.ts +27 -0
  437. package/dist/treedx/federated-client.js +158 -0
  438. package/dist/treedx/generated/openapi-types.d.ts +3558 -0
  439. package/dist/treedx/generated/openapi-types.js +0 -0
  440. package/dist/treedx/graph-adapter.d.ts +33 -0
  441. package/dist/treedx/graph-adapter.js +156 -0
  442. package/dist/treedx/index.d.ts +14 -0
  443. package/dist/treedx/index.js +48 -0
  444. package/dist/treedx/market-integration.d.ts +27 -0
  445. package/dist/treedx/market-integration.js +131 -0
  446. package/dist/treedx/ports.d.ts +166 -0
  447. package/dist/treedx/ports.js +231 -0
  448. package/dist/treedx/query-adapter.d.ts +19 -0
  449. package/dist/treedx/query-adapter.js +62 -0
  450. package/dist/treedx/registry-client.d.ts +11 -0
  451. package/dist/treedx/registry-client.js +19 -0
  452. package/dist/treedx/repository-adapter.d.ts +45 -0
  453. package/dist/treedx/repository-adapter.js +319 -0
  454. package/dist/treedx/sdk-integration.d.ts +27 -0
  455. package/dist/treedx/sdk-integration.js +63 -0
  456. package/dist/treedx/types.d.ts +1084 -0
  457. package/dist/treedx/types.js +8 -0
  458. package/dist/treedx/workspace-adapter.d.ts +27 -0
  459. package/dist/treedx/workspace-adapter.js +65 -0
  460. package/dist/treedx-backends.d.ts +220 -0
  461. package/dist/treedx-backends.js +634 -0
  462. package/dist/treedx-client.d.ts +89 -0
  463. package/dist/treedx-client.js +184 -0
  464. package/dist/treeseed/services/compose.yml +2 -3
  465. package/dist/treeseed/template-catalog/catalog.fixture.json +27 -27
  466. package/dist/types/agents.d.ts +256 -0
  467. package/dist/types/agents.js +42 -1
  468. package/dist/verification.d.ts +1 -0
  469. package/dist/verification.js +143 -14
  470. package/dist/workflow/operations.d.ts +580 -523
  471. package/dist/workflow/operations.js +2084 -1279
  472. package/dist/workflow/policy.js +19 -7
  473. package/dist/workflow/runs.d.ts +9 -3
  474. package/dist/workflow/runs.js +66 -26
  475. package/dist/workflow/session.d.ts +4 -1
  476. package/dist/workflow/session.js +50 -10
  477. package/dist/workflow/worktrees.d.ts +6 -2
  478. package/dist/workflow/worktrees.js +98 -15
  479. package/dist/workflow-state.d.ts +17 -4
  480. package/dist/workflow-state.js +68 -41
  481. package/dist/workflow-support.d.ts +26 -17
  482. package/dist/workflow-support.js +84 -24
  483. package/dist/workflow.d.ts +51 -16
  484. package/dist/workflow.js +19 -7
  485. package/drizzle/d1/0000_treeseed_d1.sql +0 -18
  486. package/drizzle/market/0000_market_control_plane.sql +1793 -1499
  487. package/drizzle/market/0004_treedx_market_integration.sql +99 -0
  488. package/drizzle/market/0005_agent_capacity_coordination.sql +145 -0
  489. package/drizzle/market/0006_provider_assignment_lifecycle.sql +9 -0
  490. package/drizzle/market/0007_agent_architecture_gap_closure.sql +157 -0
  491. package/drizzle/market/0008_agent_capacity_operational_closure.sql +27 -0
  492. package/drizzle/market/0009_agent_capacity_runtime_hardening.sql +29 -0
  493. package/drizzle/market/0010_secrets_capability_persistence.sql +130 -0
  494. package/drizzle/market/0011_github_app_adapter.sql +47 -0
  495. package/drizzle/market/0012_workday_test_runs.sql +44 -0
  496. package/drizzle/market/0013_runtime_task_lifecycle.sql +39 -0
  497. package/drizzle/market/0014_governance_pipeline.sql +189 -0
  498. package/package.json +106 -18
  499. package/scripts/{verify-driver.mjs → verify-driver.ts} +19 -4
  500. package/templates/github/deploy-web.workflow.yml +49 -12
  501. package/dist/operations/services/mailpit-runtime.d.ts +0 -20
  502. package/dist/operations/services/mailpit-runtime.js +0 -67
  503. package/dist/operations/services/release-candidate.d.ts +0 -41
  504. package/dist/operations/services/release-candidate.js +0 -616
  505. package/dist/scripts/ensure-mailpit.js +0 -48
  506. package/dist/scripts/logs-mailpit.js +0 -2
  507. package/dist/scripts/run-ts.js +0 -45
  508. package/dist/scripts/stop-mailpit.js +0 -5
  509. package/dist/scripts/treeseed-run-ts.js +0 -45
@@ -1,7 +1,8 @@
1
- import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
2
  import { basename, resolve, relative } from "node:path";
4
3
  import { spawn, spawnSync } from "node:child_process";
4
+ import { tmpdir } from "node:os";
5
+ import { classifyTreeseedGitMode, runTreeseedGitText } from "./git-runner.js";
5
6
  import {
6
7
  ensureSshPushUrlForOrigin,
7
8
  remoteWriteUrl,
@@ -11,8 +12,8 @@ import {
11
12
  generateRepositoryCommitMessage
12
13
  } from "./commit-message-provider.js";
13
14
  import {
14
- createDevTagMessage,
15
15
  createPackageDependencyReference,
16
+ normalizeGitRemoteForDependency,
16
17
  updateInternalDependencySpecs
17
18
  } from "./package-reference-policy.js";
18
19
  import {
@@ -33,15 +34,39 @@ import {
33
34
  } from "./workspace-save.js";
34
35
  import {
35
36
  hasCompleteTreeseedPackageCheckout,
36
- run,
37
37
  sortWorkspacePackages,
38
38
  workspacePackages
39
39
  } from "./workspace-tools.js";
40
- import { collectDeploymentLockfileWorkspaceIssues } from "./workspace-dependency-mode.js";
40
+ import { collectDeploymentLockfileWorkspaceIssues, ensureLocalWorkspaceLinks } from "./workspace-dependency-mode.js";
41
41
  import {
42
42
  createBuildWarningSummary,
43
43
  formatAllowedBuildWarnings
44
44
  } from "./build-warning-policy.js";
45
+ import {
46
+ readTreeseedVerificationCache,
47
+ writeTreeseedVerificationCache
48
+ } from "./verification-cache.js";
49
+ import {
50
+ discoverTreeseedPackageAdapters
51
+ } from "./package-adapters.js";
52
+ import {
53
+ discoverTreeseedManagedRepositories,
54
+ parseGitmodulesPaths,
55
+ readTreeseedTemplateRepositoryManifest
56
+ } from "./managed-repositories.js";
57
+ function runGit(args, options) {
58
+ return runTreeseedGitText(args, {
59
+ cwd: options.cwd,
60
+ mode: classifyTreeseedGitMode(args),
61
+ timeoutMs: options.timeoutMs,
62
+ maxBuffer: options.maxBuffer
63
+ });
64
+ }
65
+ function sleepMs(durationMs) {
66
+ return new Promise((resolvePromise) => {
67
+ setTimeout(resolvePromise, durationMs);
68
+ });
69
+ }
45
70
  class RepositorySaveError extends Error {
46
71
  exitCode;
47
72
  details;
@@ -71,11 +96,66 @@ function emitProgress(options, node, phase, message, stream = "stdout") {
71
96
  function prefixedOutput(node, phase, output) {
72
97
  return String(output ?? "").split(/\r?\n/u).map((line) => line.trimEnd()).filter(Boolean).map((line) => `${progressPrefix(node, phase)} ${line}`).join("\n");
73
98
  }
99
+ function withShortProcessTempEnv(env = {}) {
100
+ const merged = { ...process.env, ...env };
101
+ if (process.platform === "win32") {
102
+ return merged;
103
+ }
104
+ const shortTemp = tmpdir();
105
+ const tempKeys = ["TMPDIR", "TMP", "TEMP"];
106
+ for (const key of tempKeys) {
107
+ const value = merged[key];
108
+ if (value && value.length > shortTemp.length) {
109
+ merged[key] = shortTemp;
110
+ }
111
+ }
112
+ return merged;
113
+ }
114
+ function npmCacheForCwd(cwd) {
115
+ const cacheDir = resolve(cwd, ".treeseed", "cache", "npm");
116
+ mkdirSync(cacheDir, { recursive: true });
117
+ return cacheDir;
118
+ }
119
+ function npmWorkflowEnv(env = {}, cwd = process.cwd()) {
120
+ const npmCache = env.npm_config_cache ?? env.NPM_CONFIG_CACHE ?? process.env.npm_config_cache ?? process.env.NPM_CONFIG_CACHE ?? npmCacheForCwd(cwd);
121
+ return withShortProcessTempEnv({
122
+ ...env,
123
+ NPM_CONFIG_CACHE: npmCache,
124
+ npm_config_audit: env.npm_config_audit ?? process.env.npm_config_audit ?? "false",
125
+ npm_config_cache: npmCache,
126
+ npm_config_fetch_retries: env.npm_config_fetch_retries ?? process.env.npm_config_fetch_retries ?? "2",
127
+ npm_config_fund: env.npm_config_fund ?? process.env.npm_config_fund ?? "false",
128
+ npm_config_foreground_scripts: env.npm_config_foreground_scripts ?? process.env.npm_config_foreground_scripts ?? "true",
129
+ npm_config_loglevel: env.npm_config_loglevel ?? process.env.npm_config_loglevel ?? "warn",
130
+ npm_config_maxsockets: env.npm_config_maxsockets ?? process.env.npm_config_maxsockets ?? "4",
131
+ npm_config_prefer_offline: env.npm_config_prefer_offline ?? process.env.npm_config_prefer_offline ?? "true",
132
+ npm_config_progress: env.npm_config_progress ?? process.env.npm_config_progress ?? "false"
133
+ });
134
+ }
135
+ function npmCommandForSpawn(args) {
136
+ if (process.platform === "win32") {
137
+ return { command: "npm", args };
138
+ }
139
+ return {
140
+ command: "bash",
141
+ args: [
142
+ "-lc",
143
+ 'ulimit -n 65535 2>/dev/null || ulimit -n 32768 2>/dev/null || ulimit -n 16384 2>/dev/null || true; exec npm "$@"',
144
+ "npm-fd-guard",
145
+ ...args
146
+ ]
147
+ };
148
+ }
149
+ function displayCommand(command, args) {
150
+ return command === "npm" ? `npm ${args.join(" ")}` : `${command} ${args.join(" ")}`;
151
+ }
74
152
  function runCapturedCommand(node, options, phase, command, args, commandOptions = {}) {
75
- emitProgress(options, node, phase, `$ ${command} ${args.join(" ")}`);
76
- const result = spawnSync(command, args, {
77
- cwd: commandOptions.cwd ?? node.path,
78
- env: { ...process.env, ...commandOptions.env ?? {} },
153
+ const cwd = commandOptions.cwd ?? node.path;
154
+ const spawnCommand = command === "npm" ? npmCommandForSpawn(args) : { command, args };
155
+ emitProgress(options, node, phase, `$ ${displayCommand(command, args)}`);
156
+ const result = spawnSync(spawnCommand.command, spawnCommand.args, {
157
+ cwd,
158
+ env: command === "npm" ? npmWorkflowEnv(commandOptions.env, cwd) : withShortProcessTempEnv(commandOptions.env),
79
159
  stdio: "pipe",
80
160
  encoding: "utf8",
81
161
  timeout: commandOptions.timeoutMs
@@ -88,12 +168,12 @@ function runCapturedCommand(node, options, phase, command, args, commandOptions
88
168
  }
89
169
  if (result.status !== 0) {
90
170
  const message = (result.error?.message ? `${result.error.message}
91
- ` : "") + (prefixedOutput(node, phase, stderr) || prefixedOutput(node, phase, stdout) || `${progressPrefix(node, phase)} ${command} ${args.join(" ")} failed`);
171
+ ` : "") + (prefixedOutput(node, phase, stderr) || prefixedOutput(node, phase, stdout) || `${progressPrefix(node, phase)} ${displayCommand(command, args)} failed`);
92
172
  throw new RepositorySaveError(message, {
93
173
  details: {
94
174
  failingRepo: node.name,
95
175
  phase,
96
- command: `${command} ${args.join(" ")}`
176
+ command: displayCommand(command, args)
97
177
  }
98
178
  });
99
179
  }
@@ -121,9 +201,11 @@ function isNoOpGitCommitError(error) {
121
201
  return /nothing to commit|no changes added to commit/u.test(error.message);
122
202
  }
123
203
  function runQuietCommand(node, phase, command, args, commandOptions = {}) {
124
- const result = spawnSync(command, args, {
125
- cwd: commandOptions.cwd ?? node.path,
126
- env: { ...process.env, ...commandOptions.env ?? {} },
204
+ const cwd = commandOptions.cwd ?? node.path;
205
+ const spawnCommand = command === "npm" ? npmCommandForSpawn(args) : { command, args };
206
+ const result = spawnSync(spawnCommand.command, spawnCommand.args, {
207
+ cwd,
208
+ env: command === "npm" ? npmWorkflowEnv(commandOptions.env, cwd) : withShortProcessTempEnv(commandOptions.env),
127
209
  stdio: "pipe",
128
210
  encoding: "utf8",
129
211
  timeout: commandOptions.timeoutMs
@@ -133,14 +215,14 @@ function runQuietCommand(node, phase, command, args, commandOptions = {}) {
133
215
  if (result.status !== 0) {
134
216
  throw new RepositorySaveError(
135
217
  [
136
- `${progressPrefix(node, phase)} ${command} ${args.join(" ")} failed`,
218
+ `${progressPrefix(node, phase)} ${displayCommand(command, args)} failed`,
137
219
  stderr || stdout
138
220
  ].filter(Boolean).join("\n"),
139
221
  {
140
222
  details: {
141
223
  failingRepo: node.name,
142
224
  phase,
143
- command: `${command} ${args.join(" ")}`
225
+ command: displayCommand(command, args)
144
226
  }
145
227
  }
146
228
  );
@@ -148,11 +230,13 @@ function runQuietCommand(node, phase, command, args, commandOptions = {}) {
148
230
  return stdout;
149
231
  }
150
232
  async function runStreamingCommand(node, options, phase, command, args, commandOptions = {}) {
151
- emitProgress(options, node, phase, `$ ${command} ${args.join(" ")}`);
233
+ const cwd = commandOptions.cwd ?? node.path;
234
+ const spawnCommand = command === "npm" ? npmCommandForSpawn(args) : { command, args };
235
+ emitProgress(options, node, phase, `$ ${displayCommand(command, args)}`);
152
236
  return await new Promise((resolvePromise, reject) => {
153
- const child = spawn(command, args, {
154
- cwd: commandOptions.cwd ?? node.path,
155
- env: { ...process.env, ...commandOptions.env ?? {} },
237
+ const child = spawn(spawnCommand.command, spawnCommand.args, {
238
+ cwd,
239
+ env: command === "npm" ? npmWorkflowEnv(commandOptions.env, cwd) : withShortProcessTempEnv(commandOptions.env),
156
240
  stdio: ["ignore", "pipe", "pipe"]
157
241
  });
158
242
  let stdout = "";
@@ -179,7 +263,7 @@ async function runStreamingCommand(node, options, phase, command, args, commandO
179
263
  if (settled) return;
180
264
  settled = true;
181
265
  child.kill("SIGTERM");
182
- reject(new Error(`${progressPrefix(node, phase)} ${command} ${args.join(" ")} timed out after ${commandOptions.timeoutMs}ms`));
266
+ reject(new Error(`${progressPrefix(node, phase)} ${displayCommand(command, args)} timed out after ${commandOptions.timeoutMs}ms`));
183
267
  }, commandOptions.timeoutMs) : null;
184
268
  child.stdout?.on("data", (chunk) => {
185
269
  const text = chunk.toString();
@@ -219,12 +303,12 @@ async function runStreamingCommand(node, options, phase, command, args, commandO
219
303
  return;
220
304
  }
221
305
  reject(new RepositorySaveError(
222
- prefixedOutput(node, phase, stderr) || prefixedOutput(node, phase, stdout) || `${progressPrefix(node, phase)} ${command} ${args.join(" ")} failed with exit code ${code ?? "unknown"}`,
306
+ prefixedOutput(node, phase, stderr) || prefixedOutput(node, phase, stdout) || `${progressPrefix(node, phase)} ${displayCommand(command, args)} failed with exit code ${code ?? "unknown"}`,
223
307
  {
224
308
  details: {
225
309
  failingRepo: node.name,
226
310
  phase,
227
- command: `${command} ${args.join(" ")}`
311
+ command: displayCommand(command, args)
228
312
  }
229
313
  }
230
314
  ));
@@ -235,7 +319,9 @@ function packageScripts(packageJson) {
235
319
  const scripts = packageJson?.scripts;
236
320
  return scripts && typeof scripts === "object" && !Array.isArray(scripts) ? Object.fromEntries(Object.entries(scripts).map(([key, value]) => [key, String(value)])) : {};
237
321
  }
238
- function classifyRepoKind(packageJson) {
322
+ function classifyRepoKind(packageJson, managedKind) {
323
+ if (managedKind === "template") return "template";
324
+ if (managedKind === "fixture") return "fixture";
239
325
  if (typeof packageJson?.name !== "string" || typeof packageJson?.version !== "string") {
240
326
  return "project";
241
327
  }
@@ -255,12 +341,19 @@ function repoIdForPath(root, repoDir) {
255
341
  }
256
342
  function isGitRepo(repoDir) {
257
343
  try {
258
- run("git", ["rev-parse", "--is-inside-work-tree"], { cwd: repoDir, capture: true });
344
+ runGit(["rev-parse", "--is-inside-work-tree"], { cwd: repoDir, capture: true });
259
345
  return true;
260
346
  } catch {
261
347
  return false;
262
348
  }
263
349
  }
350
+ function isIndependentGitRepo(repoDir) {
351
+ try {
352
+ return resolve(repoRoot(repoDir)) === resolve(repoDir);
353
+ } catch {
354
+ return false;
355
+ }
356
+ }
264
357
  function originRemoteUrlSafe(repoDir) {
265
358
  try {
266
359
  return originRemoteUrl(repoDir);
@@ -278,17 +371,23 @@ function ensureWritableRemote(node, options) {
278
371
  function repoDisplayName(repoDir, packageJson) {
279
372
  return typeof packageJson?.name === "string" && packageJson.name.length > 0 ? packageJson.name : basename(repoDir);
280
373
  }
374
+ function emptyManifestVerifyCommands() {
375
+ return { fast: null, local: null, release: null };
376
+ }
377
+ function verifyCommandFromString(repoDir, label, command) {
378
+ return command ? { label, command: "bash", args: ["-lc", command], cwd: repoDir } : null;
379
+ }
380
+ function templateVerifyCommands(repoDir) {
381
+ const manifest = readTreeseedTemplateRepositoryManifest(repoDir);
382
+ if (!manifest) return null;
383
+ return {
384
+ fast: verifyCommandFromString(repoDir, "fast", manifest.verify.fast),
385
+ local: verifyCommandFromString(repoDir, "local", manifest.verify.local),
386
+ release: verifyCommandFromString(repoDir, "release", manifest.verify.release)
387
+ };
388
+ }
281
389
  function parseGitmodules(root) {
282
- const gitmodulesPath = resolve(root, ".gitmodules");
283
- if (!existsSync(gitmodulesPath)) {
284
- return [];
285
- }
286
- const source = readFileSync(gitmodulesPath, "utf8");
287
- const paths = [];
288
- for (const match of source.matchAll(/^\s*path\s*=\s*(.+?)\s*$/gmu)) {
289
- paths.push(match[1].replaceAll("\\", "/"));
290
- }
291
- return paths;
390
+ return parseGitmodulesPaths(root);
292
391
  }
293
392
  function slugBranch(branch) {
294
393
  return branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "dev";
@@ -312,18 +411,38 @@ function isDevVersionForBranch(version, branch) {
312
411
  function packageVersionAtHead(node) {
313
412
  if (!node.packageJsonPath) return null;
314
413
  try {
315
- const source = run("git", ["show", "HEAD:package.json"], { cwd: node.path, capture: true });
414
+ const source = runGit(["show", "HEAD:package.json"], { cwd: node.path, capture: true });
316
415
  const packageJson = JSON.parse(source);
317
416
  return typeof packageJson.version === "string" ? packageJson.version : null;
318
417
  } catch {
319
418
  return null;
320
419
  }
321
420
  }
421
+ function headCommitOrPlanPlaceholder(cwd) {
422
+ try {
423
+ return headCommit(cwd);
424
+ } catch {
425
+ return "HEAD";
426
+ }
427
+ }
428
+ function remoteBranchCommitSafe(cwd, branch) {
429
+ try {
430
+ const output = runGit(["ls-remote", "origin", `refs/heads/${branch}`], { cwd, capture: true });
431
+ const [sha] = output.trim().split(/\s+/u);
432
+ return /^[a-f0-9]{40}$/u.test(sha ?? "") ? sha : null;
433
+ } catch {
434
+ return null;
435
+ }
436
+ }
437
+ function canManagePackageJsonVersion(node) {
438
+ return node.kind === "package" && Boolean(node.packageJsonPath) && typeof node.packageJson?.version === "string";
439
+ }
322
440
  function packageVersionEligibleForBranch(node, version, options) {
323
441
  return node.branchMode === "package-release-main" ? isStableSemverVersion(version) : isDevVersionForBranch(version, node.branch || options.branch);
324
442
  }
325
443
  function packageVersionTagConflictsWithHead(node, options) {
326
444
  if (node.kind !== "package") return false;
445
+ if (node.branchMode === "package-dev-save" && (options.devDependencyReferenceMode ?? "git-commit") === "git-commit") return false;
327
446
  const version = typeof node.packageJson?.version === "string" ? node.packageJson.version : null;
328
447
  if (!version || !packageVersionEligibleForBranch(node, version, options)) return false;
329
448
  const head = headCommit(node.path);
@@ -332,7 +451,7 @@ function packageVersionTagConflictsWithHead(node, options) {
332
451
  }
333
452
  function selectPackageVersion(node, options) {
334
453
  const current = String(node.packageJson?.version ?? "0.0.0");
335
- if (node.branchMode === "package-dev-save" && isDevVersionForBranch(current, node.branch || options.branch) && !tagExists(node.path, current)) {
454
+ if (node.branchMode === "package-dev-save" && isDevVersionForBranch(current, node.branch || options.branch) && ((options.devDependencyReferenceMode ?? "git-commit") === "git-commit" || !tagExists(node.path, current))) {
336
455
  return { version: current, reused: true };
337
456
  }
338
457
  if (node.branchMode === "package-release-main") {
@@ -363,8 +482,6 @@ function createReport(node) {
363
482
  publishWait: null,
364
483
  version: typeof node.packageJson?.version === "string" ? node.packageJson.version : null,
365
484
  dependencySpec: node.plannedDependencySpec,
366
- devTagMetadata: null,
367
- replacedDevTags: [],
368
485
  branchMode: node.branchMode,
369
486
  verification: null,
370
487
  install: null,
@@ -377,29 +494,43 @@ function createReport(node) {
377
494
  }
378
495
  function discoverRepositorySaveNodes(root, gitRoot = repoRoot(root), branch = currentBranch(gitRoot), options = {}) {
379
496
  const repoDirs = /* @__PURE__ */ new Map();
380
- repoDirs.set(".", gitRoot);
497
+ const packageAdaptersByDir = new Map(discoverTreeseedPackageAdapters(root).map((adapter) => [resolve(adapter.dir), adapter]));
498
+ const managedRepositoriesByDir = new Map(discoverTreeseedManagedRepositories(root).map((repo) => [resolve(repo.dir), repo]));
499
+ for (const repo of managedRepositoriesByDir.values()) {
500
+ repoDirs.set(repo.relativeDir, repo.dir);
501
+ }
502
+ if (!repoDirs.has(".")) {
503
+ repoDirs.set(".", gitRoot);
504
+ }
381
505
  if (hasCompleteTreeseedPackageCheckout(root)) {
382
506
  for (const pkg of workspacePackages(root)) {
383
- if (isGitRepo(pkg.dir)) {
507
+ if (isIndependentGitRepo(pkg.dir)) {
384
508
  repoDirs.set(pkg.relativeDir, pkg.dir);
385
509
  }
386
510
  }
387
511
  }
512
+ for (const adapter of packageAdaptersByDir.values()) {
513
+ if (isIndependentGitRepo(adapter.dir)) {
514
+ repoDirs.set(adapter.relativeDir, adapter.dir);
515
+ }
516
+ }
388
517
  for (const submodulePath of parseGitmodules(root)) {
389
518
  const dir = resolve(root, submodulePath);
390
- if (existsSync(dir) && isGitRepo(dir)) {
519
+ if (existsSync(dir) && isIndependentGitRepo(dir)) {
391
520
  repoDirs.set(submodulePath, dir);
392
521
  }
393
522
  }
394
523
  const nodes = [...repoDirs.entries()].map(([relativePath, repoDir]) => {
395
524
  const packageJsonPath = resolve(repoDir, "package.json");
396
525
  const packageJson = existsSync(packageJsonPath) ? readJson(packageJsonPath) : null;
397
- const kind = classifyRepoKind(packageJson);
526
+ const adapter = packageAdaptersByDir.get(resolve(repoDir)) ?? null;
527
+ const managed = managedRepositoriesByDir.get(resolve(repoDir)) ?? null;
528
+ const kind = adapter && !packageJson ? "package" : classifyRepoKind(packageJson, managed?.kind);
398
529
  const repoBranch = relativePath === "." ? currentBranch(repoDir) || branch || null : branch || currentBranch(repoDir) || null;
399
530
  const branchMode = kind === "project" ? "project-save" : options.stablePackageRelease === true && repoBranch === PRODUCTION_BRANCH ? "package-release-main" : "package-dev-save";
400
531
  return {
401
532
  id: relativePath,
402
- name: repoDisplayName(repoDir, packageJson),
533
+ name: managed?.kind === "template" || managed?.kind === "fixture" ? managed.name : adapter?.id ?? repoDisplayName(repoDir, packageJson),
403
534
  path: repoDir,
404
535
  relativePath,
405
536
  kind,
@@ -408,6 +539,7 @@ function discoverRepositorySaveNodes(root, gitRoot = repoRoot(root), branch = cu
408
539
  packageJsonPath: packageJson ? packageJsonPath : null,
409
540
  packageJson,
410
541
  scripts: packageScripts(packageJson),
542
+ manifestVerifyCommands: adapter?.verifyCommands ?? templateVerifyCommands(repoDir) ?? emptyManifestVerifyCommands(),
411
543
  remoteUrl: originRemoteUrlSafe(repoDir),
412
544
  dependencies: [],
413
545
  dependents: [],
@@ -502,9 +634,16 @@ function runLimited(items, limit, action) {
502
634
  });
503
635
  return Promise.all(workers);
504
636
  }
637
+ function repositorySaveConcurrency(options) {
638
+ if (options.verifyMode && options.verifyMode !== "skip") {
639
+ return 1;
640
+ }
641
+ const configured = Number.parseInt(process.env.TREESEED_SAVE_REPOSITORY_CONCURRENCY ?? "", 10);
642
+ return Number.isFinite(configured) && configured > 0 ? configured : 3;
643
+ }
505
644
  function remoteBranchExistsSafe(repoDir, branch) {
506
645
  try {
507
- run("git", ["rev-parse", "--verify", `refs/remotes/origin/${branch}`], { cwd: repoDir, capture: true });
646
+ runGit(["rev-parse", "--verify", `refs/remotes/origin/${branch}`], { cwd: repoDir, capture: true });
508
647
  return true;
509
648
  } catch {
510
649
  }
@@ -553,13 +692,18 @@ function commitSubject(message) {
553
692
  return String(message ?? "").split(/\r?\n/u)[0]?.trim() || null;
554
693
  }
555
694
  function gitDiffSummary(repoDir) {
556
- const changedFiles = run("git", ["status", "--porcelain"], { cwd: repoDir, capture: true });
557
- const diff = run("git", ["diff", "--cached"], { cwd: repoDir, capture: true });
695
+ const changedFiles = runGit(["status", "--porcelain"], { cwd: repoDir, capture: true });
696
+ const rawDiff = runGit(["diff", "--cached"], { cwd: repoDir, capture: true, maxBuffer: 1024 * 1024 * 32 });
697
+ const maxDiffChars = 12e4;
698
+ const diff = rawDiff.length > maxDiffChars ? `${rawDiff.slice(0, maxDiffChars)}
699
+
700
+ [treeseed-save: diff truncated from ${rawDiff.length} characters for commit-message generation]
701
+ ` : rawDiff;
558
702
  return { changedFiles, diff };
559
703
  }
560
704
  function hasStagedChanges(repoDir) {
561
705
  try {
562
- return run("git", ["diff", "--cached", "--name-only"], { cwd: repoDir, capture: true }).trim().length > 0;
706
+ return runGit(["diff", "--cached", "--name-only"], { cwd: repoDir, capture: true }).trim().length > 0;
563
707
  } catch {
564
708
  return false;
565
709
  }
@@ -572,6 +716,54 @@ function updateDependencyReferences(node, finalizedReferences) {
572
716
  }
573
717
  return changed;
574
718
  }
719
+ function isRootWorkspaceRepository(node, options) {
720
+ const packageJson = node.packageJson ?? (existsSync(resolve(node.path, "package.json")) ? readJson(resolve(node.path, "package.json")) : null);
721
+ return node.path === options.root && Array.isArray(packageJson?.workspaces);
722
+ }
723
+ function syncDirectGitDependencyLockfileEntries(node, options, references) {
724
+ if (references.length === 0) return false;
725
+ const lockfilePath = resolve(node.path, "package-lock.json");
726
+ if (!existsSync(lockfilePath)) return false;
727
+ const lockfile = readJson(lockfilePath);
728
+ const rootPackage = lockfile.packages && typeof lockfile.packages === "object" && !Array.isArray(lockfile.packages) ? lockfile.packages[""] : null;
729
+ const packageEntries = lockfile.packages && typeof lockfile.packages === "object" && !Array.isArray(lockfile.packages) ? lockfile.packages : null;
730
+ if (!rootPackage || !packageEntries) return false;
731
+ let changed = false;
732
+ for (const reference of references) {
733
+ const manifestSpec = reference.manifestSpec ?? reference.spec;
734
+ for (const field of dependencyFields(rootPackage)) {
735
+ const values = rootPackage[field];
736
+ if (!values || typeof values !== "object" || Array.isArray(values)) continue;
737
+ const dependencies = values;
738
+ if (reference.packageName in dependencies && dependencies[reference.packageName] !== manifestSpec) {
739
+ dependencies[reference.packageName] = manifestSpec;
740
+ changed = true;
741
+ }
742
+ }
743
+ const entryKey = `node_modules/${reference.packageName}`;
744
+ const entry = packageEntries[entryKey];
745
+ if (entry) {
746
+ const nextResolved = normalizeGitRemoteForDependency(reference.remoteUrl ?? "", "ssh");
747
+ const resolved = nextResolved ? `${nextResolved}#${manifestSpec.slice(manifestSpec.lastIndexOf("#") + 1)}` : manifestSpec;
748
+ if (entry.resolved !== resolved) {
749
+ entry.resolved = resolved;
750
+ changed = true;
751
+ }
752
+ if (typeof reference.version === "string" && reference.version && entry.version !== reference.version) {
753
+ entry.version = reference.version;
754
+ changed = true;
755
+ }
756
+ if ("integrity" in entry) {
757
+ delete entry.integrity;
758
+ changed = true;
759
+ }
760
+ }
761
+ }
762
+ if (!changed) return false;
763
+ writeJson(lockfilePath, lockfile);
764
+ emitProgress(options, node, "lockfile", "Synchronized direct internal Git dependency lockfile entries without npm git preparation.");
765
+ return true;
766
+ }
575
767
  function planPackageVersion(node, options) {
576
768
  if (!node.packageJson || !node.packageJsonPath) return null;
577
769
  const current = String(node.packageJson.version ?? "0.0.0");
@@ -593,44 +785,106 @@ function shouldSkipGitDependencySmoke(options) {
593
785
  function hasNpmLockfile(repoDir) {
594
786
  return existsSync(resolve(repoDir, "package-lock.json")) || existsSync(resolve(repoDir, "npm-shrinkwrap.json"));
595
787
  }
596
- async function runGitDependencySmoke(node, options, reference) {
597
- if (reference.mode !== "dev-git-tag" || shouldSkipGitDependencySmoke(options)) return;
598
- const installSpec = reference.installSpec ?? reference.spec;
599
- const tempRoot = mkdtempSync(resolve(tmpdir(), "treeseed-git-dep-smoke-"));
600
- const npmCacheRoot = resolve(tempRoot, ".npm-cache");
601
- try {
602
- emitProgress(options, node, "smoke", `Installing ${installSpec} in a temporary project.`);
603
- writeFileSync(resolve(tempRoot, "package.json"), JSON.stringify({
604
- name: "treeseed-git-dependency-smoke",
605
- version: "0.0.0",
606
- private: true,
607
- type: "module",
608
- dependencies: {
609
- [reference.packageName]: installSpec
788
+ function syncRootWorkspaceLockfileMetadata(node, options) {
789
+ if (node.path !== options.root || !Array.isArray(node.packageJson?.workspaces)) return false;
790
+ const lockfilePath = resolve(node.path, "package-lock.json");
791
+ if (!existsSync(lockfilePath)) return false;
792
+ const lockfile = readJson(lockfilePath);
793
+ const packages = lockfile.packages;
794
+ if (!packages || typeof packages !== "object" || Array.isArray(packages)) return false;
795
+ let changed = false;
796
+ const packageEntries = packages;
797
+ const rootEntry = packageEntries[""] ?? {};
798
+ const workspacePackageList = workspacePackages(node.path);
799
+ const workspaceVersionByName = new Map(workspacePackageList.map((workspacePackage) => [String(workspacePackage.packageJson.name ?? ""), String(workspacePackage.packageJson.version ?? "")]).filter(([name, version]) => name.length > 0 && version.length > 0));
800
+ const rootDependencySpecs = /* @__PURE__ */ new Map();
801
+ for (const field of dependencyFields(node.packageJson)) {
802
+ const rootDeps = node.packageJson[field];
803
+ if (!rootDeps || typeof rootDeps !== "object" || Array.isArray(rootDeps)) continue;
804
+ for (const [dependencyName, dependencySpec] of Object.entries(rootDeps)) {
805
+ if (typeof dependencySpec === "string") {
806
+ rootDependencySpecs.set(dependencyName, dependencySpec);
610
807
  }
611
- }, null, 2), "utf8");
612
- let lastError = null;
613
- for (let attempt = 1; attempt <= 5; attempt += 1) {
614
- emitProgress(options, node, "smoke", `npm install --cache ${npmCacheRoot} attempt ${attempt}/5.`);
615
- try {
616
- await runStreamingCommand(node, options, "smoke", "npm", ["install", "--cache", npmCacheRoot], { cwd: tempRoot });
617
- return;
618
- } catch (error) {
619
- lastError = error instanceof Error ? error.message : String(error);
808
+ }
809
+ }
810
+ const stableWorkspaceDependencySpec = (dependencyName) => rootDependencySpecs.get(dependencyName) ?? workspaceVersionByName.get(dependencyName) ?? null;
811
+ const normalizeWorkspaceDependencySpecs = (value) => Object.fromEntries(
812
+ Object.entries(value).map(([dependencyName, dependencySpec]) => [
813
+ dependencyName,
814
+ workspaceVersionByName.has(dependencyName) ? stableWorkspaceDependencySpec(dependencyName) : dependencySpec
815
+ ])
816
+ );
817
+ if (JSON.stringify(rootEntry.workspaces ?? []) !== JSON.stringify(node.packageJson.workspaces)) {
818
+ rootEntry.workspaces = node.packageJson.workspaces;
819
+ packageEntries[""] = rootEntry;
820
+ changed = true;
821
+ }
822
+ for (const field of dependencyFields(node.packageJson)) {
823
+ const nextValue = node.packageJson[field];
824
+ if (nextValue && typeof nextValue === "object" && !Array.isArray(nextValue)) {
825
+ const currentValue = rootEntry[field];
826
+ const currentDeps = currentValue && typeof currentValue === "object" && !Array.isArray(currentValue) ? currentValue : {};
827
+ const mergedDeps = { ...currentDeps };
828
+ let fieldChanged = false;
829
+ for (const [dependencyName, dependencySpec] of Object.entries(nextValue)) {
830
+ if (mergedDeps[dependencyName] != null) continue;
831
+ mergedDeps[dependencyName] = dependencySpec;
832
+ fieldChanged = true;
620
833
  }
621
- if (attempt < 5) {
622
- emitProgress(options, node, "smoke", "npm install failed; retrying in 60 seconds.", "stderr");
623
- spawnSync("sleep", ["60"], { stdio: "ignore" });
834
+ if (fieldChanged) {
835
+ rootEntry[field] = mergedDeps;
836
+ packageEntries[""] = rootEntry;
837
+ changed = true;
624
838
  }
625
839
  }
626
- throw new RepositorySaveError([
627
- `Git dependency smoke install failed for ${reference.packageName} after 5 attempts.`,
628
- `Spec: ${installSpec}`,
629
- lastError ?? ""
630
- ].join("\n"));
631
- } finally {
632
- rmSync(tempRoot, { recursive: true, force: true });
633
840
  }
841
+ for (const workspacePackage of workspacePackageList) {
842
+ const relativeDir = workspacePackage.relativeDir.replace(/\\/gu, "/");
843
+ const packageJson = workspacePackage.packageJson;
844
+ const packageName = String(packageJson.name ?? "");
845
+ if (!packageName) continue;
846
+ const packageEntry = packageEntries[relativeDir] ?? {};
847
+ if (packageEntry.name !== packageName) {
848
+ packageEntry.name = packageName;
849
+ changed = true;
850
+ }
851
+ if (typeof packageJson.version === "string" && packageEntry.version !== packageJson.version) {
852
+ packageEntry.version = packageJson.version;
853
+ changed = true;
854
+ }
855
+ for (const field of dependencyFields(packageJson)) {
856
+ const nextValue = packageJson[field];
857
+ if (nextValue && typeof nextValue === "object" && !Array.isArray(nextValue)) {
858
+ const normalizedValue = normalizeWorkspaceDependencySpecs(nextValue);
859
+ if (JSON.stringify(packageEntry[field] ?? {}) !== JSON.stringify(normalizedValue)) {
860
+ packageEntry[field] = normalizedValue;
861
+ changed = true;
862
+ }
863
+ }
864
+ }
865
+ packageEntries[relativeDir] = packageEntry;
866
+ const linkKey = `node_modules/${packageName}`;
867
+ const linkEntry = packageEntries[linkKey] ?? {};
868
+ if (linkEntry.resolved !== relativeDir) {
869
+ linkEntry.resolved = relativeDir;
870
+ changed = true;
871
+ }
872
+ if (linkEntry.link !== true) {
873
+ linkEntry.link = true;
874
+ changed = true;
875
+ }
876
+ packageEntries[linkKey] = linkEntry;
877
+ }
878
+ if (!changed) return false;
879
+ lockfile.packages = packageEntries;
880
+ writeJson(lockfilePath, lockfile);
881
+ emitProgress(options, node, "lockfile", "Synchronized root workspace lockfile metadata before validation.");
882
+ return true;
883
+ }
884
+ async function runGitDependencySmoke(node, options, reference) {
885
+ void node;
886
+ void options;
887
+ void reference;
634
888
  }
635
889
  async function runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs = []) {
636
890
  if (shouldSkipNetworkInstall()) {
@@ -640,7 +894,7 @@ async function runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs =
640
894
  let lastError = null;
641
895
  const packageJson = node.packageJson ?? (existsSync(resolve(node.path, "package.json")) ? readJson(resolve(node.path, "package.json")) : null);
642
896
  const rootWorkspaceInstall = node.path === options.root && Array.isArray(packageJson?.workspaces);
643
- const installFlags = ["--package-lock-only", "--ignore-scripts"];
897
+ const installFlags = rootWorkspaceInstall ? ["--package-lock-only", "--ignore-scripts"] : node.branchMode === "project-save" ? ["--ignore-scripts"] : ["--package-lock-only", "--ignore-scripts"];
644
898
  const args = rootWorkspaceInstall ? gitDependencyRefreshSpecs.length > 0 ? ["install", ...gitDependencyRefreshSpecs, ...installFlags, "--force"] : ["install", ...installFlags] : gitDependencyRefreshSpecs.length > 0 ? ["install", ...gitDependencyRefreshSpecs, ...installFlags, "--force", "--workspaces=false"] : ["install", ...installFlags, "--workspaces=false"];
645
899
  for (let attempt = 1; attempt <= 5; attempt += 1) {
646
900
  emitProgress(options, node, "install", `npm ${args.join(" ")} attempt ${attempt}/5.`);
@@ -652,12 +906,42 @@ async function runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs =
652
906
  }
653
907
  if (attempt < 5) {
654
908
  emitProgress(options, node, "install", "npm install failed; retrying in 60 seconds.", "stderr");
655
- spawnSync("sleep", ["60"], { stdio: "ignore" });
909
+ await sleepMs(6e4);
656
910
  }
657
911
  }
658
912
  throw new RepositorySaveError(`npm install failed after 5 attempts.
659
913
  ${lastError ?? ""}`);
660
914
  }
915
+ async function runProjectVerificationInstallWithRetry(node, options) {
916
+ if (!hasNpmLockfile(node.path)) return;
917
+ if (shouldSkipNetworkInstall()) {
918
+ emitProgress(options, node, "install", "Skipped project verification dependency install because network install mode is disabled.");
919
+ return;
920
+ }
921
+ let lastError = null;
922
+ const packageJson = node.packageJson ?? (existsSync(resolve(node.path, "package.json")) ? readJson(resolve(node.path, "package.json")) : null);
923
+ const rootWorkspaceInstall = node.path === options.root && Array.isArray(packageJson?.workspaces);
924
+ if (rootWorkspaceInstall) {
925
+ emitProgress(options, node, "install", "Skipped root npm ci project verification install; lockfile dry-run and restored workspace links provide save-time dependency proof.");
926
+ return;
927
+ }
928
+ const args = rootWorkspaceInstall ? ["ci"] : ["ci", "--workspaces=false"];
929
+ for (let attempt = 1; attempt <= 5; attempt += 1) {
930
+ emitProgress(options, node, "install", `npm ${args.join(" ")} for project verification attempt ${attempt}/5.`);
931
+ try {
932
+ await runStreamingCommand(node, options, "install", "npm", args);
933
+ return;
934
+ } catch (error) {
935
+ lastError = error instanceof Error ? error.message : String(error);
936
+ }
937
+ if (attempt < 5) {
938
+ emitProgress(options, node, "install", "npm ci for verification failed; retrying in 60 seconds.", "stderr");
939
+ await sleepMs(6e4);
940
+ }
941
+ }
942
+ throw new RepositorySaveError(`Project verification dependency install failed after 5 attempts.
943
+ ${lastError ?? ""}`);
944
+ }
661
945
  function lockfileValidationCommand(node, options) {
662
946
  const packageJson = node.packageJson ?? (existsSync(resolve(node.path, "package.json")) ? readJson(resolve(node.path, "package.json")) : null);
663
947
  const rootWorkspaceInstall = node.path === options.root && Array.isArray(packageJson?.workspaces);
@@ -668,6 +952,7 @@ async function validateRepositoryLockfile(node, options) {
668
952
  if (!hasNpmLockfile(node.path)) {
669
953
  return { status: "skipped", command: null, issues: [], error: "no npm lockfile" };
670
954
  }
955
+ syncRootWorkspaceLockfileMetadata(node, options);
671
956
  const issues = collectDeploymentLockfileWorkspaceIssues(node.path).map((issue) => `${issue.filePath}: ${issue.packageName} ${issue.reason}`);
672
957
  if (issues.length > 0) {
673
958
  throw new RepositorySaveError([
@@ -713,38 +998,99 @@ async function validateRepositoryLockfile(node, options) {
713
998
  function hasScript(node, scriptName) {
714
999
  return typeof node.scripts[scriptName] === "string" && node.scripts[scriptName].length > 0;
715
1000
  }
1001
+ function manifestVerifyCommand(node, key) {
1002
+ return node.manifestVerifyCommands[key] ?? null;
1003
+ }
1004
+ function hasAnyVerificationCommand(node) {
1005
+ return hasScript(node, "verify:action") || hasScript(node, "verify:local") || hasScript(node, "verify") || Boolean(manifestVerifyCommand(node, "local")) || Boolean(manifestVerifyCommand(node, "fast"));
1006
+ }
716
1007
  async function runScript(node, options, scriptName) {
717
1008
  await runStreamingCommand(node, options, "verify", "npm", ["run", scriptName]);
718
1009
  }
1010
+ async function runManifestVerifyCommand(node, options, verifyMode, key) {
1011
+ const manifestCommand = manifestVerifyCommand(node, key);
1012
+ if (!manifestCommand) {
1013
+ throw new RepositorySaveError(`${node.name} is missing a ${key} verification command in treeseed.package.yaml.`);
1014
+ }
1015
+ const command = `${manifestCommand.command} ${manifestCommand.args.join(" ")}`;
1016
+ const cacheInput = {
1017
+ workspaceRoot: options.root,
1018
+ repoName: node.name,
1019
+ repoPath: node.path,
1020
+ command,
1021
+ verifyMode,
1022
+ env: process.env
1023
+ };
1024
+ const cached = readTreeseedVerificationCache(cacheInput);
1025
+ if (cached) {
1026
+ emitProgress(options, node, "verify", `[verify][cache] Reused ${node.name} ${manifestCommand.label} for ${cached.headSha.slice(0, 12)}.`);
1027
+ return { cached: true };
1028
+ }
1029
+ const started = Date.now();
1030
+ await runStreamingCommand(node, options, "verify", manifestCommand.command, manifestCommand.args, { cwd: manifestCommand.cwd });
1031
+ writeTreeseedVerificationCache(cacheInput, Date.now() - started);
1032
+ return { cached: false };
1033
+ }
1034
+ async function runCachedScript(node, options, verifyMode, scriptName) {
1035
+ const command = `npm run ${scriptName}`;
1036
+ const cacheInput = {
1037
+ workspaceRoot: options.root,
1038
+ repoName: node.name,
1039
+ repoPath: node.path,
1040
+ command,
1041
+ verifyMode,
1042
+ env: process.env
1043
+ };
1044
+ const cached = readTreeseedVerificationCache(cacheInput);
1045
+ if (cached) {
1046
+ emitProgress(options, node, "verify", `[verify][cache] Reused ${node.name} ${scriptName} for ${cached.headSha.slice(0, 12)}.`);
1047
+ return { cached: true };
1048
+ }
1049
+ const started = Date.now();
1050
+ await runScript(node, options, scriptName);
1051
+ writeTreeseedVerificationCache(cacheInput, Date.now() - started);
1052
+ return { cached: false };
1053
+ }
719
1054
  async function runRepoVerification(node, options, verifyMode) {
720
1055
  if (verifyMode === "skip") {
721
1056
  emitProgress(options, node, "verify", "Skipped verification by request.");
722
1057
  return { mode: verifyMode, status: "skipped", primary: null, fallbackUsed: false, error: null };
723
1058
  }
724
- if (node.kind !== "package") {
725
- emitProgress(options, node, "verify", "Skipped package verification for project repository.");
1059
+ if (node.kind !== "package" && !hasAnyVerificationCommand(node)) {
1060
+ emitProgress(options, node, "verify", "Skipped verification because project repository does not declare a Treeseed verify script.");
726
1061
  return { mode: verifyMode, status: "skipped", primary: null, fallbackUsed: false, error: null };
727
1062
  }
1063
+ await runProjectVerificationInstallWithRetry(node, options);
728
1064
  if (verifyMode === "local-only") {
1065
+ if (hasScript(node, "verify:local")) {
1066
+ await runCachedScript(node, options, verifyMode, "verify:local");
1067
+ return { mode: verifyMode, status: "passed", primary: "verify:local", fallbackUsed: false, error: null };
1068
+ }
1069
+ if (manifestVerifyCommand(node, "local")) {
1070
+ await runManifestVerifyCommand(node, options, verifyMode, "local");
1071
+ return { mode: verifyMode, status: "passed", primary: "manifest:local", fallbackUsed: false, error: null };
1072
+ }
1073
+ if (manifestVerifyCommand(node, "fast")) {
1074
+ await runManifestVerifyCommand(node, options, verifyMode, "fast");
1075
+ return { mode: verifyMode, status: "passed", primary: "manifest:fast", fallbackUsed: false, error: null };
1076
+ }
729
1077
  if (!hasScript(node, "verify:local")) {
730
- throw new RepositorySaveError(`Package ${node.name} is missing required verify:local script.`);
1078
+ throw new RepositorySaveError(`${node.kind === "package" ? "Package" : "Project"} ${node.name} is missing required verify:local script.`);
731
1079
  }
732
- await runScript(node, options, "verify:local");
733
- return { mode: verifyMode, status: "passed", primary: "verify:local", fallbackUsed: false, error: null };
734
1080
  }
735
- if (!hasScript(node, "verify:action") && !hasScript(node, "verify:local")) {
736
- throw new RepositorySaveError(`Package ${node.name} is missing required verify:action or verify:local script.`);
1081
+ if (!hasAnyVerificationCommand(node)) {
1082
+ throw new RepositorySaveError(`${node.kind === "package" ? "Package" : "Project"} ${node.name} is missing required verify:action, verify:local, or verify script.`);
737
1083
  }
738
1084
  if (hasScript(node, "verify:action")) {
739
1085
  try {
740
- await runScript(node, options, "verify:action");
1086
+ await runCachedScript(node, options, verifyMode, "verify:action");
741
1087
  return { mode: verifyMode, status: "passed", primary: "verify:action", fallbackUsed: false, error: null };
742
1088
  } catch (error) {
743
1089
  if (!hasScript(node, "verify:local")) {
744
1090
  throw error;
745
1091
  }
746
1092
  emitProgress(options, node, "verify", "verify:action failed; falling back to verify:local.", "stderr");
747
- await runScript(node, options, "verify:local");
1093
+ await runCachedScript(node, options, verifyMode, "verify:local");
748
1094
  return {
749
1095
  mode: verifyMode,
750
1096
  status: "passed",
@@ -754,7 +1100,19 @@ async function runRepoVerification(node, options, verifyMode) {
754
1100
  };
755
1101
  }
756
1102
  }
757
- await runScript(node, options, "verify:local");
1103
+ if (hasScript(node, "verify:local")) {
1104
+ await runCachedScript(node, options, verifyMode, "verify:local");
1105
+ return { mode: verifyMode, status: "passed", primary: "verify:local", fallbackUsed: true, error: null };
1106
+ }
1107
+ if (manifestVerifyCommand(node, "local")) {
1108
+ await runManifestVerifyCommand(node, options, verifyMode, "local");
1109
+ return { mode: verifyMode, status: "passed", primary: "manifest:local", fallbackUsed: true, error: null };
1110
+ }
1111
+ if (manifestVerifyCommand(node, "fast")) {
1112
+ await runManifestVerifyCommand(node, options, verifyMode, "fast");
1113
+ return { mode: verifyMode, status: "passed", primary: "manifest:fast", fallbackUsed: true, error: null };
1114
+ }
1115
+ await runCachedScript(node, options, verifyMode, "verify");
758
1116
  return { mode: verifyMode, status: "passed", primary: "verify:local", fallbackUsed: true, error: null };
759
1117
  }
760
1118
  function pullRebaseFromOrigin(node, options, branch) {
@@ -800,9 +1158,57 @@ function pushCurrentBranch(node, options, branch, tagName) {
800
1158
  combinedBranchAndTagPush: pushedTag
801
1159
  };
802
1160
  }
1161
+ async function finishRepositorySavePublish(node, options, state, report, input) {
1162
+ const reference = input.reference ?? null;
1163
+ const tagName = input.tagName ?? reference?.tagName ?? null;
1164
+ const shouldDeferPush = options.deferPushUntilVerified === true && node.id === ".";
1165
+ if (shouldDeferPush) {
1166
+ state.deferredPushes.push({
1167
+ node,
1168
+ report,
1169
+ branch: input.branch,
1170
+ tagName,
1171
+ rebase: input.rebase,
1172
+ reference
1173
+ });
1174
+ report.pushed = false;
1175
+ report.publishWait = {
1176
+ ...input.rebase,
1177
+ deferredPush: true
1178
+ };
1179
+ return;
1180
+ }
1181
+ const push = pushCurrentBranch(node, options, input.branch, tagName);
1182
+ if (reference) {
1183
+ await runGitDependencySmoke(node, options, reference);
1184
+ report.dependencySpec = reference.spec;
1185
+ }
1186
+ report.pushed = push.pushed;
1187
+ report.publishWait = {
1188
+ ...input.rebase,
1189
+ ...push
1190
+ };
1191
+ }
1192
+ async function publishDeferredRepositoryPushes(options, state) {
1193
+ if (state.deferredPushes.length === 0) return;
1194
+ for (const deferred of state.deferredPushes) {
1195
+ const push = pushCurrentBranch(deferred.node, options, deferred.branch, deferred.tagName);
1196
+ if (deferred.reference) {
1197
+ await runGitDependencySmoke(deferred.node, options, deferred.reference);
1198
+ deferred.report.dependencySpec = deferred.reference.spec;
1199
+ }
1200
+ deferred.report.pushed = push.pushed;
1201
+ deferred.report.publishWait = {
1202
+ ...deferred.rebase,
1203
+ ...push,
1204
+ deferredPush: true
1205
+ };
1206
+ }
1207
+ state.deferredPushes = [];
1208
+ }
803
1209
  function tagExists(repoDir, tagName) {
804
1210
  try {
805
- run("git", ["rev-parse", "--verify", `refs/tags/${tagName}`], { cwd: repoDir, capture: true });
1211
+ runGit(["rev-parse", "--verify", `refs/tags/${tagName}`], { cwd: repoDir, capture: true });
806
1212
  return true;
807
1213
  } catch {
808
1214
  return false;
@@ -810,14 +1216,14 @@ function tagExists(repoDir, tagName) {
810
1216
  }
811
1217
  function localTagCommit(repoDir, tagName) {
812
1218
  try {
813
- return run("git", ["rev-list", "-n", "1", tagName], { cwd: repoDir, capture: true }).trim();
1219
+ return runGit(["rev-list", "-n", "1", tagName], { cwd: repoDir, capture: true }).trim();
814
1220
  } catch {
815
1221
  return null;
816
1222
  }
817
1223
  }
818
1224
  function remoteTagCommit(repoDir, tagName) {
819
1225
  try {
820
- const output = run("git", ["ls-remote", "--tags", "origin", `refs/tags/${tagName}*`], { cwd: repoDir, capture: true });
1226
+ const output = runGit(["ls-remote", "--tags", "origin", `refs/tags/${tagName}*`], { cwd: repoDir, capture: true });
821
1227
  const lines = output.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
822
1228
  const dereferenced = lines.find((line) => line.endsWith(`refs/tags/${tagName}^{}`));
823
1229
  const exact = lines.find((line) => line.endsWith(`refs/tags/${tagName}`));
@@ -829,7 +1235,7 @@ function remoteTagCommit(repoDir, tagName) {
829
1235
  }
830
1236
  function localTagMessage(repoDir, tagName) {
831
1237
  try {
832
- return run("git", ["tag", "-l", tagName, "--format=%(contents)"], { cwd: repoDir, capture: true }).trim();
1238
+ return runGit(["tag", "-l", tagName, "--format=%(contents)"], { cwd: repoDir, capture: true }).trim();
833
1239
  } catch {
834
1240
  return null;
835
1241
  }
@@ -870,13 +1276,9 @@ function assertTagStateMatchesHead(node, tagName, state, head) {
870
1276
  }
871
1277
  }
872
1278
  function createPackageTagMessage(node, tagName, branch, workflowRunId) {
873
- return tagName.includes("-dev.") ? createDevTagMessage({
874
- packageName: node.name,
875
- version: tagName,
876
- branch,
877
- commitSha: headCommit(node.path),
878
- workflowRunId
879
- }) : `release: ${tagName}`;
1279
+ void branch;
1280
+ void workflowRunId;
1281
+ return `release: ${tagName}`;
880
1282
  }
881
1283
  function ensureRemoteAccessBeforeVerification(node, options, state) {
882
1284
  if (shouldSkipRemoteAccessPreflight()) return;
@@ -906,12 +1308,6 @@ function ensureRemoteAccessBeforeVerification(node, options, state) {
906
1308
  function shouldSkipRemoteAccessPreflight() {
907
1309
  return process.env.TREESEED_SAVE_REMOTE_PREFLIGHT === "skip";
908
1310
  }
909
- function localTreeseedTagWasCreatedByThisRun(node, tagName, workflowRunId) {
910
- const message = localTagMessage(node.path, tagName);
911
- if (!message?.includes("treeseed-dev-tag: true")) return false;
912
- if (!workflowRunId) return true;
913
- return message.includes(`workflowRunId: ${workflowRunId}`);
914
- }
915
1311
  function ensurePackageTagReady(node, options, tagName, branch, workflowRunId) {
916
1312
  let message = null;
917
1313
  ensureWritableRemote(node, options);
@@ -936,7 +1332,7 @@ function ensurePackageTagReady(node, options, tagName, branch, workflowRunId) {
936
1332
  }
937
1333
  function treeCommitForPath(repoDir, ref, path) {
938
1334
  try {
939
- const output = run("git", ["ls-tree", ref, "--", path], { cwd: repoDir, capture: true }).trim();
1335
+ const output = runGit(["ls-tree", ref, "--", path], { cwd: repoDir, capture: true }).trim();
940
1336
  const match = output.match(/^\d+\s+commit\s+([0-9a-f]{40})\t/u);
941
1337
  return match?.[1] ?? null;
942
1338
  } catch {
@@ -950,7 +1346,7 @@ function collectSubmodulePointerChanges(node, finalizedCommits) {
950
1346
  if (!childRelativePath) continue;
951
1347
  const childPath = resolve(node.path, childRelativePath);
952
1348
  if (!existsSync(childPath) || !isGitRepo(childPath)) continue;
953
- const status = run("git", ["status", "--porcelain", "--", childRelativePath], { cwd: node.path, capture: true });
1349
+ const status = runGit(["status", "--porcelain", "--", childRelativePath], { cwd: node.path, capture: true });
954
1350
  const oldSha = treeCommitForPath(node.path, "HEAD", childRelativePath);
955
1351
  const newSha = finalizedCommit || headCommit(childPath);
956
1352
  if (status.trim().length > 0 || oldSha && newSha && oldSha !== newSha) {
@@ -1007,7 +1403,7 @@ function refreshRepositoryNodePackageMetadata(node) {
1007
1403
  node.packageJson = packageJson;
1008
1404
  node.scripts = packageScripts(packageJson);
1009
1405
  node.remoteUrl = originRemoteUrlSafe(node.path);
1010
- if (node.kind === "package") {
1406
+ if (node.kind === "package" && packageJson) {
1011
1407
  node.name = repoDisplayName(node.path, packageJson);
1012
1408
  }
1013
1409
  }
@@ -1017,7 +1413,8 @@ function finalizePackageReference(node, version, options) {
1017
1413
  version,
1018
1414
  branchMode: node.branchMode === "package-release-main" ? "package-release-main" : "package-dev-save",
1019
1415
  remoteUrl: node.remoteUrl,
1020
- devDependencyReferenceMode: options.devDependencyReferenceMode ?? "git-tag",
1416
+ commitSha: headCommit(node.path),
1417
+ devDependencyReferenceMode: options.devDependencyReferenceMode ?? "git-commit",
1021
1418
  gitDependencyProtocol: options.gitDependencyProtocol ?? "preserve-origin"
1022
1419
  });
1023
1420
  node.plannedDependencySpec = reference.spec;
@@ -1029,21 +1426,25 @@ async function finalizeCleanPackageVersion(node, options, state, report, branch)
1029
1426
  return false;
1030
1427
  }
1031
1428
  const head = headCommit(node.path);
1032
- const currentTagState = tagState(node.path, version);
1033
- assertTagStateMatchesHead(node, version, currentTagState, head);
1429
+ const reference = finalizePackageReference(node, version, options);
1430
+ const currentTagState = reference.tagName ? tagState(node.path, reference.tagName) : null;
1431
+ if (reference.tagName && currentTagState) {
1432
+ assertTagStateMatchesHead(node, reference.tagName, currentTagState, head);
1433
+ }
1034
1434
  const remoteBranchExists2 = remoteBranchExistsSafe(node.path, branch);
1035
- const finalizedRemotely = currentTagState.localCommit === head && currentTagState.remoteCommit === head && remoteBranchExists2;
1435
+ const remoteBranchCommit = remoteBranchCommitSafe(node.path, branch);
1436
+ const finalizedRemotely = remoteBranchExists2 && remoteBranchCommit === head && (!reference.tagName || currentTagState?.localCommit === head && currentTagState?.remoteCommit === head);
1036
1437
  report.version = version;
1037
- report.tagName = version;
1438
+ report.tagName = reference.tagName;
1038
1439
  report.commitSha = head;
1039
- report.dependencySpec = finalizePackageReference(node, version, options).spec;
1440
+ report.dependencySpec = reference.spec;
1040
1441
  state.finalizedVersions.set(node.name, version);
1041
- state.finalizedReferences.set(node.name, finalizePackageReference(node, version, options));
1442
+ state.finalizedReferences.set(node.name, reference);
1042
1443
  state.finalizedCommits.set(node.relativePath, head);
1043
1444
  if (finalizedRemotely) {
1044
1445
  report.pushed = true;
1045
1446
  report.skippedReason = "already-finalized";
1046
- report.publishWait = { recoveredPartialSave: true, remoteBranchExisted: true, tagAlreadyPushed: true };
1447
+ report.publishWait = { recoveredPartialSave: true, remoteBranchExisted: true, tagAlreadyPushed: Boolean(reference.tagName) };
1047
1448
  emitProgress(options, node, "finalize", `Using existing finalized package version ${version}.`);
1048
1449
  return true;
1049
1450
  }
@@ -1053,36 +1454,24 @@ async function finalizeCleanPackageVersion(node, options, state, report, branch)
1053
1454
  report.lockfileValidation = await validateRepositoryLockfile(node, options);
1054
1455
  }
1055
1456
  const rebase = pullRebaseFromOrigin(node, options, branch);
1056
- if (currentTagState.localCommit === head && localTreeseedTagWasCreatedByThisRun(node, version, options.workflowRunId)) {
1057
- emitProgress(options, node, "verify", `Reusing verification from interrupted tag ${version}.`);
1058
- report.verification = {
1059
- mode: options.verifyMode ?? "action-first",
1060
- status: "skipped",
1061
- primary: null,
1062
- fallbackUsed: false,
1063
- error: "verified-before-interruption"
1064
- };
1065
- report.verified = true;
1066
- } else {
1067
- ensureRemoteAccessBeforeVerification(node, options, state);
1068
- report.verification = await runRepoVerification(node, options, options.verifyMode ?? "action-first");
1069
- report.verified = report.verification.status === "passed";
1070
- }
1071
- const tagMessage = ensurePackageTagReady(node, options, version, branch, options.workflowRunId);
1072
- report.devTagMetadata = tagMessage?.includes("treeseed-dev-tag: true") ? tagMessage : null;
1073
- const reference = finalizePackageReference(node, version, options);
1074
- const push = pushCurrentBranch(node, options, branch, version);
1075
- await runGitDependencySmoke(node, options, reference);
1457
+ ensureRemoteAccessBeforeVerification(node, options, state);
1458
+ report.verification = await runRepoVerification(node, options, options.verifyMode ?? "action-first");
1459
+ report.verified = report.verification.status === "passed";
1460
+ const tagMessage = reference.tagName ? ensurePackageTagReady(node, options, reference.tagName, branch, options.workflowRunId) : null;
1461
+ void tagMessage;
1076
1462
  report.dependencySpec = reference.spec;
1077
- report.pushed = push.pushed;
1078
1463
  report.skippedReason = "finalized-partial-save";
1079
1464
  report.commitSha = headCommit(node.path);
1080
1465
  state.finalizedCommits.set(node.relativePath, report.commitSha);
1081
- report.publishWait = {
1082
- ...rebase,
1083
- ...push,
1084
- recoveredPartialSave: true
1085
- };
1466
+ await finishRepositorySavePublish(node, options, state, report, {
1467
+ branch,
1468
+ rebase: {
1469
+ ...rebase,
1470
+ recoveredPartialSave: true
1471
+ },
1472
+ reference,
1473
+ tagName: reference.tagName
1474
+ });
1086
1475
  return true;
1087
1476
  }
1088
1477
  function branchModeLabel(branchMode) {
@@ -1115,9 +1504,9 @@ function repoPlanCommands(node, options, plannedVersion, plannedDependencySpec,
1115
1504
  const rootWorkspaceInstall = node.path === options.root && Array.isArray(packageJson?.workspaces);
1116
1505
  if (node.kind === "package" && plannedVersion) {
1117
1506
  commands.push(`update package.json version to ${plannedVersion}`);
1118
- commands.push("npm install --workspaces=false # explicitly refresh changed git-tag dependencies with --force; retry up to 5 times with 60s delay");
1119
- } else if (node.kind === "project" && dependencyUpdates.length > 0 && hasNpmLockfile(node.path)) {
1120
- commands.push(rootWorkspaceInstall ? "npm install # refresh root workspace lockfile against the real checked-in manifest" : "npm install --workspaces=false # refresh project lockfile after internal dependency updates");
1507
+ commands.push("npm install --workspaces=false # explicitly refresh changed git commit dependencies with --force; retry up to 5 times with 60s delay");
1508
+ } else if (node.kind === "project" && (dependencyUpdates.length > 0 || rootWorkspaceInstall && node.submoduleDependencies.length > 0) && hasNpmLockfile(node.path)) {
1509
+ commands.push(rootWorkspaceInstall ? "npm install --package-lock-only --ignore-scripts # refresh root workspace lockfile without installing git dependencies" : "npm install --workspaces=false # refresh project lockfile after internal dependency updates");
1121
1510
  }
1122
1511
  if (hasNpmLockfile(node.path) && (node.kind === "project" || plannedVersion || dependencyUpdates.length > 0 || node.submoduleDependencies.length > 0)) {
1123
1512
  commands.push(rootWorkspaceInstall ? "npm ci --ignore-scripts --dry-run # validate root manifest, workspaces, and lockfile before commit" : "npm ci --ignore-scripts --dry-run --workspaces=false # validate deployment lockfile before commit");
@@ -1128,24 +1517,39 @@ function repoPlanCommands(node, options, plannedVersion, plannedDependencySpec,
1128
1517
  commands.push(
1129
1518
  remoteExists ? `git pull --rebase --recurse-submodules=no origin ${branch}` : `skip pull --rebase # origin/${branch} does not exist yet`
1130
1519
  );
1131
- if (node.kind === "package") {
1132
- const verifyMode = options.verifyMode ?? "action-first";
1133
- if (verifyMode === "skip") {
1134
- commands.push("skip package verification");
1135
- } else if (verifyMode === "local-only") {
1520
+ const verifyMode = options.verifyMode ?? "action-first";
1521
+ if (verifyMode === "skip") {
1522
+ commands.push(node.kind === "package" ? "skip package verification" : "skip project verification");
1523
+ } else if (hasScript(node, "verify:action") || hasScript(node, "verify:local") || hasScript(node, "verify")) {
1524
+ if (verifyMode === "local-only") {
1136
1525
  commands.push("npm run verify:local");
1137
- } else {
1526
+ } else if (hasScript(node, "verify:action")) {
1138
1527
  commands.push("npm run verify:action # fallback to npm run verify:local on failure");
1528
+ } else if (hasScript(node, "verify:local")) {
1529
+ commands.push("npm run verify:local");
1530
+ } else {
1531
+ commands.push("npm run verify");
1139
1532
  }
1140
- if (plannedVersion) {
1141
- commands.push(`git tag -a ${plannedVersion} -m <${plannedVersion.includes("-dev.") ? "dev metadata" : "release"}>`);
1533
+ } else if (manifestVerifyCommand(node, "local") || manifestVerifyCommand(node, "fast")) {
1534
+ const command = verifyMode === "local-only" ? manifestVerifyCommand(node, "local") ?? manifestVerifyCommand(node, "fast") : manifestVerifyCommand(node, "local") ?? manifestVerifyCommand(node, "fast");
1535
+ if (command) commands.push(`${command.command} ${command.args.join(" ")} # treeseed.package.yaml verification`);
1536
+ } else if (node.kind !== "package") {
1537
+ commands.push("skip verification # project repository has no Treeseed verify script");
1538
+ }
1539
+ if (node.kind === "package") {
1540
+ const plansTag = plannedVersion && node.branchMode === "package-release-main";
1541
+ if (plannedVersion && plansTag) {
1542
+ commands.push(`git tag -a ${plannedVersion} -m <release>`);
1142
1543
  commands.push(remoteExists ? `git push origin ${branch} ${plannedVersion}` : `git push -u origin ${branch} ${plannedVersion}`);
1544
+ } else if (plannedVersion) {
1545
+ commands.push(remoteExists ? `git push origin ${branch}` : `git push -u origin ${branch}`);
1143
1546
  if (plannedDependencySpec && node.branchMode === "package-dev-save") {
1144
- commands.push(`smoke install ${plannedDependencySpec}`);
1547
+ commands.push(`npm install ${node.name}@${plannedDependencySpec} --package-lock-only --ignore-scripts --force # validate dependency commit reachability`);
1145
1548
  }
1549
+ } else {
1550
+ commands.push(remoteExists ? `git push origin ${branch}` : `git push -u origin ${branch}`);
1146
1551
  }
1147
1552
  } else {
1148
- commands.push("skip package verification # project repository");
1149
1553
  commands.push(remoteExists ? `git push origin ${branch}` : `git push -u origin ${branch}`);
1150
1554
  }
1151
1555
  return commands;
@@ -1173,7 +1577,7 @@ function planRepositorySave(options) {
1173
1577
  return dependency?.dirty || Boolean(dependency?.plannedVersion);
1174
1578
  });
1175
1579
  const dirty = hasMeaningfulChanges(node.path);
1176
- const packageNeedsVersion = node.kind === "package" && (dirty || dependencyChanged || submoduleChanged);
1580
+ const packageNeedsVersion = canManagePackageJsonVersion(node) && (dirty || dependencyChanged || submoduleChanged);
1177
1581
  const currentVersion = typeof node.packageJson?.version === "string" ? node.packageJson.version : null;
1178
1582
  const plannedVersion = packageNeedsVersion ? selectPackageVersion(node, options).version : null;
1179
1583
  let plannedDependencySpec = null;
@@ -1183,7 +1587,8 @@ function planRepositorySave(options) {
1183
1587
  version: plannedVersion,
1184
1588
  branchMode: node.branchMode === "package-release-main" ? "package-release-main" : "package-dev-save",
1185
1589
  remoteUrl: node.remoteUrl,
1186
- devDependencyReferenceMode: options.devDependencyReferenceMode ?? "git-tag",
1590
+ commitSha: headCommitOrPlanPlaceholder(node.path),
1591
+ devDependencyReferenceMode: options.devDependencyReferenceMode ?? "git-commit",
1187
1592
  gitDependencyProtocol: options.gitDependencyProtocol ?? "preserve-origin"
1188
1593
  });
1189
1594
  plannedDependencySpec = reference.spec;
@@ -1195,7 +1600,7 @@ function planRepositorySave(options) {
1195
1600
  const notes = [
1196
1601
  `${branchModeLabel(node.branchMode)} on top-level ${options.branch}`,
1197
1602
  ...current && current !== branch ? [`current branch ${current} will be switched to ${branch}`] : [],
1198
- ...node.kind === "package" && plannedVersion?.includes("-dev.") ? ["dev Git tag only; publish workflows reject prerelease/dev tags"] : []
1603
+ ...node.kind === "package" && plannedVersion?.includes("-dev.") ? ["development and staging dependency refs use the package commit SHA; no Git tag is created"] : []
1199
1604
  ];
1200
1605
  const repoPlan = {
1201
1606
  id: node.id,
@@ -1212,7 +1617,7 @@ function planRepositorySave(options) {
1212
1617
  submoduleDependencies: node.submoduleDependencies,
1213
1618
  currentVersion,
1214
1619
  plannedVersion,
1215
- plannedTag: plannedVersion,
1620
+ plannedTag: plannedReferences.get(node.name)?.tagName ?? null,
1216
1621
  plannedDependencySpec,
1217
1622
  remoteUrl: node.remoteUrl,
1218
1623
  commands: repoPlanCommands(node, options, plannedVersion, plannedDependencySpec, dependencyUpdates),
@@ -1240,7 +1645,7 @@ function planRepositorySave(options) {
1240
1645
  mode,
1241
1646
  branch: options.branch,
1242
1647
  scope,
1243
- devDependencyReferenceMode: options.devDependencyReferenceMode ?? "git-tag",
1648
+ devDependencyReferenceMode: options.devDependencyReferenceMode ?? "git-commit",
1244
1649
  gitDependencyProtocol: options.gitDependencyProtocol ?? "preserve-origin",
1245
1650
  verifyMode: options.verifyMode ?? "action-first",
1246
1651
  commitMessageMode: options.commitMessageMode ?? "auto",
@@ -1269,6 +1674,7 @@ async function refreshAndValidateRootWorkspaceLockfileForSave(options) {
1269
1674
  packageJsonPath: packageJson ? packageJsonPath : null,
1270
1675
  packageJson,
1271
1676
  scripts: packageScripts(packageJson),
1677
+ manifestVerifyCommands: emptyManifestVerifyCommands(),
1272
1678
  remoteUrl: originRemoteUrlSafe(repoDir),
1273
1679
  dependencies: [],
1274
1680
  dependents: [],
@@ -1297,13 +1703,15 @@ async function saveOneRepository(node, options, state) {
1297
1703
  report.branch = node.branch;
1298
1704
  refreshRepositoryNodePackageMetadata(node);
1299
1705
  ensureWritableRemote(node, options);
1300
- const dependencyUpdates = updateDependencyReferences(node, state.finalizedReferences);
1706
+ const dependencyUpdates = isRootWorkspaceRepository(node, options) ? [] : updateDependencyReferences(node, state.finalizedReferences);
1301
1707
  const dependencyChanged = dependencyUpdates.length > 0;
1302
- const gitDependencyRefreshSpecs = dependencyUpdates.map((update) => state.finalizedReferences.get(update.packageName)).filter((reference) => Boolean(reference) && reference.mode === "dev-git-tag").map((reference) => `${reference.packageName}@${reference.installSpec ?? reference.spec}`);
1708
+ const gitDependencyRefreshReferences = dependencyUpdates.map((update) => state.finalizedReferences.get(update.packageName)).filter((reference) => Boolean(reference) && reference.mode === "dev-git-commit");
1709
+ const lockfileGitDependenciesSynced = syncDirectGitDependencyLockfileEntries(node, options, gitDependencyRefreshReferences);
1710
+ const gitDependencyRefreshSpecs = lockfileGitDependenciesSynced ? [] : gitDependencyRefreshReferences.map((reference) => `${reference.packageName}@${reference.installSpec ?? reference.spec}`);
1303
1711
  const submodulePointers = collectSubmodulePointerChanges(node, state.finalizedCommits);
1304
1712
  const submodulesChanged = submodulePointers.length > 0;
1305
1713
  const packageHasMeaningfulChanges = hasMeaningfulChanges(node.path);
1306
- const packageNeedsVersion = node.kind === "package" && (packageHasMeaningfulChanges || dependencyChanged || submodulesChanged || packageVersionTagConflictsWithHead(node, options));
1714
+ const packageNeedsVersion = canManagePackageJsonVersion(node) && (packageHasMeaningfulChanges || dependencyChanged || submodulesChanged || packageVersionTagConflictsWithHead(node, options));
1307
1715
  let plannedVersion = null;
1308
1716
  if (packageNeedsVersion) {
1309
1717
  const selection = selectPackageVersion(node, options);
@@ -1317,18 +1725,18 @@ async function saveOneRepository(node, options, state) {
1317
1725
  applyPackageVersion(node, plannedVersion);
1318
1726
  }
1319
1727
  node.plannedVersion = plannedVersion;
1320
- node.plannedTag = plannedVersion;
1321
1728
  report.version = plannedVersion;
1322
- report.tagName = plannedVersion;
1323
1729
  if (!selection.reused) {
1324
1730
  emitProgress(options, node, "version", `Planned ${plannedVersion}.`);
1325
1731
  }
1326
1732
  const reference = finalizePackageReference(node, plannedVersion, options);
1733
+ node.plannedTag = reference.tagName;
1734
+ report.tagName = reference.tagName;
1327
1735
  report.dependencySpec = reference.spec;
1328
1736
  report.install = await runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs);
1329
1737
  } else if (node.kind === "package") {
1330
1738
  report.version = String(node.packageJson?.version ?? report.version ?? "");
1331
- } else if (node.kind === "project" && dependencyChanged && hasNpmLockfile(node.path)) {
1739
+ } else if (node.kind === "project" && (dependencyChanged || node.path === options.root && submodulesChanged) && hasNpmLockfile(node.path)) {
1332
1740
  report.install = await runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs);
1333
1741
  }
1334
1742
  if (hasNpmLockfile(node.path) && (node.kind === "project" || packageNeedsVersion || dependencyChanged || submodulesChanged)) {
@@ -1351,14 +1759,9 @@ async function saveOneRepository(node, options, state) {
1351
1759
  return report;
1352
1760
  }
1353
1761
  }
1354
- if (node.id === ".") {
1762
+ if (node.kind === "project" || node.kind === "package" && !canManagePackageJsonVersion(node)) {
1355
1763
  const rebase2 = pullRebaseFromOrigin(node, options, branch);
1356
- const push = pushCurrentBranch(node, options, branch);
1357
- report.pushed = push.pushed;
1358
- report.publishWait = {
1359
- ...rebase2,
1360
- ...push
1361
- };
1764
+ await finishRepositorySavePublish(node, options, state, report, { branch, rebase: rebase2 });
1362
1765
  report.commitSha = headCommit(node.path);
1363
1766
  }
1364
1767
  state.finalizedCommits.set(node.relativePath, report.commitSha);
@@ -1376,14 +1779,9 @@ async function saveOneRepository(node, options, state) {
1376
1779
  return report;
1377
1780
  }
1378
1781
  }
1379
- if (node.id === ".") {
1782
+ if (node.kind === "project" || node.kind === "package" && !canManagePackageJsonVersion(node)) {
1380
1783
  const rebase2 = pullRebaseFromOrigin(node, options, branch);
1381
- const push = pushCurrentBranch(node, options, branch);
1382
- report.pushed = push.pushed;
1383
- report.publishWait = {
1384
- ...rebase2,
1385
- ...push
1386
- };
1784
+ await finishRepositorySavePublish(node, options, state, report, { branch, rebase: rebase2 });
1387
1785
  report.commitSha = headCommit(node.path);
1388
1786
  }
1389
1787
  state.finalizedCommits.set(node.relativePath, report.commitSha);
@@ -1421,14 +1819,9 @@ async function saveOneRepository(node, options, state) {
1421
1819
  return report;
1422
1820
  }
1423
1821
  }
1424
- if (node.id === ".") {
1822
+ if (node.kind === "project" || node.kind === "package" && !canManagePackageJsonVersion(node)) {
1425
1823
  const rebase2 = pullRebaseFromOrigin(node, options, branch);
1426
- const push = pushCurrentBranch(node, options, branch);
1427
- report.pushed = push.pushed;
1428
- report.publishWait = {
1429
- ...rebase2,
1430
- ...push
1431
- };
1824
+ await finishRepositorySavePublish(node, options, state, report, { branch, rebase: rebase2 });
1432
1825
  report.commitSha = headCommit(node.path);
1433
1826
  }
1434
1827
  state.finalizedCommits.set(node.relativePath, report.commitSha);
@@ -1437,35 +1830,31 @@ async function saveOneRepository(node, options, state) {
1437
1830
  report.committed = true;
1438
1831
  const rebase = pullRebaseFromOrigin(node, options, branch);
1439
1832
  const verifyMode = options.verifyMode ?? "action-first";
1833
+ if (node.kind === "project" && node.path === options.root && Array.isArray(node.packageJson?.workspaces)) {
1834
+ const linkReport = ensureLocalWorkspaceLinks(options.root);
1835
+ const restoredLinks = Array.isArray(linkReport.created) ? linkReport.created.length : 0;
1836
+ if (restoredLinks > 0) {
1837
+ emitProgress(options, node, "install", `Restored ${restoredLinks} local workspace package link${restoredLinks === 1 ? "" : "s"} before project verification.`);
1838
+ }
1839
+ }
1440
1840
  if (node.kind === "package") {
1441
1841
  ensureRemoteAccessBeforeVerification(node, options, state);
1442
1842
  }
1443
1843
  report.verification = await runRepoVerification(node, options, verifyMode);
1444
1844
  report.verified = report.verification.status === "passed";
1445
- if (node.kind === "package") {
1845
+ if (canManagePackageJsonVersion(node)) {
1446
1846
  const version = plannedVersion ?? String(readJson(resolve(node.path, "package.json")).version ?? report.version ?? "");
1447
- const tagMessage = ensurePackageTagReady(node, options, version, branch, options.workflowRunId);
1448
- report.tagName = version;
1449
- report.version = version;
1450
- report.devTagMetadata = tagMessage?.includes("treeseed-dev-tag: true") ? tagMessage : null;
1451
1847
  const reference = finalizePackageReference(node, version, options);
1452
- const push = pushCurrentBranch(node, options, branch, version);
1453
- await runGitDependencySmoke(node, options, reference);
1848
+ const tagMessage = reference.tagName ? ensurePackageTagReady(node, options, reference.tagName, branch, options.workflowRunId) : null;
1849
+ void tagMessage;
1850
+ report.tagName = reference.tagName;
1851
+ report.version = version;
1454
1852
  report.dependencySpec = reference.spec;
1455
1853
  state.finalizedVersions.set(node.name, version);
1456
1854
  state.finalizedReferences.set(node.name, reference);
1457
- report.pushed = push.pushed;
1458
- report.publishWait = {
1459
- ...rebase,
1460
- ...push
1461
- };
1855
+ await finishRepositorySavePublish(node, options, state, report, { branch, rebase, reference, tagName: reference.tagName });
1462
1856
  } else {
1463
- const push = pushCurrentBranch(node, options, branch);
1464
- report.pushed = push.pushed;
1465
- report.publishWait = {
1466
- ...rebase,
1467
- ...push
1468
- };
1857
+ await finishRepositorySavePublish(node, options, state, report, { branch, rebase });
1469
1858
  }
1470
1859
  report.commitSha = headCommit(node.path);
1471
1860
  report.skippedReason = null;
@@ -1490,10 +1879,12 @@ async function runRepositorySaveOrchestrator(options) {
1490
1879
  finalizedCommits: /* @__PURE__ */ new Map(),
1491
1880
  reports: new Map(nodes.map((node) => [node.id, createReport(node)])),
1492
1881
  remoteAccessChecked: /* @__PURE__ */ new Set(),
1493
- workflowGates: []
1882
+ workflowGates: [],
1883
+ deferredPushes: []
1494
1884
  };
1885
+ const concurrency = repositorySaveConcurrency(options);
1495
1886
  for (const [index, wave] of waves.entries()) {
1496
- await runLimited(wave, 3, async (node) => {
1887
+ await runLimited(wave, concurrency, async (node) => {
1497
1888
  try {
1498
1889
  await saveOneRepository(node, options, state);
1499
1890
  } catch (error) {
@@ -1554,6 +1945,7 @@ async function runRepositorySaveOrchestrator(options) {
1554
1945
  state.workflowGates.push(...waveGates);
1555
1946
  }
1556
1947
  }
1948
+ await publishDeferredRepositoryPushes(options, state);
1557
1949
  const rootNode = nodes.find((node) => node.id === ".") ?? allNodes.find((node) => node.id === ".");
1558
1950
  const rootReport = rootNode ? state.reports.get(rootNode.id) ?? createReport(rootNode) : createReport({
1559
1951
  id: ".",