-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathbb.edn
More file actions
494 lines (423 loc) · 26.5 KB
/
bb.edn
File metadata and controls
494 lines (423 loc) · 26.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
{:paths ["bb" ".bbum/lib" "bases/main/src" "bases/main/resources"]
:bbin/bin {psi {:main-opts ["-m" "psi.launcher-main"]}}
:tasks
{:requires ([babashka.tasks :refer [shell]]
[clojure.string :as str])
:init (do
(load-file "bb/dispatch_architecture_check.clj")
(load-file "bb/release.clj")
(load-file "bb/kaocha_runner.clj")
(def chiasmus-home
(str (System/getProperty "user.home") "/src/chiasmus"))
(defn parse-review-plan-opts [args]
(loop [args args
opts {:focus "quick" :limit nil}]
(if-let [arg (first args)]
(cond
(= arg "--focus")
(if-let [focus (second args)]
(if (#{"all" "security" "architecture" "correctness" "quick"} focus)
(recur (nnext args) (assoc opts :focus focus))
(throw (ex-info "--focus must be one of: all, security, architecture, correctness, quick"
{:args args})))
(throw (ex-info "Missing value for --focus" {:args args})))
(= arg "--limit")
(if-let [n (some-> (second args) parse-long)]
(recur (nnext args) (assoc opts :limit n))
(throw (ex-info "--limit requires an integer" {:args args})))
:else
(throw (ex-info "Unknown argument" {:arg arg :args args})))
opts)))
(defn parse-chiasmus-check-opts [args]
(loop [args args
opts {:limit nil :full false}]
(if-let [arg (first args)]
(cond
(= arg "--limit")
(if-let [n (some-> (second args) parse-long)]
(recur (nnext args) (assoc opts :limit n))
(throw (ex-info "--limit requires an integer" {:args args})))
(= arg "--full")
(recur (next args) (assoc opts :full true))
:else
(throw (ex-info "Unknown argument" {:arg arg :args args})))
opts)))
(defn parse-top-files-opts [args]
(loop [args args
opts {:n 10 :since 100 :match nil :path nil :merges? true :sort :count :by :commits}]
(if-let [arg (first args)]
(cond
(= arg "--match")
(if-let [pattern (second args)]
(recur (nnext args) (assoc opts :match pattern))
(throw (ex-info "Missing value for --match" {:args args})))
(= arg "--path")
(if-let [path (second args)]
(recur (nnext args) (assoc opts :path path))
(throw (ex-info "Missing value for --path" {:args args})))
(= arg "--since")
(if-let [n (some-> (second args) parse-long)]
(recur (nnext args) (assoc opts :since n))
(throw (ex-info "--since requires an integer" {:args args})))
(= arg "--sort")
(case (second args)
"count" (recur (nnext args) (assoc opts :sort :count))
"path" (recur (nnext args) (assoc opts :sort :path))
(throw (ex-info "--sort must be one of: count, path" {:args args})))
(= arg "--by")
(case (second args)
"commits" (recur (nnext args) (assoc opts :by :commits))
"churn" (recur (nnext args) (assoc opts :by :churn))
(throw (ex-info "--by must be one of: commits, churn" {:args args})))
(= arg "--merges")
(recur (next args) (assoc opts :merges? true))
(= arg "--no-merges")
(recur (next args) (assoc opts :merges? false))
(parse-long arg)
(recur (next args) (assoc opts :n (parse-long arg)))
:else
(throw (ex-info "Unknown argument" {:arg arg :args args})))
opts)))
(defn top-files-path-match? [{:keys [match path]}]
(let [match? (if match
(let [pattern (re-pattern match)]
(fn [file-path] (boolean (re-find pattern file-path))))
(constantly true))
path? (if path
(fn [file-path] (str/includes? file-path path))
(constantly true))]
(fn [file-path]
(and (match? file-path)
(path? file-path)))))
(defn git-log-output [since merges? & args]
(:out (apply shell {:out :string}
(concat ["git" "log" "-n" (str since)]
(when-not merges? ["--no-merges"])
args))))
(defn sort-top-file-rows [sort rows]
(case sort
:path (sort-by key rows)
:count (sort-by (juxt (comp - val) key) rows)))
(defn top-file-rows [{:keys [by since merges?] :as opts}]
(let [path-match? (top-files-path-match? opts)]
(case by
:commits
(->> (git-log-output since merges? "--name-only" "--pretty=format:")
str/split-lines
(remove str/blank?)
(filter path-match?)
frequencies)
:churn
(->> (git-log-output since merges? "--numstat" "--pretty=format:")
str/split-lines
(reduce (fn [totals line]
(let [[added deleted path] (str/split line (re-pattern "\\t") 3)]
(if (and added deleted path
(re-matches (re-pattern "\\d+") added)
(re-matches (re-pattern "\\d+") deleted)
(path-match? path))
(update totals path (fnil + 0)
(+ (parse-long added) (parse-long deleted)))
totals)))
{})))))
(defn print-top-files [fmt rows]
(run! (fn [[path value]]
(println (format fmt value path)))
rows))
(def scry-results-dir ".scry-results/")
(defn scry-runner-error? [result]
(and (pos? (:exit result))
(str/includes? (or (:err result) "") "scry CLI error:")))
(defn print-shell-result! [result]
(print (:out result))
(when (seq (:err result))
(binding [*out* *err*]
(print (:err result))))
(flush))
(defn print-scry-results-hint! []
(binding [*out* *err*]
(println "Scry failure/error details are written under" scry-results-dir
"as EDN files.")))
(defn scry-test-failure? [result]
(and (pos? (:exit result))
(not (scry-runner-error? result))))
(defn run-scry-cli!
"Run focused clojure.test tests with Scry and point failures/errors
at the structured result-data directory."
[args]
(let [result (apply shell {:continue true :out :string :err :string}
"clojure" "-M:test-paths" "-m" "scry.cli"
args)]
(print-shell-result! result)
(when (scry-test-failure? result)
(print-scry-results-hint!))
(:exit result)))
(defn run-scry-kaocha-suite!
"Run a Kaocha suite through Scry first, falling back to the existing
Kaocha wrapper only when the Scry runner itself cannot execute. Test
failures stay as Scry failures so the structured .scry-results data
remains the default diagnostic path."
[suite kaocha-args]
(let [result (shell {:continue true :out :string :err :string}
"clojure" "-X:test" "scry.cli/run"
":runner" ":kaocha" ":suite" (str ":" suite))]
(print-shell-result! result)
(if (scry-runner-error? result)
(do
(binding [*out* *err*]
(println "Scry runner unavailable; falling back to Kaocha."))
(bb.kaocha-runner/run! kaocha-args))
(do
(when (scry-test-failure? result)
(print-scry-results-hint!))
(:exit result))))))
fmt:check
{:doc "Check Clojure and EDN formatting with cljfmt CLI."
:task (shell "cljfmt check bb.edn deps.edn .lsp/config.edn .psi/startup-prompts.edn components extensions spec tests.edn extensions/tests.edn")}
lint
{:doc "Lint repository Clojure and EDN sources with clj-kondo."
:task (shell "clojure -M:lint")}
commit-check:rama-cc
{:doc "Run rama-cc against components/ and bases/ with commit-check thresholds. Skips when rama-cc is not on PATH."
:task (do
(when (-> (shell {:continue true :out :string :err :string} "which" "rama-cc") :exit pos?)
(println "rama-cc not found on PATH, skipping.")
(System/exit 0))
(let [result (shell {:continue true}
"rama-cc" "--threshold" "21" "--fail-above" "20" "components/" "bases/")]
(System/exit (:exit result))))}
commit-check:file-lengths
{:doc "Fail when any src/ or test/ file under components/ or bases/ exceeds 800 lines."
:task (let [result (shell {:out :string}
"find" "components" "bases" "-type" "f"
"(" "-path" "*/src/*" "-o" "-path" "*/test/*" ")"
"-exec" "wc" "-l" "{}" ";")
lines (str/split-lines (:out result))
bad (->> lines
(keep (fn [line]
(let [[_ n path] (re-matches (re-pattern "\\s*(\\d+)\\s+(.+)") line)]
(when (and n path (> (Long/parseLong n) 800))
(str path " (" n " lines)")))))
vec)]
(when (seq bad)
(binding [*out* *err*]
(println "Files longer than 800 lines:")
(doseq [line bad]
(println line)))
(System/exit 1)))}
commit-check:dispatch-architecture
{:doc "Run psi-specific dispatch architecture checks. Fails on effect parity drift; reports handler purity and canonical state write boundary advisories."
:task (System/exit (bb.dispatch-architecture-check/run! *command-line-args*))}
clojure:test:unit
{:doc "Run Clojure unit tests through Scry's structured runner; fall back to raw Kaocha only if Scry cannot execute."
:task (System/exit (run-scry-kaocha-suite! "unit" ["--focus" "unit"]))}
clojure:test:extensions
{:doc "Run Clojure extension tests through Scry's structured runner; fall back to raw Kaocha only if Scry cannot execute."
:task (System/exit (run-scry-kaocha-suite! "extensions" ["--focus" "extensions"]))}
clojure:test:integration
{:doc "Run Clojure integration tests through Scry's structured runner; fall back to raw Kaocha only if Scry cannot execute."
:task (System/exit (run-scry-kaocha-suite! "integration" ["--focus" "integration"]))}
clojure:test:scry
{:doc "Run focused clojure.test tests with Scry structured results. On failures/errors, inspect .scry-results/*.edn."
:task (System/exit (run-scry-cli! *command-line-args*))}
smoke:test
{:doc "Smoke-test the live runtime: RPC handshake subprocess test + TUI tmux startup scenario."
:task (System/exit (run-scry-kaocha-suite! "integration" ["--focus" "integration"]))}
tui:delegate:e2e
{:doc "Run the live TUI tmux /delegate scenario against the current worktree."
:task (shell "clojure -M:test --focus integration --focus psi.tui.tmux-integration-harness-test/tui-tmux-delegate-live-sequence-scenario-test")}
clojure:test
{:doc "Run both Clojure unit and extension test suites."
:depends [clojure:test:unit clojure:test:extensions]}
coverage
{:doc "Run Kaocha unit suite with Cloverage plugin and write coverage report to target/coverage."
:task (shell "clojure -M:test:coverage --plugin kaocha.plugin/cloverage --cov-output target/coverage --cov-summary --cov-html --no-cov-text --focus unit")}
coverage:ai
{:doc "Run isolated AI suite with Kaocha Cloverage report written to target/coverage-ai."
:task (shell "clojure -M:test:coverage --plugin kaocha.plugin/cloverage -c tests-component-isolated.edn --cov-output target/coverage-ai --cov-summary --cov-html --no-cov-text --focus :ai")}
emacs:test
{:doc "Run Emacs frontend ERT suites (psi-rpc + split psi + widget)."
:task (shell "emacs -Q --batch -L components/emacs-ui -l components/emacs-ui/test/psi-test-support.el -l components/emacs-ui/test/psi-rpc-test.el -l components/emacs-ui/test/psi-buffer-lifecycle-test.el -l components/emacs-ui/test/psi-dispatch-test.el -l components/emacs-ui/test/psi-draft-end-position-test.el -l components/emacs-ui/test/psi-footer-exclusion-test.el -l components/emacs-ui/test/psi-streaming-transcript-test.el -l components/emacs-ui/test/psi-streaming-render-optimization-test.el -l components/emacs-ui/test/psi-tool-output-mode-test.el -l components/emacs-ui/test/psi-extension-ui-test.el -l components/emacs-ui/test/psi-capf-test.el -l components/emacs-ui/test/psi-session-tree-test.el -l components/emacs-ui/test/psi-anthropic-rehydrate-regression-test.el -l components/emacs-ui/test/psi-new-anthropic-flow-regression-test.el -l components/emacs-ui/test/psi-widget-renderer-test.el -l components/emacs-ui/test/psi-widget-projection-test.el -l components/emacs-ui/test/psi-widget-projection-timers-test.el -f ert-run-tests-batch-and-exit")}
emacs:reload
{:doc "Reload Emacs frontend modules into the running Emacs session via emacsclient."
:task (let [dir (str (System/getProperty "user.dir") "/components/emacs-ui")
files ["psi-globals.el" "psi-regions.el" "psi-rpc.el" "psi-mode.el"
"psi-compose.el" "psi-run-state.el" "psi-assistant-render.el"
"psi-tool-rows.el" "psi-session-commands.el" "psi-projection.el"
"psi-dialogs.el" "psi-events.el" "psi-lifecycle.el"
"psi-entry.el" "psi-completion.el"
"psi-widget-renderer.el" "psi-widget-projection.el"
"psi.el"]
loads (str/join " " (map (fn [f] (str "(load \"" dir "/" f "\" nil t t)")) files))
expr (str "(progn " loads " \"reloaded\")")]
(shell (str "emacsclient -e " (pr-str expr))))}
emacs:byte-compile
{:doc "Byte-compile Emacs frontend modules and clean generated .elc artifacts."
:task (shell "bash -lc 'set -euo pipefail; trap \"rm -f components/emacs-ui/*.elc\" EXIT; emacs -Q --batch -L components/emacs-ui -f batch-byte-compile components/emacs-ui/psi.el components/emacs-ui/psi-globals.el components/emacs-ui/psi-entry.el components/emacs-ui/psi-lifecycle.el components/emacs-ui/psi-events.el components/emacs-ui/psi-compose.el components/emacs-ui/psi-run-state.el components/emacs-ui/psi-assistant-render.el components/emacs-ui/psi-tool-rows.el components/emacs-ui/psi-session-commands.el components/emacs-ui/psi-projection.el components/emacs-ui/psi-dialogs.el components/emacs-ui/psi-mode.el components/emacs-ui/psi-rpc.el components/emacs-ui/psi-widget-renderer.el components/emacs-ui/psi-widget-projection.el'")}
emacs:e2e
{:doc "Run Emacs frontend end-to-end harness against live --rpc-edn backend."
:task (shell "emacs -Q --batch -L components/emacs-ui -l components/emacs-ui/test/psi-e2e-test.el -f psi-e2e-run")}
emacs:delegate:e2e
{:doc "Run the focused Emacs frontend /delegate end-to-end harness against the current worktree backend."
:task (shell "emacs -Q --batch -L components/emacs-ui -l components/emacs-ui/test/psi-delegate-e2e-test.el -f psi-delegate-e2e-run")}
emacs:tool-details:e2e
{:doc "Run the focused Emacs frontend tool-details end-to-end harness against a deterministic rpc-edn backend."
:task (shell "emacs -Q --batch -L components/emacs-ui -l components/emacs-ui/test/psi-tool-details-e2e-test.el -f psi-tool-details-e2e-run")}
emacs:check
{:doc "Run byte-compile guard and Emacs frontend tests."
:depends [emacs:byte-compile emacs:test]}
test
{:doc "Run all repository tests checked in CI."
:depends [clojure:test emacs:check]}
delegate:e2e
{:doc "Run both focused live /delegate end-to-end checks: TUI tmux + Emacs frontend."
:depends [tui:delegate:e2e emacs:delegate:e2e]}
;; Component-isolated tests to avoid circular dependencies
test:agent-core
{:doc "Test agent-core component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :agent-core")}
test:memory
{:doc "Test memory component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :memory")}
test:query
{:doc "Test query component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :query")}
test:engine
{:doc "Test engine component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :engine")}
test:history
{:doc "Test history component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :history")}
test:recursion
{:doc "Test recursion component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :recursion")}
test:ai
{:doc "Test ai component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :ai")}
test:tui
{:doc "Test tui component in isolation"
:task (shell "clojure -M:test -c tests-component-isolated.edn --focus :tui")}
test:components
{:doc "Test all components in isolation (bypasses circular dependencies)"
:depends [test:agent-core test:memory test:query test:engine test:history test:recursion test:ai test:tui]}
git:top-files
{:doc "Show top N files. Defaults: --by commits, N=10, --since 100, --merges, --sort count. Optional: --by commits|churn, --match REGEX, --path SUBSTRING, --no-merges, --sort count|path."
:task (let [{:keys [n by] :as opts} (parse-top-files-opts *command-line-args*)
fmt (case by
:commits "%4d %s"
:churn "%6d %s")]
(->> (top-file-rows opts)
(sort-top-file-rows (:sort opts))
(take n)
(print-top-files fmt)))}
chiasmus:review-plan
{:doc "Generate a Chiasmus code review plan for components/, bases/, and extensions/. Options: --focus all|security|architecture|correctness|quick, --limit N."
:task (let [{:keys [focus limit]} (parse-review-plan-opts *command-line-args*)
find-cmd (str "find components bases extensions -type f \\\\( -path '*/src/*' -o -path '*/test/*' \\\\)"
(when limit (str " | head -n " limit)))]
(when-not (.exists (java.io.File. chiasmus-home))
(binding [*out* *err*]
(println "Chiasmus checkout not found at" chiasmus-home))
(System/exit 1))
(when-not (.exists (java.io.File. (str chiasmus-home "/dist/index.js")))
(shell "bash" "-lc" (str "cd " chiasmus-home " && npm install && npm run build")))
(shell
"node" "--input-type=module" "-e"
(str
"import { buildReviewPlan } from '" chiasmus-home "/dist/index.js';"
"import { execSync } from 'node:child_process';"
"const files = execSync(\"" find-cmd "\", { encoding: 'utf8' })"
".trim().split(/\\n+/).filter(Boolean).map(f => process.cwd() + '/' + f);"
"if (files.length === 0) { console.error('No files found under components/, bases/, or extensions/'); process.exit(1); }"
"const plan = buildReviewPlan({ files, focus: '" focus "' });"
"console.log(JSON.stringify(plan, null, 2));")))}
chiasmus:review-plan:full
{:doc "Generate a full Chiasmus code review plan for components/, bases/, and extensions/. Options: --limit N."
:task (let [{:keys [limit]} (parse-chiasmus-check-opts *command-line-args*)
args (cond-> ["chiasmus:review-plan" "--focus" "all"]
limit (into ["--limit" (str limit)]))]
(apply shell "bb" args))}
commit-check:chiasmus
{:doc "Run a Chiasmus architecture check on components/, bases/, and extensions/. Options: --limit N, --full. Default runs summary + dead-code only; --full adds cycles and fails on cycles."
:task (let [{:keys [limit full]} (parse-chiasmus-check-opts *command-line-args*)
find-cmd (str "find components bases extensions -type f \\\\( -path '*/src/*' -o -path '*/test/*' \\\\)"
(when limit (str " | head -n " limit)))]
(when-not (.exists (java.io.File. chiasmus-home))
(binding [*out* *err*]
(println "Chiasmus checkout not found at" chiasmus-home))
(System/exit 1))
(when-not (.exists (java.io.File. (str chiasmus-home "/dist/index.js")))
(shell "bash" "-lc" (str "cd " chiasmus-home " && npm install && npm run build")))
(let [result (shell {:continue true}
"node" "--input-type=module" "-e"
(str
"import { runAnalysis } from '" chiasmus-home "/dist/index.js';"
"import { execSync } from 'node:child_process';"
"const startAll = Date.now();"
"const full = " (if full "true" "false") ";"
"const seconds = (ms) => (ms / 1000).toFixed(1) + 's';"
"console.log('Chiasmus architecture check');"
"console.log('Mode: ' + (full ? 'full' : 'quick'));"
"console.log('Collecting files...');"
"const files = execSync(\"" find-cmd "\", { encoding: 'utf8' })"
".trim().split(/\\n+/).filter(Boolean).map(f => process.cwd() + '/' + f);"
"if (files.length === 0) { console.error('No files found under components/, bases/, or extensions/'); process.exit(1); }"
"console.log('Found ' + files.length + ' files');"
"const analyses = full ? ['summary','dead-code','cycles'] : ['summary','dead-code'];"
"const out = {};"
"for (const analysis of analyses) {"
" const start = Date.now();"
" console.log('Running ' + analysis + '...');"
" const r = await runAnalysis(files, { analysis });"
" out[analysis] = r.result;"
" console.log('Finished ' + analysis + ' in ' + seconds(Date.now() - start));"
"}"
"const dead = Array.isArray(out['dead-code']) ? out['dead-code'] : [];"
"const cycles = Array.isArray(out['cycles']) ? out['cycles'] : [];"
"if (!full) console.log('Skipping cycles analysis (run with --full to enable)');"
"console.log('Summary:');"
"console.log(JSON.stringify({ mode: full ? 'full' : 'quick', summary: out.summary, deadCodeCount: dead.length, cycleCount: cycles.length, elapsed: seconds(Date.now() - startAll) }, null, 2));"
"if (dead.length) {"
" console.log('Dead code candidates (info):');"
" for (const x of dead.slice(0, 50)) console.log(' - ' + x);"
" if (dead.length > 50) console.log(' ... +' + (dead.length - 50) + ' more');"
"}"
"if (full && cycles.length) {"
" console.error('Cycles (failing):');"
" for (const x of cycles.slice(0, 50)) console.error(' - ' + JSON.stringify(x));"
" if (cycles.length > 50) console.error(' ... +' + (cycles.length - 50) + ' more');"
" process.exit(1);"
"}"
"console.log('Chiasmus check passed');"))]
(System/exit (:exit result))))}
build:lib
{:doc "Build target/psi-VERSION.jar (library jar for Clojars) via tools.build."
:task (shell "clojure -T:build lib")}
build:jar
{:doc "Build target/psi.jar and target/psi wrapper script via tools.build."
:task (shell "clojure -T:build uber")}
build:install-local
{:doc "Build target/psi-VERSION.jar and install it into the current local Maven repo via tools.build."
:task (shell "clojure -T:build install-local")}
deploy
{:doc "Deploy library jar to Clojars. Requires CLOJARS_USERNAME and CLOJARS_PASSWORD."
:task (shell "clojure -T:deploy deploy")}
changelog:check
{:doc "Fail if CHANGELOG.md has no entries in the [Unreleased] section."
:task (bb.release/check-changelog! *command-line-args*)}
release
{:doc "Cut a release and push: stamp changelog, commit, tag, reset, then push master + tags to origin. With --dry-run, only dispatch the non-publishing Release workflow dry run."
:task (bb.release/release-and-push! *command-line-args*)}
release:tag
{:doc "Cut a release (tag only, no push): stamp changelog, bake version, commit, tag vMAJOR.MINOR.PATCH, reset to unreleased."
:task (bb.release/release! *command-line-args*)}
ref-report
{:doc
"Report public fns used only by tests and fns that could be private",
:requires ([hugoduncan.bb-task-lib.ref-report :as ref-report]),
:task (ref-report/run)}
gordian
{:doc
"Analyze codebase architecture with gordian; all args passed through (run with --help for full usage)",
:requires ([hugoduncan.bb-task-lib.gordian :as gordian]),
:task (gordian/run)}}}