Pure response-parsing functions for AI outputs.
FC/IS rule: no I/O here — receives raw AI response strings, returns parsed data or error maps.
Pure response-parsing functions for AI outputs. FC/IS rule: no I/O here — receives raw AI response strings, returns parsed data or error maps.
(delimiter-balance source)Count of still-open delimiters in source, or nil if they do not nest.
A generated namespace that hits the model's output limit is cut off
mid-form — measured, one stopped at result (s — and writing that file
produces EOF while reading rather than anything the caller can use.
Counting open delimiters is the cheap, provider-free way to see it: a
complete file ends at 0.
A count alone is not enough. (is (= 1 1]) nets to zero and is not
readable, so the kind of each opener is tracked and a closer that does not
match the innermost one is rejected. Depth-only, all three of (is (= 1 1]),
([)] and (deftest a (is [1 2)]) were reported complete.
Args: source - Clojure source string
Returns: Number of unclosed delimiters (0 means balanced), or nil when a closer appears with nothing open or with the wrong opener — which is damage rather than truncation, and equally unreadable.
Count of still-open delimiters in `source`, or nil if they do not nest. A generated namespace that hits the model's output limit is cut off mid-form — measured, one stopped at `result (s` — and writing that file produces `EOF while reading` rather than anything the caller can use. Counting open delimiters is the cheap, provider-free way to see it: a complete file ends at 0. A count alone is not enough. `(is (= 1 1])` nets to zero and is not readable, so the kind of each opener is tracked and a closer that does not match the innermost one is rejected. Depth-only, all three of `(is (= 1 1])`, `([)]` and `(deftest a (is [1 2)])` were reported complete. Args: source - Clojure source string Returns: Number of unclosed delimiters (0 means balanced), or nil when a closer appears with nothing open or with the wrong opener — which is damage rather than truncation, and equally unreadable.
(ensure-standard-requires test-source)Add :require entries for standard namespaces test-source uses but omits.
Only touches a namespace form that already has a :require clause; a
generated test namespace always does, and synthesising one would mean
guessing where it belongs.
Args: test-source - generated Clojure source string
Returns: The source with the missing requires added, or nil for nil input.
Add `:require` entries for standard namespaces `test-source` uses but omits. Only touches a namespace form that already has a `:require` clause; a generated test namespace always does, and synthesising one would mean guessing where it belongs. Args: test-source - generated Clojure source string Returns: The source with the missing requires added, or nil for nil input.
(ensure-test-metadata test-source test-type)Tag every unmetadata'd deftest in test-source with ^:<test-type>.
Kaocha selects suites on this metadata, so a test namespace without it runs
in no suite — present in the file, absent from every run. The system prompt
asks the model for it; measured against two of the framework's own core
namespaces, it produced 15 deftests and tagged none of them. The test type
is already derived in Clojure by context/determine-test-type, so this is a
fact the code knows and does not need to ask for.
A deftest that already carries metadata is left alone — the model may have tagged it more precisely than the path-based default, and a second tag would be noise at best.
Args: test-source - generated Clojure source string test-type - :unit, :integration or :contract
Returns: The source with metadata applied, or nil for nil input.
Tag every unmetadata'd `deftest` in `test-source` with `^:<test-type>`. Kaocha selects suites on this metadata, so a test namespace without it runs in no suite — present in the file, absent from every run. The system prompt asks the model for it; measured against two of the framework's own core namespaces, it produced 15 deftests and tagged none of them. The test type is already derived in Clojure by `context/determine-test-type`, so this is a fact the code knows and does not need to ask for. A deftest that already carries metadata is left alone — the model may have tagged it more precisely than the path-based default, and a second tag would be noise at best. Args: test-source - generated Clojure source string test-type - :unit, :integration or :contract Returns: The source with metadata applied, or nil for nil input.
(missing-standard-requires test-source)Standard namespaces test-source uses without requiring.
Two shapes, both measured against the framework's own namespaces: an alias
use (str/join with no alias bound to str, which fails with
No such namespace: str) and a fully qualified use
(clojure.set/subset? with no require, which fails with
ClassNotFoundException: clojure.set). They are checked separately because
satisfying one does not satisfy the other — a namespace can be required
under a different alias, and an alias can be bound to a different namespace.
Args: test-source - generated Clojure source string
Returns:
Sorted seq of {:alias str :namespace str :aliased? bool}, empty when
nothing is missing. :aliased? distinguishes the two shapes so the repair
can add [ns :as alias] only where an alias is actually needed.
Standard namespaces `test-source` uses without requiring.
Two shapes, both measured against the framework's own namespaces: an alias
use (`str/join` with no alias bound to `str`, which fails with
`No such namespace: str`) and a fully qualified use
(`clojure.set/subset?` with no require, which fails with
`ClassNotFoundException: clojure.set`). They are checked separately because
satisfying one does not satisfy the other — a namespace can be required
under a different alias, and an alias can be bound to a different namespace.
Args:
test-source - generated Clojure source string
Returns:
Sorted seq of {:alias str :namespace str :aliased? bool}, empty when
nothing is missing. `:aliased?` distinguishes the two shapes so the repair
can add `[ns :as alias]` only where an alias is actually needed.(module-spec->cli-args {:keys [module-name entity fields http web]})Convert a parsed module spec map into CLI args for the scaffolder.
Args: spec - normalised module spec map from parse-module-spec
Returns: Vector of string args for wagoe.scaffolder.shell.cli-entry.
Convert a parsed module spec map into CLI args for the scaffolder. Args: spec - normalised module spec map from parse-module-spec Returns: Vector of string args for wagoe.scaffolder.shell.cli-entry.
(normalise-module-spec module-spec)Normalise a provider-parsed module spec map into canonical scaffolder shape.
Args: module-spec - map parsed from provider JSON mode
Returns: Normalised map with keyword keys and validated field specs, or {:error str} on failure.
Normalise a provider-parsed module spec map into canonical scaffolder shape.
Args:
module-spec - map parsed from provider JSON mode
Returns:
Normalised map with keyword keys and validated field specs,
or {:error str} on failure.(parse-generated-tests response-text)Extract Clojure test code from an AI response.
The AI should return raw Clojure, but may wrap in code fences.
Args: response-text - raw AI response string
Returns: Clean Clojure source string.
Extract Clojure test code from an AI response. The AI should return raw Clojure, but may wrap in code fences. Args: response-text - raw AI response string Returns: Clean Clojure source string.
(parse-json-response text)Parse a JSON string from an AI response.
Handles responses that may include markdown code fences or leading text.
Args: text - raw AI response string
Returns: Parsed map on success, {:error str :raw text} on failure.
Parse a JSON string from an AI response.
Handles responses that may include markdown code fences or leading text.
Args:
text - raw AI response string
Returns:
Parsed map on success, {:error str :raw text} on failure.(parse-module-spec response-text)Parse an AI-generated module specification JSON into a normalised map.
Expected AI output shape: {"module-name": "product", "entity": "Product", "fields": [{"name": "price", "type": "decimal", "required": true}], "http": true, "web": true}
Returns: Normalised map with keyword keys and validated field specs, or {:error str} on failure.
Parse an AI-generated module specification JSON into a normalised map.
Expected AI output shape:
{"module-name": "product", "entity": "Product",
"fields": [{"name": "price", "type": "decimal", "required": true}],
"http": true, "web": true}
Returns:
Normalised map with keyword keys and validated field specs,
or {:error str} on failure.(parse-sql-response response-text)Parse an AI-generated SQL copilot response.
Expected AI output: {"honeysql": "...", "explanation": "...", "raw-sql": "..."}
Returns: Map with :honeysql :explanation :raw-sql, or {:error str} on failure.
Parse an AI-generated SQL copilot response.
Expected AI output:
{"honeysql": "...", "explanation": "...", "raw-sql": "..."}
Returns:
Map with :honeysql :explanation :raw-sql,
or {:error str} on failure.(require-clause source)The text of the namespace form's :require clause, or nil.
Whether a namespace is required cannot be answered by searching the whole
file: clojure.set/subset? in a test body contains the text clojure.set,
so a whole-file search calls it required and the file then dies at load with
ClassNotFoundException: clojure.set. Measured — that is exactly how a
generated namespace failed.
Args: source - Clojure source string
Returns:
Substring covering (:require ...) inclusive, or nil when there is none.
The text of the namespace form's `:require` clause, or nil. Whether a namespace is required cannot be answered by searching the whole file: `clojure.set/subset?` in a test body contains the text `clojure.set`, so a whole-file search calls it required and the file then dies at load with `ClassNotFoundException: clojure.set`. Measured — that is exactly how a generated namespace failed. Args: source - Clojure source string Returns: Substring covering `(:require ...)` inclusive, or nil when there is none.
Aliases the generator uses in test bodies but routinely forgets to require.
Restricted to clojure.* namespaces with one conventional alias each, because the repair below infers the namespace from the alias — which is only sound where the mapping is unambiguous. An alias outside this map is left to fail at compile time rather than guessed at.
Aliases the generator uses in test bodies but routinely forgets to require. Restricted to clojure.* namespaces with one conventional alias each, because the repair below infers the namespace from the alias — which is only sound where the mapping is unambiguous. An alias outside this map is left to fail at compile time rather than guessed at.
(strip-noncode source)source with string, regex, character-literal and comment content blanked.
Both checks below need to reason about structure, and both were wrong
without this: a ( inside a docstring is not an open paren, and an
edn/read inside a test's string literal is not a use of the edn alias.
The second was measured — a generated namespace that merely quoted
(edn/read d) in a test string had [clojure.edn :as edn] added to its
requires, which clj-kondo then flags as unused.
Blanked, not removed: offsets and line structure are preserved, so a caller can still relate a position back to the original.
wagoe.tools.parsing/strip-comments-and-strings does the same job for the
quality gates. It is not shared: libs/tools is Babashka-only and declares no
Wagoe dependency, libs/ai declares none either, and a common home would mean
one of them taking on the other's dependency tree. Two small copies beat
that, but they are copies — a fix here is worth checking against there.
Args: source - Clojure source string
Returns: Source of the same length with non-code characters replaced by spaces (newlines kept), or nil for nil input.
`source` with string, regex, character-literal and comment content blanked. Both checks below need to reason about structure, and both were wrong without this: a `(` inside a docstring is not an open paren, and an `edn/read` inside a test's string literal is not a use of the `edn` alias. The second was measured — a generated namespace that merely *quoted* `(edn/read d)` in a test string had `[clojure.edn :as edn]` added to its requires, which clj-kondo then flags as unused. Blanked, not removed: offsets and line structure are preserved, so a caller can still relate a position back to the original. `wagoe.tools.parsing/strip-comments-and-strings` does the same job for the quality gates. It is not shared: libs/tools is Babashka-only and declares no Wagoe dependency, libs/ai declares none either, and a common home would mean one of them taking on the other's dependency tree. Two small copies beat that, but they are copies — a fix here is worth checking against there. Args: source - Clojure source string Returns: Source of the same length with non-code characters replaced by spaces (newlines kept), or nil for nil input.
(truncated? source)Whether source looks cut off mid-form rather than complete.
Args: source - Clojure source string
Returns: true when delimiters are left open (or a stray closer appears).
Whether `source` looks cut off mid-form rather than complete. Args: source - Clojure source string Returns: true when delimiters are left open (or a stray closer appears).
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |