@svenroth-ai/shipwright 0.25.0 → 0.26.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 (134) hide show
  1. package/client/dist/assets/{EmbeddedTerminal-5QvKe3_a.js → EmbeddedTerminal-D3wpPNZP.js} +1 -1
  2. package/client/dist/assets/{abnfDiagram-VRR7QNED-CXi9HegQ.js → abnfDiagram-VRR7QNED-BkLIltB3.js} +1 -1
  3. package/client/dist/assets/{arc-DLh4GiqR.js → arc-Sw-Gh0nI.js} +1 -1
  4. package/client/dist/assets/{architectureDiagram-ZJ3FMSHR-DUypQ8dQ.js → architectureDiagram-ZJ3FMSHR-rdxUi9Ks.js} +1 -1
  5. package/client/dist/assets/{blockDiagram-677ZJIJ3-D2cVMKJG.js → blockDiagram-677ZJIJ3-Ch_9dj8a.js} +1 -1
  6. package/client/dist/assets/{c4Diagram-LMCZKHZV-DtruBGJw.js → c4Diagram-LMCZKHZV-CkFlO65x.js} +1 -1
  7. package/client/dist/assets/channel-BYPbLjRl.js +1 -0
  8. package/client/dist/assets/{chunk-2Q5K7J3B-CAfQkFw2.js → chunk-2Q5K7J3B-CypX83xu.js} +1 -1
  9. package/client/dist/assets/{chunk-32BRIVSS-CPWXoLnQ.js → chunk-32BRIVSS-DxVLYGdl.js} +1 -1
  10. package/client/dist/assets/{chunk-5VM5RSS4-xVc3oppQ.js → chunk-5VM5RSS4-Cxk1evhx.js} +1 -1
  11. package/client/dist/assets/{chunk-EX3LRPZG-CUpl6fRR.js → chunk-EX3LRPZG-n9q5S4yJ.js} +1 -1
  12. package/client/dist/assets/{chunk-JWPE2WC7-NejUp56x.js → chunk-JWPE2WC7-jeL4xvir.js} +1 -1
  13. package/client/dist/assets/{chunk-MOJQB5TN-C9-2lLg0.js → chunk-MOJQB5TN-DDSHN0cj.js} +1 -1
  14. package/client/dist/assets/{chunk-RYQCIY6F-BAPtwABy.js → chunk-RYQCIY6F-C0bo383g.js} +1 -1
  15. package/client/dist/assets/{chunk-V7JOEXUC-Dwzihw26.js → chunk-V7JOEXUC-C0jDXXAU.js} +1 -1
  16. package/client/dist/assets/{chunk-VR4S4FIN-9-iArFJM.js → chunk-VR4S4FIN-h-Rr4Pn2.js} +1 -1
  17. package/client/dist/assets/{chunk-XXDRQBXY-DiWHOrqe.js → chunk-XXDRQBXY-BexSo0-s.js} +1 -1
  18. package/client/dist/assets/classDiagram-OUVF2IWQ-CsFIeTju.js +1 -0
  19. package/client/dist/assets/classDiagram-v2-EOCWNBFH-CsFIeTju.js +1 -0
  20. package/client/dist/assets/{cose-bilkent-JH36ORCC-Cp04EOcH.js → cose-bilkent-JH36ORCC-BQXzaioj.js} +1 -1
  21. package/client/dist/assets/{cynefin-VYW2F7L2-D--GN1v8.js → cynefin-VYW2F7L2-BJIofhfe.js} +1 -1
  22. package/client/dist/assets/{cynefinDiagram-TSTJHNR4-BGH0o_qp.js → cynefinDiagram-TSTJHNR4-DmPqTM1T.js} +1 -1
  23. package/client/dist/assets/{dagre-VKFMJZFB-DSKkIKlj.js → dagre-VKFMJZFB-1xljvw6X.js} +1 -1
  24. package/client/dist/assets/{diagram-FQU43EPY-Cs_C4lb-.js → diagram-FQU43EPY-iyL9muCG.js} +1 -1
  25. package/client/dist/assets/{diagram-G47NLZAW-oDtAA8O_.js → diagram-G47NLZAW-DREp2Th6.js} +1 -1
  26. package/client/dist/assets/{diagram-NH7WQ7WH-BiFMjqSH.js → diagram-NH7WQ7WH-h0X3Is2S.js} +1 -1
  27. package/client/dist/assets/{diagram-OA4YK3LP-Ccs3thCj.js → diagram-OA4YK3LP-Bj8YZRFJ.js} +1 -1
  28. package/client/dist/assets/{diagram-WEI45ONY-CdZNqgO3.js → diagram-WEI45ONY-BZOf805d.js} +1 -1
  29. package/client/dist/assets/{ebnfDiagram-CCIWWBDH-CapTA0VL.js → ebnfDiagram-CCIWWBDH-DO5JnXda.js} +1 -1
  30. package/client/dist/assets/{erDiagram-Q63AITRT-BcRNEP8s.js → erDiagram-Q63AITRT-DRgcqg1T.js} +1 -1
  31. package/client/dist/assets/{flowDiagram-23GEKE2U-CJ_zF7e_.js → flowDiagram-23GEKE2U-C3J9QmrW.js} +1 -1
  32. package/client/dist/assets/{ganttDiagram-NO4QXBWP-CnJdcg9A.js → ganttDiagram-NO4QXBWP-BIEl3Tkm.js} +1 -1
  33. package/client/dist/assets/{gitGraphDiagram-IHSO6WYX-YTUQ_ELk.js → gitGraphDiagram-IHSO6WYX-BNF0tFWC.js} +1 -1
  34. package/client/dist/assets/{index-irQhGNpn.js → index-BnTOrFHb.js} +203 -202
  35. package/client/dist/assets/index-CYPc6qxw.css +1 -0
  36. package/client/dist/assets/{infoDiagram-FWYZ7A6U-CLOP_ZWD.js → infoDiagram-FWYZ7A6U-DEyFxiyY.js} +1 -1
  37. package/client/dist/assets/{ishikawaDiagram-FXEZZL3T-aFTIzr7o.js → ishikawaDiagram-FXEZZL3T-B_ehlg3I.js} +1 -1
  38. package/client/dist/assets/{journeyDiagram-5HDEW3XC-CbPJB4xd.js → journeyDiagram-5HDEW3XC-Dp00FyJS.js} +1 -1
  39. package/client/dist/assets/{kanban-definition-HUTT4EX6-BoyhytIS.js → kanban-definition-HUTT4EX6-CTAf16q8.js} +1 -1
  40. package/client/dist/assets/{linear-1VXA-uBg.js → linear-9vNUgmYd.js} +1 -1
  41. package/client/dist/assets/{mermaid.core-D_lO2yK9.js → mermaid.core-A6tjaEVD.js} +4 -4
  42. package/client/dist/assets/{mindmap-definition-LN4V7U3C-iFK7LXMx.js → mindmap-definition-LN4V7U3C-DRW3bmCY.js} +1 -1
  43. package/client/dist/assets/{pegDiagram-2B236MQR-DNmcmfA-.js → pegDiagram-2B236MQR-DJFCjHp2.js} +1 -1
  44. package/client/dist/assets/{pieDiagram-ENE6RG2P-ekx83O5b.js → pieDiagram-ENE6RG2P-KRd-sGvC.js} +1 -1
  45. package/client/dist/assets/{quadrantDiagram-ABIIQ3AL-DayyD1l8.js → quadrantDiagram-ABIIQ3AL-9TLZE3Hu.js} +1 -1
  46. package/client/dist/assets/{railroadDiagram-RFXS5EU6-Bdgz7S6G.js → railroadDiagram-RFXS5EU6-y4tLtjXg.js} +1 -1
  47. package/client/dist/assets/{requirementDiagram-TGXJPOKE-szP3Muld.js → requirementDiagram-TGXJPOKE-B9EOQDQM.js} +1 -1
  48. package/client/dist/assets/{sankeyDiagram-HTMAVEWB-Ct3rzgUj.js → sankeyDiagram-HTMAVEWB-zfwiDzkT.js} +1 -1
  49. package/client/dist/assets/{sequenceDiagram-DBY2YBRQ-_69EfmOU.js → sequenceDiagram-DBY2YBRQ-DW1LWepL.js} +1 -1
  50. package/client/dist/assets/{sizeCapture-X5ZJPWSS-BSOU06J2.js → sizeCapture-X5ZJPWSS-BM81Mik0.js} +1 -1
  51. package/client/dist/assets/{stateDiagram-2N3HPSRC-HmOmFwQm.js → stateDiagram-2N3HPSRC-D0EMIw1w.js} +1 -1
  52. package/client/dist/assets/stateDiagram-v2-6OUMAXLB-B3E4_xxT.js +1 -0
  53. package/client/dist/assets/{swimlanes-5IMT3BWC-CVg_4oT7.js → swimlanes-5IMT3BWC-CKXNCX6M.js} +2 -2
  54. package/client/dist/assets/swimlanesDiagram-G3AALYLV-BegQBqnC.js +8 -0
  55. package/client/dist/assets/{timeline-definition-FHXFAJF6-CBBaDKXm.js → timeline-definition-FHXFAJF6-ByzIfvL5.js} +1 -1
  56. package/client/dist/assets/{vennDiagram-L72KCM5P-CBXiWPMc.js → vennDiagram-L72KCM5P-Xep38xBH.js} +1 -1
  57. package/client/dist/assets/{wardleyDiagram-EHGQE667-uhBCZoW0.js → wardleyDiagram-EHGQE667-DKWDpx6g.js} +1 -1
  58. package/client/dist/assets/{xychartDiagram-FW5EYKEG-xxE_wY5O.js → xychartDiagram-FW5EYKEG-BGcwZTnu.js} +1 -1
  59. package/client/dist/index.html +27 -27
  60. package/lib/claude-cli.mjs +173 -173
  61. package/lib/preflight.mjs +252 -252
  62. package/lib/probe-path.mjs +84 -84
  63. package/lib/server.mjs +369 -369
  64. package/lib/util.mjs +157 -157
  65. package/lib/win32-spawn.mjs +253 -253
  66. package/package.json +68 -68
  67. package/scripts/deploy-procs.mjs +208 -208
  68. package/scripts/deploy-swap.mjs +276 -276
  69. package/scripts/kill-targets.js +140 -140
  70. package/scripts/repair-claude-json.mjs +295 -295
  71. package/server/dist/config/default-actions.json +90 -90
  72. package/server/dist/core/mission-context/association.d.ts +46 -0
  73. package/server/dist/core/mission-context/association.d.ts.map +1 -1
  74. package/server/dist/core/mission-context/association.js +69 -0
  75. package/server/dist/core/mission-context/association.js.map +1 -1
  76. package/server/dist/core/mission-context/resolver-iterate.d.ts +6 -0
  77. package/server/dist/core/mission-context/resolver-iterate.d.ts.map +1 -0
  78. package/server/dist/core/mission-context/resolver-iterate.js +202 -0
  79. package/server/dist/core/mission-context/resolver-iterate.js.map +1 -0
  80. package/server/dist/core/mission-context/resolver-parts.d.ts +20 -0
  81. package/server/dist/core/mission-context/resolver-parts.d.ts.map +1 -1
  82. package/server/dist/core/mission-context/resolver-parts.js +92 -0
  83. package/server/dist/core/mission-context/resolver-parts.js.map +1 -1
  84. package/server/dist/core/mission-context/resolver.d.ts +1 -1
  85. package/server/dist/core/mission-context/resolver.d.ts.map +1 -1
  86. package/server/dist/core/mission-context/resolver.js +17 -199
  87. package/server/dist/core/mission-context/resolver.js.map +1 -1
  88. package/server/dist/core/mission-context/run-id-recovery.d.ts +21 -5
  89. package/server/dist/core/mission-context/run-id-recovery.d.ts.map +1 -1
  90. package/server/dist/core/mission-context/run-id-recovery.js +32 -5
  91. package/server/dist/core/mission-context/run-id-recovery.js.map +1 -1
  92. package/server/dist/core/mission-context/scenario.d.ts +18 -11
  93. package/server/dist/core/mission-context/scenario.d.ts.map +1 -1
  94. package/server/dist/core/mission-context/scenario.js +40 -3
  95. package/server/dist/core/mission-context/scenario.js.map +1 -1
  96. package/server/dist/core/path-guard.d.ts +1 -1
  97. package/server/dist/core/path-guard.d.ts.map +1 -1
  98. package/server/dist/core/path-guard.js +9 -2
  99. package/server/dist/core/path-guard.js.map +1 -1
  100. package/server/dist/core/shipslog-docs-reader.d.ts +4 -0
  101. package/server/dist/core/shipslog-docs-reader.d.ts.map +1 -0
  102. package/server/dist/core/shipslog-docs-reader.js +181 -0
  103. package/server/dist/core/shipslog-docs-reader.js.map +1 -0
  104. package/server/dist/core/shipslog-docs-types.d.ts +15 -0
  105. package/server/dist/core/shipslog-docs-types.d.ts.map +1 -0
  106. package/server/dist/core/shipslog-docs-types.js +14 -0
  107. package/server/dist/core/shipslog-docs-types.js.map +1 -0
  108. package/server/dist/external/design-review/serve.js +27 -27
  109. package/server/dist/external/mission-context/association-write.d.ts +22 -0
  110. package/server/dist/external/mission-context/association-write.d.ts.map +1 -0
  111. package/server/dist/external/mission-context/association-write.js +90 -0
  112. package/server/dist/external/mission-context/association-write.js.map +1 -0
  113. package/server/dist/external/mission-context/routes.d.ts.map +1 -1
  114. package/server/dist/external/mission-context/routes.js +18 -31
  115. package/server/dist/external/mission-context/routes.js.map +1 -1
  116. package/server/dist/external/routes.d.ts.map +1 -1
  117. package/server/dist/external/routes.js +9 -6
  118. package/server/dist/external/routes.js.map +1 -1
  119. package/server/dist/external/shipslog-docs/routes.d.ts +10 -0
  120. package/server/dist/external/shipslog-docs/routes.d.ts.map +1 -0
  121. package/server/dist/external/shipslog-docs/routes.js +41 -0
  122. package/server/dist/external/shipslog-docs/routes.js.map +1 -0
  123. package/server/package.json +1 -1
  124. package/server/profiles/PROFILE_SCHEMA_VERSION +1 -1
  125. package/server/profiles/README.md +27 -27
  126. package/server/profiles/python-plugin-monorepo.json +35 -35
  127. package/server/profiles/supabase-nextjs.json +253 -253
  128. package/server/profiles/vite-hono.json +92 -92
  129. package/client/dist/assets/channel-C_k-9jsa.js +0 -1
  130. package/client/dist/assets/classDiagram-OUVF2IWQ-CsEPq9Ac.js +0 -1
  131. package/client/dist/assets/classDiagram-v2-EOCWNBFH-CsEPq9Ac.js +0 -1
  132. package/client/dist/assets/index-6nZ1bt0a.css +0 -1
  133. package/client/dist/assets/stateDiagram-v2-6OUMAXLB-BW8H33dv.js +0 -1
  134. package/client/dist/assets/swimlanesDiagram-G3AALYLV-BaRlxcPD.js +0 -8
package/lib/util.mjs CHANGED
@@ -1,157 +1,157 @@
1
- /**
2
- * util.mjs — pure, dependency-free helpers shared across the bootstrapper.
3
- *
4
- * Everything here is side-effect free and unit-testable in isolation: SemVer
5
- * parsing/compare (attach-vs-swap + stale-copy decisions ride on it), platform
6
- * detection, and the actionable install hints a missing prerequisite must
7
- * print. No `claude`, no network, no filesystem — those live in the sibling
8
- * modules behind injected seams.
9
- */
10
-
11
- /** @param {string} v @returns {[number, number, number] | null} */
12
- export function parseSemver(v) {
13
- if (typeof v !== "string") return null;
14
- const m = /^\s*v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
15
- if (!m) return null;
16
- return [Number(m[1]), Number(m[2]), Number(m[3])];
17
- }
18
-
19
- /**
20
- * Compare two SemVer-shaped strings by MAJOR.MINOR.PATCH.
21
- * Returns -1 (a<b), 0 (equal or either unparseable), 1 (a>b). Pre-release
22
- * tails are ignored — this is the comparator for "is tool X new enough"
23
- * (Node/Python floors), where only the numeric triple can matter.
24
- *
25
- * The attach-vs-swap decision needs the OPPOSITE — it must distinguish two
26
- * `@next` builds that share a triple (`0.24.7-next.0` vs `0.24.7-next.1`) —
27
- * so it uses `compareSemverFull` instead.
28
- * @param {string} a @param {string} b @returns {-1 | 0 | 1}
29
- */
30
- export function compareSemver(a, b) {
31
- const pa = parseSemver(a);
32
- const pb = parseSemver(b);
33
- if (!pa || !pb) return 0;
34
- for (let i = 0; i < 3; i++) {
35
- if (pa[i] < pb[i]) return -1;
36
- if (pa[i] > pb[i]) return 1;
37
- }
38
- return 0;
39
- }
40
-
41
- /**
42
- * Extract the SemVer pre-release identifiers (the dot-separated tail after the
43
- * first `-`, before any `+build` metadata), or `null` when there is none.
44
- * `0.24.7-next.1` -> ["next", "1"]; `0.24.7` -> null; junk after the triple
45
- * without a `-` (e.g. `2.1.132 (Claude Code)`) -> null.
46
- * @param {string} v @returns {string[] | null}
47
- */
48
- export function parsePrerelease(v) {
49
- if (typeof v !== "string") return null;
50
- const m = /^\s*v?\d+\.\d+\.\d+-([0-9A-Za-z.-]+)/.exec(v.trim());
51
- if (!m) return null;
52
- return m[1].split(".");
53
- }
54
-
55
- /**
56
- * Compare two pre-release identifier lists per SemVer §11.4: a version WITHOUT
57
- * a pre-release outranks one WITH; numeric identifiers rank below alphanumeric
58
- * ones; a longer list outranks its own prefix. Both `null` (no pre-release) → 0.
59
- * @param {string[] | null} a @param {string[] | null} b @returns {-1 | 0 | 1}
60
- */
61
- function comparePrereleaseIds(a, b) {
62
- // A version WITHOUT a pre-release outranks one WITH (1.0.0 > 1.0.0-next).
63
- if (a === null && b === null) return 0;
64
- if (a === null) return 1;
65
- if (b === null) return -1;
66
- const len = Math.min(a.length, b.length);
67
- for (let i = 0; i < len; i++) {
68
- const ai = a[i];
69
- const bi = b[i];
70
- const an = /^\d+$/.test(ai);
71
- const bn = /^\d+$/.test(bi);
72
- if (an && bn) {
73
- const d = Number(ai) - Number(bi);
74
- if (d !== 0) return d < 0 ? -1 : 1;
75
- } else if (an !== bn) {
76
- // Numeric identifiers always have lower precedence than alphanumeric.
77
- return an ? -1 : 1;
78
- } else if (ai !== bi) {
79
- return ai < bi ? -1 : 1;
80
- }
81
- }
82
- if (a.length === b.length) return 0;
83
- return a.length < b.length ? -1 : 1;
84
- }
85
-
86
- /**
87
- * Compare two SemVer-shaped strings by triple AND pre-release tail. Returns
88
- * -1 / 0 / 1, or 0 when either triple is unparseable (never a false swap
89
- * trigger). This is the attach-vs-swap comparator: successive `@next` test
90
- * builds share a MAJOR.MINOR.PATCH and differ ONLY in `-next.N`, so a
91
- * triple-only compare would report them equal and ATTACH to the stale server
92
- * instead of swapping in the freshly published build.
93
- * @param {string} a @param {string} b @returns {-1 | 0 | 1}
94
- */
95
- export function compareSemverFull(a, b) {
96
- const triple = compareSemver(a, b);
97
- if (triple !== 0) return triple;
98
- // Triples equal (or a side unparseable → compareSemver already returned 0
99
- // and we must not manufacture a difference from the tails). Only when BOTH
100
- // triples parsed does the pre-release tail decide.
101
- if (!parseSemver(a) || !parseSemver(b)) return 0;
102
- return comparePrereleaseIds(parsePrerelease(a), parsePrerelease(b));
103
- }
104
-
105
- /** @param {NodeJS.Platform} [platform] */
106
- export function isWindows(platform = process.platform) {
107
- return platform === "win32";
108
- }
109
-
110
- /**
111
- * Platform-correct, copy-pasteable install command for a missing prerequisite.
112
- * A missing tool must never be a vague "install it" — the user gets the exact
113
- * line for THEIR OS. Mirrors the pointers `scripts/verify-setup.sh` prints.
114
- * @param {"uv"|"python"|"node"|"git"|"claude"} tool
115
- * @param {NodeJS.Platform} [platform]
116
- * @returns {string}
117
- */
118
- export function installHint(tool, platform = process.platform) {
119
- const win = isWindows(platform);
120
- switch (tool) {
121
- case "uv":
122
- return win
123
- ? 'powershell -c "irm https://astral.sh/uv/install.ps1 | iex"'
124
- : "curl -LsSf https://astral.sh/uv/install.sh | sh";
125
- case "python":
126
- // Coupled to uv: the whole stack runs via `uv run`, which resolves a
127
- // uv-MANAGED Python — a system python3 is neither required nor sufficient.
128
- // Lead with the uv path; the OS package manager / python.org is the fallback.
129
- return win
130
- ? "install Python 3.11+ via uv (`uv python install 3.11`) — or from https://www.python.org/downloads/ (NOT the Microsoft Store stub)"
131
- : "install Python 3.11+ via uv (`uv python install 3.11`) — or your package manager (e.g. `brew install python@3.11`)";
132
- case "node":
133
- return "install Node.js >= 20.12.0 from https://nodejs.org/";
134
- case "git":
135
- return win
136
- ? "install Git from https://git-scm.com/download/win"
137
- : "install git (e.g. `brew install git` / `apt install git`)";
138
- case "claude":
139
- return win
140
- ? 'powershell -c "irm https://claude.ai/install.ps1 | iex"'
141
- : "curl -fsSL https://claude.ai/install.sh | bash";
142
- default:
143
- return "";
144
- }
145
- }
146
-
147
- /** Minimum Node the packaged server needs (mirrors server/package.json engines). */
148
- export const MIN_NODE = "20.12.0";
149
-
150
- /** ASCII status glyphs — no emoji, PowerShell-5.1-safe, honest verdict blocks. */
151
- export const MARK = Object.freeze({
152
- pass: "[OK]",
153
- fail: "[!!]",
154
- warn: "[??]",
155
- skip: "[--]",
156
- info: "[..]",
157
- });
1
+ /**
2
+ * util.mjs — pure, dependency-free helpers shared across the bootstrapper.
3
+ *
4
+ * Everything here is side-effect free and unit-testable in isolation: SemVer
5
+ * parsing/compare (attach-vs-swap + stale-copy decisions ride on it), platform
6
+ * detection, and the actionable install hints a missing prerequisite must
7
+ * print. No `claude`, no network, no filesystem — those live in the sibling
8
+ * modules behind injected seams.
9
+ */
10
+
11
+ /** @param {string} v @returns {[number, number, number] | null} */
12
+ export function parseSemver(v) {
13
+ if (typeof v !== "string") return null;
14
+ const m = /^\s*v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
15
+ if (!m) return null;
16
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
17
+ }
18
+
19
+ /**
20
+ * Compare two SemVer-shaped strings by MAJOR.MINOR.PATCH.
21
+ * Returns -1 (a<b), 0 (equal or either unparseable), 1 (a>b). Pre-release
22
+ * tails are ignored — this is the comparator for "is tool X new enough"
23
+ * (Node/Python floors), where only the numeric triple can matter.
24
+ *
25
+ * The attach-vs-swap decision needs the OPPOSITE — it must distinguish two
26
+ * `@next` builds that share a triple (`0.24.7-next.0` vs `0.24.7-next.1`) —
27
+ * so it uses `compareSemverFull` instead.
28
+ * @param {string} a @param {string} b @returns {-1 | 0 | 1}
29
+ */
30
+ export function compareSemver(a, b) {
31
+ const pa = parseSemver(a);
32
+ const pb = parseSemver(b);
33
+ if (!pa || !pb) return 0;
34
+ for (let i = 0; i < 3; i++) {
35
+ if (pa[i] < pb[i]) return -1;
36
+ if (pa[i] > pb[i]) return 1;
37
+ }
38
+ return 0;
39
+ }
40
+
41
+ /**
42
+ * Extract the SemVer pre-release identifiers (the dot-separated tail after the
43
+ * first `-`, before any `+build` metadata), or `null` when there is none.
44
+ * `0.24.7-next.1` -> ["next", "1"]; `0.24.7` -> null; junk after the triple
45
+ * without a `-` (e.g. `2.1.132 (Claude Code)`) -> null.
46
+ * @param {string} v @returns {string[] | null}
47
+ */
48
+ export function parsePrerelease(v) {
49
+ if (typeof v !== "string") return null;
50
+ const m = /^\s*v?\d+\.\d+\.\d+-([0-9A-Za-z.-]+)/.exec(v.trim());
51
+ if (!m) return null;
52
+ return m[1].split(".");
53
+ }
54
+
55
+ /**
56
+ * Compare two pre-release identifier lists per SemVer §11.4: a version WITHOUT
57
+ * a pre-release outranks one WITH; numeric identifiers rank below alphanumeric
58
+ * ones; a longer list outranks its own prefix. Both `null` (no pre-release) → 0.
59
+ * @param {string[] | null} a @param {string[] | null} b @returns {-1 | 0 | 1}
60
+ */
61
+ function comparePrereleaseIds(a, b) {
62
+ // A version WITHOUT a pre-release outranks one WITH (1.0.0 > 1.0.0-next).
63
+ if (a === null && b === null) return 0;
64
+ if (a === null) return 1;
65
+ if (b === null) return -1;
66
+ const len = Math.min(a.length, b.length);
67
+ for (let i = 0; i < len; i++) {
68
+ const ai = a[i];
69
+ const bi = b[i];
70
+ const an = /^\d+$/.test(ai);
71
+ const bn = /^\d+$/.test(bi);
72
+ if (an && bn) {
73
+ const d = Number(ai) - Number(bi);
74
+ if (d !== 0) return d < 0 ? -1 : 1;
75
+ } else if (an !== bn) {
76
+ // Numeric identifiers always have lower precedence than alphanumeric.
77
+ return an ? -1 : 1;
78
+ } else if (ai !== bi) {
79
+ return ai < bi ? -1 : 1;
80
+ }
81
+ }
82
+ if (a.length === b.length) return 0;
83
+ return a.length < b.length ? -1 : 1;
84
+ }
85
+
86
+ /**
87
+ * Compare two SemVer-shaped strings by triple AND pre-release tail. Returns
88
+ * -1 / 0 / 1, or 0 when either triple is unparseable (never a false swap
89
+ * trigger). This is the attach-vs-swap comparator: successive `@next` test
90
+ * builds share a MAJOR.MINOR.PATCH and differ ONLY in `-next.N`, so a
91
+ * triple-only compare would report them equal and ATTACH to the stale server
92
+ * instead of swapping in the freshly published build.
93
+ * @param {string} a @param {string} b @returns {-1 | 0 | 1}
94
+ */
95
+ export function compareSemverFull(a, b) {
96
+ const triple = compareSemver(a, b);
97
+ if (triple !== 0) return triple;
98
+ // Triples equal (or a side unparseable → compareSemver already returned 0
99
+ // and we must not manufacture a difference from the tails). Only when BOTH
100
+ // triples parsed does the pre-release tail decide.
101
+ if (!parseSemver(a) || !parseSemver(b)) return 0;
102
+ return comparePrereleaseIds(parsePrerelease(a), parsePrerelease(b));
103
+ }
104
+
105
+ /** @param {NodeJS.Platform} [platform] */
106
+ export function isWindows(platform = process.platform) {
107
+ return platform === "win32";
108
+ }
109
+
110
+ /**
111
+ * Platform-correct, copy-pasteable install command for a missing prerequisite.
112
+ * A missing tool must never be a vague "install it" — the user gets the exact
113
+ * line for THEIR OS. Mirrors the pointers `scripts/verify-setup.sh` prints.
114
+ * @param {"uv"|"python"|"node"|"git"|"claude"} tool
115
+ * @param {NodeJS.Platform} [platform]
116
+ * @returns {string}
117
+ */
118
+ export function installHint(tool, platform = process.platform) {
119
+ const win = isWindows(platform);
120
+ switch (tool) {
121
+ case "uv":
122
+ return win
123
+ ? 'powershell -c "irm https://astral.sh/uv/install.ps1 | iex"'
124
+ : "curl -LsSf https://astral.sh/uv/install.sh | sh";
125
+ case "python":
126
+ // Coupled to uv: the whole stack runs via `uv run`, which resolves a
127
+ // uv-MANAGED Python — a system python3 is neither required nor sufficient.
128
+ // Lead with the uv path; the OS package manager / python.org is the fallback.
129
+ return win
130
+ ? "install Python 3.11+ via uv (`uv python install 3.11`) — or from https://www.python.org/downloads/ (NOT the Microsoft Store stub)"
131
+ : "install Python 3.11+ via uv (`uv python install 3.11`) — or your package manager (e.g. `brew install python@3.11`)";
132
+ case "node":
133
+ return "install Node.js >= 20.12.0 from https://nodejs.org/";
134
+ case "git":
135
+ return win
136
+ ? "install Git from https://git-scm.com/download/win"
137
+ : "install git (e.g. `brew install git` / `apt install git`)";
138
+ case "claude":
139
+ return win
140
+ ? 'powershell -c "irm https://claude.ai/install.ps1 | iex"'
141
+ : "curl -fsSL https://claude.ai/install.sh | bash";
142
+ default:
143
+ return "";
144
+ }
145
+ }
146
+
147
+ /** Minimum Node the packaged server needs (mirrors server/package.json engines). */
148
+ export const MIN_NODE = "20.12.0";
149
+
150
+ /** ASCII status glyphs — no emoji, PowerShell-5.1-safe, honest verdict blocks. */
151
+ export const MARK = Object.freeze({
152
+ pass: "[OK]",
153
+ fail: "[!!]",
154
+ warn: "[??]",
155
+ skip: "[--]",
156
+ info: "[..]",
157
+ });