master
46 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ade2ab95fa |
Give :discouraged-var and :discouraged-namespace their own per-symbol ratchet budgets (#83352)
* Remove stale :discouraged-namespace ignores These six ignores suppressed a namespace-level policy that no longer exists in config.edn -- clojure.pprint, clojure.java.io, clj-yaml.core, metabase.util.jvm, and metabase.config.core aren't configured as discouraged namespaces anymore (clojure.pprint is only discouraged at the var level now). Verified clean with a scoped kondo run requesting :discouraged-namespace at :warning over just these files: zero findings. * Give :discouraged-var and :discouraged-namespace their own per-symbol ratchet budgets One shared budget per linter let a new ignore on a previously-clean var land silently as long as the linter's aggregate count had room, and made "introduce a policy for just this one var" impossible. Each configured symbol now gets its own field: :discouraged-var-counts and :discouraged-namespace-counts, siblings of :ignore-counts, keyed to mirror the symbol's own namespace and name rather than the linter that discourages it (metabase.entity-retrieval.core/search-unfiltered -> :entity-retrieval.core/search-unfiltered; an enterprise namespace of the same shape gets an `ee.` prefix so it can't collide with its OSS counterpart). Attribution resolves which symbol an inline ignore covers by running a scoped `clojure -M:kondo` analysis pass over the files that carry one of these two ignores (JSON output, since a generated var can have a name that isn't valid EDN) and matching each ignore to the nearest covered usage of a known symbol -- this is the one thing in the ratchet tooling that needs a live kondo run, so `./bin/mage kondo-ratchets` is no longer pure Babashka. `:comment-exempt` is untouched: occurrences still carry the bare linter name in :linters, only the two new count fields are keyed per symbol. `--seed :discouraged-var`/`:discouraged-namespace` (no specific symbol) seeds every symbol currently configured under it in one shot, unused ones at zero, the same ergonomics as landing a new linter. A stale flat :ignore-counts entry for either linter is self-healing: `fix!` drops it every run and `check` fails if one reappears, since it would otherwise double-count every occurrence the new field already covers. Ported onto the prod/test ratchet split: both ratchet files carry the per-symbol fields, and each file's occurrences are attributed separately. * Attribute discouraged-var ignores from kondo findings, not nearest usage Matching each ignore to the nearest later usage of a configured symbol counted usages kondo never flagged (scoped :config-in-ns entries, :off overrides) and let a stale ignore claim an unrelated usage. Attribution now lints copies of the files with the discouraged ignores disabled, joins each finding to its var or namespace usage at the same row and column, and charges it to the last ignore before it naming the linter. An ignore counts once per file and distinct symbol it covers. The copies rename the ignore key to one of the same length rather than blanking the ignore, so every form reads as before and every row and column stays put. A failed kondo run now throws instead of zeroing every budget. Findings that resolve to no configured symbol fail the check and stop the shrink from writing; ignores that cover no finding only warn. Without an :attribute, a discouraged ignore throws rather than reading as zero. Each ratchet file's occurrences are attributed separately. `--seed :discouraged-var/clojure.core/println` seeds one symbol, a bare `--seed :discouraged-var` seeds every symbol at its actual count, and an empty suffix is rejected. Bulk seeding at zero is gone: the next shrink dropped those zeros anyway. The shrink workflow runs prepare-backend, since the shrink now needs the Clojure CLI. Budgets are re-seeded from the new attribution. Several were undercounted or charged to the wrong symbol before. This squashes the unpushed review-fix commits on top of the per-symbol commit, since #83383 rewrote the same functions each of them changed. * Remove a stale :discouraged-namespace ignore on clj-yaml.core clj-yaml.core is no longer a discouraged namespace, so the ignore on its require in lib-be.metadata.jvm suppresses nothing. * Don't charge findings kondo reports with the ignores in place Attribution charged every discouraged finding on the disabled-ignore copies to the last ignore before it, including findings no ignore suppresses. Unsuppressed usages in dev/ inflated the eval, println and prn budgets, and a new usage of a symbol an earlier ignore already covers went uncounted. Lint the original files in the same kondo run and drop any finding they also report, so only findings an ignore suppresses are attributed. An ignore nested inside another for the same linter still takes the findings under it, leaving the outer one reported as covering nothing. * Parse kondo's JSON output with babashka.json mage already depends on babashka.json, so attribution doesn't need cheshire or the :discouraged-namespace ignore that came with it. That returns the cheshire.core budget to 4. * Attribute prod and test ignores in one kondo run check and fix! attributed the prod and test occurrences in separate calls, and each call started its own JVM and lint pass. :attribute now takes a seq of occurrence groups and returns one attribution per group, so attribute-occurrences! lints every file once and splits the result. kondo-ratchets drops from about 39s to 33s. * Drop budgets for symbols no longer configured instead of refusing Removing a symbol from config.edn left its per-symbol budget naming an unknown key, and both check and the shrink threw on it. The shrink, the tool meant to repair that drift, couldn't. Two PRs could each pass alone and break the shrink on master: one seeding a symbol, the other removing it from the config. check now warns about such a budget, and the shrink drops it at zero like any unused budget, since attribution never charges an unconfigured symbol. --seed still rejects an unconfigured symbol. A bare --seed :discouraged-var no longer warns once per configured symbol with no ignores, and check's ok line counts the per-symbol budgets alongside :ignore-counts. * Share the ratchet setup and config walks between their callers check and fix! repeated the same setup: validate the linters, read the module and test ratchet files, scan, split prod from test, and attribute. They now share read-policies and scan-and-attribute. fix! still resolves and validates --seed before the scan, so a bad seed fails without waiting on kondo. The repository-linter and discouraged-symbol lookups each walked config.edn with an atom to collect nested :linters maps. Both now read them from one linters-maps. The budget-line formatter is shared, and the empty and mergeable field sets derive from ratchet-field-order. Dropped the unused one-argument discouraged-symbols. * Correct the ratchet task's runtime claim and plain up its wording The kondo-ratchets :doc said the check takes seconds; with the kondo attribution pass it takes about half a minute. The CLAUDE.md paragraph and the ratchet-file headers no longer open sentences with code. The unattributed-ignore warning now names a nested ignore for the same linter as a cause: the inner ignore takes the finding, so the outer one reads as covering nothing even though removing it would surface a warning. * Test that a failed kondo run throws rather than reading as no findings If a failed or garbled kondo run read as an empty result, attribution would count zero ignores and the shrink would drop every per-symbol budget. The exit and parse checks move into parse-kondo-output, which tests now cover for a nonzero exit and for output that isn't a map. The attribution tests build matching findings and var usages with one var-hits helper, and the check test's attribution stub reads as what it returns. * Refuse colliding discouraged symbols before an unseeded shrink Dropping the unknown-key validation also dropped the shrink's only call to discouraged-count-keys, which throws when two configured symbols share a ratchet key. An unseeded shrink could then merge both symbols' ignores into one budget. read-policies now reads the configured keys up front, so check and fix! both refuse a collision before scanning. * Reject kondo output without a findings list A run that exits cleanly but prints a map with no :findings, such as {}, parsed as a result with no findings. Attribution would then count zero ignores and the shrink would drop every per-symbol budget. parse-kondo-output now requires :findings to be a list. A missing analysis bucket already fails loudly, since every finding comes back unresolved. * Note where nested ignores leave a budget short Attribution charges a finding to the last ignore before it, so an outer ignore around a nested one for the same linter counts toward no budget. State that next to the rule, where a reader of the code will look for it. * Flag the nested-ignore undercount as a known gap Lead the comment with the gotcha and its consequence, a budget one short that an unattributed warning hides behind, so a reader doesn't mistake the last-ignore rule for a complete one. * Key aliased discouraged vars by the namespace the file requires Kondo resolves a re-exported var to the namespace it learned the var came from, and what it has learned depends on its cache and on which files share the run. lib.core re-exports ->legacy-MBQL from lib.convert, so after an ordinary lint of lib/core.cljc the same ten ignores moved from :lib.core/->legacy-MBQL to :lib.convert/->legacy-MBQL and the check failed. A var used through an alias now takes its namespace from the file's own require for that alias, per language so a .cljc file's clj and cljs requires stay apart, and the attribution lint runs with --cache false. * Split the attribution test by behaviour One deftest each for counting, which findings are charged, and how a finding resolves to a symbol, so a failure names the behaviour that broke. * Say a stale flat discouraged budget is never read, not double-counted The check removes these linters from the occurrences before comparing :ignore-counts, so a leftover flat entry is ignored rather than counted a second time. * Mark the nested-ignore gap as a way past the ratchet, with a TODO A new ignore wrapped around an existing one for the same linter needs no budget, so say that plainly and record the planned fix: lint copies with one ignore disabled each and charge a finding to the copy that reveals it. |
||
|
|
e902d2623c |
Name module cycles and fail when they merge (#82386)
The file .clj-kondo/config/modules/cycle-clusters.edn names each cyclic cluster of the module require graph by an anchor module. The cluster holding the anchor carries the name. Membership is computed, never recorded, so a named cluster can grow or shrink without an edit. The test metabase.core.module-cycles-test fails until every cluster holds exactly one anchor. A merge of two named clusters fails and shows the requires joining them. A split asks its author to name the new half, and an anchor that leaves every cycle has to be re-anchored or retired. The name-module-cycle skill suggests space-themed names. |
||
|
|
e4471f76d3 |
Split kondo ratchet budgets into separate prod and test files (#83383)
* Split kondo ratchet budgets into separate prod and test files Prod and test kondo suppressions shared one .clj-kondo/ratchets.edn budget per linter, so a pile of test-code ignores could mask a prod-code regression (or vice versa) inside the same number. Adds .clj-kondo/ratchets-test.edn, tracking :ignore-counts for anything under a `test` directory, independent of the prod file's budget. :config-counts (.clj-kondo/config.edn suppressions) stays combined in the prod file -- config.edn's own scoping mixes test and prod namespaces in several groups, so it can't be reliably split per suppression. Updates every consumer: dev.kondo-ratchet's fix!/check, bin/merge-kondo-ratchets, the backport and release-cut disable scripts, the shrink workflow, and file-paths.yaml/.gitignore. * Fix test-ratchets validation and exemption-suggestion bugs from roborev fix!/check read the test ratchets file with *ratchets-file* rebound to *test-ratchets-file*, but validated it after that binding ended, so an unknown linter there was reported against the prod path instead. Validation now runs inside the binding, and both now reject a nonempty :config-counts on the test file instead of letting fix! silently drop it. kondo-insert-ignores also only ever suggested adding a new linter's :comment-exempt to the prod file, even when every unjustified occurrence was test-only. It now checks prod and test occurrences against each file's own exemptions independently and names whichever file(s) actually need it. * Dedupe test-ratchets read+validate, simplify test-occurrence? fix! and check each rebuilt the same read-validate-test-file sequence inline; extracted into read-validated-test-ratchets. test-occurrence? no longer builds a throwaway set just to check one membership. * Note what splitting :config-counts later would actually cost * Fix Eastwood reflection warning in new test helper empty-test-ratchets-file! returned an untyped value, so .getPath on it needed reflection at each call site. Hinting the return type as java.io.File fixes it -- CI's changed-tests Eastwood run caught this. * Move return type hint onto the arg vector Clj-Kondo prefers the hint on the arg vector to the one on the fn name. * Move shrink PR body file reading into dev.kondo-ratchet The bb task only parses its arguments now; reading and combining the ratchet files lives next to shrink-pr-body, with a test. * Give the test ratchets file its own header and drop its :config-counts The test file was rendered with the prod header, whose last lines point readers at ratchets-test.edn and say :config-counts "stays here" -- both wrong inside the test file. It also carried an empty :config-counts that the check rejects whenever it's nonempty. render-test writes the test file with its own header and no :config-counts, and throws on a nonempty one rather than dropping it. fix!, the normalization check, and the three-way merge (a new "test" kind) all use it. |
||
|
|
a632ed1799 |
Interactive module graph viewers (#82375)
* Consolidate the module viewers into one explorer `./bin/mage modules-tree --html` writes a single page with three views of the module config: the tree, a coupling ranking, and a dependency graph around focused modules. `dev.module-explorer/open!` builds the same page in a dev REPL with namespace-level edges, so the graph can show which API namespaces each consumer uses. The graph replaces dev.module-viz and its Jetty server. Source paths, stats and teams go through the shared resolver, so nested and renamed modules report correctly. module-team moves into hooks.common.modules. * Label module counts as used by and uses * Refine module explorer semantics and state |
||
|
|
4f02379e40 |
Organize backend modules into domain subtrees (#82367)
Group things in a way that seems natural |
||
|
|
dd9d8d0579 |
Ratchet module boundary escape hatches (#82300)
Protect against bad things happening because agents and reviewers aren't aware that they're bad. |
||
|
|
eff3209a34 |
Add hierarchy to the backend module system (#82301)
This will allow us to resolve a lot a cycles in the module graph, and increase encapsulation considerably. |
||
|
|
30f9f25ba9 |
Refine kondo ratchet commands and prompts (#81776)
* Make ratchet shrinking explicit, and check it in one place The ratchet test fixture ran `fix!` on every non-CI run, so `project-tests ratchets` rewrote .clj-kondo/ratchets.edn in your working tree before asserting against it. A check that edits what it checks leaves a diff nobody asked for, in the file every PR conflicts over, and its only reachable failure was a bug in the fixer that had just run. Removing the auto-fix forces the dev/CI split to go too: a strict local test that no longer tightens would fail on every branch that removes an ignore. So over-budget is now the rule everywhere, and shrinking is only `./bin/mage kondo-ratchets-shrink`, which the shrink workflow runs on master. That left two implementations of nearly the same check. The babashka `check-report` gains the justification finding — the one assertion the JVM suite had that it lacked — so `./bin/mage kondo-ratchets` covers over-budget, config over-budget, unjustified ignores, normalization and unknown linters on its own. CI runs it instead of booting a JVM, and the five now-duplicated in-repo assertions leave the test namespace, which drops from 6.2s to 0.8s because it no longer walks the source tree. Task names follow: check-kondo-ratchets (which nothing called) becomes kondo-ratchets, fix-kondo-ratchets becomes kondo-ratchets-shrink, and the merge task becomes private -kondo-ratchets-merge, since only the git merge driver invokes it. bin/merge-kondo-ratchets keeps its name; installed merge drivers point at it. * Polish kondo ratchet guidance * Prime the ratchet shrink workflow Intentionally raise the :case-symbol-test budget from 1 to 2 without adding a suppression. The read-only check allows the unused budget; after this PR merges, the shrink workflow should restore it to 1 through a Tighten ratchets automation PR. * Make the ratchet suite check the project Keep the ratchet tooling unit tests in the backend suite, but make project-tests ratchets run the read-only repository check. Include it in the bare project-tests command so local project-level checks still validate the checked-in ratchet policy. * List project-test suites once * Address review findings on the ratchet check check-report threw NPE on a ratchets map without :comment-exempt, while tolerating a missing :ignore-counts or :config-counts. Default it, so the three policy keys behave alike. The run-project-ratchet-checks description and the step comment both still described the old wiring. The check now runs alongside the backend checks, which no longer include it, and "no JVM" is not quite true: resolving clj-kondo on a cold cache shells out to the JDK prepare-backend installs. * Warn about stale comment exemptions The only check that a :comment-exempt entry is still needed was a local-only test, which went vacuous when the dev and CI semantics were unified, so nothing named a stale exemption any more. The check now reports one the way it reports an :unlimited policy nobody uses: informational, never a failure, since only the shrink workflow removes them. * Say why the merge driver finds mage through its own path The script runs against whatever repository holds the conflict, which is not always the one it lives in -- the tests merge stages in a throwaway repo. A relative bin/mage looks plausible and fails there, so record the reason. * Stop wrapping two comments that did not need it One fits on a single line once it says less. The other breaks at its sentence boundary rather than mid-clause. * Break the ratchets header on its sentences Four sentences started mid-line and wrapped mid-clause, one of them trailing "in the PR." onto a line of its own. Same words, one sentence per line. * Keep comment exemptions until someone removes them fix! dropped a :comment-exempt entry as soon as every ignore of its linter carried a comment, or the last uncommented one was deleted -- so an exemption could disappear as a side effect of unrelated work, and getting it back is a change to defend in review. An exemption records a decision about a linter, not a count, which is the reason :unlimited policies are already kept at zero and only warned about. Treat the two alike: the check and the shrinker name a stale exemption, and a person deletes it. |
||
|
|
7f73a7464c |
Add merge tool for ratchet conflicts (#81341)
* Add a manual kondo ratchet conflict merger * Split kondo ratchet merge tests * Document the ratchet conflict merger * Simplify unlimited ratchet merge policy * Simplify ratchet merger for final policy schema * Resolve ratchet conflicts to the stricter policy Concurrent policy changes now always resolve: the smaller budget wins, a finite budget beats :unlimited, and a removed entry beats any budget. Comment exemptions merge on their own. Every stage is read through read-ratchets and shape-checked before a disabled stage decides the result. bin/merge-kondo-ratchets reads the index stages by number, so a file deleted on one side and changed on the other exits nonzero with nothing staged, and both-sides-add merges against an empty base. It runs from any directory in the repository and stages only .clj-kondo/ratchets.edn. mage.merge-kondo-ratchets-test drives the script against real conflicts in temporary repositories. * Point the clojure-write skill at the ratchet merge resolver * Reject malformed stages before merging ratchets The shape checks move out of read-ratchets into validate-policies, and merge-ratchets runs them on every stage, so a stage with :config-counts [] or :comment-exempt [] is an error rather than an empty set of policies that the merge would read as removals. * Cover unreadable stages in the merger tests An empty file, two forms, or a non-map stage now fails the reader instead of merging as no policies; the script leaves such a conflict for a human and stages nothing. * Reject a non-map ratchet stage before merging A nil stage passed every field check because dissoc, keys, and the defaults all accept nil, so a malformed stage could merge as an empty set of policies. * Reject non-keyword linter names The merge keys on (str linter) and renders the same way, so a string, nil or symbol name was rewritten as a keyword, collided with one, or produced a file that no longer read back -- and the script still exited 0 saying it had staged the merge. Also describe the two sides by git stage: "current" and "incoming" are inverted during a rebase. * Run each refused merge once in the tests leaves-unresolved? and output each ran the script, so every refusal ran it twice and the "changed nothing" assertion covered a different run from the one whose message was checked. Read the merged policies with the EDN reader while here. * Compare the conflicted file, not just its status A conflicted path reports UU, DU or UD however its contents are rewritten, so comparing git status alone did not show that a refused run left the file alone. * Tighten merge-tool docstrings and the ratchet conflict docs * Compare the index stages of a refused merge too Porcelain status reports a conflicted path as DU/UU whatever its stages hold, so it alone does not show that a refused run left the conflict alone. The stages carry each side's blob. * Spell out the unmerged status codes UU/DU/UD as bare shorthand assumes the reader knows git's porcelain codes for a conflicted path. * Clarify the unmerged-state snapshot comment |
||
|
|
bc0200ece0 |
Enforce and maintain ratchet policies (#81319)
* Add trusted master ratchet shrinker * Retry ratchet shrink after concurrent master pushes * Test the check-kondo-ratchets report * Apply ratchet policy to the check report * Add severity-based inline ratchet policies * Update ratchet policy documentation * Finalize ratchet policy enforcement * Move seeded linters to bounded ratchets * Simplify unlimited ratchet policies * Finish ratchet CI integration * Document unlimited ratchet policy values * Keep ignore counts as the policy map * Validate ratchet policy keys against known linters Policy keys in .clj-kondo/ratchets.edn must name a clj-kondo built-in from the pinned version, a linter configured under .clj-kondo/, or an allowlisted external diagnostic. The check and the fixer fail with one error listing every unknown name instead of counting it. Config budgets must be non-negative integers. Drop :unresolved-require, a linter that never existed, from the semantic-search ignore and the ratchets file. * Keep unlimited ratchet policies with no ignores left An :unlimited entry in :ignore-counts records a decision about the linter, so the fixer no longer drops it when its count reaches zero. The check and the fixer print one sorted warning naming such entries; it never fails CI. Bounded zero budgets are still dropped, and :comment-exempt is unchanged. * Deliver ratchet shrinking through an automation PR * Validate seeds and read only tracked kondo configs The known-linter set now comes from git-tracked `.edn` files under `.clj-kondo`, so the dependency configs `mage kondo` copies in locally (gitignored, absent from a clean checkout) no longer make validation depend on the machine. `--seed` is checked against the same set before anything is written, and a hand-written bounded `0` with no ignores left is reported when the fixer drops it. * Reopen the ratchet automation PR after a close The "already carries this result" skip now requires an open PR, so a branch left behind by a PR closed without merging is overwritten and gets a fresh PR instead of silencing every later run. The compare step reads PR state with the default token under `pull-requests: read`. * Let the ratchet automation PR approve and merge itself Copies the backport recipe: github-actions[bot] approves the PR with the default token (a user cannot approve their own PR, and the automation user owns it), then the automation user enables squash auto-merge. The commit identity is the docs generator's, which merges with that one approval. A ratchets-only change no longer flips backend_all, so the PR waits on the Project tests job alone. * Describe the self-merging ratchet automation PR * Expect the ratchet check namespace in the project-test suites * Read the ratchets file as exactly one map An empty file, a non-map, or a trailing second form used to read as an empty policy set, which the merger would have staged as the removal of every budget. Tracked kondo configs missing from the worktree (unstaged deletions, sparse checkouts) are skipped instead of failing the scan. * Skip the uberjar for a ratchets-only change * Keep fork PRs away from the ratchet automation steps The head-branch lookup matched any repository's branch of that name, so a fork PR could have reached the approval and auto-merge steps; only an open PR from this repository counts now. Approval and auto-merge key on the open PR rather than on whether the branch was pushed, so a run that failed after opening the PR is repaired by the next one. * Look up the ratchet automation PR by owner-qualified head gh pr list caps at 30 results and filtered forks client-side, so enough fork PRs on the same branch name could hide the real automation PR and make the create step fail. The REST head=owner:branch filter is applied server-side and matches only this repository's branch. * Stop checkout credentials from overriding the automation push token actions/checkout persists GITHUB_TOKEN as an Authorization header, which takes precedence over the token embedded in the push URL, so the force-push to the automation branch would be rejected. * Tolerate config budget and exemption reductions in CI CI already let inline counts fall below their budgets so the shrinker could record them after merge, but a lowered config budget or a stale comment exemption still needed an exact match, so two PRs that each removed a suppression could fail the second merge. Config counts now fail CI only when they grow, stale exemptions never fail CI, and check-kondo-ratchets reports config growth alongside inline growth. Local runs stay exact. * Say what CI rejects without listing the cases * Keep actionable ratchets bounded * Exclude the ratchets file with one pattern Path filters are OR-ed, so a separate "!.clj-kondo/ratchets.edn" entry matched every path that was not that file: backend_all became true for every PR, and no_source_code_changes could never be true. The exclusion has to live inside the pattern, as modules/drivers does above. * Arm auto-merge against a known commit The approve and merge steps keyed on the PR number alone, so nothing had checked what the fixed automation branch name currently holds. Both paths now record the sha they pushed or compared, and auto-merge matches it. Also guard the job to this repository, as the workflow it replaces did. * Report ratchet validation failures without a stack dump check read and validated the file, then read it again. Now it parses once and catches inside, so read-ratchets, validate-linters! and scan all report the same one-line message. Also: CI=false was truthy in the tests, seeding a recorded linter with no ignores said "nothing to seed" while dropping its policy, and lowered-counts rebuilt the seeded set per entry. * Keep the drift message off stdout The drifted path prints its findings and then exits; folding it into fail! added a summary line that check-reports-empty-unlimited-test pins. Split the throw from the print so only messages the run has not already shown are printed. * Tighten ratchet docstrings and the generated header * Guard against standalone negations in the path filters The patterns in a filter are OR-ed, so a "!..." entry matches every path the other entries don't instead of subtracting from them, and the filter is then always on. The ratchets exclusion is written inside one pattern for that reason; this keeps the next one from being written the other way. * Reuse the automation PR on its ratchet file alone The extra check that master had not touched ratchets.edn since the branch was cut only bites when master moved to a different value that shrinks back to the same result, since any change to the result is already caught by comparing the file. Dropping it means only a shallow fetch of the branch tip is needed. * Force-push the automation PR when it conflicts A PR whose ratchet file already matches is otherwise left alone, and a conflict is the one thing that state cannot clear on its own. Mergeability is computed lazily, so UNKNOWN is polled past and, if it never resolves, left for the next run. * Ask once whether the automation PR conflicts Polling for a definitive answer bought nothing: the next push to master runs this again within the hour, so a mergeability check still in flight can read as not conflicted and be settled then. * Say what the reuse comparison is against git diff FETCH_HEAD -- <path> reads as a commit-to-commit diff. It is against the working tree, which is the only reason comparing the branch tip answers whether the PR still delivers the right file. * Fix the circular Filter type alias Record<string, Filter> is expanded eagerly, so naming it inside Filter's own definition trips TS2456. An inline index signature is deferred and holds the same shape. * Prove a ratchets-only change skips the backend filters * Run the path-filter specs when the filters change * Use gh CLI to approve ratchet PRs |
||
|
|
3c5d1111d7 | Run project checks in a separate job (#81318) | ||
|
|
39ad1d5167 | Keep ratchets off release branches (#81033) | ||
|
|
42cf92479a |
Ratchet config-level kondo suppressions too (#78217)
* Ratchet config-level kondo suppressions too
:config-counts in ratchets.edn tracks, per linter, the :off switches and
:exclude entries in .clj-kondo/config.edn (top-level and per ns-group).
Only weakening counts: adding discouragements or turning a linter on is
free. Lowering works like the inline budgets; raising is a hand edit.
* Count every excluded item and :config-in-comment in the config ratchet
Map-shaped :exclude values with vector entries counted as one suppression
({compojure.core [GET DELETE POST PUT]} is 4, not 1), and the
:config-in-comment :off switch wasn't aggregated at all, so both could
grow without tripping CI. Reseeded.
* Count :config-in-call suppressions, and self-heal on config.edn
`:config-in-call` is a scoped `:linters` map like `:config-in-ns`, so a
linter switched off inside one was invisible to the ratchet. Both scopes
now fold into the same count.
The self-heal workflow only watched Clojure sources, which was right while
budgets came from inline ignores alone. Now that `.clj-kondo/config.edn`
feeds `:config-counts`, removing a suppression there can leave a budget
stale with no way to trigger the fix.
* Count scoped :off suppressions at any nesting depth
Linter scopes nest more than one level: :discouraged-java-method keys by
class and then by method. Counting only the first level meant an :off one
map further down waived a warning the ratchet never saw. No budget moves --
the repo's only two-deep entry is a :level :error discouragement.
* Regenerate kondo ignore budgets after restacking onto master
|
||
|
|
dd1cb0f0ba |
Require justifications for linter ignores (#77820)
* Require a justification comment above inline ignores, ratcheted per linter
An ignore of a linter outside :comment-exempt needs a substantive ;;
comment on the line above or trailing on the same line (bare ;; dividers
don't count, and no attempt is made to check the comment is about the
ignore). ~18% of ignores have one today, so 35 linters start exempt.
The set only shrinks: once a linter's last uncommented ignore gains a
comment, the fixer drops its exemption and CI holds the line.
* Locate trailing justification comments in masked source
A semicolon inside a string or character literal on the ignore's line
counted as a trailing comment, so an unjustified ignore could pass.
The masker now keeps the ; that starts a comment (and blanks the char
after a backslash), so justified? finds only real comment starts.
* Share one tree scan across the ratchet tests
Each ratchet test walked and slurped the whole source tree; a delay
runs the scan once for all three.
* Invalidate the shared tree scan per test run
The namespace-level delay cached the scan for the JVM's lifetime, so a
long-lived REPL compared fresh ratchet-file contents with stale counts.
The :once fixture now resets the cache; a directly-run test var scans
fresh.
* Scope the shared tree scan to the fixture's lifetime
The atom cache survived a run, so a later directly-invoked test var
(which skips :once fixtures) reused a stale scan. The cache now holds a
delay only while the fixture is live; without it every call scans fresh.
* Require canonical syntax for kondo ignores
Keep ignore detection deliberately lexical and fail closed: every real marker must use the key-first map form, otherwise the ratchet rejects it with an actionable error. This avoids maintaining a partial Clojure reader for the two attr maps that previously placed the ignore behind another key.
Justification comments must be substantive and either directly above the ignore or trailing on its line. Authenticate comment openers in masked source so strings and code-line comments cannot pose as justifications.
* Harden kondo ignore scanning edge cases
* Use reader delimiters for kondo marker boundaries
* Match Clojure reader whitespace in kondo markers
* Reserve clj-kondo namespaced map syntax
* Harden kondo scanner diagnostics
* Rebuild the budgets and exemptions on the named-linter base
Rebased onto the ignore-naming branch, so every budget is recomputed from
the tree and :comment-exempt is reseeded with exactly the linters that
still have an uncommented ignore: 38, up from 35. The three extra are
linters that were hidden inside bare `#_:clj-kondo/ignore` forms until
they were spelled out; :all itself drops out, having no ignores left.
* Point at :comment-exempt after inserting ignores for a new linter
kondo-insert-ignores writes comment-less ignores, which the justification
ratchet rejects for non-exempt linters.
* Leave ignores preceded by a # for a human, and gate the exemption advice
A `#` immediately before the matched form is ambiguous: in `#^{...}` it is
legacy metadata that removal must take with it, in `x#^{...}` it closes a
syntax-quote gensym that removal must leave alone. Telling them apart means
reading Clojure, which this scanner deliberately does not do -- so report
the site under :skipped, as it already does for prefixless maps and
unbalanced braces, and let a human excise it.
Separately, kondo-insert-ignores no longer advises a :comment-exempt entry
unconditionally. An inserted ignore lands under whatever comment already sat
above the flagged form, so a linter whose sites are all justified would get
an exemption that no-stale-exemptions-test then rejects.
* Align the scan-test expectations
The occurrence vector's :justified? column and the counts map both drifted
out of alignment as linters were added to the fixture.
* Regenerate kondo ignore budgets after restacking onto master
|
||
|
|
68746a6d43 |
Clean up leftover JS-era config, deps, and docs after the TypeScript migration (#80032)
* Clean up leftover JS-era config, deps, and docs after the TypeScript migration * Address review: keep .jsx in resolve.extensions, drop .js from loader tests, includes-based e2e validator filter * Keep @babel/node for a separate deps cleanup round * Keep js/jsx in rspack loader tests |
||
|
|
5672dd99c6 | Burn down redundant kondo ignores and stamp the false positives (#77787) | ||
|
|
57e0ad66ea | Add kondo ignore ratchets (#77773) | ||
|
|
ba83271710 |
Auto-fix modules config.edn (#77541)
* Auto-fix modules config.edn * Extract generic nrepl integration wrapper * Remind claude to use this * Demo the module config fixer with some tests |
||
|
|
8a36e2964f |
Claude.md instructions for docstrings (#74246)
Adds guidelines for writing docstrings to the Clojure development skill. Docstrings should describe the caller's contract (arguments, return values, preconditions), not implementation details — implementation context belongs in inline comments at the relevant code site. Document deviations from expectations, not conformance to them. |
||
|
|
4a9210a4a5 |
Document :module flag in CLAUDE.md and add --drivers=LIST to bin/test-agent (#73372)
Two related changes for backend test ergonomics: - CLAUDE.md gets a 'module-scoped runs' subsection documenting :module / :modules, the args to metabase.test-runner/find-and-run-tests-cli. - bin/test-agent gets a --drivers=LIST flag that appends :drivers:drivers-dev to the alias set and exports DRIVERS=LIST in one step. Together these let you run e.g. ./bin/test-agent --drivers=mysql,h2,postgres :module enterprise/workspaces instead of dropping down to a raw clj -X invocation for driver tests. Supersedes #73371. |
||
|
|
d4a861b3c9 |
Checker module (#70866)
* Checker module
```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Finding tests took 80.0 ms.
Running 10 tests
Running before-run hooks...
10/10 100% [==================================================] ETA: 00:00
Ran 10 tests in 0.645 seconds
39 assertions, 0 failures, 0 errors.
{:test 10, :pass 39, :fail 0, :error 0, :type :summary, :duration 644.51825, :single-threaded 10}
Ran 0 tests in parallel, 10 single-threaded.
Finding and running tests took 762.0 ms.
```
The number of public functions here is too high but will come down soon.
```
❯ tree export-dir -d -L 2
export-dir
├── collections
│ ├── BFCwHpVZqKMKqth3wVbGF_automatically_generated_dashboards
│ ├── HyB3nRtqb7pBPhFG26evI_examples
│ ├── cards
│ ├── hFKj55TbZN8uDDTNPiZgf_repro1
│ ├── transforms
│ ├── uj8O1_VYi4rPa4Y8ZGb_S_demo_day
│ └── wUkEfoXgg3dBTNCN2egsh_native_queries
├── databases
│ ├── Internal Metabase Database
│ ├── Sample Database
│ └── pg sample-dataset
├── python-libraries
└── transforms
├── transform_jobs
└── transform_tags
17 directories
```
and
```
❯ time clj -X:dev:drivers:ee nocommit.ci/cli :export '"export-dir"' :output '"/tmp/from-cli-with-error.txt"'
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-13 14:26:57,899 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
2026-03-13 14:27:04,139 INFO util.encryption :: Saved credentials encryption is DISABLED for this Metabase instance. 🔓
For more information, see https://metabase.com/docs/latest/operations-guide/encrypting-database-details-at-rest.html
clj -X:dev:drivers:ee nocommit.ci/cli :export '"export-dir"' :output 34.10s user 1.68s system 228% cpu 15.634 total
```
the output is a big edn map:
```clojure
{"fdkSuB8zWEbjEq_vWMaI-"
{:card-id 1,
:name "Heavy-Duty Silk Chair trend",
:entity-id "fdkSuB8zWEbjEq_vWMaI-",
:file
"export-dir/collections/HyB3nRtqb7pBPhFG26evI_examples/cards/fdkSuB8zWEbjEq_vWMaI-_heavy_duty_silk_chair_trend.yaml",
:refs
{:tables ["Sample Database.PUBLIC.ORDERS"], :fields ["Sample Database.PUBLIC.ORDERS.CREATED_AT"], :source-cards []},
:unresolved nil,
:bad-refs #{}}
...
;; card with unknown field
"rvxR7dn4wrKT9_7MHhy7i"
{:card-id 222,
:name "Distinct User ID",
:entity-id "rvxR7dn4wrKT9_7MHhy7i",
:file "export-dir/collections/uj8O1_VYi4rPa4Y8ZGb_S_demo_day/cards/rvxR7dn4wrKT9_7MHhy7i_distinct_user_id.yaml",
:refs
{:tables
["pg sample-dataset.public.products" "pg sample-dataset.public.people" "pg sample-dataset.public.demo_transform"],
:fields [],
:source-cards []},
:unresolved [{:type :field, :path ["pg sample-dataset" "public" "demo_transform" "user_iddddd"]}],
:bad-refs #{}}
;; card with unknown table:
"srEGaobVwDUI177CowuLy"
{:card-id 226,
:name "Orders + People + Products model for real by Total",
:entity-id "srEGaobVwDUI177CowuLy",
:file
"export-dir/collections/uj8O1_VYi4rPa4Y8ZGb_S_demo_day/cards/srEGaobVwDUI177CowuLy_orders___people___products_model_for_real_by_total.yaml",
:refs
{:tables ["pg sample-dataset.public.begone" "pg sample-dataset.public.products" "pg sample-dataset.public.people"],
:fields ["pg sample-dataset.public.begone.total"],
:source-cards []},
:unresolved [{:type :table, :path ["pg sample-dataset" "public" "begoneeee"]}],
:bad-refs #{}}
```
* handle foreign key refs in metadata, schemaless dbs like sqlite
checker=> (write-results! (check "export-dir-2") "/tmp/realized.txt")
Results written to: /tmp/realized.txt
```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Ran 14 tests in 0.936 seconds
58 assertions, 0 failures, 0 errors.
{:test 14, :pass 58, :fail 0, :error 0, :type :summary, :duration 936.312333, :single-threaded 14}
Ran 0 tests in parallel, 14 single-threaded.
Finding and running tests took 1.1 s.
All tests passed.
```
* yaml structural checks:
```shell
❯ clj -X:ee metabase-enterprise.checker.structural/cli :export '"/Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based"'
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-17 17:16:18,363 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
Structural validation complete:
Valid files: 444
Invalid files: 1
Errors:
/Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based/collections/HyB3nRtqb7pBPhFG26evI_examples/cards/-1mRtcrAiZoe-GjYrQFHX_order_value_distribution_by_category.yaml (card)
#ordered/map ([:name ["missing required key"]])
```
* report errors
```
❯ clj -X:ee metabase-enterprise.checker.structural/cli :export '"/Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based"'
Structural validation complete:
Valid files: 444
Invalid files: 1
Errors:
/Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based/collections/HyB3nRtqb7pBPhFG26evI_examples/cards/-1mRtcrAiZoe-GjYrQFHX_order_value_distribution_by_category.yaml (card)
- Missing required key 'name' - found 'nname' which may be a typo
```
```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Running tests in ("enterprise/backend/test/metabase_enterprise/checker")
Looking for test namespaces in directory enterprise/backend/test/metabase_enterprise/checker
Finding tests took 208.0 ms.
Running 22 tests
Running before-run hooks...
22/22 100% [==================================================] ETA: 00:00
Ran 22 tests in 0.948 seconds
104 assertions, 0 failures, 0 errors.
{:test 22, :pass 104, :fail 0, :error 0, :type :summary, :duration 947.840541, :single-threaded 22}
Ran 0 tests in parallel, 22 single-threaded.
Finding and running tests took 1.2 s.
All tests passed.
Running after-run hooks...
```
* Start to extract format from checker; add readme
ideally the runner persists and we can change the backends that power
it. Now that things are largely working on serdes, i'm extracting the
specifics about the format from the checker stuff so we can begin to
swap them out to simpler formats
* Refactor checker to use MetadataSource protocol
Introduce protocol-based architecture for the checker module:
- Add `source.clj` with `MetadataSource` protocol for entity resolution
- Refactor `checker.clj` to use source protocol instead of global state
- Session state is now per-check, not global (no more reset-state!)
- Source and enumerators passed in, enabling different backends
- Update `format/serdes.clj` to implement MetadataSource
- Add `make-source`, `check`, `check-cards`, `make-enumerators`
- High-level API now lives here, not in checker.clj
- Update tests to use new API
- In-memory sources via `reify` for edge case testing
- No fixture cleanup needed between tests
- Add DESIGN.md documenting two-protocol architecture:
- EntityResolver: "does this reference exist?"
- FolderLayout: "where are files in this directory?" (future)
- Update README.md with current architecture and usage
This prepares for supporting multiple file formats (serdes, simple LLM-friendly)
and multiple backends (files, memory, app-db) with the same checker core.
```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Running tests with options {:mode :cli/local, :namespace-pattern #"^(?:(?:metabase.*)|(?:hooks\..*))", :exclude-directories [".clj-kondo/src" "classes" "dev" "enterprise/backend/src" "local" "resources" "resources-ee" "src" "target" "test_config" "test_resources"], :test-warn-time 3000, :exclude-tags [:metabot-v3/e2e], :module enterprise/checker, :only ("enterprise/backend/test/metabase_enterprise/checker")}
Running tests in ("enterprise/backend/test/metabase_enterprise/checker")
Looking for test namespaces in directory enterprise/backend/test/metabase_enterprise/checker
Finding tests took 196.0 ms.
Running 22 tests
Running before-run hooks...
22/22 100% [==================================================] ETA: 00:00
Ran 22 tests in 0.976 seconds
110 assertions, 0 failures, 0 errors.
{:test 22, :pass 110, :fail 0, :error 0, :type :summary, :duration 976.301375, :single-threaded 22}
Ran 0 tests in parallel, 22 single-threaded.
Finding and running tests took 1.2 s.
All tests passed.
Running after-run hooks...
```
* proper cli entries
```
❯ clj -M:ee -m metabase.core.bootstrap --mode checker --checker cards --export /Users/dan/projects/work/stats-remote-sync --output /tmp/output.edn
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-19 12:27:47,656 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
2026-03-19 12:27:52,987 INFO util.encryption :: Saved credentials encryption is DISABLED for this Metabase instance. 🔓
For more information, see https://metabase.com/docs/latest/operations-guide/encrypting-database-details-at-rest.html
Results written to: /tmp/output.edn
Card Check Results
==================
Total cards: 2
OK: 0
Errors: 2
Unresolved refs: 0
Issues: 0
Failures:
---------
=== Account [beKpAz4TM1cmVN_0HiAvi] ===
Card ID: 1
UNRESOLVED REFERENCES:
- database: Analytics Data Warehouse
...
```
and structural:
```
❯ clj -M:ee -m metabase.core.bootstrap --mode checker --checker cards --export /Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based --output /tmp/output.edn
Results written to: /tmp/output.edn
Card Check Results
==================
Total cards: 83
OK: 83
Errors: 0
Unresolved refs: 0
Issues: 0
```
```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Running tests with options {:mode :cli/local, :namespace-pattern #"^(?:(?:metabase.*)|(?:hooks\..*))", :exclude-directories [".clj-kondo/src" "classes" "dev" "enterprise/backend/src" "local" "resources" "resources-ee" "src" "target" "test_config" "test_resources"], :test-warn-time 3000, :exclude-tags [:metabot-v3/e2e], :module enterprise/checker, :only ("enterprise/backend/test/metabase_enterprise/checker")}
Running tests in ("enterprise/backend/test/metabase_enterprise/checker")
Looking for test namespaces in directory enterprise/backend/test/metabase_enterprise/checker
Finding tests took 228.0 ms.
Running 22 tests
Running before-run hooks...
22/22 100% [==================================================] ETA: 00:00
Ran 22 tests in 1.076 seconds
110 assertions, 0 failures, 0 errors.
{:test 22, :pass 110, :fail 0, :error 0, :type :summary, :duration 1075.697958, :single-threaded 22}
Ran 0 tests in parallel, 22 single-threaded.
Finding and running tests took 1.3 s.
All tests passed.
Running after-run hooks...
```
* whoops forgot to add new files
* hybrid format concise db schema, serdes assets
* clean up models.serialization
two things we are cleaning up:
1. protocol
there should be a real protocol that we can implement, rather than
knowing which five dynamic vars we need to override/bind a bunch of
dynamic vars, let's have a real protocol and a way to swap it out.
```
;; for yaml
(def ^:private checker-import-resolver
(reify resolve/SerdesImportResolver
(import-fk [_ eid model] (import-fk eid model))
(import-fk-keyed [_ portable model field] (import-fk-keyed portable model field))
(import-user [_ _email] nil) ; checker doesn't resolve users
(import-table-fk [_ path] (import-table-fk path))
(import-field-fk [_ path] (import-field-fk path))))
```
note: future work will put the file system state inside of this and not
as some dynamic var spread throughout the code. But one step at a time.
Now to convert a dataset query we have:
```clojure
(defn- convert-dataset-query
"Convert a dataset query from serdes format, resolving paths to IDs."
[query]
(when query
(let [db-name (:database query)
db-id (when (string? db-name) (resolve-db-name db-name))
query (if db-id (assoc query :database db-id) query)]
(binding [resolve/*import-resolver* checker-import-resolver]
(resolve/import-mbql query)))))
```
instead of this:
```clojure
(defn- convert-dataset-query
"Convert a dataset query from serdes format, resolving paths to IDs."
[query]
(when query
(let [db-name (:database query)
db-id (when (string? db-name) (resolve-db-name db-name))
query (if db-id (assoc query :database db-id) query)]
(binding [serdes/*import-field-fk* import-field-fk
serdes/*import-table-fk* import-table-fk
serdes/*import-fk* import-fk
serdes/*import-fk-keyed* import-fk-keyed]
(serdes/import-mbql query)))))
```
And in the future it will hopefully be something like this:
```clojure
(defn- convert-dataset-query
"Convert a dataset query from serdes format, resolving paths to IDs."
[yaml-store query]
(when query
(let [db-name (:database query)
db-id (when (string? db-name) (resolve-db-name db-name))
query (if db-id (assoc query :database db-id) query)]
(binding [resolve/*import-resolver* (checker-import-resolver yaml-store)]
(resolve/import-mbql query)))))
```
2. not require toucan in the checker.
Before this change, we had to require `metabase.models.serialization`
which brought in `[toucan2.core :as t2]`, models.interface, all kinds of
database specific stuff. We now have the protocol and can implement it
against the yaml store rather than the db. Hopefully startup is faster
now.
```
clj -M:ee -m metabase.core.bootstrap --mode checker --checker cards --export /Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based-mixed-versions --output /tmp/mixed.output
Results written to: /tmp/mixed.output
Card Check Results
==================
Total cards: 83
OK: 83
Errors: 0
Unresolved refs: 0
Issues: 0
```
has gone from 11s to 9s. Small wins, but ~10% savings.
Also, it seems like the vast majority of time is spent in stuff we don't
care about which generates these logs:
```
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-20 12:15:32,319 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
```
* clean up, build file interpreter rather than imperative
* lenient mode
writes out a manifest file of fields that are used
* lenient checker
* add serdes workflow and yaml-edit skill files, fix bootstrap fallthrough
Two new Claude skill files for the serdes demo workflow:
- serdes-workflow: export/validate/import pipeline with exact commands
- serdes-yaml-edit: editing YAML with portable refs, self-healing loop,
checker error interpretation, and reference lookup guide
Fix bootstrap.clj to return false when no --mode flag is present,
allowing normal Metabase startup (import/export commands) to proceed.
* allow for separate db schema directory
checker: --schema-dir, --errors-only, sentinel IDs for unresolved refs
--schema-dir lets database schemas live in a separate directory from the
export. The directory is treated as the databases dir directly (contains
db entries, not a databases/ subdirectory).
--errors-only outputs just errors to stdout in a concise format for LLM
consumption — no summary, no status lines.
Unresolved field/table refs now get sentinel IDs instead of nil, so
lib/query can construct the query and lib/find-bad-refs can flag the
missing refs. Previously these blew up with "should be a positive int,
got: nil".
Without --lenient, missing schemas now throw instead of silently falling
back to lenient mode.
Want me to go ahead and commit with that?
* validate native sql
* filter false-positive :missing-column errors from SQL validation
Schema-less databases (e.g., SQLite) store tables with nil schema, but
sql-tools/validate-query assumes the driver's default schema ("public")
for unqualified table references. This mismatch causes valid columns to
be reported as missing.
Cross-check :missing-column errors against the store and suppress any
where the column actually exists for that database.
* extract the notion of the store
* down to just a single format
* cleanup, rename some args
* rename to semantic
* dashboard checker, extract a bit of common semantic checks
* check dashboard references
* extra schemas to bespoke file so we can move to a public repo
* Remove schema checker
we are semantic only. Now no reason to use a mode param
* Simplify checker module: remove formats, add dashboard/document, norm
Major refactoring of the checker module:
- Remove format abstraction: Delete concise, lenient, and hybrid
formats. Serdes is the only format. Delete format/hybrid.clj,
format/concise.clj, format/lenient.clj and their tests.
- Remove structural checker: Schemas moved to separate repo. Delete
structural.clj, schemas.clj, structural_test.clj.
- Rename checker.clj → semantic.clj: The "cards" checker is now the
"semantic" checker. CLI no longer has a --checker flag.
- Simplify store: Replace enumerator thunks with direct file index. Add
fields-by-table reverse index for O(1) table→fields lookup.
- Remove requiring-resolve: checker.clj (now semantic.clj) directly
requires format.serdes. No circular dependency indirection.
- schema-dir is required: No auto-detection. Export dir and schema dir
are always separate arguments.
- Add CompositeSource: In source.clj, delegates db/table/field to one
source and cards to another.
- Index entities by model field: Collections tree indexed by reading
serdes/meta model from YAML content rather than path patterns. Supports
both traditional serdes layout and the new flat layout.
- Dashboard validation: Validate dashcard card_id refs, dashboard_tab_id refs, and grid bounds (24-column grid).
- Document validation: Walk ProseMirror content tree, validate cardEmbed card refs.
- collection_id and dashboard_id validation: Any entity with these fields is checked against the index.
- Duplicate entity_id detection: Detected at index build time, reported with file paths.
- MBQL normalization pipeline: keywordize string operators → resolve
portable refs → legacy normalize. Known temporal unit strings
keywordized. Malli enforcement disabled during checking so lib/query
accepts valid clauses the schema doesn't enumerate.
- Disambiguate internal naming: Accumulator atom renamed from unresolved
to !failures. Qualified keys renamed to ::resolution-failures and
::missing-database.
* segment and measure support
* compile sql, handle native query snippet
* implement transforms checks
* using binding to prevent running t2 middleware
* fixup merge failures
* directory agnostic schema dir; sql parsing fixes
realized we weren't keeping around db information. now we are so we
don't have to bind driver information
```diff
- compiled (binding [driver/*driver* driver
- qp.i/*skip-middleware-because-app-db-access* true]
+ compiled (binding [qp.i/*skip-middleware-because-app-db-access* true]
```
* use lib for native check
* parse transform sql, store tests, fixups
* rename check-cards → check-entities; add direct native validation tests
check-cards was misleading — it validates cards, dashboards,
collections, documents, transforms, and more. Renamed to check-entities
with an updated docstring that documents the three parameters.
Added 6 direct tests for native.clj functions (extract-sql-refs,
validate-native-sql) covering valid queries, missing columns, syntax
errors, unknown databases, and nil db-name.
* remove bespoke native checking, use deps analysis
* compute result metadata for processing:
change:
before:
```
=== Native Query - Card and Snippet References [HiBFSt0BNx5s5MxVDLLKB] ===
Card ID: 94
Tables: Sample Database.PUBLIC.ORDERS
Fields: Sample Database.PUBLIC.ORDERS.CREATED_AT
Source Cards: Basic Aggregations
NATIVE SQL ERRORS:
- {:type :missing-column, :name "total_revenue", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
- {:type :missing-column, :name "created_attt", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
- {:type :missing-column, :name "cooount", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
Status: NATIVE SQL ERRORS
```
after:
```
=== Native Query - Card and Snippet References [HiBFSt0BNx5s5MxVDLLKB] ===
Card ID: 94
Tables: Sample Database.PUBLIC.ORDERS
Fields: Sample Database.PUBLIC.ORDERS.CREATED_AT
Source Cards: Basic Aggregations
NATIVE SQL ERRORS:
- {:type :missing-column, :name "created_attt", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
- {:type :missing-column, :name "cooount", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
Status: NATIVE SQL ERRORS
```
The total_revenue issue is related to not knowing that the card's
aggregation creates this column. Before it didnt' recognize it. Now with
metadata it does recognize it
* cleanup a bit
- println -> output! to silence the warnings and suppress in one place
- -main -> entrypoint so it doesn't appear as a proper entrypoint for
the jar, we still go through bootstrap and dispatch
* some dumentation on checker shape
* cljfmt files
* fixup
* include resolver arg
* fix readme and docstrings
* clean things up, introduce provider
provider is what wraps a store into a metadata provider. it's very
qp. the store is what cares about how to load assets and schema from
disk (or anywhere else?)
* e2e tests, report all checked items, roadmap and readme
* fix lint errors
* Fix dependencies module dependency
was using deps.analysis for it's check-entity multimethod. I at first
put an entry in deps.core that passes through to this, but deps.core
brings in t2 which i don't want.
Future work should split this namespace so it has some protocol of how
to load up it's information for deps analysis and the checker can figure
out how to adapt it's store and index for these purposes. Then all the
t2 stuff goes away and we're golden
* sort the api namespaces
* fmt fixes
metabase on yaml-backed-metadata-provider [$?] via 🥟 v1.3.8 via ☕ v25.0.2 on ☁️ metabase-query
❯ bin/mage cljfmt-files enterprise/backend/test/metabase_enterprise/checker
Reformatted enterprise/backend/test/metabase_enterprise/checker/semantic_test.clj
metabase on yaml-backed-metadata-provider [$!?] via 🥟 v1.3.8 via ☕ v25.0.2 on ☁️ metabase-query
❯ bin/mage cljfmt-files enterprise/backend/src/metabase_enterprise/serialization/cmd.clj
Reformatted enterprise/backend/src/metabase_enterprise/serialization/cmd.clj
* Checker: unify entity checking, split protocols, extract provider
Major refactoring of the checker module for clarity and consistency:
- Split MetadataSource into SchemaSource (databases/tables/fields) and
AssetsSource (cards/snippets/transforms/segments/dashboards/collections/
documents/measures). All entity types now go through source protocols
and the store — no more direct YAML file loading fallbacks.
- Extract provider.clj from semantic.clj. The provider adapts the store
into a lib MetadataProvider: reference resolution, MBQL normalization,
data conversion. semantic.clj is now just validation + orchestration.
- Replace custom keywordize-mbql-operators (~40 lines) with lib/normalize.
- Unify entity checking: cards are no longer special-cased. check-entity
dispatches by kind for all entity types. check-one works for any entity,
not just cards. check-entities defaults to checking everything.
- Store sources are now private. Provider creates resolvers internally.
resolve-ref uses store load functions instead of reaching through to
source protocols directly.
- Add dashboard/collection/document/measure to source protocols, store
load functions, and test helpers. Tests use in-memory sources throughout
— no more temp YAML files.
- Add e2e tests against test_resources/serialization_baseline with shape
assertions for joins, snippets, measures/metrics/segments, and
transitive ref propagation.
- Add forward-declare wrapper for deps.analysis/check-entity.
- Track snippets, measures, metrics, segments in card :refs, propagating
transitively through source-card chains.
- All non-card entities now appear in results (not just failures).
* handle multiple databases
* cleanup entrypoint a bit
* Checker: lazy on-demand indexing for schema directories
Index only database YAMLs (~40 files) at startup instead of parsing all
563k YAML files. Tables, fields resolved on-demand from directory structure.
Segments/measures indexed only from export dir, not schema dir.
Key changes:
- SchemaSource protocol gains enumeration methods (fields-for-table,
tables-for-database, all-table-paths, etc.)
- db-name->dir mapping handles slugified directory names
- Provider scoped to current database (tables-for-database not all-table-paths)
- Store delegates to source instead of maintaining field index
Setup time: 64s → ~100ms for 40 databases, 22k tables, 541k fields.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Checker: handle table-type template tags, lightweight table listing
- Native queries with {{table_one}} (type: table) template tags now get
placeholder substitution instead of passing through as raw mustache
syntax that SQLGlot can't parse
- Filter out validation errors referencing table placeholder names
- Skip result-metadata for table-tag queries (columns unknown at check time)
- Provider uses ensure-table-id! for table enumeration (path-derived
metadata, no YAML parse) instead of load-table! (full YAML parse)
- Remove debug instrumentation from semantic.clj
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Checker: update docs to reflect on-demand indexing architecture
- DESIGN.md: rewrite to reflect current architecture (SchemaSource/AssetsSource
protocols, on-demand field/table resolution, db-name->dir mapping, parallelism
readiness, performance characteristics)
- CHECKER_RESULTS.md: add analysis of 177-entity run (17 true errors, 2 remaining
bugs, 1 fixed template tag bug)
- Fix stale docstrings in store.clj and serdes.clj
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Checker: consolidate docs into README, remove standalone md files
Move architecture, performance, and on-demand indexing docs into
the existing README. Remove DESIGN.md and CHECKER_RESULTS.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Checker: fix test helpers for new SchemaSource protocol methods
- Add fields-for-table, all-field-paths, all-database-names,
all-table-paths, tables-for-database to test helper reify
- Update index assertion tests to check via source protocol
instead of pre-indexed table/field counts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Merge branch 'master' into yaml-backed-metadata-provider
* remove bashing in place, unused function
* Checker: single-atom schema model with two-level lazy resolution
Replace the three separate cache atoms (indexed-schemas,
table-cache,field-cache) with a single schema-model atom that owns all
database, table, and field knowledge. Separate from the assets-index
which holds cards, dashboards, segments, etc.
Schema model shape:
{db-name {:db-file "path"
:schemas {schema ::not-indexed
| {table {:table-file "path"
:fields ::not-indexed | {field "path"}}}}}}
Two levels of laziness:
- Schema → tables: populated when any table in that schema is resolved,
or when tables-for-database is called. Walks the schema's tables/
directory and reads each table's name: from YAML via fast regex.
- Table → fields: populated when resolve-field or fields-for-table is
called for that specific table. Walks the table's fields/ directory
and reads each field's name: from YAML via fast regex.
Schema name resolution handles the case where directory names differ
from real entity names (e.g. H2: dir=public, real=PUBLIC). This is
isolated in resolve-schema-name as the single point to change when
schema resolution improves.
Also: store paths as strings not Files, fix exists? for databases (now
checked via schema source not assets index), remove dead
has-card-template-tags?.
* inline util lower-case-en
don't want to pull in all the stuff so i just use the definition inline
here
---------
Co-authored-by: Bryan Maass <bryan.maass@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
69a5020367 | Adjust CLAUDE.md backend test instructions (#72136) | ||
|
|
ce15b2d9f0 | fix metabot sql tools incorrectly checked query type (#71428) | ||
|
|
9796110af3 |
Ai service port (#68463)
* Experiment with communicating with LLMs in Clojure
* Libor comments
* switch to new openai /v1/responses api, check for structured output support
* support for claude to show how different adapters are
* linter fixes
* Implement native Clojure agent for MetaBot v3
This commit implements a production-ready native Clojure agent loop that
replaces the external Python AI Service with an in-process implementation,
leveraging the existing llm-inside streaming infrastructure.
Key Components:
- agent/core.clj: Main agent loop with iteration control and streaming
- agent/memory.clj: Conversation state and step tracking
- agent/messages.clj: Message formatting and LLM communication
- agent/profiles.clj: Profile configurations (embedding, internal, transforms)
- agent/tools.clj: Tool registry with 13 wrapped tools
Integration:
- api.clj: Feature-flagged routing via use-native-agent setting
- settings.clj: New use-native-agent boolean setting (default: false)
Benefits:
- Direct Toucan2 database access (no HTTP round-trips)
- Single JVM process deployment
- Lower latency for tool execution
- Unified logging and error handling
- Reuses 80%+ of existing streaming infrastructure
Test Coverage:
- 5 test files with comprehensive coverage
- All tests passing (133 total assertions)
- Unit tests for memory, messages, profiles, tools
- Integration tests for agent loop with mocked LLM
The implementation is production-ready and can be enabled via feature flag
for gradual rollout and validation against the Python service.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Integrate Phase 2A: Prompts & Context System into agent loop
This commit completes Phase 2A of the native Clojure agent implementation,
integrating the templated prompt system with context enrichment into the
main agent execution loop.
## New Features
### Prompt System (prompts.clj)
- Selmer template loading with caching
- SQL dialect instruction injection (14 dialects supported)
- Tool instruction extraction from docstrings
- System message template rendering with context variables
### Context Enrichment (user_context.clj)
- Timezone-aware time formatting
- SQL dialect extraction from context
- Viewing context formatting for all types:
- Adhoc queries (notebook editor)
- Native SQL queries with error context
- Transforms with source information
- Code editor with buffer/cursor/selection details
- Entity details (tables, models, questions, metrics, dashboards)
- Recent views formatting with relevance hints
- Full context enrichment pipeline for template variable injection
### Resource Files
- 3 system prompt templates (embedding, internal, transform-codegen)
- 14 SQL dialect instruction files
- All templates support dynamic context injection
## Integration Changes
### Agent Core (core.clj)
- Updated build-messages-for-llm to accept context, profile, and tools
- Integrated system message building with enriched context
- Fixed pre-existing async blocking issue in run-llm-with-tools
(changed from blocking a/<!! to returning channel for non-blocking a/<!)
- Agent now builds fresh system messages with context on each iteration
### Messages (messages.clj)
- Updated build-system-message to use templated prompts
- Accepts context, profile, and tools parameters
- Calls prompts/build-system-message-content for rendering
### Profiles (profiles.clj)
- Added :prompt-template mapping to all three profiles
- Maps profile IDs to template files in resources/metabot/prompts/system/
## Tests
All Phase 2A functionality is comprehensively tested:
- prompts-test: 6 tests, 46 assertions ✅
- user-context-test: 5 tests, 81 assertions ✅
- messages-test: 3 tests, 47 assertions ✅
- profiles-test: 1 test, 36 assertions ✅
Total: 15 tests, 210 assertions passing
Note: core-test integration tests have pre-existing failures due to blocking
calls in self/core.clj tool-executor-rff (line 116), which existed before
this change. The async pipeline architecture issue is tracked separately.
## Architecture
The agent now follows this flow on each iteration:
1. Enrich raw context (user_context/enrich-context-for-template)
2. Load and render system prompt template (prompts/build-system-message-content)
3. Build full message array with system message + history
4. Execute LLM call with enriched context
5. Process tool calls and responses
6. Repeat until completion
This matches the Python implementation's prompt and context system while
leveraging Clojure's functional architecture.
* Add Selmer dependency for template rendering
Selmer v1.12.70 provides Jinja-like templating for Clojure,
used by the Phase 2A prompts system for rendering system messages
with dynamic context variables.
* Fix async blocking issue in agent loop by using pipeline-blocking
The tool-executor-rff reducing function blocks in its completion arity
to wait for tool results. Using a/pipeline causes 'Invalid blocking call
in dispatch thread' errors because the regular pipeline uses a fixed
thread pool that doesn't allow blocking.
Solution: Use a/pipeline-blocking instead, which uses a separate thread
pool designed for blocking operations. This allows the tool executor's
completion arity to safely block while waiting for tool results.
Fixes the core-test integration test failures.
* Implement Phase 2B: SQL Query Tools
Add four SQL query manipulation tools to enable the agent to create, edit,
and search SQL queries:
New Tools:
- create_sql_query: Create new SQL queries with validation
- edit_sql_query: Apply structured edits (replace, append, insert, etc.)
- replace_sql_query: Replace SQL content entirely while preserving metadata
- sql_search: Search for SQL queries by content with relevance ranking
Features:
- Direct Toucan2 Card model access (no HTTP round-trips)
- Database and permissions validation
- Structured edit operations (replace, replace-all, append, prepend, insert-after, insert-before)
- Search with ranking by name match, description match, SQL position, and recency
- Integration with existing agent tool registry
- Added to metabot-internal profile and new metabot-sql-only profile
Testing:
- Comprehensive test coverage for all SQL tools
- Integration tests for full workflow (create → edit → search)
- All tests passing (18 assertions, 0 failures)
Architecture:
- Follows existing tool pattern (mu/defn with Malli schemas)
- Returns structured output for agent state tracking
- Error handling with agent-error? flag for LLM-friendly messages
Phase 2B complete. Ready for Phase 2C: Visualization Tools.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix SQL tools to handle lib/query format
Fixed multiple issues with SQL tools not handling the transformed
lib/query format when Card models are read from the database:
## Issues Fixed
1. **extract-sql-content not finding SQL**: Card dataset_query transforms
from {:native {:query "SQL"}} to {:stages [{:native "SQL"}]} when read.
Updated extract-sql-content in all SQL tools to handle both formats.
2. **OffsetDateTime vs Date**: The rank-result function in sql_search
called .getTime on timestamps, but Toucan2 returns OffsetDateTime.
Added instanceof check to convert appropriately.
3. **Missing visualization_settings**: Test code using t2/insert! needed
:visualization_settings {} field (database constraint).
4. **Test permissions**: Wrapped all test code with mt/with-current-user
to provide proper permission context.
5. **Fixed typo in sql_search**: Changed accessible-cards variable reference
from undefined to all-cards.
## Files Changed
- edit_sql_query.clj: Handle both lib/query and legacy formats
- replace_sql_query.clj: Handle both lib/query and legacy formats
- sql_search.clj: Handle both formats + OffsetDateTime conversion
- sql_tools_test.clj: Add visualization_settings, fix permissions,
add clojure.string alias, fix indentation
## Test Results
All 19 assertions passing (4 test functions):
- edit-sql-query-test: 4 edit types tested
- replace-sql-query-test: Replace with metadata preservation
- sql-search-test: Search, filter by database, limit parameter
- integration-create-edit-search-test: Full workflow
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Add read_resource tool for fetching Metabase resources via URIs
Implement the foundational read_resource tool that provides unified
interface for fetching detailed information about Metabase resources
using URI patterns. This tool implements the "Context Engineering with
Links" pattern where URIs serve as lightweight, token-efficient
references that can be fetched on-demand at the appropriate detail level.
Supported URI patterns:
- metabase://table/{id} - Basic table info
- metabase://table/{id}/fields - Table structure with fields
- metabase://table/{id}/fields/{field_id} - Specific field details
- metabase://model/{id}/fields - Model structure with fields
- metabase://metric/{id}/dimensions - Metric dimensions
- metabase://transform/{id} - Transform details (not yet implemented)
- metabase://dashboard/{id} - Dashboard details (not yet implemented)
Key features:
- Parses metabase:// URIs and routes to appropriate handlers
- Supports fetching multiple resources in parallel (max 5)
- Handles field IDs with slashes (e.g., c75/17)
- Returns formatted XML-like output for LLM consumption
- Provides detailed error messages for invalid URIs
This tool is critical for:
- Enabling agents to get more context about resources on demand
- Supporting workflows where search returns URIs that agents fetch later
- Reducing token usage by deferring detail fetching until needed
Changes:
- Created read_resource.clj with URI parsing and resource handlers
- Added read-resource-tool wrapper in tools.clj with Malli schema
- Updated all 4 profiles to include read_resource tool
- Added comprehensive tests for URI parsing and resource fetching
Tests: 24 assertions, 0 failures
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Implement Phase 2C chart tools, 2F profiles, and fix async blocking
Phase 2C - Chart Tools:
- Add create_chart tool to create visualizations from queries
- Add edit_chart tool to modify existing chart types
- Register chart tools in agent tools registry
Phase 2F - Profiles & Templates:
- Port sql-querying-only.selmer from Jinja2 (fixes broken :metabot-sql-only)
- Port metabot-next.selmer template
- Port natural-language-querying-only.selmer template
- Add :metabot-next, :metabot-nlq-only, :metabot-beta-customer profiles
Async Blocking Fix:
- Use a/thread instead of a/pipeline-blocking in run-llm-with-tools
- tool-executor-rff uses a/<!! in completion arity which cannot run in go-blocks
- Synchronous transducer application in thread context is now aligned with self code
- Fix stream-parts-to-output! and finalize-stream! to use a/>! in go blocks
- Fix api.clj syntax error with metadata on string literal
- Update tests to generate proper Claude raw SSE format
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add AI SDK v4 line protocol output format for native agent
The Python ai-service outputs a specific line protocol format that the
frontend expects. This adds the conversion layer to output the same format
from the native Clojure agent.
Format examples:
- 0:"text content" - Text (JSON-encoded string)
- 2:{"type":...} - Data parts
- 3:"error" - Errors
- 9:{"toolCallId":..., "toolName":..., "args":...} - Tool calls
- a:{"toolCallId":..., "result":...} - Tool results
- d:{"finishReason":"stop", "usage":{...}} - Finish message
- f:{"messageId":...} - Start message
Changes:
- Add format-* functions for each part type in self/core.clj
- Add aisdk-line-xf transducer for batch conversion
- Export new functions via self.clj
- Update api.clj to stream parts in line protocol format
- Add comprehensive tests for all format functions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix native agent loop for multi-turn conversations with tools
Key fixes:
- Fix premature loop exit: Continue when tool calls present, regardless of text
- Fix Claude API integration: Extract system message separately, pass via :system param
- Fix tool name mismatch: Pass tools as [name, var] pairs so Claude sees registry keys
- Fix message format for tool_use: Use content array format for assistant messages
- Handle START_STEP/FINISH_STEP types in aisdk->messages
- Rename profiles to simpler keys (:embedding, :internal, etc.)
- Add /native-agent-streaming endpoint for testing (bypasses feature flag)
- Add debug logging for message building
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add state management for query/chart IDs in native agent tools
State-dependent tools (show_results_to_user, create_chart, edit_chart) now
look up queries and charts from agent memory by ID instead of expecting raw
query objects. This mirrors the Python AI service pattern.
Key changes:
- memory.clj: Add query/chart storage functions (remember-query, find-query,
store-chart, find-chart, load-queries-from-state, load-charts-from-state)
- core.clj: Extract queries/charts from tool outputs and store in memory
- tools.clj: Wrap state-dependent tools with memory atom access
- claude.clj: Update schema to accept wrapped tool maps with :fn/:doc/:schema
- self/core.clj: Handle wrapped tool maps in tool-executor-rff
- show_results_to_user.clj: Accept query-id and look up from queries-state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add metabase:// link resolution for native agent streaming
The native agent now resolves metabase:// URIs in text parts before
streaming to clients. This mirrors the Python AI service's
MarkdownLinkBuffer behavior.
Link types supported:
- metabase://query/{uuid} - Resolves to /question# using stored query
- metabase://chart/{uuid} - Resolves to /question# via chart's query
- metabase://model/{id} - Resolves to /model/{id}
- metabase://metric/{id} - Resolves to /metric/{id}
- metabase://dashboard/{id} - Resolves to /dashboard/{id}
- metabase://question/{id} - Resolves to /question/{id}
- metabase://transform/{id} - Resolves to /admin/transforms/{id}
Changes:
- links.clj: New module for link resolution with markdown parsing
- core.clj: Integrate link processing in stream-parts-to-output!
- Tests for link resolution and streaming integration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tool result formatting for LLM and link resolution
Key changes:
- Format tool results as XML for LLM consumption (messages.clj)
- Query results now show query ID clearly: <query id="..." type="query">
- Chart results include chart ID and reference link
- This matches Python ai-service format so LLM can extract IDs properly
- Add fallback for chart link resolution (links.clj)
- When LLM uses metabase://chart/ with a query ID, fall back to
treating it as a query link
- Handles common LLM mistake of using wrong link type
- Fix chart extraction logic (core.clj)
- Charts identified by :chart-id + :query-id (without :query)
- Distinguishes charts from query results correctly
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add InstructionResultSchema pattern for native agent tool outputs
Align Clojure native agent tool outputs with Python AI Service patterns
by wrapping results in <result>/<instructions> XML format for consistent
LLM guidance.
- Add llm_representations.clj with entity-to-XML formatting utilities
- Add instructions.clj with LLM guidance constants for each tool type
- Update messages.clj to use format-with-instructions wrapper
- Add type-specific formatters for search, entity, query, chart results
- Include comprehensive tests for LLM representation functions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Align Clojure LLM representations exactly with Python AI Service
Updates tool output formatting to match Python AI Service patterns exactly:
- field->xml: Add quotes around name, database_type attr, ## Description header
- metric->xml: Add collection, default_time_dimension_field, metabase:// URIs
- table->xml: Add database_engine, fully_qualified_name, related_tables section
- model->xml: Use <metabase-model> tag, slug-based fqn, related_tables
- query->xml: Include database_id, query-content, query_results with rows
- Add question->xml, dashboard->xml, collection->xml, user->xml formats
- Add visualization->xml for full chart representation
- Add field-metadata->xml, get-metadata-result->xml for metadata results
- Update search result tags: model→metabase-model, card→metabase_question
- Update instructions.clj with complete Python-matching guidance text
- Update messages.clj to use <metabase-models> tag
- Expand tests to verify Python format compatibility
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add auto-navigation and fix tool output handling for native agent
- Add redirect reactions to SQL query tools (create, edit, replace) for
auto-navigation after query operations
- Add streaming module to convert reactions to AI SDK data parts
- Fix structured output extraction to handle both :structured-output and
:structured_output key formats (Clojure vs JSON style)
- Add role normalization in message formatting to handle both keyword
and string roles from frontend
- Include :query key in SQL tool returns for memory storage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Implement AI Service parity for native Clojure agent
This commit adds feature parity between the Clojure native agent and
Python AI service, implementing all phases from the parity plan.
New Tools:
- todo_write / todo_read: Task management with todo_list data parts
- navigate_user: Page and entity navigation with redirect reactions
- ask_for_sql_clarification: SQL clarification with final-response flag
- write_transform_sql / write_transform_python: Transform code editing
- create_autogenerated_dashboard: X-ray dashboard generation
- list_snippets / get_snippet_details: SQL snippet access
Core Changes:
- State data part format: {type: data, data-type: "state", version: 1}
- Finish message format: {type: finish, finish-reason: "stop"|"error"}
- New data part types: todo_list, code_edit, transform_suggestion
- Data part streaming from tool results via :data-parts key
- Final response pattern: tools return :final-response? true to stop loop
- Capability-based tool filtering with tool-capabilities map
Profile Updates:
- internal: Added navigate_user, create_autogenerated_dashboard
- transforms-codegen: Added todo tools, transform write tools, snippets
- sql-only: Added ask_for_sql_clarification, :required-tool-call? flag
- nlq-only: Added navigate_user, create_chart, edit_chart
Tests:
- Comprehensive tests for all new tools (29 new tests, 200+ assertions)
- Updated existing tests for new formats and capabilities
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add streaming link buffer and improve AI service compatibility
Key changes:
- Add markdown_link_buffer.clj for handling links split across streaming
text chunks (matches Python AI Service MarkdownLinkBuffer behavior)
- Seed agent state with queries from user_is_viewing context
- Support both snake_case and kebab-case keys in context for prompts
- Normalize entity types (keyword/string) in message formatting
- Decode tool arguments from JSON strings when needed
- Add table link resolution (metabase://table/{id})
- Preserve system-instructions when wrapping state-dependent tools
- Fix iteration boundary check (use inc iteration for comparison)
- Gracefully handle unresolvable links by falling back to link text
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor SQL tools to use in-memory state and data parts
Key changes:
- SQL tools (edit_sql_query, replace_sql_query) now operate on in-memory
query state instead of persisting to database
- Convert tools from reactions to data-parts pattern (navigate_to)
- Add semantic_queries and keyword_queries to search tool schema
- Make edit_sql_query and replace_sql_query state-dependent tools
- Support string query IDs (in addition to int) for memory-based queries
- Extract SQL dialect only from native query context type
- Use streaming.navigate-to-part instead of direct reactions
- Add instructions pattern to SQL tools for better LLM guidance
This aligns the SQL tools with the AI Service's transient query model
where queries exist in conversation state until explicitly saved.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Cleanup
* AI Service cleanup
* Refactor metabot tool registration
* Render LLM representations with Selmer
* Refactor tool result formatting
* define caps by metadata
* Refactor metabot v3 tools namespace
* Route agent tools to core implementations
* Simplify metabot tool wrappers
* factor out core.async as much as possible
* Refactor metabot v3 to use IReduceInit reducibles instead of core.async
- Replace channels with IReduceInit (reducible) pattern throughout
- Standard Clojure transducers work directly: (into [] xf (llm-call {...}))
- Remove core.async dependency from self namespace
- Rename chan? -> reducible?, sse-chan -> sse-reducible
- Rename tool-executor-rff -> tool-executor-xf
- Add malli schemas for agent state, messages, parts
- Simplify agent loop using reducible composition
- Update tests to use reducible patterns
* clarify
* ehhh sideeffect
* fix capabilities/nicer profiles
* tools tests
* api tests
* tools tests
* llm-repr test fixes
* fix profiles.clj
* clj-kondo woes
* is this the last test
* damn
* cljfmt
* fix api test
* update path for e2e tests
* little things
* output text fluidly to a user
* update modules config
* cleaning up links code duplication
* cleanup user context templates
* integration test
* cljfmt
* test
* fix read resource output format
* make clj-kondo happier
* simpler tool executor function
* better error messages from metabot backend
* potentially: ability to reload repl state when js is compiled differently
* metabot error messages and tests
* cleanup agent.messages code/tests a bit
* configurable api urls for llms
* use simple templates more
* format tools to string at their definition
* collecting usage and schema on tool
* system prompts
* datadog observability
* fix modules config
* make clj-kondo happy
* fix read_resource
* fix fe formatting
* more prompt changes
* improve search results tool response
* little cleanups and field-metadata with instructions
* sql dialect
* proper context instructions for sql tools
* report tool args well
* tool result should always have 'result' key
* coerce tool args to json
* good filters validation/errors to llm
* fix construct rendering in system prompt
* fix 'SQL Fixing'
* put queries in the context
* proper rendering for query data
* no :system-instructions
* proper capability check
* handle inline llm errors
* ability to debug llm queries
* fixes to improve llm behavior
* unify query formatting
* accept both variations of db id
* debug for python ai-service
* do not escape instructions
* fix claude usage tracking
* python has no header for tool instructions
* instruct llms to use fq table names
* slight prompt rewording to bring it closer to what benchmarks measure
* fancy field select - reduces amount of tool calls needed
* more clear naming
* simpler date filters handling
* retry logic
* great namespace movement
* no more stacktraces on agent errors
* fix token undercounting
* make sure request cancellation works through with native agent streaming
* Replace raw/endraw tags with safe/endsafe
* Fix issues with the sql profile
* openrouter as main provider
* review comments
* fix test for closing connections
* better schema for incoming messages
* fix agent tests
* cumulative usage from agent loop to reduce need for arithmetics
* clj-kondo/cljfmt
* transforms in read_resource tool
* get-dashboard-details
* test fixes
* fix temporal checks
* fix field_stats test
* fix remove-unreadable-transforms
* oh yeah very important type
* fix usage in native streaming
* fix api test formatting
* Get tools back (#69901)
* drop separate url for native agent, only have single url with a toggle
* fix error message in custom-elements e2e
* prompt generation (#69970)
* feat: documents content generation for clojure metabot (#69807)
* llm model select (#70061)
* BOT-866 Add prometheus metrics to metabot clojure agent part II (#70094)
* BOT-866 Add prometheus metrics to metabot clojure agent
* BOT-866 Also count inline aisdk errors
* BOT-866 push prometheus token counting down into call-llm
This is more in keeping with how current ai-service reports the metric, i.e. per llm call rather than a single batched
token usage per agent request.
This also has the advantage of keeping most prometheus metrics localized to the llm layer.
* Ensure that :metabase-metabot/llm-requests counts retries
This is more consistent with the existing ai-service llm_invocations_total metric
* Make :metabase-metabot/llm-duration-ms per llm invocation rather than cumulative
This more closely matches the existing ai-service llm_duration_ms metric
* Add :metabase-metabot/agent-duration-ms prometheus metric
Records the full duration of the outer agent loop, now that :metabase-metabot/llm-duration-ms records individual llm calls.
* Add comment on :metabase-metabot/llm-duration-ms buckets
And extend the low end of the range down to 100ms
* fixup! Add :metabase-metabot/agent-duration-ms prometheus metric
* Fix whitespace
* Add prometheus metrics tracking to call-llm-structured
* Allow llm callers to specificy the source label for prometheus metrics
And pass "example-question-generation" for question generation.
* Fix call-llm-prometheus-test
After rebasing on #70061 these tests need to include the "openrouter/" provider prefix along with the model.
* BOT-1051 Fix provider in generate-example-questions (#70121)
After #70061 call-llm-structured now expects the model to include a
provider prefix, so "anthropic/claude-haiku-4-5" was getting parsed as
:provider "anthropic" :model "claude-haiku-4-5".
Update metabase-enterprise.metabot-v3.example-question-generator/call-llm to
use the provider + model configured via llm/ee-ai-metabot-provider
* BOT-1039 Add snowplow token_usage event tracking to the clojure agent (#70049)
* BOT-1039 Include the profile :name in the profile map in register-profile!
Sometimes it's useful to be able to exract the profile name when you only
have a reference to the profile.
* Add token_usage snowplow event tracking to the clojure agent
- refactor register-profile! to single-arg form with :name now stored in the profile map and returned by get-profile
- report-token-usage-xf now emits a :snowplow/token_usage event per LLM call alongside existing prometheus metrics
- pass tracking-opts from loop-step through call-llm into the token-usage transducer
- likewise for callers of call-llm-structured
- init-agent generates a per-run request-id UUID
- run-agent-loop accepts an optional conversation-id that turns into the snowplow token_usage session_id
* Add :model to tracking-opts and drop separate source arg
The prometheus source label should always be the same as the snowplow :tag (that's how it works in ai-service), so
drop the separate `source` arg from various functions and just take the source label from the tracking-opts :tag.
* Destructure token-opts in report-token-usage-xf
* Allow passing tracking-opts to run-agent-loop
And use this to override the :source in metabot-v3.api.document/native-generate-content
* Improve docstring for tracking-opts
* Provide model and source defaults in report-aisdk-errors-xf
* Fallback to analytics-uuid if premium-embedding-token is unset
* Fix typo: snowplot -> snowplow
* Compute hashed-token-or-uuid once rather than for each usage part in report-token-usage-xf
* BOT-1039 Move token-usage funcs into analytics module (#70213)
* BOT-1052 Move token-usage funcs into analytics module
So they can be shared between oss sql gen and metabot.
* Add some snowplot tests for self/call-llm and call-llm-structured
* Add prometheus tests for call-llm-structured
* Add basic snowplow and prometheus tests for example question generation
* Modify agent loop snowplot test to use with-fake-snowplow-collector
* Modify native-generate-content-snowplow-test to use with-fake-snowplow-collector
* Fix whitespace
* fix: context not included in sql profile
* drop unused tool find-outliers (#70424)
* fix: add error field to transforms
* fix: common library not used by agent
* BOT-1053 Add snowplow events for agent tool calls (#70338)
* BOT-1039 Create metabase.analytics.util
And move some func there from metabase.analytics.llm-token-usage that will also be needed for agent_used_tool events.
* Add ai_service_event snowplow schema
In preparation for tracking the following events:
- agent_tool_used
- user_intent
- agent_action_summary
* Track snowplow agent_used_tool events in metabase-enterprise.metabot-v3.self/call-llm
- add agent loop :iteration to tracking-opts
- add :duration-ms to :tool-output parts
- add report-tool-usage-xf to transducer chain in metabase-enterprise.metabot-v3.self/call-llm to track
:snowplow/ai_service_event with :event "agent_used_tool"
* BOT-1054 Add snowplow events for user intent classification (#70351)
* BOT-1054 Add snowplow events for user intent classification
* Move run-agent-loop :conversation-id into :tracking-opts :session-id
It does make some sense as a top-level argument, but it's currently only used for snowplow tracking, so push it down
into the tracking-opts.
* Move run-agent-loop :track-user-intent? into :tracking-opts
* Add ee-ai-metabot-provider-lite defsetting and use it in classify-user-intent!
This allows using a lighter weight model for simple tasks like user intent and/or agent response
classification. Defaults to openrouter/openai/gpt-oss-20b which is the same model used by ai-service for these tasks.
* Add run-agent-loop! test helper to run the agent for side effects
* whitespace: fix alignment
* Extract :tracking-opts schema into an mr/def similar to other agent opts
* Rename :profile-name -> :profile-id in tracking-opts
* use embeddings proxy directly rather than via ai-service (#70419)
* fix modules config
* fix: metabot has no context on the currently open transform (#70506)
* sql profile requires tool call to be forced (#70497)
* fix snowplow test
* revert back renames for ai-service embedding provider to evade migration (#70649)
* Add ai-service-port to branches list in uberjar.yml
* BOT-1085 Allow user-intent classification to be disabled (#70521)
Add a new ee-ai-metabot-internal-tasks-enabled? setting to allow disabling metabot internal "bookkeeping" tasks that might make additional llm calls, like user intent classification.
* Fix incorrect docstrings and copy-paste errors in settings and tools (#70793)
* Ai service port validate sql (#70259)
* metabot-v3.tools.util/validate-sql stub
* Add query to dialect mapping
* Hook validation into tools code
* Add validation error instruction
* query-by-id helper
* Move sql validation to agent/tools/sql.clj
* WIP convert edit-sql-query-tool
* Revert edit-sql-query tool
* Drop validation code from tools/util.clj
* Add tools/sql_validation.clj
* Use sql-validation in tools/create_sql_query.clj
* Use sql-validation in agent.tools.sql/create-sql-query-tool
* Add transpile_sql to sql_tools.py
* Add transpile-sql to sql-parsing
* Macaw implementation stub
* Add transpile-sql interface multimethod
* Update transpile_sql in sql_tools.py
* sql-tools/transpile-sql stub
* Add template tag sentinel to sql_tools.py
* sql-tools/transpile-sql schema
* Sqlglot transpile-sql wrapper
* sql-parsing/transpile-sql result normalization
* Hook sql-tools/transpile-sql into metabot-v3.tools.sql-validation/validate-sql
* Adjust create-sql-query-tool to validate-sql changes
* Adjust reason in sql_tools.py
* Add create-sql-query-validation-failure-test
* Addjust validate-sql
* Revert edit-sql-query tool
* Add validation to metabot_v3/tools/edit_sql_query.clj
* Add schema and update returns for create-sql-query
* Add metabot-v3.tools.sql.common
* Update edit_sql_query
* Update edit_sql_query tool
* Add validation failure test for edit-sql-query-tool
* Adjust edit-sql-query-code-edit-tool
* Adjust create-sql-query-code-edit-tool
* Update replace-sql-query and related
* Adjust replace-sql-query-tool
* Update replace-sql-query-code-edit-tool
* Adjsutment for test
* metabot-v3.tools.sql.validation ns
* validate-sql sentinels
* Hoist template tags sentinel into tools.sql.validation
* Update schema
* Validation tests
* Add transpilation tests and adjust sql_tools.py
* Add dialect mapping
* Adjust validation and tests
* Adjust test
* linter
* Cleanup
* Add tests
* Tests and validation
* More tests and cleanup
* Drop redundant tests
* Use original dialect
* Adjust tests
* whitespace
* Remove direct query manipulation from tests
* Remove direct mbql manipulation from create-sql-query
* Remove more of direct query manipulation
* More remarks
* surface openrouter error messages
* API keys are sensitive (#70847)
* Generate tool manifest from endpoint metadata (#70780)
* Generate tool manifest from endpoint metadta
* Delete reference file
* tweak-and-tidy
* More testing, more robustness
* Fix kondo and cljfmt
* Scrap name inference
* Clean up tool assertions
* Remove silly separators
* Switch away from tool registry approach
* rename + slightly simplify prefer-tool-descriptions
* Replace definitions binding with an explicitly threaded atom
* Minor cleanups/refactor
* Pull over some annotations from my prior PR; tweak test comments
* address comments
---------
Co-authored-by: Noah Moss <noahbmoss@gmail.com>
* properly fetch data for user-is-viewing context (#70857)
* serialize error to string if it is not - backend expects a string anyways
* chore: trigger CI
* [BOT-1056] Metabot Analyze Chart in CLJ Port (#70177)
* init analyze chart tool
* fix compilation error; add interpretation instructions; reduce memory leak
* Add license overrides for techascent/tech.config and net.openhft/zero-allocation-hashing
* Remove hard-coded :debug true in /agent-streaming endpoint
* Fix conflict resolution due to tools.analytics after rebase
tools.analytics and find-outliers were dropped in
|
||
|
|
647861b624 |
test: agent-specific test runner (#70821)
* test: agent-specific test runner Adds an "agent" test runner that filters the output of running `clojure -X:...:test` commands to make it more relevant for agents preventing them from needing to use tail or grep to find information and reducing context pollution. Also implements randomized ports for test web-servers allowing multiple agents to run tests in parallel (or to run multiple commands in parallel). This removes the directive to use the ./bin/mage-based test runner, to direct agents to use the agent-specific runner. * fixup! missing import * add the test agent script |
||
|
|
9529117f1e |
GDGT-1668: Migrate notifications to TypeScript (#69104)
* Update Claude instructions * Some DashboardSubscription type usage refactoring * Migrate SendTestPulse to TS. Introduce DashboardSubscriptionData. Related refactoring * Migrate ArchivePulseModal to TS * Migrate UnsubscribePulseModal to TS * Migrate ArchiveModal and test * Migrate to TS: UnsubscribeModal. Related refactoring * Migrate to TS: Notifications actions and routes * Migrate to TS: ModalRoute refinement * Fix eslint * Adjust types of ModalRoutecomponents * Fixes following bot code review * Some more type refinement * Address PR comments * More fixes/refactoring * Update route files. Convert to components * Rename ArchiveNotificationModal to avoid confusion with collections. Fix export * Fix more exports. Remove type casting. Add Response type guard * Refactor routes back to getter functions * Add early return for modals rendering. Add isObject in type-guard * Refine Response type-guard |
||
|
|
e6bc40c223 | add analytics skill (#68419) | ||
|
|
68b1a18e1f |
Ensure Claude prefers clojure-mcp if it is available. (#66860)
For those of us who use clojure-mcp (https://github.com/bhauman/clojure-mcp), our Claude configuration directly contradicts instructions we're giving it. This PR only adds to our `CLAUDE.md` and our skills a note that **if** clojure-mcp is available, it should prefer it over shell-based methods (like `./bin/mage -repl`). |
||
|
|
2f8cc0f153 |
skills (#65676)
* skills * docs review workflow * update skills * update bug fix workflow to use skills * headings * update code workflow to use skills * updates * add workflow dispatch to docs review * edits |
||
|
|
94d2daaafd |
Backend module detangling pre Clojure/conj 2025 edition (#65681)
* Split warehouses and warehouses-rest
* Split warehouse-schema and warehouse-schema-rest
* Split actions and actions-rest
* Split collections and collections-rest
* Split `dashboards` and `dashboards-rest`
* Split public-sharing and public-sharing-rest
* Split embedding and embedding-rest; eliminated driver deps: xrays segments sample-data tiles
* Split users and users-rest
* Split setup and setup-rest
* Some more dev functions
* Update Kondo modules config
* Fix test namespace name
* Fix test failure 🔧
|
||
|
|
ae17be3ee1 |
Add custom Kondo linter to enforce kebab-case in REST API routes (#65100)
* Add Kondo linter to enforce kebab-case in REST API routes * Remove Claude guidelines (no longer needed) |
||
|
|
7611b467f3 |
Remove some instructions from CLAUDE.md (#65103)
Removed instructions and comments regarding emoji usage, spelling conventions, tarot card readings, and formatting for specific users. |
||
|
|
8d09595bb8 |
Remove the instruction to indent docstrings (#65094)
* Remove the instruction to indent docstrings * Remove example |
||
|
|
e23658dd9a | Closes EMB-928 sdk popover flashing at 0,0 (#65086) | ||
|
|
aa200bc31f |
X-Rays => Lib (#64901)
* Rework X-Rays to use Lib
* Test change
* Fix the last few failing tests
* Remove dead code
* PR feedback
* Claude updates
* Fix typos
* Oops
* e2e test fix 🔧
|
||
|
|
c2515e7921 |
Remove rules about number of semicolons, Claude cannot understand them. (#64547)
* Remove rules about number of semicolons, Claude cannot understand them. * Restore the docstring indentation rule |
||
|
|
0ee3c2ffae | Make claude code review bot delete old comments (#64544) | ||
|
|
dcef144251 |
Claude code review (experimental) (#64487)
* Claude code review (experimental) * Run my job PLEASE! * Fix syntax * More guidelines * Minor change to see if we can get some useful feedback * Seriously run my damn workflow * Add GH token * Remove paths * Appease Claude * Tell Claude how to get the Clojure Style Guide * Attempt Clojure style guide again * Give Claude ReadFile perms for the style guide * Move info about community style guide to CLAUDE.md * Try again * Another pass * Seriously please follow Community style guidelines * More advice. * LEARN ABOUT TEST NAMING CLAUDE!!!! * More advice * Appease Kondo * I have a lot to say I guess * Iterate on CLAUDE.md * Clarify module linter cheating rule * CLAUDE u iz dumb * Chaos * Dumb as a rocc * EVEN. MORE. GUIDELINES. * Clarify semicolon rules more * Threaten our AI overlords * Are you dense? * Annoyed * BE SUPER DUPER EXPLICIT * Fun time * fun * More dumb comment examples * Clarify defmethod rules * SERIOUSLY!!! * I don't think Claude can count * More examples of what to avoid * U not listening * Clarify * Use emojys * Parens * Be. Even. More. Explicit. About. Semicolons. * Be SUPER EXPLICIT * Clarify * Discourage `qp.store/with-metadata-provider` |
||
|
|
899d6edb3c | Mage run tests via nrepl (#64236) | ||
|
|
bfd69205ef |
feat: Pg Replication Preview modal (#61522)
* etc: kickoff comments * get quotas statuses from token-check (#61560) * get quotas statuses from token-check * Check quota and estimated-row-count of dbs before replication * Evict token-check cache to get updated quotas * Turn the MB pg replication button into a modal (#61701) Closes: https://linear.app/metabase/issue/CLO-4308/turn-the-mb-pg-replication-button-into-a-modal * Fix schema-filters-patterns split * split token-check row estimates counts (#61783) * Split token-check row estimates counts * add preview endpoint * Align replication schema-filters with HM Fixes: |
||
|
|
afabd18f56 |
Add CLAUDE.md and copilot-instructions.md (#61313)
* Add CLAUDE.md and copilot-instructions.md |
||
|
|
11c45ea42d |
add docs png image optimizer workflow (#60581)
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com> Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be> Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com> Co-authored-by: Metabase Automation <github-automation@metabase.com> |
||
|
|
5578b587a5 |
[Backend modularization] metabase.db => metabase.app-db.core (#58200)
* Remove most outdated :consistent-alias specs
* `metabase.db` => `metabase.app-db.core`
* Kondo fixes 🔧
|
||
|
|
d3fafe9cf2 |
Metabot v3 PoC (#52436)
Co-authored-by: Tyler Plude <tsplude@gmail.com> Co-authored-by: Phoomparin Mano <poom@metabase.com> Co-authored-by: Mihael Konjević <konjevic@gmail.com> Co-authored-by: John Swanson <john.swanson@metabase.com> Co-authored-by: Aleksandr Lesnenko <alxnddr@gmail.com> Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com> Co-authored-by: Thomas Schmidt <thomas@metabase.com> Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be> Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com> Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev> Co-authored-by: github-automation-metabase <166700802+github-automation-metabase@users.noreply.github.com> Co-authored-by: Nicolò Pretto <info@npretto.com> Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com> Co-authored-by: Phoomparin Mano <phoomparin@gmail.com> |
||
|
|
b3cce298ba |
Enable equals-true Kondo linter and teach Claude about Clojure (#56573)
* teach claude how to write backend code
* Add solid instructions for calling into the repl
* teach it how to behave when there's no repl
* remove mcp stuff for now
* add lint pr quick command
* fix: Add missing docstrings in mage/be_dev.clj
Added docstrings to parse-int, nrepl-port, and nrepl-eval functions to fix clj-kondo linting warnings.
* flesh out Lint PR quick command
* feat: enable equals-true Kondo linter and fix warnings
This change enables the `:equals-true` warning in the clj-kondo linter configuration,
which identifies code patterns where `(= true x)` is used instead of the more idiomatic
`(true? x)` form in Clojure.
Key changes:
- Enable `:equals-true` linter with warning level in .clj-kondo/config.edn
- Fix all instances of `(= true x)` throughout the codebase, replacing with `(true? x)`
- Add a new utility to check balanced parentheses in Clojure forms
- Add a new mage command `-balance-parens-check` to validate balanced parentheses
- Update CLAUDE.md with documentation about the balanced parentheses utility
This change improves code quality by promoting idiomatic Clojure usage and adds
a helpful tool for ensuring proper Clojure syntax when editing code.
* fix: update code to resolve equals-true Kondo linter warnings
This commit addresses the newly enabled equals-true linter warnings by:
- Adding #_{:clj-kondo/ignore [:equals-true]} to are forms that legitimately need to test both true/false values
- Replacing (= true x) with (true? x) and (= false x) with (false? x) in test assertions where appropriate
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix more, refine claude instructions
* fix more = true -> true? linter errors automatically
* fix remaining (= true x) -> (true? x) linter warnings by adding clj-kondo ignores
We need the ignores for 'are' macro clj-kondo tests where (= expected (fn x)) is the idiomatic pattern.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* move the linter to the things-we-use section
* update claude.md from github copliot's review
* balanced-parens? -> is-readable-code?
- Returns explicit errors, usually with line numbers.
* update claude.md (mostly formatting)
* Fix kondo-updated when there are no matching files
* Update CLAUDE.md to replace -balance-parens-check with -code-is-readable
* Refactor mage -eval to mage -repl with improved usability and reliability
The implementation uses eval and read-string strategically to control when and where code is evaluated:
1. The code from the command line must first be transported as a string to the running REPL server
2. read-string allows parsing the code without executing it
3. eval then executes the code in the correct namespace context
4. This creates a "REPL inside a REPL" where namespace switching and evaluation happen in a controlled environment
5. This approach prevents premature evaluation and ensures code runs in the intended scope
* -check-readable task naming
* cleanup -check-readable
* fix output + fix user.clj
* shuffle code around
* Update CLAUDE.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
||
|
|
e1fd2d1cdd | devex: add claude tooling (#56391) |