supipowers 1.5.3 → 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 +131 -42
  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 +18 -8
  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 +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  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 +1 -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 +1399 -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
@@ -9,18 +9,53 @@ export const LspDiagnosticsGateConfigSchema = Type.Object(
9
9
  { additionalProperties: false },
10
10
  );
11
11
 
12
+ export const CommandGateRunTargetSchema = Type.Union([
13
+ Type.Object(
14
+ {
15
+ scope: Type.Literal("all-targets"),
16
+ },
17
+ { additionalProperties: false },
18
+ ),
19
+ Type.Object(
20
+ {
21
+ scope: Type.Literal("root"),
22
+ },
23
+ { additionalProperties: false },
24
+ ),
25
+ Type.Object(
26
+ {
27
+ scope: Type.Literal("all-workspaces"),
28
+ },
29
+ { additionalProperties: false },
30
+ ),
31
+ Type.Object(
32
+ {
33
+ scope: Type.Literal("workspace"),
34
+ relativeDir: Type.String({ minLength: 1 }),
35
+ },
36
+ { additionalProperties: false },
37
+ ),
38
+ ]);
39
+
40
+ export const CommandGateRunSchema = Type.Object(
41
+ {
42
+ command: Type.String({ minLength: 1 }),
43
+ target: CommandGateRunTargetSchema,
44
+ },
45
+ { additionalProperties: false },
46
+ );
47
+
12
48
  export const CommandGateConfigSchema = Type.Union([
13
49
  Type.Object(
14
50
  {
15
51
  enabled: Type.Literal(false),
16
- command: Type.Optional(Type.Union([Type.String(), Type.Null()])),
17
52
  },
18
53
  { additionalProperties: false },
19
54
  ),
20
55
  Type.Object(
21
56
  {
22
57
  enabled: Type.Literal(true),
23
- command: Type.String({ minLength: 1 }),
58
+ runs: Type.Array(CommandGateRunSchema, { minItems: 1 }),
24
59
  },
25
60
  { additionalProperties: false },
26
61
  ),
@@ -4,6 +4,7 @@ import type { Platform, PlatformContext } from "../platform/types.js";
4
4
  import type {
5
5
  ConfigScope,
6
6
  ProjectFacts,
7
+ ProjectFactsTarget,
7
8
  QualityGatesConfig,
8
9
  SetupGatesResult,
9
10
  SetupProposal,
@@ -11,8 +12,10 @@ import type {
11
12
  import type { InspectionLoadResult } from "../config/schema.js";
12
13
  import { validateQualityGates } from "../config/schema.js";
13
14
  import { writeQualityGatesConfig } from "../config/loader.js";
15
+ import { resolvePackageManager } from "../workspace/package-manager.js";
16
+ import { discoverWorkspaceTargets } from "../workspace/targets.js";
14
17
  import { CANONICAL_GATE_ORDER } from "./registry.js";
15
- import { detectReviewGates } from "./review-gates.js";
18
+ import { collectReviewGateNotes, detectReviewGates } from "./review-gates.js";
16
19
  import { suggestQualityGatesWithAi } from "./ai-setup.js";
17
20
 
18
21
  export type GateSetupMode = "deterministic" | "ai-assisted";
@@ -63,17 +66,70 @@ function detectLockfiles(cwd: string): string[] {
63
66
  .filter((file) => fs.existsSync(path.join(cwd, file)));
64
67
  }
65
68
 
69
+ function normalizeScriptCommand(command: string | undefined): string | null {
70
+ const trimmed = command?.trim();
71
+ return trimmed ? trimmed : null;
72
+ }
73
+
74
+ function collectProjectTargets(cwd: string): ProjectFactsTarget[] {
75
+ const packageManager = resolvePackageManager(cwd).id;
76
+ const targets = discoverWorkspaceTargets(cwd, packageManager);
77
+ if (targets.length === 0) {
78
+ return [
79
+ {
80
+ name: path.basename(cwd) || "root",
81
+ kind: "root",
82
+ relativeDir: ".",
83
+ packageScripts: readPackageScripts(cwd),
84
+ },
85
+ ];
86
+ }
87
+
88
+ return targets.map((target) => ({
89
+ name: target.name,
90
+ kind: target.kind,
91
+ relativeDir: target.relativeDir,
92
+ packageScripts: readPackageScripts(target.packageDir),
93
+ }));
94
+ }
95
+
96
+ function collectSharedPackageScripts(targets: ProjectFactsTarget[]): Record<string, string> {
97
+ if (targets.length === 0) {
98
+ return {};
99
+ }
100
+
101
+ const shared = { ...targets[0].packageScripts };
102
+ for (const scriptName of Object.keys(shared)) {
103
+ const command = normalizeScriptCommand(shared[scriptName]);
104
+ if (!command) {
105
+ delete shared[scriptName];
106
+ continue;
107
+ }
108
+
109
+ const matchesEveryTarget = targets.slice(1).every(
110
+ (target) => normalizeScriptCommand(target.packageScripts[scriptName]) === command,
111
+ );
112
+ if (!matchesEveryTarget) {
113
+ delete shared[scriptName];
114
+ }
115
+ }
116
+
117
+ return shared;
118
+ }
119
+
66
120
  export function collectProjectFacts(
67
121
  cwd: string,
68
122
  inspection: InspectionLoadResult,
69
123
  activeTools: string[],
70
124
  ): ProjectFacts {
125
+ const targets = collectProjectTargets(cwd);
71
126
  return {
72
127
  cwd,
73
- packageScripts: readPackageScripts(cwd),
128
+ packageScripts: collectSharedPackageScripts(targets),
74
129
  lockfiles: detectLockfiles(cwd),
75
130
  activeTools,
76
131
  existingGates: inspection.effectiveConfig?.quality.gates ?? {},
132
+ targets,
77
133
  };
78
134
  }
79
135
 
@@ -87,17 +143,34 @@ export function formatGateProposal(proposal: SetupProposal): string {
87
143
  const entries = CANONICAL_GATE_ORDER
88
144
  .filter((gateId) => proposal.gates[gateId] !== undefined)
89
145
  .map((gateId) => [gateId, proposal.gates[gateId]] as const);
146
+ const sections: string[] = [];
147
+
90
148
  if (entries.length === 0) {
91
- return "No gates suggested.";
149
+ sections.push("No gates suggested.");
150
+ } else {
151
+ sections.push(
152
+ entries
153
+ .map(([gateId, config]) => `${gateId}: ${JSON.stringify(config)}`)
154
+ .join("\n"),
155
+ );
156
+ }
157
+
158
+ if (proposal.notes && proposal.notes.length > 0) {
159
+ sections.push([
160
+ "Notes:",
161
+ ...proposal.notes.map((note) => `- ${note}`),
162
+ ].join("\n"));
92
163
  }
93
164
 
94
- return entries
95
- .map(([gateId, config]) => `${gateId}: ${JSON.stringify(config)}`)
96
- .join("\n");
165
+ return sections.join("\n\n");
97
166
  }
98
167
 
99
168
  export function buildDeterministicSuggestion(projectFacts: ProjectFacts): SetupProposal {
100
- return { gates: detectReviewGates(projectFacts) };
169
+ const notes = collectReviewGateNotes(projectFacts);
170
+ return {
171
+ gates: detectReviewGates(projectFacts),
172
+ ...(notes.length > 0 ? { notes } : {}),
173
+ };
101
174
  }
102
175
 
103
176
  export async function setupGates(
@@ -122,6 +195,7 @@ export async function setupGates(
122
195
  projectFacts,
123
196
  proposal,
124
197
  }),
198
+ ...(proposal.notes && proposal.notes.length > 0 ? { notes: proposal.notes } : {}),
125
199
  };
126
200
  options.onProgress?.({ type: "ai-analysis-completed" });
127
201
  }
@@ -141,29 +215,32 @@ export async function setupGates(
141
215
  };
142
216
  }
143
217
 
144
- function parseRevisedProposal(raw: string): SetupProposal {
218
+ function parseRevisedProposal(raw: string): QualityGatesConfig {
145
219
  const parsed = JSON.parse(raw) as QualityGatesConfig;
146
220
  const validation = validateQualityGates(parsed);
147
221
  if (!validation.valid) {
148
222
  throw new Error(validation.errors.join("\n"));
149
223
  }
150
224
 
151
- return { gates: parsed };
225
+ return parsed;
152
226
  }
153
227
 
154
228
  function labelForScope(scope: ConfigScope): string {
155
- return scope === "project"
156
- ? "Project (.omp/supipowers/config.json)"
157
- : "Global (~/.omp/supipowers/config.json)";
229
+ switch (scope) {
230
+ case "global":
231
+ return "Global (~/.omp/supipowers/config.json)";
232
+ case "root":
233
+ return "Repository (.omp/supipowers/config.json)";
234
+ }
158
235
  }
159
236
 
160
237
  async function selectSaveScope(ctx: PlatformContext): Promise<ConfigScope | null> {
161
238
  const choice = await ctx.ui.select(
162
239
  "Save quality gates to",
163
- [labelForScope("project"), labelForScope("global"), "Cancel"],
240
+ [labelForScope("root"), labelForScope("global"), "Cancel"],
164
241
  {
165
242
  initialIndex: 0,
166
- helpText: "Choose whether review gates apply only to this project or all projects.",
243
+ helpText: "Choose whether review gates apply only to this repository or all repositories.",
167
244
  },
168
245
  );
169
246
 
@@ -171,7 +248,7 @@ async function selectSaveScope(ctx: PlatformContext): Promise<ConfigScope | null
171
248
  return null;
172
249
  }
173
250
 
174
- return choice === labelForScope("global") ? "global" : "project";
251
+ return choice === labelForScope("global") ? "global" : "root";
175
252
  }
176
253
 
177
254
  function buildProposalHelpText(proposal: SetupProposal, options?: GateSetupDialogOptions): string {
@@ -209,7 +286,10 @@ export async function interactivelySaveGateSetup(
209
286
  }
210
287
 
211
288
  try {
212
- proposal = parseRevisedProposal(revised);
289
+ proposal = {
290
+ ...proposal,
291
+ gates: parseRevisedProposal(revised),
292
+ };
213
293
  } catch (error) {
214
294
  ctx.ui.notify((error as Error).message, "error");
215
295
  }
@@ -57,7 +57,7 @@ function parseGitLogWithFiles(gitLog: string): GitCommitWithFiles[] {
57
57
 
58
58
  function isPathInReleaseScope(filePath: string, releaseScope: string[]): boolean {
59
59
  const normalizedFile = normalizeReleasePath(filePath);
60
- return releaseScope.some((scopePath) => normalizedFile === scopePath || normalizedFile.startsWith(`${scopePath}/`));
60
+ return releaseScope.some((scopePath) => scopePath === "." || normalizedFile === scopePath || normalizedFile.startsWith(`${scopePath}/`));
61
61
  }
62
62
 
63
63
  /**
@@ -1,5 +1,6 @@
1
1
  // src/release/channels/custom.ts — Wraps user-defined custom channel config into a ChannelHandler
2
2
  import type { CustomChannelConfig } from "../../types.js";
3
+ import { execShellCommand } from "../../utils/shell.js";
3
4
  import type { ChannelHandler, ChannelPublishContext, ChannelStatus, ExecFn } from "./types.js";
4
5
 
5
6
 
@@ -13,7 +14,7 @@ export function createCustomHandler(id: string, config: CustomChannelConfig): Ch
13
14
  return { channel: id, available: true, detail: "No detect command configured — assumed available" };
14
15
  }
15
16
  try {
16
- const result = await exec("sh", ["-c", config.detectCommand], { cwd });
17
+ const result = await execShellCommand(exec, config.detectCommand, { cwd });
17
18
  if (result.code === 0) {
18
19
  return { channel: id, available: true, detail: `Detect command succeeded` };
19
20
  }
@@ -27,9 +28,18 @@ export function createCustomHandler(id: string, config: CustomChannelConfig): Ch
27
28
  try {
28
29
  // `${tag}`/`${version}`/`${changelog}` stay in the shell template, but
29
30
  // their values cross the shell boundary via environment variables.
30
- const result = await exec("sh", ["-c", config.publishCommand], {
31
+ const result = await execShellCommand(exec, config.publishCommand, {
31
32
  cwd: ctx.cwd,
32
- env: { tag: ctx.tag, version: ctx.version, changelog: ctx.changelog },
33
+ env: {
34
+ tag: ctx.tag,
35
+ version: ctx.version,
36
+ changelog: ctx.changelog,
37
+ targetName: ctx.targetName,
38
+ targetId: ctx.targetId,
39
+ targetPath: ctx.targetPath,
40
+ manifestPath: ctx.manifestPath,
41
+ packageManager: ctx.packageManager,
42
+ },
33
43
  });
34
44
  if (result.code !== 0) {
35
45
  return { success: false, error: result.stderr || result.stdout || `Custom channel '${id}' exited with code ${result.code}` };
@@ -17,6 +17,11 @@ export interface ChannelPublishContext {
17
17
  tag: string;
18
18
  changelog: string;
19
19
  cwd: string;
20
+ targetName: string;
21
+ targetId: string;
22
+ targetPath: string;
23
+ manifestPath: string;
24
+ packageManager: string;
20
25
  }
21
26
 
22
27
  export interface ChannelHandler {
@@ -0,0 +1,90 @@
1
+ // src/release/contracts.ts
2
+ //
3
+ // Schema-backed contracts for AI-assisted release subflows. The release
4
+ // command has two AI sessions that must not degrade into free-form text:
5
+ //
6
+ // 1. Release-note polish — the model rewrites the raw changelog into
7
+ // user-facing release notes. Returns a structured artifact rather
8
+ // than arbitrary markdown so the caller can reason about title,
9
+ // body, and highlights independently.
10
+ // 2. Release doc-fix — when doc-drift is detected before a release,
11
+ // the fixer agent applies documentation edits and reports back a
12
+ // structured summary of what it changed (or declares `blocked`).
13
+ //
14
+ // Both schemas flow through runWithOutputValidation so the retry loop
15
+ // hands validation errors back to the model rather than letting the
16
+ // release command publish on malformed output.
17
+
18
+ import { Type, type Static } from "@sinclair/typebox";
19
+
20
+ // ── Release-note polish ───────────────────────────────────────
21
+
22
+ export const RELEASE_NOTE_STATUSES = ["ok", "empty"] as const;
23
+ export type ReleaseNoteStatus = (typeof RELEASE_NOTE_STATUSES)[number];
24
+
25
+ export const ReleaseNotePolishOutputSchema = Type.Object(
26
+ {
27
+ title: Type.String({ minLength: 1 }),
28
+ body: Type.String(),
29
+ highlights: Type.Array(Type.String({ minLength: 1 })),
30
+ status: Type.Union(RELEASE_NOTE_STATUSES.map((value) => Type.Literal(value))),
31
+ },
32
+ { additionalProperties: false },
33
+ );
34
+
35
+ export type ReleaseNotePolishOutput = Static<typeof ReleaseNotePolishOutputSchema>;
36
+
37
+ // ── Release doc-fix ───────────────────────────────────────────
38
+
39
+ export const RELEASE_DOC_FIX_STATUSES = ["ok", "blocked"] as const;
40
+ export type ReleaseDocFixStatus = (typeof RELEASE_DOC_FIX_STATUSES)[number];
41
+
42
+ export const ReleaseDocFixEditSchema = Type.Object(
43
+ {
44
+ file: Type.String({ minLength: 1 }),
45
+ instructions: Type.String({ minLength: 1 }),
46
+ },
47
+ { additionalProperties: false },
48
+ );
49
+
50
+ export const ReleaseDocFixOutputSchema = Type.Object(
51
+ {
52
+ edits: Type.Array(ReleaseDocFixEditSchema),
53
+ summary: Type.String({ minLength: 1 }),
54
+ status: Type.Union(RELEASE_DOC_FIX_STATUSES.map((value) => Type.Literal(value))),
55
+ },
56
+ { additionalProperties: false },
57
+ );
58
+
59
+ export type ReleaseDocFixEdit = Static<typeof ReleaseDocFixEditSchema>;
60
+ export type ReleaseDocFixOutput = Static<typeof ReleaseDocFixOutputSchema>;
61
+
62
+ /**
63
+ * Render a polished release-note artifact as markdown suitable for
64
+ * surface to the user and execution by the release pipeline. Produces a
65
+ * level-2 title, optional highlight bullets, and the grouped body.
66
+ */
67
+ export function renderPolishedChangelog(output: ReleaseNotePolishOutput): string {
68
+ if (output.status === "empty") {
69
+ return output.body.trim().length > 0
70
+ ? output.body.trim()
71
+ : "_No notable changes in this release._";
72
+ }
73
+
74
+ const parts: string[] = [];
75
+ parts.push(`## ${output.title}`);
76
+ if (output.highlights.length > 0) {
77
+ parts.push("");
78
+ parts.push("### Highlights");
79
+ parts.push("");
80
+ for (const h of output.highlights) {
81
+ parts.push(`- ${h}`);
82
+ }
83
+ }
84
+ const body = output.body.trim();
85
+ if (body.length > 0) {
86
+ parts.push("");
87
+ parts.push(body);
88
+ }
89
+ return parts.join("\n");
90
+ }
@@ -1,18 +1,26 @@
1
1
  // src/release/executor.ts — Release execution: build, version bump, git ops, channel publishing
2
2
  import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import type { CustomChannelConfig, ReleaseChannel, ReleaseResult } from "../types.js";
3
+ import type {
4
+ CustomChannelConfig,
5
+ ReleaseChannel,
6
+ ReleaseResult,
7
+ ReleaseTarget,
8
+ } from "../types.js";
5
9
  import { commitStaged } from "../git/commit.js";
6
10
  import { formatTag } from "./version.js";
7
11
  import { resolveChannelHandler } from "./channels/registry.js";
8
12
  import type { ExecFn } from "./channels/types.js";
13
+ import { getRunScriptCommand } from "../workspace/package-manager.js";
9
14
 
10
15
  /** Callback to report step progress during release execution. */
11
16
  export type ReleaseProgressFn = (step: string, status: "active" | "done" | "error", detail?: string) => void;
12
17
 
13
18
  export interface ExecuteReleaseOptions {
14
19
  exec: ExecFn;
20
+ /** Repository root — git and channel publish steps run here. */
15
21
  cwd: string;
22
+ /** Selected release target whose manifest/build should be mutated. */
23
+ target: ReleaseTarget;
16
24
  version: string;
17
25
  changelog: string;
18
26
  channels: ReleaseChannel[];
@@ -20,7 +28,7 @@ export interface ExecuteReleaseOptions {
20
28
  tagFormat: string;
21
29
  /** User-defined custom channel configurations */
22
30
  customChannels?: Record<string, CustomChannelConfig>;
23
- /** Skip the package.json version write (version was already set locally). */
31
+ /** Skip the target manifest version write (version was already set locally). */
24
32
  skipBump?: boolean;
25
33
  /** Reuse/update the local git tag after the rebase step instead of creating a new one. */
26
34
  skipTag?: boolean;
@@ -28,26 +36,79 @@ export interface ExecuteReleaseOptions {
28
36
  onProgress?: ReleaseProgressFn;
29
37
  }
30
38
 
39
+ interface TargetManifest {
40
+ scripts?: Record<string, string>;
41
+ version?: string;
42
+ }
43
+
44
+ function getTargetManifestGitPath(target: ReleaseTarget): string {
45
+ return target.relativeDir === "."
46
+ ? "package.json"
47
+ : `${target.relativeDir}/package.json`;
48
+ }
49
+
50
+ function readTargetManifest(target: ReleaseTarget): TargetManifest {
51
+ return JSON.parse(fs.readFileSync(target.manifestPath, "utf-8")) as TargetManifest;
52
+ }
53
+
54
+ const PUSH_RETRY_LIMIT = 1;
55
+
56
+ export function isNonFastForwardPushError(detail: string | undefined): boolean {
57
+ if (!detail) {
58
+ return false;
59
+ }
60
+
61
+ const normalized = detail.toLowerCase();
62
+ return normalized.includes("non-fast-forward") || normalized.includes("fetch first");
63
+ }
64
+
65
+ async function refreshReleaseTag(
66
+ exec: ExecFn,
67
+ cwd: string,
68
+ tagName: string,
69
+ tagMessage: string,
70
+ progress: ReleaseProgressFn,
71
+ ): Promise<{ success: true } | { success: false; error: string }> {
72
+ progress("git-tag", "active", `Refreshing ${tagName}`);
73
+ const gitTag = await exec("git", ["tag", "-a", "-f", tagName, "-m", tagMessage], { cwd });
74
+ if (gitTag.code !== 0) {
75
+ const detail = gitTag.stderr || gitTag.stdout || `exit code ${gitTag.code}`;
76
+ progress("git-tag", "error", detail);
77
+ return { success: false, error: `git tag: ${detail}` };
78
+ }
79
+
80
+ progress("git-tag", "done", "Refreshed existing tag");
81
+ return { success: true };
82
+ }
83
+
31
84
  /**
32
- * Execute a full release: optional build, package.json version bump, git
85
+ * Execute a full release: optional build, target manifest version bump, git
33
86
  * commit+tag+push, then per-channel publishing.
34
- *
35
- * - Build and git steps are fatal (throw or return early on non-zero exit).
36
- * - Channel failures are non-fatal; each is tried independently and errors
37
- * are recorded in the result.
38
- * - In dry-run mode no exec calls are made; the result reflects what would
39
- * happen (all flags true) so callers can preview without side-effects.
40
87
  */
41
88
  export async function executeRelease(opts: ExecuteReleaseOptions): Promise<ReleaseResult> {
42
- const { exec, cwd, version, changelog, channels, dryRun, tagFormat, skipBump, skipTag, onProgress, customChannels = {} } = opts;
89
+ const {
90
+ exec,
91
+ cwd,
92
+ target,
93
+ version,
94
+ changelog,
95
+ channels,
96
+ dryRun,
97
+ tagFormat,
98
+ skipBump,
99
+ skipTag,
100
+ onProgress,
101
+ customChannels = {},
102
+ } = opts;
43
103
  const progress = onProgress ?? (() => {});
44
104
  const tagName = formatTag(version, tagFormat);
45
105
  const tagMessage = `Release ${tagName}\n\n${changelog}`;
106
+ const stagedPaths = [getTargetManifestGitPath(target)];
46
107
 
47
108
  if (dryRun) {
48
- console.log(`[dry-run] Would build (if scripts.build exists)`);
49
- console.log(`[dry-run] Would bump version to ${version}`);
50
- console.log(`[dry-run] Would git add -A`);
109
+ console.log(`[dry-run] Would build ${target.name} in ${target.packageDir} (if scripts.build exists)`);
110
+ console.log(`[dry-run] Would bump ${stagedPaths[0]} to ${version}`);
111
+ console.log(`[dry-run] Would git add -- ${stagedPaths.join(" ")}`);
51
112
  console.log(`[dry-run] Would git commit -m "chore(release): ${tagName}"`);
52
113
  console.log(`[dry-run] Would git pull --rebase origin`);
53
114
  if (skipTag) {
@@ -56,8 +117,8 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
56
117
  console.log(`[dry-run] Would git tag -a ${tagName}`);
57
118
  }
58
119
  console.log(`[dry-run] Would git push origin HEAD --follow-tags`);
59
- for (const ch of channels) {
60
- console.log(`[dry-run] Would publish to channel: ${ch}`);
120
+ for (const channel of channels) {
121
+ console.log(`[dry-run] Would publish to channel: ${channel}`);
61
122
  }
62
123
  return {
63
124
  version,
@@ -67,15 +128,12 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
67
128
  };
68
129
  }
69
130
 
70
- // ── 1. Optional build ─────────────────────────────────────────────────────
71
- const pkgPath = path.join(cwd, "package.json");
72
- const pkgRaw = fs.readFileSync(pkgPath, "utf-8");
73
- const pkg = JSON.parse(pkgRaw) as Record<string, unknown>;
131
+ const manifest = readTargetManifest(target);
74
132
 
75
- const scripts = pkg["scripts"] as Record<string, string> | undefined;
76
- if (scripts?.["build"]) {
77
- progress("build", "active", "Running build script");
78
- const buildResult = await exec("bun", ["run", "build"], { cwd });
133
+ if (manifest.scripts?.build) {
134
+ progress("build", "active", `Running build script for ${target.name}`);
135
+ const buildCommand = getRunScriptCommand(target.packageManager, "build");
136
+ const buildResult = await exec(buildCommand.command, buildCommand.args, { cwd: target.packageDir });
79
137
  if (buildResult.code !== 0) {
80
138
  progress("build", "error", buildResult.stderr || "Non-zero exit");
81
139
  throw new Error(`Build failed: ${buildResult.stderr || "non-zero exit"}`);
@@ -83,26 +141,21 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
83
141
  progress("build", "done");
84
142
  }
85
143
 
86
- // ── 2. Bump version in package.json ──────────────────────────────────────
87
144
  if (skipBump) {
88
145
  progress("version-bump", "done", "Already set");
89
146
  } else {
90
- progress("version-bump", "active", `Bumping to ${version}`);
91
- pkg["version"] = version;
92
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
147
+ progress("version-bump", "active", `Bumping ${stagedPaths[0]} to ${version}`);
148
+ manifest.version = version;
149
+ fs.writeFileSync(target.manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
93
150
  progress("version-bump", "done");
94
151
  }
95
152
 
96
- // ── 3–6. Git operations ──────────────────────────────────────────────────
97
-
98
- // When skipBump is true the version was already committed locally —
99
- // there's nothing to stage or commit, so skip straight to tag+push.
100
153
  if (skipBump) {
101
154
  progress("git-add", "done", "Skipped (no changes)");
102
155
  progress("git-commit", "done", "Skipped (already committed)");
103
156
  } else {
104
- progress("git-add", "active", "git add -A");
105
- const gitAdd = await exec("git", ["add", "-A"], { cwd });
157
+ progress("git-add", "active", `git add -- ${stagedPaths.join(" ")}`);
158
+ const gitAdd = await exec("git", ["add", "--", ...stagedPaths], { cwd });
106
159
  if (gitAdd.code !== 0) {
107
160
  const detail = gitAdd.stderr || gitAdd.stdout || `exit code ${gitAdd.code}`;
108
161
  progress("git-add", "error", detail);
@@ -110,7 +163,7 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
110
163
  }
111
164
  progress("git-add", "done");
112
165
 
113
- const commitMessage = `chore(release): ${formatTag(version, tagFormat)}`;
166
+ const commitMessage = `chore(release): ${tagName}`;
114
167
  progress("git-commit", "active", commitMessage);
115
168
  const commitResult = await commitStaged(exec, cwd, commitMessage);
116
169
  if (!commitResult.success) {
@@ -144,19 +197,38 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
144
197
  }
145
198
  progress("git-tag", "done", skipTag ? "Refreshed existing tag" : undefined);
146
199
 
147
- progress("git-push", "active", "Pushing to origin");
148
- const gitPush = await exec("git", ["push", "origin", "HEAD", "--follow-tags"], { cwd });
149
- if (gitPush.code !== 0) {
200
+ let pushAttempt = 0;
201
+ while (true) {
202
+ progress("git-push", "active", pushAttempt === 0 ? "Pushing to origin" : "Retrying push after rebase");
203
+ const gitPush = await exec("git", ["push", "origin", "HEAD", "--follow-tags"], { cwd });
204
+ if (gitPush.code === 0) {
205
+ progress("git-push", "done");
206
+ break;
207
+ }
208
+
150
209
  const detail = gitPush.stderr || gitPush.stdout || `exit code ${gitPush.code}`;
151
- progress("git-push", "error", detail);
152
- // Tag was created or refreshed locally but push failed
153
- return { version, tagCreated: true, pushed: false, channels: [], error: `git push: ${detail}` };
210
+ if (pushAttempt >= PUSH_RETRY_LIMIT || !isNonFastForwardPushError(detail)) {
211
+ progress("git-push", "error", detail);
212
+ return { version, tagCreated: true, pushed: false, channels: [], error: `git push: ${detail}` };
213
+ }
214
+
215
+ pushAttempt += 1;
216
+ progress("git-pull", "active", "Push rejected — rebasing before retry");
217
+ const retryPull = await exec("git", ["pull", "--rebase", "origin"], { cwd });
218
+ if (retryPull.code !== 0) {
219
+ const retryDetail = retryPull.stderr || retryPull.stdout || `exit code ${retryPull.code}`;
220
+ progress("git-pull", "error", retryDetail);
221
+ return { version, tagCreated: true, pushed: false, channels: [], error: `git pull: ${retryDetail}` };
222
+ }
223
+ progress("git-pull", "done", "Rebased after push rejection");
224
+
225
+ const refreshTag = await refreshReleaseTag(exec, cwd, tagName, tagMessage, progress);
226
+ if (!refreshTag.success) {
227
+ return { version, tagCreated: true, pushed: false, channels: [], error: refreshTag.error };
228
+ }
154
229
  }
155
- progress("git-push", "done");
156
230
 
157
- // ── 7. Channel publishing ─────────────────────────────────────────────────
158
231
  const channelResults: ReleaseResult["channels"] = [];
159
-
160
232
  for (const channel of channels) {
161
233
  progress(`publish-${channel}`, "active", `Publishing to ${channel}`);
162
234
 
@@ -170,9 +242,14 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
170
242
 
171
243
  const result = await handler.publish(exec, {
172
244
  version,
173
- tag: formatTag(version, tagFormat),
245
+ tag: tagName,
174
246
  changelog,
175
247
  cwd,
248
+ targetName: target.name,
249
+ targetId: target.id,
250
+ targetPath: target.relativeDir,
251
+ manifestPath: target.manifestPath,
252
+ packageManager: target.packageManager,
176
253
  });
177
254
 
178
255
  if (result.success) {