supipowers 1.5.3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +135 -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 +268 -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
@@ -1,4 +1,6 @@
1
1
  import { formatTag } from "./version.js";
2
+ import { renderSchemaText } from "../ai/schema-text.js";
3
+ import { ReleaseNotePolishOutputSchema } from "./contracts.js";
2
4
 
3
5
  export interface BuildPolishPromptOpts {
4
6
  changelog: string;
@@ -10,7 +12,9 @@ export interface BuildPolishPromptOpts {
10
12
  * Build a prompt for a headless agent session that polishes raw changelog
11
13
  * text into clear, user-facing release notes.
12
14
  *
13
- * The agent returns only the polished markdown — no commands, no confirmation.
15
+ * The agent returns a structured JSON artifact matching
16
+ * ReleaseNotePolishOutputSchema. The release command renders the polished
17
+ * changelog from that artifact — no free-form text, no regex extraction.
14
18
  */
15
19
  export function buildPolishPrompt(opts: BuildPolishPromptOpts): string {
16
20
  const { changelog, version } = opts;
@@ -66,6 +70,18 @@ export function buildPolishPrompt(opts: BuildPolishPromptOpts): string {
66
70
  "",
67
71
  "## Output",
68
72
  "",
69
- "Return **only** the polished markdown changelog. No preamble, no explanation, no code fences wrapping the whole output.",
73
+ "Respond with a JSON object that matches this TypeScript shape exactly:",
74
+ "",
75
+ "```ts",
76
+ renderSchemaText(ReleaseNotePolishOutputSchema),
77
+ "```",
78
+ "",
79
+ "Field guide:",
80
+ "- `title`: short one-line heading for the release (no leading `#`).",
81
+ "- `body`: the grouped markdown sections (Breaking Changes, Features, Fixes, ...). Omit empty sections.",
82
+ "- `highlights`: 0–5 short bullet strings summarising the most user-visible changes. Each string is a plain sentence, no leading `-`.",
83
+ "- `status`: `\"ok\"` when the release has notable changes; `\"empty\"` when the raw changelog had nothing worth polishing (highlights may be empty and body may be a short placeholder).",
84
+ "",
85
+ "Respond with only the JSON object. You may wrap it in a ```json fence.",
70
86
  ].join("\n");
71
87
  }
@@ -0,0 +1,86 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { PackageManagerId, ReleaseTarget, WorkspaceTarget } from "../types.js";
4
+ import { discoverWorkspaceTargets } from "../workspace/targets.js";
5
+
6
+ interface ReleaseManifest {
7
+ name?: string;
8
+ version?: string;
9
+ private?: boolean;
10
+ files?: unknown;
11
+ }
12
+
13
+ const ROOT_TAG_FORMAT = "v${version}";
14
+
15
+ function normalizeReleasePath(value: string): string {
16
+ return value
17
+ .replace(/\\/g, "/")
18
+ .replace(/^\.\//, "")
19
+ .replace(/^\//, "")
20
+ .replace(/\/package\.json$/, "")
21
+ .replace(/\/+$/, "");
22
+ }
23
+
24
+ function readReleaseManifest(manifestPath: string): ReleaseManifest | null {
25
+ try {
26
+ return JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as ReleaseManifest;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ function normalizeFilesWhitelist(files: unknown): string[] {
33
+ if (!Array.isArray(files)) {
34
+ return [];
35
+ }
36
+
37
+ return files
38
+ .filter((entry): entry is string => typeof entry === "string")
39
+ .map((entry) => normalizeReleasePath(entry))
40
+ .filter(Boolean);
41
+ }
42
+
43
+ function joinTargetPath(relativeDir: string, entry: string): string {
44
+ if (relativeDir === ".") {
45
+ return entry;
46
+ }
47
+ return `${relativeDir}/${entry}`;
48
+ }
49
+
50
+ function buildPublishScopePaths(target: WorkspaceTarget, manifest: ReleaseManifest | null): string[] {
51
+ const manifestPath = joinTargetPath(target.relativeDir, "package.json");
52
+ const filesWhitelist = normalizeFilesWhitelist(manifest?.files);
53
+ if (filesWhitelist.length > 0) {
54
+ return [...new Set([manifestPath, ...filesWhitelist.map((entry) => joinTargetPath(target.relativeDir, entry))])];
55
+ }
56
+
57
+ const packageDirScope = target.relativeDir === "." ? "." : target.relativeDir;
58
+ return [...new Set([manifestPath, packageDirScope])];
59
+ }
60
+
61
+ function getDefaultTagFormat(target: WorkspaceTarget): string {
62
+ return target.kind === "root" ? ROOT_TAG_FORMAT : `${target.name}@${ROOT_TAG_FORMAT.replace("v", "")}`;
63
+ }
64
+
65
+ function hasReleaseVersion(manifest: ReleaseManifest | null): manifest is ReleaseManifest & { version: string } {
66
+ return typeof manifest?.version === "string" && manifest.version.trim().length > 0;
67
+ }
68
+
69
+ function toReleaseTarget(target: WorkspaceTarget, manifest = readReleaseManifest(target.manifestPath)): ReleaseTarget {
70
+ return {
71
+ ...target,
72
+ publishScopePaths: buildPublishScopePaths(target, manifest),
73
+ defaultTagFormat: getDefaultTagFormat(target),
74
+ };
75
+ }
76
+
77
+ export function discoverReleaseTargets(repoRoot: string, packageManager: PackageManagerId): ReleaseTarget[] {
78
+ return discoverWorkspaceTargets(repoRoot, packageManager).flatMap((target) => {
79
+ const manifest = readReleaseManifest(target.manifestPath);
80
+ return hasReleaseVersion(manifest) ? [toReleaseTarget(target, manifest)] : [];
81
+ });
82
+ }
83
+
84
+ export function getPublishableReleaseTargets(targets: ReleaseTarget[]): ReleaseTarget[] {
85
+ return targets.filter((target) => !target.private);
86
+ }
@@ -1,6 +1,5 @@
1
1
  import fs from "fs";
2
- import path from "path";
3
- import type { BumpType, CategorizedCommits } from "../types.js";
2
+ import type { BumpType, CategorizedCommits, ReleaseTarget } from "../types.js";
4
3
 
5
4
  type ExecFn = (
6
5
  cmd: string,
@@ -8,6 +7,18 @@ type ExecFn = (
8
7
  opts?: { cwd?: string },
9
8
  ) => Promise<{ stdout: string; stderr: string; code: number }>;
10
9
 
10
+ interface PackageManifest {
11
+ version?: string;
12
+ }
13
+
14
+ interface ParsedReleaseTag {
15
+ tag: string;
16
+ version: string;
17
+ }
18
+
19
+ const LEGACY_RELEASE_TAG_FORMAT = "v${version}";
20
+ const SEMVER_CAPTURE = "([0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?)";
21
+
11
22
  /**
12
23
  * Replace `${version}` in a tag format template with the actual version string.
13
24
  * If the template contains no placeholder, it is returned as-is.
@@ -32,7 +43,6 @@ export function suggestBump(commits: CategorizedCommits): BumpType {
32
43
  * Returns a plain "X.Y.Z" string — never prefixed with "v".
33
44
  */
34
45
  export function bumpVersion(current: string, bump: BumpType): string {
35
- // Strip pre-release / build metadata before parsing
36
46
  const core = current.split("-")[0].split("+")[0];
37
47
  const parts = core.split(".").map(Number);
38
48
 
@@ -40,8 +50,7 @@ export function bumpVersion(current: string, bump: BumpType): string {
40
50
  throw new Error(`Invalid semver string: "${current}"`);
41
51
  }
42
52
 
43
- let [major, minor, patch] = parts;
44
-
53
+ const [major, minor, patch] = parts;
45
54
  switch (bump) {
46
55
  case "major":
47
56
  return `${major + 1}.0.0`;
@@ -52,56 +61,38 @@ export function bumpVersion(current: string, bump: BumpType): string {
52
61
  }
53
62
  }
54
63
 
55
- /**
56
- * Read the parsed package.json object from `<cwd>/package.json`. Returns null
57
- * when the file is absent or invalid.
58
- */
59
- function readPackageJson(cwd: string): { version?: string; files?: unknown } | null {
60
- const pkgPath = path.join(cwd, "package.json");
64
+ function readPackageJson(manifestPath: string): PackageManifest | null {
61
65
  try {
62
- const raw = fs.readFileSync(pkgPath, "utf-8");
63
- return JSON.parse(raw) as { version?: string; files?: unknown };
66
+ return JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as PackageManifest;
64
67
  } catch {
65
68
  return null;
66
69
  }
67
70
  }
68
71
 
69
72
  /**
70
- * Read the `version` field from `<cwd>/package.json`.
71
- * Returns `"0.0.0"` when the file is absent or carries no version field.
73
+ * Read the current version from the selected release target's manifest.
74
+ * Returns "0.0.0" when the manifest is absent or carries no version field.
72
75
  */
73
- export function getCurrentVersion(cwd: string): string {
74
- return readPackageJson(cwd)?.version ?? "0.0.0";
76
+ export function getCurrentVersion(target: ReleaseTarget): string {
77
+ return readPackageJson(target.manifestPath)?.version ?? "0.0.0";
75
78
  }
76
79
 
77
- /**
78
- * Return the publishable package paths used to scope release-note commits.
79
- * Returns null when the package manifest does not declare a `files` whitelist.
80
- */
81
- export function getPublishedPackagePaths(cwd: string): string[] | null {
82
- const files = readPackageJson(cwd)?.files;
83
- if (!Array.isArray(files)) {
84
- return null;
85
- }
86
-
87
- const normalized = files
88
- .filter((entry): entry is string => typeof entry === "string")
89
- .map((entry) => entry.replace(/^\.\//, "").replace(/\/+$/, ""))
90
- .filter(Boolean);
91
-
92
- if (normalized.length === 0) {
93
- return null;
94
- }
95
-
96
- return [...new Set(["package.json", ...normalized])];
80
+ /** Return the target-specific publish scope already computed during discovery. */
81
+ export function getPublishedPackagePaths(target: ReleaseTarget): string[] {
82
+ return [...target.publishScopePaths];
97
83
  }
98
84
 
99
- interface ParsedReleaseTag {
100
- tag: string;
101
- version: string;
85
+ /** Resolve the effective tag format for a selected target. */
86
+ export function getReleaseTagFormat(target: ReleaseTarget, rootTagFormat: string): string {
87
+ return target.kind === "root" ? rootTagFormat : target.defaultTagFormat;
102
88
  }
103
89
 
104
- const SEMVER_CAPTURE = "([0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?)";
90
+ function getTagFormatsForTarget(target: ReleaseTarget, rootTagFormat: string): string[] {
91
+ const effectiveTagFormat = getReleaseTagFormat(target, rootTagFormat);
92
+ return target.kind === "root"
93
+ ? [...new Set([effectiveTagFormat, LEGACY_RELEASE_TAG_FORMAT])]
94
+ : [effectiveTagFormat];
95
+ }
105
96
 
106
97
  function escapeRegExp(value: string): string {
107
98
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -117,9 +108,15 @@ function extractVersionFromTag(tag: string, tagFormat: string): string | null {
117
108
  return match?.[1] ?? null;
118
109
  }
119
110
 
120
- function parseReleaseTag(tag: string, tagFormat: string): ParsedReleaseTag | null {
121
- const version = extractVersionFromTag(tag, tagFormat) ?? extractVersionFromTag(tag, LEGACY_RELEASE_TAG_FORMAT);
122
- return version ? { tag, version } : null;
111
+ function parseReleaseTag(tag: string, target: ReleaseTarget, rootTagFormat: string): ParsedReleaseTag | null {
112
+ for (const tagFormat of getTagFormatsForTarget(target, rootTagFormat)) {
113
+ const version = extractVersionFromTag(tag, tagFormat);
114
+ if (version) {
115
+ return { tag, version };
116
+ }
117
+ }
118
+
119
+ return null;
123
120
  }
124
121
 
125
122
  function compareSemver(left: string, right: string): number {
@@ -142,10 +139,10 @@ function compareSemver(left: string, right: string): number {
142
139
  return left.includes("-") ? -1 : 1;
143
140
  }
144
141
 
145
- async function isTagAtHead(exec: ExecFn, cwd: string, tag: string): Promise<boolean> {
142
+ async function isTagAtHead(exec: ExecFn, repoRoot: string, tag: string): Promise<boolean> {
146
143
  const [tagCommit, headCommit] = await Promise.all([
147
- exec("git", ["rev-list", "-n", "1", tag], { cwd }),
148
- exec("git", ["rev-parse", "HEAD"], { cwd }),
144
+ exec("git", ["rev-list", "-n", "1", tag], { cwd: repoRoot }),
145
+ exec("git", ["rev-parse", "HEAD"], { cwd: repoRoot }),
149
146
  ]);
150
147
 
151
148
  return tagCommit.code === 0
@@ -154,14 +151,39 @@ async function isTagAtHead(exec: ExecFn, cwd: string, tag: string): Promise<bool
154
151
  && tagCommit.stdout.trim() === headCommit.stdout.trim();
155
152
  }
156
153
 
154
+ export async function getLatestReleaseTag(
155
+ exec: ExecFn,
156
+ target: ReleaseTarget,
157
+ rootTagFormat: string,
158
+ ): Promise<string | null> {
159
+ try {
160
+ const localTags = await exec("git", ["tag", "--merged", "HEAD"], { cwd: target.repoRoot });
161
+ if (localTags.code !== 0) {
162
+ return null;
163
+ }
164
+
165
+ const latest = localTags.stdout
166
+ .split(/\r?\n/)
167
+ .map((tag) => tag.trim())
168
+ .filter(Boolean)
169
+ .map((tag) => parseReleaseTag(tag, target, rootTagFormat))
170
+ .filter((candidate): candidate is ParsedReleaseTag => Boolean(candidate))
171
+ .sort((left, right) => compareSemver(right.version, left.version))[0];
172
+
173
+ return latest?.tag ?? null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
157
179
  export async function findResumableLocalRelease(
158
180
  exec: ExecFn,
159
- cwd: string,
181
+ target: ReleaseTarget,
160
182
  currentVersion: string,
161
- tagFormat: string,
183
+ rootTagFormat: string,
162
184
  ): Promise<{ version: string; tag: string } | null> {
163
185
  try {
164
- const localTags = await exec("git", ["tag", "--merged", "HEAD"], { cwd });
186
+ const localTags = await exec("git", ["tag", "--merged", "HEAD"], { cwd: target.repoRoot });
165
187
  if (localTags.code !== 0) {
166
188
  return null;
167
189
  }
@@ -170,18 +192,18 @@ export async function findResumableLocalRelease(
170
192
  .split(/\r?\n/)
171
193
  .map((tag) => tag.trim())
172
194
  .filter(Boolean)
173
- .map((tag) => parseReleaseTag(tag, tagFormat))
195
+ .map((tag) => parseReleaseTag(tag, target, rootTagFormat))
174
196
  .filter((candidate): candidate is ParsedReleaseTag => Boolean(candidate))
175
197
  .filter((candidate) => compareSemver(candidate.version, currentVersion) > 0)
176
198
  .sort((left, right) => compareSemver(right.version, left.version));
177
199
 
178
200
  for (const candidate of candidates) {
179
- const remoteTag = await exec("git", ["ls-remote", "--tags", "origin", candidate.tag], { cwd });
201
+ const remoteTag = await exec("git", ["ls-remote", "--tags", "origin", candidate.tag], { cwd: target.repoRoot });
180
202
  if (remoteTag.code !== 0 || remoteTag.stdout.trim() !== "") {
181
203
  continue;
182
204
  }
183
205
 
184
- if (await isTagAtHead(exec, cwd, candidate.tag)) {
206
+ if (await isTagAtHead(exec, target.repoRoot, candidate.tag)) {
185
207
  return { version: candidate.version, tag: candidate.tag };
186
208
  }
187
209
  }
@@ -192,37 +214,41 @@ export async function findResumableLocalRelease(
192
214
  }
193
215
  }
194
216
 
195
-
196
- const LEGACY_RELEASE_TAG_FORMAT = "v${version}";
197
-
198
- function getReleaseTagCandidates(version: string, tagFormat: string): string[] {
199
- return [...new Set([formatTag(version, tagFormat), formatTag(version, LEGACY_RELEASE_TAG_FORMAT)])];
217
+ function getReleaseTagCandidates(
218
+ target: ReleaseTarget,
219
+ version: string,
220
+ rootTagFormat: string,
221
+ ): string[] {
222
+ return [...new Set(getTagFormatsForTarget(target, rootTagFormat).map((tagFormat) => formatTag(version, tagFormat)))];
200
223
  }
201
224
 
202
225
  async function hasMatchingReleaseTag(
203
226
  exec: ExecFn,
204
- cwd: string,
227
+ target: ReleaseTarget,
205
228
  args: string[],
206
229
  version: string,
207
- tagFormat: string,
230
+ rootTagFormat: string,
208
231
  ): Promise<boolean> {
209
- const result = await exec("git", [...args, ...getReleaseTagCandidates(version, tagFormat)], { cwd });
232
+ const result = await exec(
233
+ "git",
234
+ [...args, ...getReleaseTagCandidates(target, version, rootTagFormat)],
235
+ { cwd: target.repoRoot },
236
+ );
210
237
  return result.code === 0 && result.stdout.trim().length > 0;
211
238
  }
212
239
 
213
240
  /**
214
241
  * Check whether a tag for the given version already exists locally.
215
- * Returns `true` when either the current-format tag or the legacy `v{version}`
216
- * tag is already known locally, which means this version was already released.
242
+ * Root targets keep the legacy `v${version}` fallback; workspace targets do not.
217
243
  */
218
244
  export async function isVersionReleased(
219
245
  exec: ExecFn,
220
- cwd: string,
246
+ target: ReleaseTarget,
221
247
  version: string,
222
- tagFormat: string,
248
+ rootTagFormat: string,
223
249
  ): Promise<boolean> {
224
250
  try {
225
- return await hasMatchingReleaseTag(exec, cwd, ["tag", "-l"], version, tagFormat);
251
+ return await hasMatchingReleaseTag(exec, target, ["tag", "-l"], version, rootTagFormat);
226
252
  } catch {
227
253
  return false;
228
254
  }
@@ -230,17 +256,16 @@ export async function isVersionReleased(
230
256
 
231
257
  /**
232
258
  * Check whether a tag for the given version exists on the remote (origin).
233
- * Returns true when either the current-format tag or the legacy `v{version}`
234
- * tag is found via `git ls-remote --tags origin`.
259
+ * Root targets keep the legacy `v${version}` fallback; workspace targets do not.
235
260
  */
236
261
  export async function isTagOnRemote(
237
262
  exec: ExecFn,
238
- cwd: string,
263
+ target: ReleaseTarget,
239
264
  version: string,
240
- tagFormat: string,
265
+ rootTagFormat: string,
241
266
  ): Promise<boolean> {
242
267
  try {
243
- return await hasMatchingReleaseTag(exec, cwd, ["ls-remote", "--tags", "origin"], version, tagFormat);
268
+ return await hasMatchingReleaseTag(exec, target, ["ls-remote", "--tags", "origin"], version, rootTagFormat);
244
269
  } catch {
245
270
  return false;
246
271
  }