@rse/ase 1.0.6 → 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 (279) hide show
  1. package/dst/ase-agent-definition-api.d.ts +184 -0
  2. package/dst/ase-agent-definition-api.js +6 -0
  3. package/dst/ase-agent-definition-builtin/condenser.js +42 -0
  4. package/dst/ase-agent-definition-builtin/editor.js +45 -0
  5. package/dst/ase-agent-definition-builtin/estimator.js +98 -0
  6. package/dst/ase-agent-definition-builtin/implementer.js +121 -0
  7. package/dst/ase-agent-definition-builtin/integrator.js +56 -0
  8. package/dst/ase-agent-definition-builtin/qualifier.js +61 -0
  9. package/dst/ase-agent-definition-builtin/rebooter.js +41 -0
  10. package/dst/ase-agent-definition-builtin/researcher.js +77 -0
  11. package/dst/ase-agent-definition-core.js +827 -0
  12. package/dst/ase-agent-fleet.js +772 -0
  13. package/dst/ase-agent-process.js +232 -0
  14. package/dst/ase-agent-relay-client.js +509 -0
  15. package/dst/ase-agent-relay-core.js +213 -0
  16. package/dst/ase-agent-relay-server-bind.js +242 -0
  17. package/dst/ase-agent-relay-server-cli.js +50 -0
  18. package/dst/ase-agent-runtime-claim.js +189 -0
  19. package/dst/ase-agent-runtime-engine.js +1100 -0
  20. package/dst/ase-agent-runtime-exec.js +275 -0
  21. package/dst/ase-agent-runtime-phase.js +581 -0
  22. package/dst/ase-agent-runtime-request.js +272 -0
  23. package/dst/ase-agent-runtime-tool.js +207 -0
  24. package/dst/ase-agent-runtime.js +1316 -0
  25. package/dst/ase-agent.js +65 -0
  26. package/dst/ase-artifact.js +6 -5
  27. package/dst/ase-config-core.js +465 -0
  28. package/dst/ase-config-schema.js +351 -0
  29. package/dst/ase-config-scope.js +172 -0
  30. package/dst/ase-config.js +25 -585
  31. package/dst/ase-hook.js +230 -116
  32. package/dst/ase-lib-daemon-cli.js +130 -0
  33. package/dst/ase-lib-hash.js +20 -0
  34. package/dst/{ase-log.js → ase-lib-log.js} +40 -7
  35. package/dst/ase-lib-server-bind.js +115 -0
  36. package/dst/ase-lib-server-cli.js +474 -0
  37. package/dst/{ase-table.js → ase-lib-table.js} +1 -1
  38. package/dst/{ase-version.js → ase-lib-version.js} +3 -0
  39. package/dst/ase-man.js +96 -0
  40. package/dst/ase-mcp.js +166 -13
  41. package/dst/{ase-getopt.js → ase-service-getopt.js} +82 -80
  42. package/dst/{ase-markdown.js → ase-service-markdown.js} +18 -11
  43. package/dst/ase-service-otel.js +1246 -0
  44. package/dst/{ase-skills.js → ase-service-skills.js} +29 -64
  45. package/dst/ase-service-state.js +101 -0
  46. package/dst/ase-service.js +251 -76
  47. package/dst/ase-setup-common.js +141 -0
  48. package/dst/ase-setup-mcp.js +359 -0
  49. package/dst/ase-setup-settings.js +837 -0
  50. package/dst/ase-setup.js +113 -869
  51. package/dst/ase-spec.js +31 -19
  52. package/dst/ase-statusline.js +108 -43
  53. package/dst/ase-task-board-core.js +739 -0
  54. package/dst/ase-task-board-filter.js +127 -0
  55. package/dst/ase-task-board-flow.js +237 -0
  56. package/dst/ase-task-board-graph.js +497 -0
  57. package/dst/ase-task-board-stats.js +263 -0
  58. package/dst/ase-task-board-tui-control.js +775 -0
  59. package/dst/ase-task-board-tui-model.js +1008 -0
  60. package/dst/ase-task-board-tui-popup.js +626 -0
  61. package/dst/ase-task-board-tui-style.js +106 -0
  62. package/dst/ase-task-board-tui-view.js +847 -0
  63. package/dst/ase-task-board-tui.js +189 -0
  64. package/dst/ase-task-board-web-client.css +1 -0
  65. package/dst/ase-task-board-web-client.html +20 -0
  66. package/dst/ase-task-board-web-client.js +81 -0
  67. package/dst/ase-task-board-web-server.js +823 -0
  68. package/dst/ase-task-board.js +134 -0
  69. package/dst/ase-task-format.js +757 -0
  70. package/dst/ase-task-request.js +208 -0
  71. package/dst/ase-task-store-client.js +495 -0
  72. package/dst/ase-task-store-core.js +804 -0
  73. package/dst/ase-task-store-issues.js +237 -0
  74. package/dst/ase-task-store-plugin-api.js +6 -0
  75. package/dst/ase-task-store-plugin-ase.js +484 -0
  76. package/dst/ase-task-store-plugin-delegate.js +137 -0
  77. package/dst/ase-task-store-plugin-gitea.js +666 -0
  78. package/dst/ase-task-store-plugin-github.js +696 -0
  79. package/dst/ase-task-store-plugin-gitlab.js +668 -0
  80. package/dst/ase-task-store-server-bind.js +538 -0
  81. package/dst/ase-task-store-server-cli.js +154 -0
  82. package/dst/ase-task.js +1335 -357
  83. package/dst/{ase-compat.js → ase-util-compat.js} +2 -2
  84. package/dst/{ase-diagram.js → ase-util-diagram.js} +107 -32
  85. package/dst/{ase-meta.js → ase-util-meta.js} +2 -2
  86. package/dst/{ase-metric.js → ase-util-metric.js} +2 -2
  87. package/dst/{ase-mint.js → ase-util-mint.js} +19 -12
  88. package/dst/{ase-worktree.js → ase-util-worktree.js} +30 -19
  89. package/dst/ase-util.js +20 -0
  90. package/dst/ase.js +108 -32
  91. package/dst/man/ase-agent-adhoc.1 +515 -0
  92. package/dst/man/ase-agent-condenser.1 +193 -0
  93. package/dst/man/ase-agent-editor.1 +212 -0
  94. package/dst/man/ase-agent-estimator.1 +214 -0
  95. package/dst/man/ase-agent-fleet.1 +185 -0
  96. package/dst/man/ase-agent-implementer.1 +233 -0
  97. package/dst/man/ase-agent-integrator.1 +213 -0
  98. package/dst/man/ase-agent-qualifier.1 +214 -0
  99. package/dst/man/ase-agent-rebooter.1 +193 -0
  100. package/dst/man/ase-agent-relay.1 +128 -0
  101. package/dst/man/ase-agent-researcher.1 +217 -0
  102. package/dst/man/ase-agent.1 +264 -0
  103. package/dst/man/ase-artifact-list.1 +38 -0
  104. package/dst/man/ase-artifact-name.1 +39 -0
  105. package/dst/man/ase-artifact.1 +56 -0
  106. package/dst/man/ase-config-delete.1 +45 -0
  107. package/dst/man/ase-config-edit.1 +48 -0
  108. package/dst/man/ase-config-get.1 +46 -0
  109. package/dst/man/ase-config-init.1 +60 -0
  110. package/dst/man/ase-config-list.1 +42 -0
  111. package/dst/man/ase-config-set.1 +52 -0
  112. package/dst/man/ase-config.1 +225 -0
  113. package/dst/man/ase-hook-permission-request.1 +36 -0
  114. package/dst/man/ase-hook-pre-tool-use.1 +59 -0
  115. package/dst/man/ase-hook-session-end.1 +47 -0
  116. package/dst/man/ase-hook-session-persona.1 +37 -0
  117. package/dst/man/ase-hook-session-start.1 +81 -0
  118. package/dst/man/ase-hook-stop.1 +34 -0
  119. package/dst/man/ase-hook-user-prompt-submit.1 +34 -0
  120. package/dst/man/ase-hook.1 +51 -0
  121. package/dst/man/ase-man.1 +67 -0
  122. package/dst/man/ase-mcp.1 +61 -0
  123. package/dst/man/ase-service-log.1 +43 -0
  124. package/dst/man/ase-service-send.1 +47 -0
  125. package/dst/man/ase-service-start.1 +54 -0
  126. package/dst/man/ase-service-status.1 +46 -0
  127. package/dst/man/ase-service-stop.1 +37 -0
  128. package/dst/man/ase-service.1 +85 -0
  129. package/dst/man/ase-setup-disable.1 +44 -0
  130. package/dst/man/ase-setup-enable.1 +45 -0
  131. package/dst/man/ase-setup-install.1 +73 -0
  132. package/dst/man/ase-setup-mcp.1 +124 -0
  133. package/dst/man/ase-setup-status.1 +60 -0
  134. package/dst/man/ase-setup-statusline.1 +92 -0
  135. package/dst/man/ase-setup-telemetry.1 +92 -0
  136. package/dst/man/ase-setup-uninstall.1 +59 -0
  137. package/dst/man/ase-setup-update.1 +64 -0
  138. package/dst/man/ase-setup.1 +93 -0
  139. package/dst/man/ase-spec-export.1 +62 -0
  140. package/dst/man/ase-spec-lint.1 +40 -0
  141. package/dst/man/ase-spec-preview.1 +40 -0
  142. package/dst/man/ase-spec.1 +46 -0
  143. package/dst/man/ase-statusline.1 +189 -0
  144. package/dst/man/ase-task-assign.1 +71 -0
  145. package/dst/man/ase-task-board.1 +384 -0
  146. package/dst/man/ase-task-delete.1 +35 -0
  147. package/dst/man/ase-task-edit.1 +46 -0
  148. package/dst/man/ase-task-idscheme.1 +65 -0
  149. package/dst/man/ase-task-lifecycle.1 +57 -0
  150. package/dst/man/ase-task-list.1 +47 -0
  151. package/dst/man/ase-task-load.1 +36 -0
  152. package/dst/man/ase-task-newid.1 +61 -0
  153. package/dst/man/ase-task-purge.1 +37 -0
  154. package/dst/man/ase-task-rename.1 +44 -0
  155. package/dst/man/ase-task-request.1 +61 -0
  156. package/dst/man/ase-task-save.1 +48 -0
  157. package/dst/man/ase-task-status.1 +55 -0
  158. package/dst/man/ase-task-store.1 +194 -0
  159. package/dst/man/ase-task-view.1 +40 -0
  160. package/dst/man/ase-task.1 +160 -0
  161. package/dst/man/ase-util-compat.1 +25 -0
  162. package/dst/man/ase-util-diagram.1 +76 -0
  163. package/dst/man/ase-util-meta.1 +45 -0
  164. package/dst/man/ase-util-metric.1 +54 -0
  165. package/dst/man/ase-util-mint.1 +59 -0
  166. package/dst/man/ase-util-worktree.1 +72 -0
  167. package/dst/man/ase-util.1 +50 -0
  168. package/dst/man/ase.1 +124 -0
  169. package/package.json +79 -21
  170. package/plugin/.claude-plugin/plugin.json +1 -1
  171. package/plugin/.codex-plugin/plugin.json +1 -1
  172. package/plugin/.github/plugin/plugin.json +1 -1
  173. package/plugin/agents/ase-meta-diagram.md +12 -8
  174. package/plugin/agents/ase-repo-review.md +322 -0
  175. package/plugin/hooks/hooks-codex.json +5 -0
  176. package/plugin/hooks/hooks-copilot.json +5 -0
  177. package/plugin/hooks/hooks.json +8 -0
  178. package/plugin/meta/ase-common-approach.md +117 -0
  179. package/plugin/meta/ase-common-changeset.md +256 -0
  180. package/plugin/meta/ase-common-code.md +10 -173
  181. package/plugin/meta/ase-common-grill.md +317 -0
  182. package/plugin/meta/ase-common-resolve.md +305 -0
  183. package/plugin/meta/ase-common-task.md +83 -4
  184. package/plugin/meta/ase-constitution.md +0 -2
  185. package/plugin/meta/ase-format-specbook.md +208 -25
  186. package/plugin/meta/ase-format-specbook.yaml +242 -851
  187. package/plugin/meta/ase-format-task.md +669 -125
  188. package/plugin/meta/ase-persona.md +3 -4
  189. package/plugin/meta/ase-skill.md +9 -0
  190. package/plugin/mods/ase-mods.ts +409 -0
  191. package/plugin/mods/claude-code.d.ts +13417 -0
  192. package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
  193. package/plugin/skills/ase-arch-analyze/help.md +2 -2
  194. package/plugin/skills/ase-code-analyze/SKILL.md +59 -8
  195. package/plugin/skills/ase-code-analyze/help.md +29 -4
  196. package/plugin/skills/ase-code-edit/SKILL.md +107 -181
  197. package/plugin/skills/ase-code-edit/help.md +118 -31
  198. package/plugin/skills/ase-code-lint/SKILL.md +64 -13
  199. package/plugin/skills/ase-code-lint/help.md +32 -2
  200. package/plugin/skills/ase-docs-proofread/SKILL.md +4 -4
  201. package/plugin/skills/ase-docs-proofread/help.md +1 -1
  202. package/plugin/skills/ase-docs-refine/SKILL.md +4 -4
  203. package/plugin/skills/ase-docs-shorten/SKILL.md +4 -4
  204. package/plugin/skills/ase-help-intent/data.md +2164 -1087
  205. package/plugin/skills/ase-help-intent/help.md +1 -1
  206. package/plugin/skills/ase-help-skill/catalog.md +12 -9
  207. package/plugin/skills/ase-meta-brainstorm/SKILL.md +1 -1
  208. package/plugin/skills/ase-meta-config/SKILL.md +3 -4
  209. package/plugin/skills/ase-meta-config/help.md +31 -10
  210. package/plugin/skills/ase-meta-mint/SKILL.md +5 -5
  211. package/plugin/skills/ase-meta-mint/help.md +10 -5
  212. package/plugin/skills/ase-meta-why/help.md +1 -1
  213. package/plugin/skills/ase-meta-workflow/SKILL.md +7 -7
  214. package/plugin/skills/ase-meta-workflow/help.md +4 -4
  215. package/plugin/skills/ase-meta-workflow/sample.md +3 -3
  216. package/plugin/skills/ase-meta-workflow/workflow.txt +22 -18
  217. package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/SKILL.md +4 -3
  218. package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/help.md +5 -5
  219. package/plugin/skills/{ase-meta-commit → ase-repo-commit}/SKILL.md +6 -6
  220. package/plugin/skills/{ase-meta-commit → ase-repo-commit}/help.md +5 -5
  221. package/plugin/skills/{ase-meta-diff → ase-repo-diff}/SKILL.md +6 -6
  222. package/plugin/skills/{ase-meta-diff → ase-repo-diff}/help.md +8 -8
  223. package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/SKILL.md +19 -19
  224. package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/help.md +10 -12
  225. package/plugin/skills/ase-repo-merge/SKILL.md +346 -0
  226. package/plugin/skills/ase-repo-merge/help.md +107 -0
  227. package/plugin/skills/ase-repo-resolve/SKILL.md +158 -0
  228. package/plugin/skills/ase-repo-resolve/help.md +102 -0
  229. package/plugin/skills/ase-repo-review/SKILL.md +406 -0
  230. package/plugin/skills/ase-repo-review/help.md +181 -0
  231. package/plugin/skills/ase-spec-edit/SKILL.md +117 -183
  232. package/plugin/skills/ase-spec-edit/help.md +107 -26
  233. package/plugin/skills/ase-sync-reconcile/help.md +1 -1
  234. package/plugin/skills/ase-task-assign/SKILL.md +111 -0
  235. package/plugin/skills/ase-task-assign/help.md +119 -0
  236. package/plugin/skills/ase-task-condense/SKILL.md +21 -21
  237. package/plugin/skills/ase-task-condense/help.md +12 -10
  238. package/plugin/skills/ase-task-delete/SKILL.md +1 -1
  239. package/plugin/skills/ase-task-dissect/SKILL.md +71 -48
  240. package/plugin/skills/ase-task-dissect/help.md +10 -9
  241. package/plugin/skills/ase-task-edit/SKILL.md +379 -94
  242. package/plugin/skills/ase-task-edit/help.md +95 -22
  243. package/plugin/skills/ase-task-estimate/SKILL.md +492 -0
  244. package/plugin/skills/ase-task-estimate/help.md +158 -0
  245. package/plugin/skills/ase-task-grill/SKILL.md +246 -138
  246. package/plugin/skills/ase-task-grill/help.md +129 -26
  247. package/plugin/skills/ase-task-id/SKILL.md +8 -1
  248. package/plugin/skills/ase-task-id/help.md +6 -3
  249. package/plugin/skills/ase-task-implement/SKILL.md +535 -145
  250. package/plugin/skills/ase-task-implement/help.md +138 -47
  251. package/plugin/skills/ase-task-integrate/SKILL.md +549 -0
  252. package/plugin/skills/ase-task-integrate/help.md +187 -0
  253. package/plugin/skills/ase-task-list/SKILL.md +55 -23
  254. package/plugin/skills/ase-task-list/help.md +45 -21
  255. package/plugin/skills/ase-task-reboot/SKILL.md +84 -43
  256. package/plugin/skills/ase-task-reboot/help.md +28 -8
  257. package/plugin/skills/ase-task-rename/SKILL.md +10 -2
  258. package/plugin/skills/ase-task-research/SKILL.md +386 -0
  259. package/plugin/skills/ase-task-research/help.md +102 -0
  260. package/plugin/skills/ase-task-status/SKILL.md +137 -0
  261. package/plugin/skills/ase-task-status/help.md +80 -0
  262. package/plugin/skills/ase-task-view/SKILL.md +49 -24
  263. package/plugin/skills/ase-task-view/help.md +19 -9
  264. package/plugin/agents/ase-meta-review.md +0 -187
  265. package/plugin/skills/ase-code-craft/SKILL.md +0 -264
  266. package/plugin/skills/ase-code-craft/help.md +0 -122
  267. package/plugin/skills/ase-code-refactor/SKILL.md +0 -232
  268. package/plugin/skills/ase-code-refactor/help.md +0 -109
  269. package/plugin/skills/ase-code-resolve/SKILL.md +0 -289
  270. package/plugin/skills/ase-code-resolve/help.md +0 -116
  271. package/plugin/skills/ase-meta-review/SKILL.md +0 -210
  272. package/plugin/skills/ase-meta-review/help.md +0 -96
  273. package/plugin/skills/ase-task-preflight/SKILL.md +0 -166
  274. package/plugin/skills/ase-task-preflight/help.md +0 -82
  275. /package/dst/{ase-ignore.js → ase-lib-ignore.js} +0 -0
  276. /package/dst/{ase-stdio.js → ase-lib-stdio.js} +0 -0
  277. /package/dst/{ase-kv.js → ase-service-kv.js} +0 -0
  278. /package/dst/{ase-sleep.js → ase-service-sleep.js} +0 -0
  279. /package/dst/{ase-timestamp.js → ase-service-timestamp.js} +0 -0
@@ -3,7 +3,7 @@
3
3
  ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
5
  */
6
- import { writeStdout } from "./ase-stdio.js";
6
+ import { writeStdout } from "./ase-lib-stdio.js";
7
7
  /* the canonical expected values for every ase-meta-compat probe,
8
8
  keyed by "<category>/<probe-name>" as used in the skill */
9
9
  const EXPECTED = {
@@ -39,7 +39,7 @@ const EXPECTED = {
39
39
  };
40
40
  /* format the expected values as "<id>: <value>\n" lines */
41
41
  const formatExpected = () => Object.entries(EXPECTED).map(([id, value]) => `${id}: ${value}`).join("\n") + "\n";
42
- /* CLI command "ase compat" */
42
+ /* CLI command "ase util compat" */
43
43
  export default class CompatCommand {
44
44
  register(program) {
45
45
  program
@@ -5,9 +5,8 @@
5
5
  */
6
6
  import fs from "node:fs";
7
7
  import { InvalidArgumentError } from "commander";
8
- import { renderMermaidASCII, renderMermaidSVG } from "beautiful-mermaid";
9
8
  import { z } from "zod";
10
- import { readStdin } from "./ase-stdio.js";
9
+ import { readStdin } from "./ase-lib-stdio.js";
11
10
  /* custom argument parser for Commander: non-negative integer */
12
11
  const parseInteger = (name) => (value) => {
13
12
  const n = Number.parseInt(value, 10);
@@ -21,12 +20,34 @@ const parseColorMode = (name) => (value) => {
21
20
  throw new InvalidArgumentError(`${name} must be "none", "ansi16", or "ansi256"`);
22
21
  return value;
23
22
  };
23
+ /* custom argument parser for Commander: diagram language */
24
+ const parseLang = (name) => (value) => {
25
+ if (value !== "mermaid" && value !== "d2")
26
+ throw new InvalidArgumentError(`${name} must be "mermaid" or "d2"`);
27
+ return value;
28
+ };
24
29
  /* custom argument parser for Commander: output format */
25
30
  const parseFormat = (name) => (value) => {
26
31
  if (value !== "ascii" && value !== "svg")
27
32
  throw new InvalidArgumentError(`${name} must be "ascii" or "svg"`);
28
33
  return value;
29
34
  };
35
+ /* the D2 compiler, created on first use only (as it spins up the
36
+ WebAssembly build of D2 in a worker thread, which keeps the process
37
+ alive until the compiler is disposed) */
38
+ let d2 = null;
39
+ /* turn a D2 compile failure (a JSON-encoded list of errors) into a message */
40
+ const d2Error = (err) => {
41
+ const message = err instanceof Error ? err.message : String(err);
42
+ try {
43
+ return JSON.parse(message)
44
+ .map((error) => error.errmsg.replace(/^index:/, "line "))
45
+ .join("; ");
46
+ }
47
+ catch {
48
+ return message;
49
+ }
50
+ };
30
51
  /* scan a CSI escape sequence starting at line[i] (where line[i]===ESC and
31
52
  line[i+1]==="["); return the index just past the terminating letter, or
32
53
  -1 if the sequence is unterminated within the line */
@@ -141,19 +162,48 @@ export class Diagram {
141
162
  }
142
163
  return mode;
143
164
  }
144
- /* pure rendering helper: turn a Mermaid source string plus options into
145
- a rendered Unicode/ASCII diagram string, or an SVG document string
146
- when "svg" format is requested. Throws on render failure. */
147
- static render(src, opts) {
165
+ /* dispose the D2 compiler (if any), so its worker thread no longer
166
+ keeps the process alive (used by the short-lived CLI only) */
167
+ static async dispose() {
168
+ if (d2 === null)
169
+ return;
170
+ const compiler = d2;
171
+ d2 = null;
172
+ await (await compiler).dispose();
173
+ }
174
+ /* render a D2 source string as Unicode/ASCII art or SVG document
175
+ ("@d2lang/d2" is loaded on first use only; the ANSI "colorMode"
176
+ and the node margins/padding are not supported by D2) */
177
+ static async renderD2(src, opts) {
178
+ d2 ??= import("@d2lang/d2").then((module) => new module.D2()).catch((err) => {
179
+ d2 = null;
180
+ throw err;
181
+ });
182
+ const compiler = await d2;
183
+ try {
184
+ const result = await compiler.compile(src);
185
+ return await compiler.render(result.diagram, {
186
+ ...result.renderOptions,
187
+ ascii: opts.format === "ascii",
188
+ asciiMode: opts.ascii ? "standard" : "extended"
189
+ });
190
+ }
191
+ catch (err) {
192
+ throw new Error(d2Error(err), { cause: err });
193
+ }
194
+ }
195
+ /* render a Mermaid source string as Unicode/ASCII art or SVG document
196
+ ("beautiful-mermaid" is loaded on first use only) */
197
+ static async renderMermaid(src, opts) {
198
+ const { renderMermaidASCII, renderMermaidSVG } = await import("beautiful-mermaid");
148
199
  /* render as a self-contained SVG document using the library's
149
- themed defaults (the ANSI "colorMode" and the terminal
150
- clipping below are meaningful only for ASCII art) */
200
+ themed defaults (the ANSI "colorMode" is meaningful only for ASCII art) */
151
201
  if (opts.format === "svg")
152
202
  return renderMermaidSVG(src);
153
203
  /* determine theme colors (gray shades once colors are enabled) */
154
204
  const colored = opts.colorMode !== "none";
155
205
  /* create diagram rendering */
156
- let out = renderMermaidASCII(src, {
206
+ return renderMermaidASCII(src, {
157
207
  useAscii: opts.ascii,
158
208
  paddingX: opts.nodeMarginX,
159
209
  paddingY: opts.nodeMarginY,
@@ -168,6 +218,24 @@ export class Diagram {
168
218
  corner: colored ? "#707070" : "#000000"
169
219
  }
170
220
  });
221
+ }
222
+ /* pure rendering helper: turn a Mermaid or D2 source string plus
223
+ options into a rendered Unicode/ASCII diagram string, or an SVG
224
+ document string when "svg" format is requested. Throws on render
225
+ failure. (the terminal clipping is meaningful only for ASCII art) */
226
+ static async render(src, opts) {
227
+ /* create diagram rendering */
228
+ let out = opts.lang === "d2" ?
229
+ await Diagram.renderD2(src, opts) :
230
+ await Diagram.renderMermaid(src, opts);
231
+ if (opts.format === "svg")
232
+ return out;
233
+ /* determine language-specific regeneration hints */
234
+ const lang = opts.lang === "d2" ? "D2" : "Mermaid";
235
+ const portrait = opts.lang === "d2" ?
236
+ "(\"direction: down\", top-to-bottom) over landscape (\"direction: right\"/\"left\"/\"up\")" :
237
+ "(\"flowchart TB\", top-to-bottom) over landscape (\"LR\"/\"RL\"/\"BT\")";
238
+ const nesting = opts.lang === "d2" ? "nested container" : "nested subgraph";
171
239
  /* optionally clip diagram rendering */
172
240
  const termWidth = opts.terminalWidth;
173
241
  const termHeight = opts.terminalHeight;
@@ -182,19 +250,19 @@ export class Diagram {
182
250
  const widest = lines.reduce((m, l) => Math.max(m, visibleWidth(l)), 0);
183
251
  if (widest > maxWidth)
184
252
  widthWarn =
185
- `ase diagram: WARNING: rendered diagram width ${widest} exceeds budget ${maxWidth}; ` +
186
- "rightmost content was clipped. Please regenerate the Mermaid source to fit " +
253
+ `ase util diagram: WARNING: rendered diagram width ${widest} exceeds budget ${maxWidth}; ` +
254
+ `rightmost content was clipped. Please regenerate the ${lang} source to fit ` +
187
255
  `within ${maxWidth} chars by preferring a portrait orientation ` +
188
- "(\"flowchart TB\", top-to-bottom) over landscape (\"LR\"/\"RL\"/\"BT\"), " +
256
+ `${portrait}, ` +
189
257
  "reducing siblings per row, abbreviating node labels, or restructuring " +
190
- "into nested subgraph hierarchies.";
258
+ `into ${nesting} hierarchies.`;
191
259
  lines = lines.map((l) => truncateAnsiLine(l, maxWidth));
192
260
  }
193
261
  if (maxHeight > 0 && lines.length > maxHeight) {
194
262
  const overflow = lines.length - maxHeight;
195
263
  heightWarn =
196
- `ase diagram: WARNING: rendered diagram height ${lines.length} exceeds budget ${maxHeight}; ` +
197
- `bottom ${overflow} line(s) were clipped. Please regenerate the Mermaid source to fit ` +
264
+ `ase util diagram: WARNING: rendered diagram height ${lines.length} exceeds budget ${maxHeight}; ` +
265
+ `bottom ${overflow} line(s) were clipped. Please regenerate the ${lang} source to fit ` +
198
266
  `within ${maxHeight} lines by reducing depth or splitting into multiple diagrams.`;
199
267
  lines = lines.slice(0, maxHeight);
200
268
  }
@@ -217,20 +285,21 @@ export default class DiagramCommand {
217
285
  register(program) {
218
286
  program
219
287
  .command("diagram")
220
- .description("Render Mermaid diagram specification as Unicode/ASCII art or SVG")
221
- .option("-i, --input <file>", "read Mermaid source from file instead of stdin")
288
+ .description("Render Mermaid or D2 diagram specification as Unicode/ASCII art or SVG")
289
+ .option("-i, --input <file>", "read diagram source from file instead of stdin")
290
+ .option("-l, --lang <lang>", "diagram language (\"mermaid\" or \"d2\")", parseLang("--lang"), "mermaid")
222
291
  .option("-f, --format <format>", "output format (\"ascii\" or \"svg\")", parseFormat("--format"), "ascii")
223
292
  .option("-a, --ascii", "emit plain ASCII (+-|) instead of Unicode box-drawing", false)
224
- .option("-c, --color-mode <mode>", "force color mode (\"none\", \"ansi16\", or \"ansi256\")", parseColorMode("--color-mode"), Diagram.detectColorMode())
225
- .option("--node-margin-x <n>", "horizontal margin between nodes of <n> characters", parseInteger("--node-margin-x"), 3)
226
- .option("--node-margin-y <n>", "vertical margin between nodes of <n> lines", parseInteger("--node-margin-y"), 3)
227
- .option("--node-padding <n>", "horizontal and vertical inner node padding with <n> characters", parseInteger("--node-padding"), 1)
293
+ .option("-c, --color-mode <mode>", "force color mode (\"none\", \"ansi16\", or \"ansi256\") (Mermaid only)", parseColorMode("--color-mode"), Diagram.detectColorMode())
294
+ .option("--node-margin-x <n>", "horizontal margin between nodes of <n> characters (Mermaid only)", parseInteger("--node-margin-x"), 3)
295
+ .option("--node-margin-y <n>", "vertical margin between nodes of <n> lines (Mermaid only)", parseInteger("--node-margin-y"), 3)
296
+ .option("--node-padding <n>", "horizontal and vertical inner node padding with <n> characters (Mermaid only)", parseInteger("--node-padding"), 1)
228
297
  .option("--diagram-clip-x <n>", "extra horizontal clipping of diagram to terminal width minus <n> characters", parseInteger("--diagram-clip-x"), 0)
229
298
  .option("--diagram-clip-y <n>", "extra vertical clipping of diagram to terminal height minus <n> lines", parseInteger("--diagram-clip-y"), 0)
230
299
  .option("--terminal-width <n>", "width of terminal of <n> characters (for diagram clipping)", parseInteger("--terminal-width"), Diagram.detectTermWidth())
231
300
  .option("--terminal-height <n>", "height of terminal of <n> lines (for diagram clipping)", parseInteger("--terminal-height"), Diagram.detectTermHeight())
232
301
  .action(async (opts) => {
233
- /* fetch Mermaid diagram specification from file or stdin */
302
+ /* fetch diagram specification from file or stdin */
234
303
  let src;
235
304
  if (opts.input !== undefined) {
236
305
  try {
@@ -245,19 +314,22 @@ export default class DiagramCommand {
245
314
  else
246
315
  src = await readStdin();
247
316
  if (src.trim() === "") {
248
- this.log.write("error", "diagram: empty Mermaid diagram specification");
317
+ this.log.write("error", "diagram: empty diagram specification");
249
318
  process.exit(1);
250
319
  }
251
320
  /* create diagram rendering */
252
321
  let out;
253
322
  try {
254
- out = Diagram.render(src, opts);
323
+ out = await Diagram.render(src, opts);
255
324
  }
256
325
  catch (err) {
257
326
  const message = err instanceof Error ? err.message : String(err);
258
327
  this.log.write("error", `diagram: render failed: ${message}`);
259
328
  process.exit(1);
260
329
  }
330
+ finally {
331
+ await Diagram.dispose();
332
+ }
261
333
  /* output diagram rendering */
262
334
  process.stdout.write(out);
263
335
  if (!out.endsWith("\n"))
@@ -270,7 +342,7 @@ export class DiagramMCP {
270
342
  register(mcp) {
271
343
  mcp.registerTool("ase_diagram", {
272
344
  title: "ASE diagram render",
273
- description: "Render a Mermaid diagram as Unicode/ASCII art or SVG. " +
345
+ description: "Render a Mermaid or D2 diagram as Unicode/ASCII art or SVG. " +
274
346
  "Use for visualizing " +
275
347
  "structure/layout/components/dependencies as a Flowchart, " +
276
348
  "control-flow/branching/concurrency as a Flowchart, " +
@@ -279,23 +351,26 @@ export class DiagramMCP {
279
351
  "data-structure/classes/methods as a UML Class Diagram, " +
280
352
  "data-model/entities/relationships as an ER Diagram, or " +
281
353
  "metrics/distributions/time-series as an XY-Chart. " +
282
- "Pass the Mermaid diagram specification as `diagram`. " +
354
+ "Pass the diagram specification as `diagram` and its language as `lang` " +
355
+ "(\"mermaid\" by default, or \"d2\"). " +
283
356
  "Returns the rendered art (or SVG document, for `format` \"svg\") as `text`.",
284
357
  inputSchema: {
285
358
  diagram: z.string()
286
- .describe("Mermaid diagram specification"),
359
+ .describe("Mermaid or D2 diagram specification"),
360
+ lang: z.enum(["mermaid", "d2"]).default("mermaid")
361
+ .describe("diagram language: \"mermaid\" for Mermaid, \"d2\" for D2"),
287
362
  format: z.enum(["ascii", "svg"]).default("ascii")
288
363
  .describe("output format: \"ascii\" for Unicode/ASCII art, \"svg\" for an SVG document"),
289
364
  ascii: z.boolean().default(false)
290
365
  .describe("emit plain ASCII (+-|) instead of Unicode box-drawing characters"),
291
366
  colorMode: z.enum(["none", "ansi16", "ansi256"]).default("none")
292
- .describe("color mode for ANSI escape sequences in the rendered output"),
367
+ .describe("color mode for ANSI escape sequences in the rendered output (Mermaid only)"),
293
368
  nodeMarginX: z.number().int().min(0).default(3)
294
- .describe("horizontal margin between nodes, in characters"),
369
+ .describe("horizontal margin between nodes, in characters (Mermaid only)"),
295
370
  nodeMarginY: z.number().int().min(0).default(3)
296
- .describe("vertical margin between nodes, in lines"),
371
+ .describe("vertical margin between nodes, in lines (Mermaid only)"),
297
372
  nodePadding: z.number().int().min(0).default(1)
298
- .describe("inner horizontal and vertical padding within each node, in characters"),
373
+ .describe("inner horizontal and vertical padding within each node, in characters (Mermaid only)"),
299
374
  diagramClipX: z.number().int().min(0).default(0)
300
375
  .describe("extra horizontal clipping: subtract this many characters from `terminalWidth`"),
301
376
  diagramClipY: z.number().int().min(0).default(0)
@@ -307,7 +382,7 @@ export class DiagramMCP {
307
382
  }
308
383
  }, async (args) => {
309
384
  try {
310
- const out = Diagram.render(args.diagram, args);
385
+ const out = await Diagram.render(args.diagram, args);
311
386
  return {
312
387
  content: [{ type: "text", text: out }]
313
388
  };
@@ -6,7 +6,7 @@
6
6
  import path from "node:path";
7
7
  import fs from "node:fs";
8
8
  import { fileURLToPath } from "node:url";
9
- import { writeStdout } from "./ase-stdio.js";
9
+ import { writeStdout } from "./ase-lib-stdio.js";
10
10
  /* reusable functionality: resolve and read plugin "meta/" files */
11
11
  export class Meta {
12
12
  /* determine the plugin "meta/" directory; the build process copies
@@ -45,7 +45,7 @@ export class Meta {
45
45
  }
46
46
  }
47
47
  }
48
- /* CLI command "ase meta" */
48
+ /* CLI command "ase util meta" */
49
49
  export default class MetaCommand {
50
50
  log;
51
51
  constructor(log) {
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import fs from "node:fs";
7
7
  import { z } from "zod";
8
- import { writeStdout } from "./ase-stdio.js";
8
+ import { writeStdout } from "./ase-lib-stdio.js";
9
9
  /* measure the metrics of the text "content", where "words" are the
10
10
  whitespace-separated tokens, "lines" count a trailing line without a
11
11
  final newline as a full line, "chars" are Unicode characters (code
@@ -42,7 +42,7 @@ export const measureInput = (file, text) => {
42
42
  else
43
43
  throw new Error("metric: either \"file\" or \"text\" has to be given");
44
44
  };
45
- /* CLI command "ase metric" */
45
+ /* CLI command "ase util metric" */
46
46
  export default class MetricCommand {
47
47
  /* register commands */
48
48
  register(program) {
@@ -7,21 +7,22 @@ import { createHash } from "node:crypto";
7
7
  import { Option } from "commander";
8
8
  import { z } from "zod";
9
9
  import UUID from "pure-uuid";
10
- import { writeStdout } from "./ase-stdio.js";
10
+ import { writeStdout } from "./ase-lib-stdio.js";
11
+ import { nanoid } from "./ase-lib-hash.js";
11
12
  /* the fixed ASE namespace UUID, itself a UUID V5 over the ASE home URL
12
13
  within the standard URL namespace, so that all hint-derived UUIDs
13
14
  stay stable over time and disjoint from those of other applications */
14
15
  const NAMESPACE = new UUID(5, "ns:URL", "https://ase.tools").format("std");
15
16
  /* mint "count" identifiers of "type" out of "hint": an empty hint
16
17
  yields "count" random UUIDs (V4), while a non-empty hint yields
17
- exactly one deterministic hash (UUID V5, SHA-1), as hashing is a
18
+ exactly one deterministic hash (UUID V5, SHA-1, NanoId), as hashing is a
18
19
  pure function of the hint and hence cannot produce distinct results
19
20
  for it -- a larger count is clamped and reported back as a warning */
20
21
  export const mint = (type, hint, count) => {
21
22
  if (!Number.isInteger(count) || count < 1)
22
23
  throw new Error("mint: count has to be a positive integer");
23
- if (type === "sha1" && hint === "")
24
- throw new Error("mint: type \"sha1\" requires a non-empty hint");
24
+ if (type !== "uuid" && hint === "")
25
+ throw new Error(`mint: type "${type}" requires a non-empty hint`);
25
26
  let warning = "";
26
27
  if (hint !== "" && count > 1) {
27
28
  warning = `WARNING: type "${type}" is deterministic for a non-empty hint, ` +
@@ -34,27 +35,29 @@ export const mint = (type, hint, count) => {
34
35
  ids.push(hint === "" ?
35
36
  new UUID(4).format("std") :
36
37
  new UUID(5, NAMESPACE, hint).format("std"));
37
- else
38
+ else if (type === "sha1")
38
39
  ids.push(createHash("sha1").update(hint).digest("hex"));
40
+ else
41
+ ids.push(nanoid(hint));
39
42
  }
40
43
  return { ids, warning };
41
44
  };
42
- /* CLI command "ase mint" */
45
+ /* CLI command "ase util mint" */
43
46
  export default class MintCommand {
44
47
  /* register commands */
45
48
  register(program) {
46
49
  program
47
50
  .command("mint")
48
- .description("Mint hash-derived identifiers (UUID, SHA-1) out of a hint")
51
+ .description("Mint hash-derived identifiers (UUID, SHA-1, NanoId) out of a hint")
49
52
  .addOption(new Option("-t, --type <type>", "identifier type")
50
- .choices(["uuid", "sha1"]).default("uuid"))
53
+ .choices(["uuid", "sha1", "nanoid"]).default("uuid"))
51
54
  .option("-c, --count <count>", "number of identifiers to mint (empty hint only)", "1")
52
55
  .argument("[hint...]", "hint the identifiers are derived from")
53
56
  .action(async (hint, opts) => {
54
57
  const count = Number(opts.count);
55
58
  const result = mint(opts.type, hint.join(" "), count);
56
59
  if (result.warning !== "")
57
- process.stderr.write(`ase mint: ${result.warning}\n`);
60
+ process.stderr.write(`ase util mint: ${result.warning}\n`);
58
61
  await writeStdout(`${result.ids.join("\n")}\n`);
59
62
  });
60
63
  }
@@ -69,15 +72,19 @@ export class MintMCP {
69
72
  "is derived from the hint, or a random UUID V4 if the hint is empty. " +
70
73
  "For `type` `sha1`, the 40-character hexadecimal SHA-1 digest of the hint " +
71
74
  "is derived, which requires a non-empty hint. " +
75
+ "For `type` `nanoid`, a 10-character, NanoId-style base62 hash of the hint " +
76
+ "(the SHA-1 digest, base62-encoded and truncated) is derived, which requires " +
77
+ "a non-empty hint, too. " +
72
78
  "Pass `count` to mint more than one random UUID V4 at once, which is " +
73
79
  "possible for an empty hint only, as hashing a non-empty hint is " +
74
80
  "deterministic and hence always yields exactly one identifier. " +
75
81
  "Returns the minted identifiers as `text`, one per line, optionally " +
76
82
  "preceded by a line starting with `WARNING:`.",
77
83
  inputSchema: {
78
- type: z.enum(["uuid", "sha1"]).default("uuid")
79
- .describe("identifier type: `uuid` (UUID V5, or V4 for an empty hint) or " +
80
- "`sha1` (SHA-1 hexadecimal digest), default `uuid`"),
84
+ type: z.enum(["uuid", "sha1", "nanoid"]).default("uuid")
85
+ .describe("identifier type: `uuid` (UUID V5, or V4 for an empty hint), " +
86
+ "`sha1` (SHA-1 hexadecimal digest), or `nanoid` (NanoId-style base62 hash), " +
87
+ "default `uuid`"),
81
88
  hint: z.string().default("")
82
89
  .describe("free-text hint the identifiers are derived from " +
83
90
  "(an empty hint is allowed for `uuid` only)"),
@@ -7,10 +7,13 @@ import path from "node:path";
7
7
  import fs from "node:fs";
8
8
  import { execaSync } from "execa";
9
9
  import { z } from "zod";
10
- import { writeStdout } from "./ase-stdio.js";
10
+ import { writeStdout } from "./ase-lib-stdio.js";
11
11
  /* the repository-root-relative path components of the base directory
12
12
  which holds all ASE-managed Git worktrees */
13
13
  const baseComponents = [".ase", "worktree"];
14
+ /* the extra path component of the base directory holding the temporary
15
+ worktrees: as ids never contain ".", it is disjoint from all regular ids */
16
+ const tempComponent = ".temp";
14
17
  /* reusable functionality: safe resolution of the ASE worktree
15
18
  directories under <repo-root>/.ase/worktree/<id> */
16
19
  export class Worktree {
@@ -23,12 +26,12 @@ export class Worktree {
23
26
  throw new Error("worktree: id must match [A-Za-z0-9_-]+");
24
27
  }
25
28
  /* determine the fully resolved top-level directory of the Git
26
- working tree, as a Git worktree can only be created from
27
- inside one */
28
- static repoRoot() {
29
+ working tree of a directory, as a Git worktree can only be
30
+ created from inside one */
31
+ static repoRoot(cwd = process.cwd()) {
29
32
  let top = "";
30
33
  try {
31
- top = execaSync("git", ["rev-parse", "--show-toplevel"], { stderr: "ignore" }).stdout.trim();
34
+ top = execaSync("git", ["rev-parse", "--show-toplevel"], { cwd, stderr: "ignore" }).stdout.trim();
32
35
  }
33
36
  catch {
34
37
  /* not inside a Git working tree */
@@ -64,9 +67,9 @@ export class Worktree {
64
67
  /* resolve the base directory holding all ASE worktrees, asserting
65
68
  that every path component below the repository root is a real
66
69
  directory; the base directory is created on demand only */
67
- static baseDir(create = false) {
68
- let dir = Worktree.repoRoot();
69
- for (const component of baseComponents) {
70
+ static baseDir(create = false, temp = false, cwd = process.cwd()) {
71
+ let dir = Worktree.repoRoot(cwd);
72
+ for (const component of temp ? [...baseComponents, tempComponent] : baseComponents) {
70
73
  dir = path.join(dir, component);
71
74
  Worktree.assertRealDir(dir);
72
75
  }
@@ -79,18 +82,18 @@ export class Worktree {
79
82
  /* resolve the worktree directory of a single id; the leaf itself is
80
83
  created by "git worktree add" and hence only has to be free of an
81
84
  aliasing entry left behind by an earlier run */
82
- static dir(id, create = false) {
85
+ static dir(id, create = false, temp = false, cwd = process.cwd()) {
83
86
  Worktree.validateId(id);
84
- const dir = path.join(Worktree.baseDir(create), id);
87
+ const dir = path.join(Worktree.baseDir(create, temp, cwd), id);
85
88
  Worktree.assertRealDir(dir);
86
89
  return dir;
87
90
  }
88
91
  }
89
- /* CLI command "ase worktree" */
92
+ /* CLI command "ase util worktree" */
90
93
  export default class WorktreeCommand {
91
94
  /* register commands */
92
95
  register(program) {
93
- /* register CLI top-level command "ase worktree" */
96
+ /* register CLI sub-command "ase util worktree" */
94
97
  const worktree = program
95
98
  .command("worktree")
96
99
  .description("Safely resolve the ASE worktree directories under <repo-root>/.ase/worktree")
@@ -98,22 +101,24 @@ export default class WorktreeCommand {
98
101
  worktree.outputHelp();
99
102
  process.exit(1);
100
103
  });
101
- /* register CLI sub-command "ase worktree base" */
104
+ /* register CLI sub-sub-command "ase util worktree base" */
102
105
  worktree
103
106
  .command("base")
104
107
  .description("Print the validated base directory holding all ASE worktrees")
105
108
  .option("-c, --create", "create the base directory if it does not exist yet")
109
+ .option("-t, --temp", "use the base directory of the temporary worktrees")
106
110
  .action(async (opts) => {
107
- await writeStdout(`${Worktree.baseDir(opts.create ?? false)}\n`);
111
+ await writeStdout(`${Worktree.baseDir(opts.create ?? false, opts.temp ?? false)}\n`);
108
112
  });
109
- /* register CLI sub-command "ase worktree path" */
113
+ /* register CLI sub-sub-command "ase util worktree path" */
110
114
  worktree
111
115
  .command("path")
112
116
  .description("Print the validated worktree directory of a single <id>")
113
117
  .argument("<id>", "Worktree identifier")
114
118
  .option("-c, --create", "create the base directory if it does not exist yet")
119
+ .option("-t, --temp", "resolve the id below the base directory of the temporary worktrees")
115
120
  .action(async (id, opts) => {
116
- await writeStdout(`${Worktree.dir(id, opts.create ?? false)}\n`);
121
+ await writeStdout(`${Worktree.dir(id, opts.create ?? false, opts.temp ?? false)}\n`);
117
122
  });
118
123
  }
119
124
  }
@@ -135,6 +140,9 @@ export class WorktreeMCP {
135
140
  "link, through a non-directory, or out of the repository, which `git worktree " +
136
141
  "add` would otherwise silently follow and thereby write outside the repository. " +
137
142
  "Set `create` to `true` to also create the base directory. " +
143
+ "Set `temp` to `true` for a *temporary* worktree, which is a worktree the caller " +
144
+ "may remove forcibly: it is then resolved below `<repo-root>/.ase/worktree/.temp` " +
145
+ "instead, a namespace disjoint from all regular worktrees. " +
138
146
  "Fails with an error if the path is unsafe or the current directory is not a " +
139
147
  "Git working tree; in that case you MUST NOT create the worktree at all.",
140
148
  inputSchema: {
@@ -142,14 +150,17 @@ export class WorktreeMCP {
142
150
  .describe("worktree identifier (allowed characters: A-Z, a-z, 0-9, '_', '-'); " +
143
151
  "if omitted, the base directory holding all ASE worktrees is returned"),
144
152
  create: z.boolean().optional()
145
- .describe("if true, create the base directory if it does not exist yet (default: false)")
153
+ .describe("if true, create the base directory if it does not exist yet (default: false)"),
154
+ temp: z.boolean().optional()
155
+ .describe("if true, resolve below the base directory of the temporary worktrees (default: false)")
146
156
  }
147
157
  }, async (args) => {
148
158
  try {
149
159
  const create = args.create ?? false;
160
+ const temp = args.temp ?? false;
150
161
  const text = args.id !== undefined ?
151
- Worktree.dir(args.id, create) :
152
- Worktree.baseDir(create);
162
+ Worktree.dir(args.id, create, temp) :
163
+ Worktree.baseDir(create, temp);
153
164
  return {
154
165
  content: [{ type: "text", text }]
155
166
  };
@@ -0,0 +1,20 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
+ */
6
+ /* CLI command "ase util" */
7
+ export default class UtilCommand {
8
+ /* register command */
9
+ register(program) {
10
+ /* register CLI top-level command "ase util" */
11
+ const util = program
12
+ .command("util")
13
+ .description("Utility commands (meta, compat, diagram, worktree, mint, metric)")
14
+ .action(() => {
15
+ util.outputHelp();
16
+ process.exit(1);
17
+ });
18
+ return util;
19
+ }
20
+ }