@stupidloud/codegraph 0.9.5 → 0.9.9

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 (302) hide show
  1. package/README.md +252 -116
  2. package/dist/bin/codegraph.js +52 -82
  3. package/dist/bin/codegraph.js.map +1 -1
  4. package/dist/context/formatter.d.ts.map +1 -1
  5. package/dist/context/formatter.js +25 -6
  6. package/dist/context/formatter.js.map +1 -1
  7. package/dist/context/index.d.ts +22 -0
  8. package/dist/context/index.d.ts.map +1 -1
  9. package/dist/context/index.js +257 -6
  10. package/dist/context/index.js.map +1 -1
  11. package/dist/context/markers.d.ts +19 -0
  12. package/dist/context/markers.d.ts.map +1 -0
  13. package/dist/context/markers.js +22 -0
  14. package/dist/context/markers.js.map +1 -0
  15. package/dist/db/queries.d.ts +88 -0
  16. package/dist/db/queries.d.ts.map +1 -1
  17. package/dist/db/queries.js +251 -7
  18. package/dist/db/queries.js.map +1 -1
  19. package/dist/db/sqlite-adapter.d.ts +7 -0
  20. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  21. package/dist/db/sqlite-adapter.js +3 -0
  22. package/dist/db/sqlite-adapter.js.map +1 -1
  23. package/dist/directory.d.ts.map +1 -1
  24. package/dist/directory.js +6 -20
  25. package/dist/directory.js.map +1 -1
  26. package/dist/extraction/generated-detection.d.ts +30 -0
  27. package/dist/extraction/generated-detection.d.ts.map +1 -0
  28. package/dist/extraction/generated-detection.js +80 -0
  29. package/dist/extraction/generated-detection.js.map +1 -0
  30. package/dist/extraction/grammars.d.ts +17 -1
  31. package/dist/extraction/grammars.d.ts.map +1 -1
  32. package/dist/extraction/grammars.js +65 -1
  33. package/dist/extraction/grammars.js.map +1 -1
  34. package/dist/extraction/index.d.ts +15 -2
  35. package/dist/extraction/index.d.ts.map +1 -1
  36. package/dist/extraction/index.js +206 -98
  37. package/dist/extraction/index.js.map +1 -1
  38. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  39. package/dist/extraction/languages/c-cpp.js +45 -0
  40. package/dist/extraction/languages/c-cpp.js.map +1 -1
  41. package/dist/extraction/languages/csharp.d.ts.map +1 -1
  42. package/dist/extraction/languages/csharp.js +2 -1
  43. package/dist/extraction/languages/csharp.js.map +1 -1
  44. package/dist/extraction/languages/go.d.ts.map +1 -1
  45. package/dist/extraction/languages/go.js +18 -2
  46. package/dist/extraction/languages/go.js.map +1 -1
  47. package/dist/extraction/languages/index.d.ts.map +1 -1
  48. package/dist/extraction/languages/index.js +2 -0
  49. package/dist/extraction/languages/index.js.map +1 -1
  50. package/dist/extraction/languages/java.d.ts.map +1 -1
  51. package/dist/extraction/languages/java.js +6 -0
  52. package/dist/extraction/languages/java.js.map +1 -1
  53. package/dist/extraction/languages/kotlin.d.ts.map +1 -1
  54. package/dist/extraction/languages/kotlin.js +6 -0
  55. package/dist/extraction/languages/kotlin.js.map +1 -1
  56. package/dist/extraction/languages/objc.d.ts +3 -0
  57. package/dist/extraction/languages/objc.d.ts.map +1 -0
  58. package/dist/extraction/languages/objc.js +133 -0
  59. package/dist/extraction/languages/objc.js.map +1 -0
  60. package/dist/extraction/mybatis-extractor.d.ts +48 -0
  61. package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
  62. package/dist/extraction/mybatis-extractor.js +198 -0
  63. package/dist/extraction/mybatis-extractor.js.map +1 -0
  64. package/dist/extraction/tree-sitter-types.d.ts +14 -0
  65. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  66. package/dist/extraction/tree-sitter.d.ts +84 -0
  67. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  68. package/dist/extraction/tree-sitter.js +681 -20
  69. package/dist/extraction/tree-sitter.js.map +1 -1
  70. package/dist/extraction/vue-extractor.d.ts +15 -0
  71. package/dist/extraction/vue-extractor.d.ts.map +1 -1
  72. package/dist/extraction/vue-extractor.js +88 -0
  73. package/dist/extraction/vue-extractor.js.map +1 -1
  74. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  75. package/dist/extraction/wasm-runtime-flags.js +1 -0
  76. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  77. package/dist/graph/traversal.d.ts.map +1 -1
  78. package/dist/graph/traversal.js +5 -2
  79. package/dist/graph/traversal.js.map +1 -1
  80. package/dist/index.d.ts +66 -3
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +105 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/installer/config-writer.d.ts +7 -8
  85. package/dist/installer/config-writer.d.ts.map +1 -1
  86. package/dist/installer/config-writer.js +7 -27
  87. package/dist/installer/config-writer.js.map +1 -1
  88. package/dist/installer/index.d.ts +3 -20
  89. package/dist/installer/index.d.ts.map +1 -1
  90. package/dist/installer/index.js +8 -39
  91. package/dist/installer/index.js.map +1 -1
  92. package/dist/installer/instructions-template.d.ts +11 -21
  93. package/dist/installer/instructions-template.d.ts.map +1 -1
  94. package/dist/installer/instructions-template.js +12 -56
  95. package/dist/installer/instructions-template.js.map +1 -1
  96. package/dist/installer/targets/antigravity.d.ts +57 -0
  97. package/dist/installer/targets/antigravity.d.ts.map +1 -0
  98. package/dist/installer/targets/antigravity.js +308 -0
  99. package/dist/installer/targets/antigravity.js.map +1 -0
  100. package/dist/installer/targets/claude.d.ts +10 -1
  101. package/dist/installer/targets/claude.d.ts.map +1 -1
  102. package/dist/installer/targets/claude.js +25 -40
  103. package/dist/installer/targets/claude.js.map +1 -1
  104. package/dist/installer/targets/codex.d.ts.map +1 -1
  105. package/dist/installer/targets/codex.js +15 -13
  106. package/dist/installer/targets/codex.js.map +1 -1
  107. package/dist/installer/targets/cursor.d.ts.map +1 -1
  108. package/dist/installer/targets/cursor.js +9 -38
  109. package/dist/installer/targets/cursor.js.map +1 -1
  110. package/dist/installer/targets/gemini.d.ts +26 -0
  111. package/dist/installer/targets/gemini.d.ts.map +1 -0
  112. package/dist/installer/targets/gemini.js +167 -0
  113. package/dist/installer/targets/gemini.js.map +1 -0
  114. package/dist/installer/targets/hermes.d.ts.map +1 -1
  115. package/dist/installer/targets/hermes.js +57 -3
  116. package/dist/installer/targets/hermes.js.map +1 -1
  117. package/dist/installer/targets/kiro.d.ts +27 -0
  118. package/dist/installer/targets/kiro.d.ts.map +1 -0
  119. package/dist/installer/targets/kiro.js +178 -0
  120. package/dist/installer/targets/kiro.js.map +1 -0
  121. package/dist/installer/targets/opencode.d.ts.map +1 -1
  122. package/dist/installer/targets/opencode.js +15 -13
  123. package/dist/installer/targets/opencode.js.map +1 -1
  124. package/dist/installer/targets/registry.d.ts.map +1 -1
  125. package/dist/installer/targets/registry.js +6 -0
  126. package/dist/installer/targets/registry.js.map +1 -1
  127. package/dist/installer/targets/shared.d.ts.map +1 -1
  128. package/dist/installer/targets/shared.js +3 -2
  129. package/dist/installer/targets/shared.js.map +1 -1
  130. package/dist/installer/targets/types.d.ts +1 -16
  131. package/dist/installer/targets/types.d.ts.map +1 -1
  132. package/dist/mcp/daemon-paths.d.ts +46 -0
  133. package/dist/mcp/daemon-paths.d.ts.map +1 -0
  134. package/dist/mcp/daemon-paths.js +125 -0
  135. package/dist/mcp/daemon-paths.js.map +1 -0
  136. package/dist/mcp/daemon.d.ts +161 -0
  137. package/dist/mcp/daemon.d.ts.map +1 -0
  138. package/dist/mcp/daemon.js +403 -0
  139. package/dist/mcp/daemon.js.map +1 -0
  140. package/dist/mcp/engine.d.ts +105 -0
  141. package/dist/mcp/engine.d.ts.map +1 -0
  142. package/dist/mcp/engine.js +270 -0
  143. package/dist/mcp/engine.js.map +1 -0
  144. package/dist/mcp/index.d.ts +67 -53
  145. package/dist/mcp/index.d.ts.map +1 -1
  146. package/dist/mcp/index.js +315 -388
  147. package/dist/mcp/index.js.map +1 -1
  148. package/dist/mcp/proxy.d.ts +81 -0
  149. package/dist/mcp/proxy.d.ts.map +1 -0
  150. package/dist/mcp/proxy.js +510 -0
  151. package/dist/mcp/proxy.js.map +1 -0
  152. package/dist/mcp/server-instructions.d.ts +1 -1
  153. package/dist/mcp/server-instructions.d.ts.map +1 -1
  154. package/dist/mcp/server-instructions.js +21 -21
  155. package/dist/mcp/session.d.ts +77 -0
  156. package/dist/mcp/session.d.ts.map +1 -0
  157. package/dist/mcp/session.js +294 -0
  158. package/dist/mcp/session.js.map +1 -0
  159. package/dist/mcp/tools.d.ts +160 -14
  160. package/dist/mcp/tools.d.ts.map +1 -1
  161. package/dist/mcp/tools.js +1622 -322
  162. package/dist/mcp/tools.js.map +1 -1
  163. package/dist/mcp/transport.d.ts +111 -29
  164. package/dist/mcp/transport.d.ts.map +1 -1
  165. package/dist/mcp/transport.js +181 -71
  166. package/dist/mcp/transport.js.map +1 -1
  167. package/dist/mcp/version.d.ts +19 -0
  168. package/dist/mcp/version.d.ts.map +1 -0
  169. package/dist/mcp/version.js +71 -0
  170. package/dist/mcp/version.js.map +1 -0
  171. package/dist/resolution/callback-synthesizer.d.ts +10 -0
  172. package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  173. package/dist/resolution/callback-synthesizer.js +1300 -0
  174. package/dist/resolution/callback-synthesizer.js.map +1 -0
  175. package/dist/resolution/frameworks/csharp.d.ts.map +1 -1
  176. package/dist/resolution/frameworks/csharp.js +36 -8
  177. package/dist/resolution/frameworks/csharp.js.map +1 -1
  178. package/dist/resolution/frameworks/drupal.d.ts.map +1 -1
  179. package/dist/resolution/frameworks/drupal.js +44 -12
  180. package/dist/resolution/frameworks/drupal.js.map +1 -1
  181. package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  182. package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  183. package/dist/resolution/frameworks/expo-modules.js +143 -0
  184. package/dist/resolution/frameworks/expo-modules.js.map +1 -0
  185. package/dist/resolution/frameworks/express.d.ts.map +1 -1
  186. package/dist/resolution/frameworks/express.js +102 -19
  187. package/dist/resolution/frameworks/express.js.map +1 -1
  188. package/dist/resolution/frameworks/fabric.d.ts +3 -0
  189. package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  190. package/dist/resolution/frameworks/fabric.js +354 -0
  191. package/dist/resolution/frameworks/fabric.js.map +1 -0
  192. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  193. package/dist/resolution/frameworks/go.js +6 -3
  194. package/dist/resolution/frameworks/go.js.map +1 -1
  195. package/dist/resolution/frameworks/index.d.ts +5 -0
  196. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  197. package/dist/resolution/frameworks/index.js +25 -1
  198. package/dist/resolution/frameworks/index.js.map +1 -1
  199. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  200. package/dist/resolution/frameworks/java.js +339 -12
  201. package/dist/resolution/frameworks/java.js.map +1 -1
  202. package/dist/resolution/frameworks/laravel.d.ts.map +1 -1
  203. package/dist/resolution/frameworks/laravel.js +17 -8
  204. package/dist/resolution/frameworks/laravel.js.map +1 -1
  205. package/dist/resolution/frameworks/nestjs.d.ts.map +1 -1
  206. package/dist/resolution/frameworks/nestjs.js +324 -0
  207. package/dist/resolution/frameworks/nestjs.js.map +1 -1
  208. package/dist/resolution/frameworks/play.d.ts +19 -0
  209. package/dist/resolution/frameworks/play.d.ts.map +1 -0
  210. package/dist/resolution/frameworks/play.js +111 -0
  211. package/dist/resolution/frameworks/play.js.map +1 -0
  212. package/dist/resolution/frameworks/python.d.ts.map +1 -1
  213. package/dist/resolution/frameworks/python.js +134 -16
  214. package/dist/resolution/frameworks/python.js.map +1 -1
  215. package/dist/resolution/frameworks/react-native.d.ts +3 -0
  216. package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  217. package/dist/resolution/frameworks/react-native.js +360 -0
  218. package/dist/resolution/frameworks/react-native.js.map +1 -0
  219. package/dist/resolution/frameworks/react.d.ts.map +1 -1
  220. package/dist/resolution/frameworks/react.js +96 -3
  221. package/dist/resolution/frameworks/react.js.map +1 -1
  222. package/dist/resolution/frameworks/ruby.d.ts.map +1 -1
  223. package/dist/resolution/frameworks/ruby.js +106 -2
  224. package/dist/resolution/frameworks/ruby.js.map +1 -1
  225. package/dist/resolution/frameworks/rust.d.ts.map +1 -1
  226. package/dist/resolution/frameworks/rust.js +102 -5
  227. package/dist/resolution/frameworks/rust.js.map +1 -1
  228. package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  229. package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  230. package/dist/resolution/frameworks/swift-objc.js +252 -0
  231. package/dist/resolution/frameworks/swift-objc.js.map +1 -0
  232. package/dist/resolution/frameworks/swift.d.ts.map +1 -1
  233. package/dist/resolution/frameworks/swift.js +30 -6
  234. package/dist/resolution/frameworks/swift.js.map +1 -1
  235. package/dist/resolution/go-module.d.ts +26 -0
  236. package/dist/resolution/go-module.d.ts.map +1 -0
  237. package/dist/resolution/go-module.js +78 -0
  238. package/dist/resolution/go-module.js.map +1 -0
  239. package/dist/resolution/import-resolver.d.ts +28 -0
  240. package/dist/resolution/import-resolver.d.ts.map +1 -1
  241. package/dist/resolution/import-resolver.js +617 -5
  242. package/dist/resolution/import-resolver.js.map +1 -1
  243. package/dist/resolution/index.d.ts +11 -0
  244. package/dist/resolution/index.d.ts.map +1 -1
  245. package/dist/resolution/index.js +156 -3
  246. package/dist/resolution/index.js.map +1 -1
  247. package/dist/resolution/name-matcher.d.ts.map +1 -1
  248. package/dist/resolution/name-matcher.js +212 -0
  249. package/dist/resolution/name-matcher.js.map +1 -1
  250. package/dist/resolution/swift-objc-bridge.d.ts +134 -0
  251. package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  252. package/dist/resolution/swift-objc-bridge.js +256 -0
  253. package/dist/resolution/swift-objc-bridge.js.map +1 -0
  254. package/dist/resolution/types.d.ts +44 -0
  255. package/dist/resolution/types.d.ts.map +1 -1
  256. package/dist/resolution/workspace-packages.d.ts +48 -0
  257. package/dist/resolution/workspace-packages.d.ts.map +1 -0
  258. package/dist/resolution/workspace-packages.js +208 -0
  259. package/dist/resolution/workspace-packages.js.map +1 -0
  260. package/dist/search/query-utils.d.ts +18 -0
  261. package/dist/search/query-utils.d.ts.map +1 -1
  262. package/dist/search/query-utils.js +30 -0
  263. package/dist/search/query-utils.js.map +1 -1
  264. package/dist/sync/git-hooks.d.ts.map +1 -1
  265. package/dist/sync/git-hooks.js +2 -0
  266. package/dist/sync/git-hooks.js.map +1 -1
  267. package/dist/sync/index.d.ts +3 -1
  268. package/dist/sync/index.d.ts.map +1 -1
  269. package/dist/sync/index.js +8 -1
  270. package/dist/sync/index.js.map +1 -1
  271. package/dist/sync/watcher.d.ts +212 -8
  272. package/dist/sync/watcher.d.ts.map +1 -1
  273. package/dist/sync/watcher.js +465 -51
  274. package/dist/sync/watcher.js.map +1 -1
  275. package/dist/sync/worktree.d.ts +54 -0
  276. package/dist/sync/worktree.d.ts.map +1 -0
  277. package/dist/sync/worktree.js +137 -0
  278. package/dist/sync/worktree.js.map +1 -0
  279. package/dist/types.d.ts +9 -1
  280. package/dist/types.d.ts.map +1 -1
  281. package/dist/types.js +3 -0
  282. package/dist/types.js.map +1 -1
  283. package/package.json +1 -1
  284. package/scripts/agent-eval/arms-F.sh +21 -0
  285. package/scripts/agent-eval/arms-matrix.sh +37 -0
  286. package/scripts/agent-eval/bench-readme.sh +28 -0
  287. package/scripts/agent-eval/bench-why-repo.sh +22 -0
  288. package/scripts/agent-eval/block-read-hook.sh +19 -0
  289. package/scripts/agent-eval/hook-settings.json +15 -0
  290. package/scripts/agent-eval/itrun.sh +24 -11
  291. package/scripts/agent-eval/parse-arms.mjs +116 -0
  292. package/scripts/agent-eval/parse-bench-readme.mjs +84 -0
  293. package/scripts/agent-eval/probe-context.mjs +21 -0
  294. package/scripts/agent-eval/probe-explore.mjs +40 -0
  295. package/scripts/agent-eval/probe-node.mjs +20 -0
  296. package/scripts/agent-eval/probe-sweep.mjs +119 -0
  297. package/scripts/agent-eval/probe-trace.mjs +20 -0
  298. package/scripts/agent-eval/run-arms.sh +56 -0
  299. package/scripts/agent-eval/seq-matrix.mjs +137 -0
  300. package/scripts/npm-sdk.js +75 -0
  301. package/scripts/pack-npm.sh +25 -1
  302. package/scripts/prepare-release.mjs +270 -0
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Promote `## [Unreleased]` content into `## [<version>]` in CHANGELOG.md
4
+ * so the release.yml workflow's `extract-release-notes.mjs <version>` call
5
+ * picks up everything that landed since the last release.
6
+ *
7
+ * **Why this exists:** the release workflow used to do a literal
8
+ * `extract-release-notes.mjs <version>` lookup with an `[Unreleased]`
9
+ * fallback. The fallback only triggers if the `[<version>]` block
10
+ * doesn't exist at all — and in practice maintainers sometimes had a
11
+ * sparse `[<version>]` block pre-populated (e.g. one early fix
12
+ * documented before the rest of the work landed). The workflow then
13
+ * extracted that sparse block, ignoring the much larger `[Unreleased]`
14
+ * section above it — so the published release notes were missing most
15
+ * of what shipped. See v0.9.5 for the canonical post-mortem.
16
+ *
17
+ * **What it does**, idempotently:
18
+ *
19
+ * Case A — `[<version>]` does not exist yet:
20
+ * Rename the `[Unreleased]` header to `[<version>] - <YYYY-MM-DD>`
21
+ * and add a fresh empty `## [Unreleased]` block above it. This is
22
+ * the common case.
23
+ *
24
+ * Case B — `[<version>]` exists AND `[Unreleased]` has content:
25
+ * Merge `[Unreleased]`'s sub-sections (### Added / ### Fixed /
26
+ * ### Changed / ### Removed / ### Deprecated / ### Security) into
27
+ * the corresponding sub-sections of `[<version>]`. Unmatched
28
+ * sub-sections are appended to `[<version>]`. The `[Unreleased]`
29
+ * block is then emptied.
30
+ *
31
+ * Case C — `[Unreleased]` has no content:
32
+ * No-op. Exit 0. Re-runs of the workflow are safe.
33
+ *
34
+ * **Where the date comes from:** for Case A, `<YYYY-MM-DD>` is the
35
+ * UTC date at run time. Matches the existing CHANGELOG convention.
36
+ *
37
+ * **Usage:**
38
+ *
39
+ * node scripts/prepare-release.mjs # reads version from package.json
40
+ * node scripts/prepare-release.mjs 1.2.3 # explicit version
41
+ *
42
+ * **Output:**
43
+ *
44
+ * Writes CHANGELOG.md in place. Prints a summary line to stdout
45
+ * like `prepare-release: 0.9.5 — promoted 6 Unreleased entries`.
46
+ * Exits non-zero on parse failures.
47
+ */
48
+
49
+ import { readFileSync, writeFileSync } from 'node:fs';
50
+ import { resolve } from 'node:path';
51
+
52
+ const CHANGELOG_PATH = resolve(process.cwd(), 'CHANGELOG.md');
53
+
54
+ function readPackageVersion() {
55
+ const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8'));
56
+ if (!pkg.version) throw new Error('package.json has no "version" field');
57
+ return pkg.version;
58
+ }
59
+
60
+ function todayUtcIsoDate() {
61
+ // YYYY-MM-DD in UTC. Matches the CHANGELOG's existing convention
62
+ // (the existing dated entries don't disclose a timezone, but UTC is
63
+ // stable across runners and is what the workflow's runner produces
64
+ // by default anyway).
65
+ return new Date().toISOString().slice(0, 10);
66
+ }
67
+
68
+ /**
69
+ * Split the CHANGELOG into a header preface + an ordered list of
70
+ * version blocks `{ header, body[] }`, preserving line content
71
+ * verbatim so we can re-join without surprises.
72
+ */
73
+ function parseChangelog(text) {
74
+ const lines = text.split('\n');
75
+ const versionHeaderRe = /^## \[([^\]]+)\](?:\s+-\s+(.+))?\s*$/;
76
+ const preface = [];
77
+ const blocks = []; // { header: string, name: string, body: string[] }
78
+ let cur = null;
79
+ for (const line of lines) {
80
+ const m = line.match(versionHeaderRe);
81
+ if (m) {
82
+ if (cur) blocks.push(cur);
83
+ cur = { header: line, name: m[1], date: m[2] ?? null, body: [] };
84
+ } else if (cur) {
85
+ cur.body.push(line);
86
+ } else {
87
+ preface.push(line);
88
+ }
89
+ }
90
+ if (cur) blocks.push(cur);
91
+ return { preface, blocks };
92
+ }
93
+
94
+ function joinChangelog({ preface, blocks }) {
95
+ const parts = [preface.join('\n')];
96
+ for (const b of blocks) {
97
+ // Reconstruct: header + body. The block body INCLUDES the blank
98
+ // line after the header (it was captured verbatim).
99
+ parts.push([b.header, ...b.body].join('\n'));
100
+ }
101
+ return parts.join('\n');
102
+ }
103
+
104
+ /**
105
+ * Split a block body into ordered sub-sections keyed by their
106
+ * `### Heading`. Lines before the first `### Heading` go in
107
+ * `leading`. Preserves the original (line-array) body inside each
108
+ * sub-section so we can splice cleanly when merging.
109
+ */
110
+ function splitSubsections(body) {
111
+ const subsectionRe = /^### (\w+)\s*$/;
112
+ const leading = [];
113
+ const subs = []; // { heading: 'Added' | 'Fixed' | …, headerLine: string, body: string[] }
114
+ let cur = null;
115
+ for (const line of body) {
116
+ const m = line.match(subsectionRe);
117
+ if (m) {
118
+ if (cur) subs.push(cur);
119
+ cur = { heading: m[1], headerLine: line, body: [] };
120
+ } else if (cur) {
121
+ cur.body.push(line);
122
+ } else {
123
+ leading.push(line);
124
+ }
125
+ }
126
+ if (cur) subs.push(cur);
127
+ return { leading, subs };
128
+ }
129
+
130
+ function rebuildBody({ leading, subs }) {
131
+ const parts = [];
132
+ if (leading.length) parts.push(leading.join('\n'));
133
+ for (const s of subs) {
134
+ parts.push([s.headerLine, ...s.body].join('\n'));
135
+ }
136
+ return parts.join('\n').split('\n');
137
+ }
138
+
139
+ /**
140
+ * Return true when the block has any meaningful entries (a bullet line
141
+ * starting with `-`, `*`, or a digit) — vs. being empty / just
142
+ * whitespace / just sub-section headers with nothing under them.
143
+ */
144
+ function blockHasContent(body) {
145
+ for (const line of body) {
146
+ if (/^\s*([-*]|\d+\.)\s+/.test(line)) return true;
147
+ }
148
+ return false;
149
+ }
150
+
151
+ /**
152
+ * Trim trailing blank lines from an array of lines, then return.
153
+ * Keeps the output tidy when merging.
154
+ */
155
+ function trimTrailingBlank(arr) {
156
+ let i = arr.length;
157
+ while (i > 0 && /^\s*$/.test(arr[i - 1])) i--;
158
+ return arr.slice(0, i);
159
+ }
160
+
161
+ function main() {
162
+ const versionArg = process.argv[2];
163
+ const version = versionArg || readPackageVersion();
164
+
165
+ const text = readFileSync(CHANGELOG_PATH, 'utf8');
166
+ const parsed = parseChangelog(text);
167
+
168
+ const unrelIdx = parsed.blocks.findIndex((b) => b.name === 'Unreleased');
169
+ const verIdx = parsed.blocks.findIndex((b) => b.name === version);
170
+
171
+ if (unrelIdx === -1) {
172
+ console.log(`prepare-release: no [Unreleased] block — nothing to do`);
173
+ return;
174
+ }
175
+
176
+ const unrel = parsed.blocks[unrelIdx];
177
+ if (!blockHasContent(unrel.body)) {
178
+ console.log(`prepare-release: [Unreleased] is empty — nothing to do`);
179
+ return;
180
+ }
181
+
182
+ if (verIdx === -1) {
183
+ // Case A — promote Unreleased → [version].
184
+ const today = todayUtcIsoDate();
185
+ const promoted = {
186
+ header: `## [${version}] - ${today}`,
187
+ name: version,
188
+ date: today,
189
+ body: trimTrailingBlank(unrel.body).concat(['']), // single trailing blank
190
+ };
191
+ const emptied = {
192
+ header: `## [Unreleased]`,
193
+ name: 'Unreleased',
194
+ date: null,
195
+ body: ['', ''], // two blank lines for the next round of entries
196
+ };
197
+ parsed.blocks.splice(unrelIdx, 1, emptied, promoted);
198
+ const next = joinChangelog(parsed);
199
+ writeFileSync(CHANGELOG_PATH, appendLinkRef(next, version));
200
+ console.log(`prepare-release: ${version} — renamed [Unreleased] to [${version}] - ${today}`);
201
+ return;
202
+ }
203
+
204
+ // Case B — merge Unreleased sub-sections into the existing
205
+ // [version] sub-sections. New sub-section headings encountered in
206
+ // Unreleased that don't exist in [version] get appended.
207
+ const ver = parsed.blocks[verIdx];
208
+ const unrelSubs = splitSubsections(unrel.body);
209
+ const verSubs = splitSubsections(ver.body);
210
+
211
+ let merged = 0;
212
+ for (const us of unrelSubs.subs) {
213
+ const target = verSubs.subs.find((s) => s.heading === us.heading);
214
+ const usBody = trimTrailingBlank(us.body);
215
+ if (usBody.length === 0) continue;
216
+ if (target) {
217
+ // Append Unreleased's entries to the end of the version's matching
218
+ // sub-section, keeping their original ordering. Insert a separating
219
+ // blank line if the existing sub-section doesn't already end in one.
220
+ const existing = trimTrailingBlank(target.body);
221
+ const sep = existing.length && !/^\s*$/.test(existing[existing.length - 1]) ? [''] : [];
222
+ target.body = existing.concat(sep, usBody, ['']);
223
+ } else {
224
+ // Append the whole sub-section to the end.
225
+ verSubs.subs.push({
226
+ heading: us.heading,
227
+ headerLine: us.headerLine,
228
+ body: usBody.concat(['']),
229
+ });
230
+ }
231
+ merged += usBody.filter((l) => /^\s*([-*]|\d+\.)\s+/.test(l)).length;
232
+ }
233
+
234
+ ver.body = rebuildBody(verSubs);
235
+ // Empty out Unreleased.
236
+ unrel.body = ['', ''];
237
+
238
+ const merged_text = joinChangelog(parsed);
239
+ writeFileSync(CHANGELOG_PATH, appendLinkRef(merged_text, version));
240
+ console.log(`prepare-release: ${version} — merged ${merged} Unreleased entries into existing [${version}] block`);
241
+ }
242
+
243
+ /**
244
+ * Append a `[X.Y.Z]: https://github.com/colbymchenry/codegraph/releases/tag/vX.Y.Z`
245
+ * link reference at the end of the file IF one doesn't already exist. The
246
+ * link ref is what makes `## [X.Y.Z]` heading text auto-link to its tag in
247
+ * GitHub's renderer; without it the heading still renders, just unlinked.
248
+ *
249
+ * Idempotent. The existing CHANGELOG mixes link refs scattered through the
250
+ * file and a sorted block at the bottom — we just append at the very end,
251
+ * which CommonMark accepts regardless.
252
+ */
253
+ function appendLinkRef(text, version) {
254
+ const refLine = `[${version}]: https://github.com/colbymchenry/codegraph/releases/tag/v${version}`;
255
+ // Already there? Look for a line that EQUALS this (anywhere in the file)
256
+ // to keep idempotency robust against the scattered-vs-block layout.
257
+ const lines = text.split('\n');
258
+ if (lines.some((l) => l.trim() === refLine)) return text;
259
+ // Append, separated by a blank line from the prior content. Preserve a
260
+ // single trailing newline at EOF.
261
+ const trailingNewline = text.endsWith('\n') ? '' : '\n';
262
+ return text + trailingNewline + refLine + '\n';
263
+ }
264
+
265
+ try {
266
+ main();
267
+ } catch (err) {
268
+ console.error(`prepare-release: ${err?.message ?? err}`);
269
+ process.exit(1);
270
+ }