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.
(code-blocks text)The fenced blocks in text, in order, as {:info str :body str}.
Fences start a line, as in Markdown. A closer is a bare ``` outside a string literal, so a fence quoted inside a generated test does not end the block. A block the output limit cut off runs to the end of the text.
The fenced blocks in `text`, in order, as {:info str :body str}.
Fences start a line, as in Markdown. A closer is a bare ``` outside a string
literal, so a fence quoted inside a generated test does not end the block.
A block the output limit cut off runs to the end of the text.(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.
Namespace prefixes whose calls reach a database, a file, a process or the network. A test calling one does real I/O.
Namespace prefixes whose calls reach a database, a file, a process or the network. A test calling one does real I/O.
(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 public-api]})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.(normalise-setup-spec data)The seven setup choices, with a default for every one the provider omitted.
Keys are read in both forms. parse-json-response keywordizes, and the
caller read string keys, so every answer fell through to its default — the
description reached the provider, came back parsed, and was then thrown away
(BOU-401). Returns string keys: this is written straight out as JSON.
The database default is sqlite, not postgresql: an absent choice must still yield a project that boots without a database server (BOU-228).
The seven setup choices, with a default for every one the provider omitted. Keys are read in both forms. `parse-json-response` keywordizes, and the caller read string keys, so every answer fell through to its default — the description reached the provider, came back parsed, and was then thrown away (BOU-401). Returns string keys: this is written straight out as JSON. The database default is sqlite, not postgresql: an absent choice must still yield a project that boots without a database server (BOU-228).
(parse-admin-entity response-text)Parse an admin entity EDN answer.
Returns: {:text edn-string :entity-name str :value map}, where :text is the EDN without fence or prose, or {:error str :raw-text str} naming what was actually wrong.
Parse an admin entity EDN answer.
Returns:
{:text edn-string :entity-name str :value map}, where :text is the EDN
without fence or prose, or {:error str :raw-text str} naming what was
actually wrong.(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, one entity or several (BOU-497): {"module-name": "billing", "entities": [{"name": "Invoice", "fields": [...]}, {"name": "InvoiceLineItem", "belongs-to": "Invoice", "fields": [...]}], "http": true, "web": true, "public-api": false}
The older singular shape — entity plus fields — is still read.
Returns: {:module-name :entities [{:name :fields :belongs-to? :min? :workflow?}] :http :web :public-api}, plus :entity and :fields for the first entity, or {:error str} on failure.
Parse an AI-generated module specification JSON into a normalised map.
Expected AI output shape, one entity or several (BOU-497):
{"module-name": "billing",
"entities": [{"name": "Invoice", "fields": [...]},
{"name": "InvoiceLineItem", "belongs-to": "Invoice",
"fields": [...]}],
"http": true, "web": true, "public-api": false}
The older singular shape — `entity` plus `fields` — is still read.
Returns:
{:module-name :entities [{:name :fields :belongs-to? :min? :workflow?}] :http :web :public-api}, plus
:entity and :fields for the first entity, 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-code-fence text)(strip-code-fence text langs)The code in text without fences or the prose around them. Text with no
fence is returned trimmed.
With langs, the blocks tagged with one of them are joined, so an answer
split over two clojure blocks stays whole and abash example ahead of
the ```json one is skipped. Failing that, untagged blocks, then the first.
Every parser here reads through this. Each used to strip its own fence —
json in one,clojure in another — and an ```edn answer from the
admin-entity generator matched neither (BOU-493).
The code in `text` without fences or the prose around them. Text with no fence is returned trimmed. With `langs`, the blocks tagged with one of them are joined, so an answer split over two ```clojure blocks stays whole and a ```bash example ahead of the ```json one is skipped. Failing that, untagged blocks, then the first. Every parser here reads through this. Each used to strip its own fence — ```json in one, ```clojure in another — and an ```edn answer from the admin-entity generator matched neither (BOU-493).
(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.
(tag-tests test-source test-type)Give every deftest in test-source exactly one pyramid tag, from what it
touches: ^:integration (^:contract for an adapter's test-type) when it
does real I/O, directly or through a helper or fixture in the file, and
^:unit otherwise. Kaocha selects suites on the tag. The model's own tag is
replaced: rc-4 tagged reify-only tests ^:integration because the source sat
under shell/ (BOU-572). Other metadata is kept.
Returns the source, or nil for nil input.
Give every deftest in `test-source` exactly one pyramid tag, from what it touches: ^:integration (^:contract for an adapter's `test-type`) when it does real I/O, directly or through a helper or fixture in the file, and ^:unit otherwise. Kaocha selects suites on the tag. The model's own tag is replaced: rc-4 tagged reify-only tests ^:integration because the source sat under shell/ (BOU-572). Other metadata is kept. Returns the source, 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 |