supipowers 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +149 -45
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +19 -9
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +298 -127
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +115 -14
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +11 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1401 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -1,11 +1,21 @@
1
+ import path from "node:path";
1
2
  import type { Platform } from "../platform/types.js";
2
3
  import { modelRegistry } from "../config/model-registry-instance.js";
3
4
  import { resolveModelForAction, createModelBridge, applyModelOverride } from "../config/model-resolver.js";
4
5
  import { loadModelConfig } from "../config/model-config.js";
5
- import type { ReleaseChannel, BumpType, ReviewReport, ResolvedModel } from "../types.js";
6
+ import type { ReleaseChannel, ReleaseTarget, BumpType, ReviewReport, ResolvedModel } from "../types.js";
6
7
  import { loadConfig, updateConfig } from "../config/loader.js";
7
8
  import { detectChannels } from "../release/detector.js";
8
9
  import type { ChannelStatus } from "../release/channels/types.js";
10
+ import { resolvePackageManager } from "../workspace/package-manager.js";
11
+ import { resolveRepoRoot } from "../workspace/repo-root.js";
12
+ import { parseTargetArg, resolveRequestedWorkspaceTarget, sortWorkspaceTargetOptions, selectWorkspaceTarget, tokenizeCliArgs, type WorkspaceTargetOption } from "../workspace/selector.js";
13
+ import {
14
+ isWorkspaceTargetLocked,
15
+ releaseWorkspaceTargetLock,
16
+ tryAcquireWorkspaceTargetLock,
17
+ } from "../workspace/locks.js";
18
+ import { discoverReleaseTargets, getPublishableReleaseTargets } from "../release/targets.js";
9
19
  import {
10
20
  parseConventionalCommits,
11
21
  buildChangelogMarkdown,
@@ -15,6 +25,8 @@ import {
15
25
  import {
16
26
  getCurrentVersion,
17
27
  getPublishedPackagePaths,
28
+ getLatestReleaseTag,
29
+ getReleaseTagFormat,
18
30
  suggestBump,
19
31
  bumpVersion,
20
32
  isVersionReleased,
@@ -27,7 +39,15 @@ import { buildPolishPrompt } from "../release/prompt.js";
27
39
  import { notifyInfo, notifySuccess, notifyError } from "../notifications/renderer.js";
28
40
  import { analyzeAndCommit } from "../git/commit.js";
29
41
  import { getWorkingTreeStatus } from "../git/status.js";
30
- import { runStructuredAgentSession } from "../quality/ai-session.js";
42
+ import { runWithOutputValidation, parseStructuredOutput } from "../ai/structured-output.js";
43
+ import { renderSchemaText } from "../ai/schema-text.js";
44
+ import {
45
+ ReleaseNotePolishOutputSchema,
46
+ ReleaseDocFixOutputSchema,
47
+ renderPolishedChangelog,
48
+ type ReleaseNotePolishOutput,
49
+ type ReleaseDocFixOutput,
50
+ } from "../release/contracts.js";
31
51
  import {
32
52
  checkDocDrift,
33
53
  buildFixPrompt,
@@ -50,6 +70,71 @@ const BUMP_OPTIONS = [
50
70
  "major — breaking changes",
51
71
  ];
52
72
 
73
+ interface ParsedReleaseArgs {
74
+ skipPolish: boolean;
75
+ isDryRun: boolean;
76
+ requestedTarget: string | null;
77
+ }
78
+
79
+ export interface ReleaseTargetOption extends WorkspaceTargetOption<ReleaseTarget> {
80
+ lastTag: string | null;
81
+ summary: string;
82
+ }
83
+
84
+
85
+ export function parseReleaseArgs(args?: string): ParsedReleaseArgs {
86
+ const tokens = tokenizeCliArgs(args);
87
+
88
+ return {
89
+ skipPolish: tokens.includes("--raw"),
90
+ isDryRun: tokens.includes("--dry-run"),
91
+ requestedTarget: parseTargetArg(args),
92
+ };
93
+ }
94
+
95
+ export function createReleaseProgressKeys(runId: string): { statusKey: string; widgetKey: string } {
96
+ return {
97
+ statusKey: `supi-release:${runId}`,
98
+ widgetKey: `supi-release:${runId}`,
99
+ };
100
+ }
101
+
102
+ export function resolveRequestedReleaseTarget(
103
+ targets: ReleaseTarget[],
104
+ requestedTarget: string | null,
105
+ ): ReleaseTarget | null {
106
+ return resolveRequestedWorkspaceTarget(targets, requestedTarget);
107
+ }
108
+
109
+ export function sortReleaseTargetOptions(options: ReleaseTargetOption[]): ReleaseTargetOption[] {
110
+ return sortWorkspaceTargetOptions(options) as ReleaseTargetOption[];
111
+ }
112
+
113
+ export function buildReleaseTargetOptionLabel(option: ReleaseTargetOption): string {
114
+ const releaseState = option.lastTag ?? "unreleased";
115
+ const changeState = option.changed
116
+ ? `changed — ${option.summary}`
117
+ : "unchanged — no publishable changes";
118
+
119
+ return `${option.target.name} — ${option.target.relativeDir} — ${releaseState} — ${changeState}`;
120
+ }
121
+
122
+ function createReleaseRunId(): string {
123
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
124
+ }
125
+
126
+ export function tryAcquireReleaseTargetLock(targetId: string): boolean {
127
+ return tryAcquireWorkspaceTargetLock("release", targetId);
128
+ }
129
+
130
+ export function releaseReleaseTargetLock(targetId: string): void {
131
+ releaseWorkspaceTargetLock("release", targetId);
132
+ }
133
+
134
+ export function isReleaseTargetLocked(targetId: string): boolean {
135
+ return isWorkspaceTargetLocked("release", targetId);
136
+ }
137
+
53
138
  /**
54
139
  * Returns true when re-running supi:release should skip the confirmation
55
140
  * dialog and proceed directly to execution.
@@ -211,12 +296,13 @@ export const RELEASE_STEPS = [
211
296
 
212
297
  type ReleaseStepKey = (typeof RELEASE_STEPS)[number]["key"];
213
298
 
214
- function createReleaseProgress(ctx: any) {
299
+ function createReleaseProgress(ctx: any, runId: string) {
300
+ const progressKeys = createReleaseProgressKeys(runId);
215
301
  const progress = createWorkflowProgress(ctx.ui, {
216
302
  title: "supi:release",
217
- statusKey: "supi-release",
303
+ statusKey: progressKeys.statusKey,
218
304
  statusLabel: "Releasing...",
219
- widgetKey: "supi-release",
305
+ widgetKey: progressKeys.widgetKey,
220
306
  clearStatusKeys: ["supi-model"],
221
307
  steps: [...RELEASE_STEPS],
222
308
  });
@@ -254,6 +340,36 @@ function createReleaseProgress(ctx: any) {
254
340
  };
255
341
  }
256
342
 
343
+ function buildPendingReleaseTargetOptions(targets: ReleaseTarget[]): ReleaseTargetOption[] {
344
+ return sortReleaseTargetOptions(
345
+ targets.map((target) => ({
346
+ target,
347
+ changed: false,
348
+ lastTag: null,
349
+ summary: "change analysis pending",
350
+ })),
351
+ );
352
+ }
353
+
354
+ export async function selectReleaseTarget(
355
+ ctx: any,
356
+ options: ReleaseTargetOption[],
357
+ requestedTarget: string | null,
358
+ ): Promise<ReleaseTarget | null> {
359
+ return selectWorkspaceTarget(
360
+ ctx,
361
+ options.map((option) => ({
362
+ ...option,
363
+ label: buildReleaseTargetOptionLabel(option),
364
+ })),
365
+ requestedTarget,
366
+ {
367
+ title: "Release target",
368
+ helpText: "Pick a publishable package to release. Target choice is runtime-only and is not persisted in config.",
369
+ },
370
+ );
371
+ }
372
+
257
373
  /**
258
374
  * Register the command for autocomplete and /help listing.
259
375
  * Actual execution goes through handleRelease via the TUI dispatch.
@@ -283,19 +399,54 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
283
399
  return;
284
400
  }
285
401
 
286
- const progress = createReleaseProgress(ctx);
287
-
288
402
  void (async () => {
403
+ let progress: ReturnType<typeof createReleaseProgress> | null = null;
404
+ let lockedTargetId: string | null = null;
289
405
  try {
290
- const skipPolish = args?.includes("--raw") ?? false;
291
- const isDryRun = args?.includes("--dry-run") ?? false;
292
- const config = loadConfig(platform.paths, ctx.cwd);
406
+ const { skipPolish, isDryRun, requestedTarget } = parseReleaseArgs(args);
407
+ const repoRoot = await resolveRepoRoot(platform, ctx.cwd);
408
+ const packageManager = resolvePackageManager(repoRoot);
409
+ const publishableTargets = getPublishableReleaseTargets(
410
+ discoverReleaseTargets(repoRoot, packageManager.id),
411
+ );
412
+ if (publishableTargets.length === 0) {
413
+ notifyError(ctx, "No publishable release targets found", "Create a non-private package.json with name and version before running /supi:release.");
414
+ return;
415
+ }
416
+
417
+ const selectedTarget = await selectReleaseTarget(
418
+ ctx,
419
+ buildPendingReleaseTargetOptions(publishableTargets),
420
+ requestedTarget,
421
+ );
422
+ if (requestedTarget && !selectedTarget) {
423
+ notifyError(ctx, "Release target not found", requestedTarget);
424
+ return;
425
+ }
426
+ if (!selectedTarget) {
427
+ return;
428
+ }
429
+ if (!tryAcquireReleaseTargetLock(selectedTarget.id)) {
430
+ notifyError(
431
+ ctx,
432
+ "Release already in progress",
433
+ `${selectedTarget.name} is already being released in this session. Wait for that run to finish before retrying.`,
434
+ );
435
+ return;
436
+ }
437
+ lockedTargetId = selectedTarget.id;
438
+
439
+ progress = createReleaseProgress(ctx, createReleaseRunId());
440
+ progress.detail(`Target: ${selectedTarget.name} (${selectedTarget.relativeDir})`);
441
+
442
+ const config = loadConfig(platform.paths, repoRoot);
293
443
  const tagFormat = config.release.tagFormat;
444
+ const effectiveTagFormat = getReleaseTagFormat(selectedTarget, tagFormat);
294
445
  let didStash = false;
295
446
 
296
447
  // ── 1. Quality checks (headless) ────────────────────────────────────
297
448
  progress.activate("checks", "Running quality gates");
298
- const checksReport = await runHeadlessChecks(platform, ctx, config, resolved);
449
+ const checksReport = await runHeadlessChecks(platform, repoRoot, config, resolved, selectedTarget);
299
450
  if (checksReport) {
300
451
  const { summary, overallStatus } = checksReport;
301
452
  const detail = `${summary.passed} passed, ${summary.failed} failed, ${summary.blocked} blocked`;
@@ -325,7 +476,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
325
476
 
326
477
  // ── 2. Doc-drift pre-check ──────────────────────────────────────────
327
478
  progress.activate("doc-drift", "Checking documentation drift");
328
- const driftResult = await checkDocDrift(platform, ctx.cwd);
479
+ const driftResult = await checkDocDrift(platform, repoRoot);
329
480
  if (driftResult?.drifted) {
330
481
  progress.complete("doc-drift", "Drift detected");
331
482
  const driftAction = await ctx.ui.select(
@@ -342,20 +493,30 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
342
493
  notifyInfo(ctx, "Updating documentation", driftResult.summary);
343
494
  const fixPrompt = buildFixPrompt(driftResult.findings);
344
495
  const { loadState: loadDriftState, saveState: saveDriftState, getHeadCommit } = await import("../docs/drift.js");
345
- const driftHead = await getHeadCommit(platform, ctx.cwd);
346
- const driftState = loadDriftState(platform.paths, ctx.cwd);
347
- saveDriftState(platform.paths, ctx.cwd, { ...driftState, lastCommit: driftHead, lastRunAt: new Date().toISOString() });
496
+ const driftHead = await getHeadCommit(platform, repoRoot);
497
+ const driftState = loadDriftState(platform.paths, repoRoot);
498
+ saveDriftState(platform.paths, repoRoot, { ...driftState, lastCommit: driftHead, lastRunAt: new Date().toISOString() });
348
499
 
349
- const fixResult = await runStructuredAgentSession(
500
+ const fixResult = await runWithOutputValidation<ReleaseDocFixOutput>(
350
501
  platform.createAgentSession.bind(platform),
351
- { cwd: ctx.cwd, prompt: fixPrompt },
502
+ {
503
+ cwd: repoRoot,
504
+ prompt: fixPrompt,
505
+ schema: renderSchemaText(ReleaseDocFixOutputSchema),
506
+ parse: (raw) => parseStructuredOutput<ReleaseDocFixOutput>(raw, ReleaseDocFixOutputSchema),
507
+ reliability: { paths: platform.paths, cwd: repoRoot, command: "release", operation: "doc-fix" },
508
+ },
352
509
  );
353
- if (fixResult.status === "ok") {
354
- progress.complete("doc-drift", "Fixed");
355
- notifySuccess(ctx, "Documentation updated");
510
+ if (fixResult.status === "ok" && fixResult.output.status === "ok") {
511
+ progress.complete("doc-drift", `Fixed — ${fixResult.output.edits.length} file(s)`);
512
+ notifySuccess(ctx, "Documentation updated", fixResult.output.summary);
356
513
  } else {
357
- progress.fail("doc-drift", fixResult.error ?? "Agent session error");
358
- notifyError(ctx, "Doc update failed", fixResult.error ?? "Agent session error");
514
+ const detail =
515
+ fixResult.status === "blocked"
516
+ ? fixResult.error
517
+ : `Doc fixer blocked: ${fixResult.output.summary}`;
518
+ progress.fail("doc-drift", detail);
519
+ notifyError(ctx, "Doc update failed", detail);
359
520
  progress.dispose();
360
521
  return;
361
522
  }
@@ -368,7 +529,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
368
529
 
369
530
  // ── 3. Check for uncommitted changes after preflight side effects ─────
370
531
  progress.activate("working-tree", "Checking working tree");
371
- const treeStatus = await getWorkingTreeStatus(platform.exec.bind(platform), ctx.cwd);
532
+ const treeStatus = await getWorkingTreeStatus(platform.exec.bind(platform), repoRoot);
372
533
  if (treeStatus.dirty) {
373
534
  progress.complete("working-tree", `${treeStatus.files.length} files changed`);
374
535
  const filePreview = treeStatus.files.slice(0, 8).join(", ");
@@ -396,7 +557,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
396
557
  notifyError(ctx, "Commit failed or cancelled", "Aborting release.");
397
558
  return;
398
559
  }
399
- const afterStatus = await getWorkingTreeStatus(platform.exec.bind(platform), ctx.cwd);
560
+ const afterStatus = await getWorkingTreeStatus(platform.exec.bind(platform), repoRoot);
400
561
  if (afterStatus.dirty) {
401
562
  notifyError(ctx, "Still uncommitted changes", "Not all changes were committed. Aborting release.");
402
563
  return;
@@ -404,7 +565,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
404
565
  progress.complete("working-tree", "Committed");
405
566
  } else if (action.startsWith("stash")) {
406
567
  progress.activate("working-tree", "Stashing changes");
407
- const stashResult = await platform.exec("git", ["stash", "push", "-m", "supi:release auto-stash"], { cwd: ctx.cwd });
568
+ const stashResult = await platform.exec("git", ["stash", "push", "-m", "supi:release auto-stash"], { cwd: repoRoot });
408
569
  if (stashResult.code !== 0) {
409
570
  progress.fail("working-tree", stashResult.stderr || "stash failed");
410
571
  progress.dispose();
@@ -421,7 +582,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
421
582
  // ── 4. Ensure channels are configured (or detect + ask) ─────────────
422
583
  progress.activate("channels", "Detecting channels");
423
584
  const customChannels = config.release.customChannels ?? {};
424
- const detectedChannels = await detectChannels(platform.exec.bind(platform), ctx.cwd, customChannels);
585
+ const detectedChannels = await detectChannels(platform.exec.bind(platform), repoRoot, customChannels);
425
586
  let channels = config.release.channels;
426
587
  // Track whether channels were already set in config before any interactive
427
588
  // setup. This distinguishes "user already decided" from "just configured now",
@@ -441,7 +602,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
441
602
 
442
603
  if (channels.length === 0) {
443
604
  progress.complete("channels", "Awaiting selection");
444
- channels = await setupChannels(platform, ctx, detectedChannels);
605
+ channels = await setupChannels(platform, ctx, repoRoot, detectedChannels);
445
606
  if (channels.length === 0) {
446
607
  progress.dispose();
447
608
  return;
@@ -451,38 +612,38 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
451
612
 
452
613
  // ── 5. Get last tag + current version + check if bump is needed ─────
453
614
  progress.activate("commits", "Parsing git history");
454
- const lastTag = await getLastTag(platform, ctx.cwd);
455
- const currentVersion = getCurrentVersion(ctx.cwd);
615
+ const lastTag = await getLatestReleaseTag(
616
+ platform.exec.bind(platform),
617
+ selectedTarget,
618
+ tagFormat,
619
+ );
620
+ const currentVersion = getCurrentVersion(selectedTarget);
456
621
  const resumableLocalRelease = await findResumableLocalRelease(
457
622
  platform.exec.bind(platform),
458
- ctx.cwd,
623
+ selectedTarget,
459
624
  currentVersion,
460
625
  tagFormat,
461
626
  );
462
627
  const localTagExists = await isVersionReleased(
463
628
  platform.exec.bind(platform),
464
- ctx.cwd,
629
+ selectedTarget,
465
630
  currentVersion,
466
631
  tagFormat,
467
632
  );
468
633
  const remoteTagExists = localTagExists
469
- ? await isTagOnRemote(platform.exec.bind(platform), ctx.cwd, currentVersion, tagFormat)
634
+ ? await isTagOnRemote(platform.exec.bind(platform), selectedTarget, currentVersion, tagFormat)
470
635
  : false;
471
636
 
472
637
  const sinceArg = lastTag ? `${lastTag}..HEAD` : "HEAD~50..HEAD";
473
- const releaseScope = getPublishedPackagePaths(ctx.cwd);
474
- const gitLogArgs = releaseScope
475
- ? ["log", sinceArg, "--format=%x1e%H%x1f%s", "--name-only"]
476
- : ["log", sinceArg, "--oneline"];
638
+ const releaseScope = getPublishedPackagePaths(selectedTarget);
639
+ const gitLogArgs = ["log", sinceArg, "--format=%x1e%H%x1f%s", "--name-only"];
477
640
  let gitLogOutput: string;
478
641
  try {
479
- const result = await platform.exec("git", gitLogArgs, { cwd: ctx.cwd });
642
+ const result = await platform.exec("git", gitLogArgs, { cwd: repoRoot });
480
643
  if (result.code !== 0) {
481
644
  gitLogOutput = "";
482
- } else if (releaseScope) {
483
- gitLogOutput = filterOnelineGitLogToPaths(result.stdout, releaseScope);
484
645
  } else {
485
- gitLogOutput = result.stdout;
646
+ gitLogOutput = filterOnelineGitLogToPaths(result.stdout, releaseScope);
486
647
  }
487
648
  } catch {
488
649
  gitLogOutput = "";
@@ -535,14 +696,14 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
535
696
  if (!localTagExists && currentVersion !== "0.0.0") {
536
697
  nextVersion = currentVersion;
537
698
  skipBump = true;
538
- progress.skip("version", `${formatTag(currentVersion, tagFormat)} (already set, not yet released)`);
539
- notifyInfo(ctx, `Using ${formatTag(currentVersion, tagFormat)}`, "Version not yet released — skipping bump");
699
+ progress.skip("version", `${formatTag(currentVersion, effectiveTagFormat)} (already set, not yet released)`);
700
+ notifyInfo(ctx, `Using ${formatTag(currentVersion, effectiveTagFormat)}`, "Version not yet released — skipping bump");
540
701
  } else if (localTagExists && !remoteTagExists) {
541
702
  nextVersion = currentVersion;
542
703
  skipBump = true;
543
704
  skipTag = true;
544
- progress.skip("version", `${formatTag(currentVersion, tagFormat)} (tag exists locally, not pushed)`);
545
- notifyInfo(ctx, `Resuming ${formatTag(currentVersion, tagFormat)}`, "Tag exists locally but not on remote — will push");
705
+ progress.skip("version", `${formatTag(currentVersion, effectiveTagFormat)} (tag exists locally, not pushed)`);
706
+ notifyInfo(ctx, `Resuming ${formatTag(currentVersion, effectiveTagFormat)}`, "Tag exists locally but not on remote — will push");
546
707
  } else {
547
708
  progress.activate("version", "Awaiting version selection");
548
709
  const suggested = suggestBump(commits);
@@ -564,7 +725,7 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
564
725
  }
565
726
  // ── 8. Build changelog ──────────────────────────────────────────────
566
727
  progress.activate("changelog", "Generating changelog");
567
- const rawChangelog = buildChangelogMarkdown(commits, nextVersion, tagFormat);
728
+ const rawChangelog = buildChangelogMarkdown(commits, nextVersion, effectiveTagFormat);
568
729
  progress.complete("changelog", `${commitCount} entries`);
569
730
 
570
731
  // ── 9. Polish release notes (default, skip with --raw) ──────────────
@@ -574,19 +735,27 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
574
735
  changelog = rawChangelog;
575
736
  } else {
576
737
  progress.activate("polish", "Polishing release notes");
577
- const polishPrompt = buildPolishPrompt({ changelog: rawChangelog, version: nextVersion, tagFormat });
578
- const polishResult = await runStructuredAgentSession(
738
+ const polishPrompt = buildPolishPrompt({ changelog: rawChangelog, version: nextVersion, tagFormat: effectiveTagFormat });
739
+ const polishResult = await runWithOutputValidation<ReleaseNotePolishOutput>(
579
740
  platform.createAgentSession.bind(platform),
580
- { cwd: ctx.cwd, prompt: polishPrompt },
741
+ {
742
+ cwd: repoRoot,
743
+ prompt: polishPrompt,
744
+ schema: renderSchemaText(ReleaseNotePolishOutputSchema),
745
+ parse: (raw) => parseStructuredOutput<ReleaseNotePolishOutput>(raw, ReleaseNotePolishOutputSchema),
746
+ reliability: { paths: platform.paths, cwd: repoRoot, command: "release", operation: "note-polish" },
747
+ },
581
748
  );
582
749
  if (polishResult.status === "ok") {
583
- changelog = polishResult.finalText;
584
- progress.complete("polish", "Polished");
750
+ changelog = renderPolishedChangelog(polishResult.output);
751
+ progress.complete("polish", polishResult.output.status === "empty" ? "No notable changes" : "Polished");
585
752
  } else {
586
- // Polish failed — fall back to raw changelog, don't block the release
587
- changelog = rawChangelog;
588
- progress.fail("polish", polishResult.error ?? "Agent error — using raw changelog");
589
- notifyInfo(ctx, "Polish failed — using raw changelog", polishResult.error ?? "");
753
+ // Contract validation failed — halt truthfully rather than ship
754
+ // an unreviewed or ambiguous changelog.
755
+ progress.fail("polish", polishResult.error);
756
+ notifyError(ctx, "Release-note polish failed validation", polishResult.error);
757
+ progress.dispose();
758
+ return;
590
759
  }
591
760
  }
592
761
 
@@ -597,15 +766,15 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
597
766
  if (isResume) {
598
767
  notifyInfo(
599
768
  ctx,
600
- `Resuming release ${formatTag(nextVersion, tagFormat)}`,
769
+ `Resuming release ${formatTag(nextVersion, effectiveTagFormat)}`,
601
770
  "Version staged and channels configured — proceeding without confirmation",
602
771
  );
603
772
  } else {
604
- const confirmLabel = isDryRun ? `[DRY RUN] Ship ${formatTag(nextVersion, tagFormat)}?` : `Ship ${formatTag(nextVersion, tagFormat)}?`;
773
+ const confirmLabel = isDryRun ? `[DRY RUN] Ship ${formatTag(nextVersion, effectiveTagFormat)}?` : `Ship ${formatTag(nextVersion, effectiveTagFormat)}?`;
605
774
  // When skipBump=true, currentVersion === nextVersion — avoid the
606
775
  // misleading "0.5.0 → 0.5.0" display.
607
776
  const versionLine = skipBump
608
- ? `${formatTag(nextVersion, tagFormat)} (staged, not yet released)`
777
+ ? `${formatTag(nextVersion, effectiveTagFormat)} (staged, not yet released)`
609
778
  : `${currentVersion} \u2192 ${nextVersion}`;
610
779
  const confirmDetail = [
611
780
  versionLine,
@@ -631,14 +800,15 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
631
800
  try {
632
801
  let result = await executeRelease({
633
802
  exec: platform.exec.bind(platform),
634
- cwd: ctx.cwd,
803
+ cwd: repoRoot,
804
+ target: selectedTarget,
635
805
  version: nextVersion,
636
806
  changelog,
637
807
  channels,
638
808
  dryRun: isDryRun,
639
809
  skipBump,
640
810
  skipTag,
641
- tagFormat,
811
+ tagFormat: effectiveTagFormat,
642
812
  customChannels,
643
813
  onProgress: progress.executorProgress(),
644
814
  });
@@ -649,14 +819,15 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
649
819
  progress.activate("execute", `Retrying with GitHub account ${switchedTo}`);
650
820
  result = await executeRelease({
651
821
  exec: platform.exec.bind(platform),
652
- cwd: ctx.cwd,
822
+ cwd: repoRoot,
823
+ target: selectedTarget,
653
824
  version: nextVersion,
654
825
  changelog,
655
826
  channels,
656
827
  dryRun: false,
657
828
  skipBump: true,
658
829
  skipTag: true,
659
- tagFormat,
830
+ tagFormat: effectiveTagFormat,
660
831
  customChannels,
661
832
  onProgress: progress.executorProgress(),
662
833
  });
@@ -696,14 +867,14 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
696
867
  const prefix = isDryRun ? "[DRY RUN] " : "";
697
868
  notifySuccess(
698
869
  ctx,
699
- `${prefix}Released ${formatTag(nextVersion, tagFormat)}`,
870
+ `${prefix}Released ${formatTag(nextVersion, effectiveTagFormat)}`,
700
871
  `Tag: ${result.tagCreated ? "✓" : "✗"} | Push: ${result.pushed ? "✓" : "✗"} | ${channelSummary}`,
701
872
  );
702
873
  } else {
703
874
  const detail = result.error
704
875
  ? result.error
705
876
  : `Tag: ${result.tagCreated ? "✓" : "✗"} | Push: ${result.pushed ? "✓" : "✗"}`;
706
- notifyError(ctx, `Release ${formatTag(nextVersion, tagFormat)} failed`, detail);
877
+ notifyError(ctx, `Release ${formatTag(nextVersion, effectiveTagFormat)} failed`, detail);
707
878
  }
708
879
  } catch (err) {
709
880
  progress.fail("execute", err instanceof Error ? err.message : "Unknown error");
@@ -716,16 +887,19 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
716
887
  );
717
888
  } finally {
718
889
  if (didStash) {
719
- const popResult = await platform.exec("git", ["stash", "pop"], { cwd: ctx.cwd });
890
+ const popResult = await platform.exec("git", ["stash", "pop"], { cwd: repoRoot });
720
891
  if (popResult.code !== 0) {
721
892
  notifyError(ctx, "Stash pop failed", "Run 'git stash pop' manually to recover your changes");
722
893
  }
723
894
  }
724
895
  }
725
896
  } catch (err) {
726
- progress.dispose();
897
+ progress?.dispose();
727
898
  notifyError(ctx, "Release error", err instanceof Error ? err.message : String(err));
728
899
  } finally {
900
+ if (lockedTargetId) {
901
+ releaseReleaseTargetLock(lockedTargetId);
902
+ }
729
903
  await modelCleanup();
730
904
  }
731
905
  })();
@@ -739,9 +913,10 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
739
913
  */
740
914
  async function runHeadlessChecks(
741
915
  platform: Platform,
742
- ctx: any,
916
+ repoRoot: string,
743
917
  config: ReturnType<typeof loadConfig>,
744
918
  resolved: ResolvedModel,
919
+ selectedTarget: ReleaseTarget,
745
920
  ): Promise<ReviewReport | null> {
746
921
  const enabledGates = Object.entries(config.quality.gates)
747
922
  .filter(([, gate]) => gate?.enabled === true);
@@ -750,9 +925,24 @@ async function runHeadlessChecks(
750
925
  return null;
751
926
  }
752
927
 
928
+ const repoWideTarget = {
929
+ id: "repo-root",
930
+ name: path.basename(repoRoot) || "repo-root",
931
+ kind: "root" as const,
932
+ repoRoot,
933
+ packageDir: repoRoot,
934
+ manifestPath: path.join(repoRoot, "package.json"),
935
+ relativeDir: ".",
936
+ version: selectedTarget.version,
937
+ private: false,
938
+ packageManager: selectedTarget.packageManager,
939
+ };
940
+
753
941
  return runQualityGates({
754
942
  platform,
755
- cwd: ctx.cwd,
943
+ cwd: repoRoot,
944
+ target: repoWideTarget,
945
+ workspaceTargets: [repoWideTarget],
756
946
  gates: config.quality.gates,
757
947
  filters: {},
758
948
  reviewModel: resolved,
@@ -760,18 +950,11 @@ async function runHeadlessChecks(
760
950
  });
761
951
  }
762
952
 
763
- async function getLastTag(platform: Platform, cwd: string): Promise<string | null> {
764
- try {
765
- const result = await platform.exec("git", ["describe", "--tags", "--abbrev=0"], { cwd });
766
- return result.code === 0 ? result.stdout.trim() : null;
767
- } catch {
768
- return null;
769
- }
770
- }
771
953
 
772
954
  async function setupChannels(
773
955
  platform: Platform,
774
956
  ctx: any,
957
+ configCwd: string,
775
958
  detected: ChannelStatus[],
776
959
  ): Promise<ReleaseChannel[]> {
777
960
  const availableChannels = detected.filter((channel) => channel.available);
@@ -825,7 +1008,7 @@ async function setupChannels(
825
1008
  }
826
1009
 
827
1010
  // Persist to config
828
- updateConfig(platform.paths, ctx.cwd, { release: { channels: selected } });
1011
+ updateConfig(platform.paths, configCwd, { release: { channels: selected } });
829
1012
  ctx.ui.notify(`Release channels set to: ${selected.join(", ")}`, "info");
830
1013
 
831
1014
  return selected;