-
Notifications
You must be signed in to change notification settings - Fork 842
Switch checks to @check pragma, improve UI
#11374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@checks pragma
b3ad069 to
46c52e8
Compare
@check pragma@check pragma, improve UI
ea1c77d to
c8e97a5
Compare
|
@vito What are "checks" in this instance? |
Oh, that is good. 🤤 @vito I assume we'll want a |
|
@charjr Yeah, that would be great!
No prerequisites, the pragma is pretty simple - it just chains a |
189c61d to
939f368
Compare
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
not actually using this Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
* don't force-reveal errors; this pretty annoying to deal with, better to focus on error origin tracking * handle Reveal at TraceRow creation time, not TraceTree, so we can use the TraceTree data to render accurate context for errors * TODO: will break when something inside an internal span errors Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Encapsulate also hides its children by default, which we don't really want. Alternatively we could just get rid of that behavior since you can already just toggle things open now. Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
fixes awkward dimming of indent guides Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
Signed-off-by: Alex Suraci <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noice
| // +check | ||
| ReleaseDryRun(context.Context) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this work/do anything on inline interfaces like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, this is just a multi-cursor edit "hallucination"
.dagger/test.go
Outdated
| // +optional | ||
| testVerbose bool, | ||
| ) (MyCheckStatus, error) { | ||
| // +check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this in the right place? same comment for line 165
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noticed this during self-review, will push soon. (We also don't actually want // +check for test specific since that's the one that takes a filter arg.)
| ```bash | ||
| dagger develop --sdk=python | ||
| # or | ||
| make sdk-generate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems hallucinated
| ```bash | ||
| # Run unit tests | ||
| cd sdk/python | ||
| pytest tests/ | ||
|
|
||
| # Test a sample module | ||
| cd /tmp | ||
| dagger init --sdk=python my-test | ||
| # Edit dagger.json module file with @function(check=True) | ||
| dagger functions # Should show the check function | ||
| dagger call lint # Should execute successfully |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't seem correct (running pytest directly rather than with dagger and editing dagger.json to add a lint func)
| // Verify that the CLI builds without actually publishing anything | ||
| func (cli *DaggerCli) ReleaseDryRun(ctx context.Context) (CheckStatus, error) { | ||
| return CheckCompleted, parallel.New(). | ||
| func (cli *DaggerCli) ReleaseDryRun(ctx context.Context) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want //+check here?
| return checkErr | ||
|
|
||
| if obj, ok := dagql.UnwrapAs[dagql.AnyObjectResult](status); ok { | ||
| // If the check returns a syncable type, sync it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I guess if a user puts a sync method on their own custom objects, we'll call it too? Is that intentional? Just checking because it gives special meaning to any method named sync, which I haven't seen before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - we already do this for dagger call (and maybe shell):
dagger/cmd/dagger/functions.go
Lines 602 to 631 in 8984278
| if fn.Name == "sync" && len(fn.SupportedArgs()) == 0 { | |
| hasSync = true | |
| } | |
| if fn.Name == "export" { | |
| for _, a := range fn.SupportedArgs() { | |
| if a.Name == "path" { | |
| hasExport = true | |
| } | |
| if a.Name == "allowParentDirPath" { | |
| hasExportAllowParentDirPath = true | |
| } | |
| } | |
| } | |
| } | |
| // Convenience for sub-selecting `export` when `--output` is used | |
| // on a core type that supports it. | |
| // TODO: Replace with interface when possible. | |
| if outputPath != "" && hasExport { | |
| q = q.Select("export").Arg("path", outputPath) | |
| if hasExportAllowParentDirPath { | |
| q = q.Arg("allowParentDirPath", true) | |
| } | |
| return q | |
| } | |
| // TODO: Replace with interface when possible. | |
| if hasSync { | |
| return q.SelectWithAlias("id", "sync") | |
| } |
I think at least defensible to just claim the sync name to always mean the same thing tbh, but it also hasn't come up in the wild yet to my knowledge.
Signed-off-by: Alex Suraci <[email protected]>
web UI still uses it, need to retire it there too Signed-off-by: Alex Suraci <[email protected]>
* switch to +check pragma, done for go sdk Signed-off-by: Alex Suraci <[email protected]> * add pragma contributing docs Signed-off-by: Alex Suraci <[email protected]> * typescript: add check decorator Signed-off-by: Alex Suraci <[email protected]> * add initial python decorator guide Signed-off-by: Alex Suraci <[email protected]> * python: add check decorator Signed-off-by: Alex Suraci <[email protected]> * remove CheckStatus, regenerate Signed-off-by: Alex Suraci <[email protected]> * switch all usage to +check pragma Signed-off-by: Alex Suraci <[email protected]> * remove Function.withCheck arg Signed-off-by: Alex Suraci <[email protected]> * initial checks TUI re-integration * go back to the regular frontend, we'll adjust from there to get to whatever we want * to clean up the UI, zoom in on a `checks` span, same as shell/prompt-mode does * add support for rolling up logs, re-use 'encapsulate' attribute to mark log roll-up boundaries (TODO: reuse this for 'reveal' boundaries too) * exit nonzero if any checks failed TODO: * we probably just need a bool attr indicating whether a span is a check; sending check name + passed feels redundant w/ span data Signed-off-by: Alex Suraci <[email protected]> * tui: replace half-dot with spinner we already moved away with empty => half => full circle, and this looks jazzier anyhow Signed-off-by: Alex Suraci <[email protected]> * spinner: clockwise Signed-off-by: Alex Suraci <[email protected]> * tui: don't roll-up verbose/global logs Signed-off-by: Alex Suraci <[email protected]> * tui: fancier spinners press ctrl+space to sync up the bpm and enter rave mode Signed-off-by: Alex Suraci <[email protected]> * checks: better exit output Signed-off-by: Alex Suraci <[email protected]> * remove previous checks frontend Signed-off-by: Alex Suraci <[email protected]> * core(checks): simplify spans, consistent order Signed-off-by: Alex Suraci <[email protected]> * core(checks): prevent infinite mutual recursion Signed-off-by: Alex Suraci <[email protected]> * tui: focus on error origins when showing error logs Signed-off-by: Alex Suraci <[email protected]> * tui: show rolled-up logs at top level also start generalizing roll-up; looking to use it to "roll up" spans too by showing dots or something on the span's header line Signed-off-by: Alex Suraci <[email protected]> * tui: offload unknown keys to other models fixes rave mode Signed-off-by: Alex Suraci <[email protected]> * tui: print special title for withExec Signed-off-by: Alex Suraci <[email protected]> * tui: roll-up spans show progress bars Signed-off-by: Alex Suraci <[email protected]> * tui/dagui: collect roll-up stats on write ...rather than doing a ton of work at render time. also now we can share this with the web UI. Signed-off-by: Alex Suraci <[email protected]> * core: remove noisy 'select: ' error prefix Signed-off-by: Alex Suraci <[email protected]> * cheaper roll-up calculating Signed-off-by: Alex Suraci <[email protected]> * tui: simplify; dont need 'recently completed' Signed-off-by: Alex Suraci <[email protected]> * tui: roll-up more statuses Signed-off-by: Alex Suraci <[email protected]> * dagui: reduce allocs in span status methods Signed-off-by: Alex Suraci <[email protected]> * tui: scale dots down as they hit screen limit Signed-off-by: Alex Suraci <[email protected]> * tui: show roll-up dots when expanded, too Signed-off-by: Alex Suraci <[email protected]> * tui: let context initialization roll up also, split into two attributes, so you can roll up spans without logs or vice versa plus a minor optimization Signed-off-by: Alex Suraci <[email protected]> * tui: prevent multiline exec headers Signed-off-by: Alex Suraci <[email protected]> * tui: only reveal origin logs, not sub-errors Not 100% on this, but at least in the `go test` + `testctx` case, the `go test` span will be the root cause, and it already has output that summarizes each test, so while it's cool that we can also show fine-grained logs for each test beneath, in that scenario it's redundant. This DOES mean `go test` users should maybe stop passing `-v`, but I was already motivated to do that from the roll-up UX. Signed-off-by: Alex Suraci <[email protected]> * lints Signed-off-by: Alex Suraci <[email protected]> * tidy Signed-off-by: Alex Suraci <[email protected]> * ts lint Signed-off-by: Alex Suraci <[email protected]> * go lints Signed-off-by: Alex Suraci <[email protected]> * fix releaser Signed-off-by: Alex Suraci <[email protected]> * sdk(dotnet): more accurate schema representation Python SDK wants omitempty. Dotnet SDK doesn't. Python SDK wins. Signed-off-by: Alex Suraci <[email protected]> * update MultiSameTrace for new fancy exec name Signed-off-by: Alex Suraci <[email protected]> * tui: add 'OK', make TestChecks failure legit Signed-off-by: Alex Suraci <[email protected]> * tui: render dots ahead of status Signed-off-by: Alex Suraci <[email protected]> * checks: install toolchain modules, too this is either a blindingly obvious fix, or skirting around the issue of how toolchains are meant to be expressed at the schema level, depending on your perspective, and depending on the accuracy of my understanding of how toolchains work at the moment. but, in any case, it works, and it's a throw-away DagQL server anyway, so the fallout if I'm wrong is pretty narrow Signed-off-by: Alex Suraci <[email protected]> * checks: auto-sync syncable objects along the way, I simplified away from the span-hiding trick, but more than happy to go back, just seems not as necessary with the new roll-up mechanism Signed-off-by: Alex Suraci <[email protected]> * tui: split toggle/selection symbol out from status Solves two issues: 1. Sometimes the fact that the selection is indicated by inverting the foreground/background colors is confusing. 1. We lose the status indicator when a span has children or logs, which is a bummer. Signed-off-by: Alex Suraci <[email protected]> * tui: hide spans for _-prefixed calls Signed-off-by: Alex Suraci <[email protected]> * experimental: pretty-print many core APIs Signed-off-by: Alex Suraci <[email protected]> * .dagger: telemetry --update uses Changeset Signed-off-by: Alex Suraci <[email protected]> * tui: move 'expand completed' to max verbosity having this so low makes a few other of the verbosities kind of useless Signed-off-by: Alex Suraci <[email protected]> * fix MultiSameTrace assertion Signed-off-by: Alex Suraci <[email protected]> * tui: back to full bar for lines+dots now that it's not connecting to a status symbol, we can make the symbols touch again Signed-off-by: Alex Suraci <[email protected]> * tui: selected chevron matches status color easier to spot, and matches the rest of the highlighted bar when there are logs Signed-off-by: Alex Suraci <[email protected]> * checks -l: tidy up spans a bit Signed-off-by: Alex Suraci <[email protected]> * core: hide runtime loading + plumbing Signed-off-by: Alex Suraci <[email protected]> * fixup: remove doug toolchain not actually using this Signed-off-by: Alex Suraci <[email protected]> * core(checks): hide logs leading up to check Signed-off-by: Alex Suraci <[email protected]> * dagui: clean up revealing / auto-revealing * don't force-reveal errors; this pretty annoying to deal with, better to focus on error origin tracking * handle Reveal at TraceRow creation time, not TraceTree, so we can use the TraceTree data to render accurate context for errors * TODO: will break when something inside an internal span errors Signed-off-by: Alex Suraci <[email protected]> * rave: use moving average Signed-off-by: Alex Suraci <[email protected]> * modules: avoid giant error strings Signed-off-by: Alex Suraci <[email protected]> * parallel: stop using nop tracer provider Signed-off-by: Alex Suraci <[email protected]> * tui: show error origin logs in-tree w/ context Signed-off-by: Alex Suraci <[email protected]> * fix attr naming Signed-off-by: Alex Suraci <[email protected]> * add explicit Boundary attribute Encapsulate also hides its children by default, which we don't really want. Alternatively we could just get rid of that behavior since you can already just toggle things open now. Signed-off-by: Alex Suraci <[email protected]> * fix unnecessary truncation Signed-off-by: Alex Suraci <[email protected]> * tui: handle errors caused beneath internal spans Signed-off-by: Alex Suraci <[email protected]> * tui: clean up top-level err print, dedupe errors Signed-off-by: Alex Suraci <[email protected]> * update telemetry gold Signed-off-by: Alex Suraci <[email protected]> * regen Signed-off-by: Alex Suraci <[email protected]> * tui: retain color for args in summarized calls fixes awkward dimming of indent guides Signed-off-by: Alex Suraci <[email protected]> * dagql: update gold Signed-off-by: Alex Suraci <[email protected]> * update TestLLM gold Signed-off-by: Alex Suraci <[email protected]> * fix a couple rogue +checks Signed-off-by: Alex Suraci <[email protected]> * add +check for ReleaseDryRun Signed-off-by: Alex Suraci <[email protected]> * retire DB.CollectErrors web UI still uses it, need to retire it there too Signed-off-by: Alex Suraci <[email protected]> --------- Signed-off-by: Alex Suraci <[email protected]> Signed-off-by: Alex Suraci <[email protected]>
* switch to +check pragma, done for go sdk Signed-off-by: Alex Suraci <[email protected]> * add pragma contributing docs Signed-off-by: Alex Suraci <[email protected]> * typescript: add check decorator Signed-off-by: Alex Suraci <[email protected]> * add initial python decorator guide Signed-off-by: Alex Suraci <[email protected]> * python: add check decorator Signed-off-by: Alex Suraci <[email protected]> * remove CheckStatus, regenerate Signed-off-by: Alex Suraci <[email protected]> * switch all usage to +check pragma Signed-off-by: Alex Suraci <[email protected]> * remove Function.withCheck arg Signed-off-by: Alex Suraci <[email protected]> * initial checks TUI re-integration * go back to the regular frontend, we'll adjust from there to get to whatever we want * to clean up the UI, zoom in on a `checks` span, same as shell/prompt-mode does * add support for rolling up logs, re-use 'encapsulate' attribute to mark log roll-up boundaries (TODO: reuse this for 'reveal' boundaries too) * exit nonzero if any checks failed TODO: * we probably just need a bool attr indicating whether a span is a check; sending check name + passed feels redundant w/ span data Signed-off-by: Alex Suraci <[email protected]> * tui: replace half-dot with spinner we already moved away with empty => half => full circle, and this looks jazzier anyhow Signed-off-by: Alex Suraci <[email protected]> * spinner: clockwise Signed-off-by: Alex Suraci <[email protected]> * tui: don't roll-up verbose/global logs Signed-off-by: Alex Suraci <[email protected]> * tui: fancier spinners press ctrl+space to sync up the bpm and enter rave mode Signed-off-by: Alex Suraci <[email protected]> * checks: better exit output Signed-off-by: Alex Suraci <[email protected]> * remove previous checks frontend Signed-off-by: Alex Suraci <[email protected]> * core(checks): simplify spans, consistent order Signed-off-by: Alex Suraci <[email protected]> * core(checks): prevent infinite mutual recursion Signed-off-by: Alex Suraci <[email protected]> * tui: focus on error origins when showing error logs Signed-off-by: Alex Suraci <[email protected]> * tui: show rolled-up logs at top level also start generalizing roll-up; looking to use it to "roll up" spans too by showing dots or something on the span's header line Signed-off-by: Alex Suraci <[email protected]> * tui: offload unknown keys to other models fixes rave mode Signed-off-by: Alex Suraci <[email protected]> * tui: print special title for withExec Signed-off-by: Alex Suraci <[email protected]> * tui: roll-up spans show progress bars Signed-off-by: Alex Suraci <[email protected]> * tui/dagui: collect roll-up stats on write ...rather than doing a ton of work at render time. also now we can share this with the web UI. Signed-off-by: Alex Suraci <[email protected]> * core: remove noisy 'select: ' error prefix Signed-off-by: Alex Suraci <[email protected]> * cheaper roll-up calculating Signed-off-by: Alex Suraci <[email protected]> * tui: simplify; dont need 'recently completed' Signed-off-by: Alex Suraci <[email protected]> * tui: roll-up more statuses Signed-off-by: Alex Suraci <[email protected]> * dagui: reduce allocs in span status methods Signed-off-by: Alex Suraci <[email protected]> * tui: scale dots down as they hit screen limit Signed-off-by: Alex Suraci <[email protected]> * tui: show roll-up dots when expanded, too Signed-off-by: Alex Suraci <[email protected]> * tui: let context initialization roll up also, split into two attributes, so you can roll up spans without logs or vice versa plus a minor optimization Signed-off-by: Alex Suraci <[email protected]> * tui: prevent multiline exec headers Signed-off-by: Alex Suraci <[email protected]> * tui: only reveal origin logs, not sub-errors Not 100% on this, but at least in the `go test` + `testctx` case, the `go test` span will be the root cause, and it already has output that summarizes each test, so while it's cool that we can also show fine-grained logs for each test beneath, in that scenario it's redundant. This DOES mean `go test` users should maybe stop passing `-v`, but I was already motivated to do that from the roll-up UX. Signed-off-by: Alex Suraci <[email protected]> * lints Signed-off-by: Alex Suraci <[email protected]> * tidy Signed-off-by: Alex Suraci <[email protected]> * ts lint Signed-off-by: Alex Suraci <[email protected]> * go lints Signed-off-by: Alex Suraci <[email protected]> * fix releaser Signed-off-by: Alex Suraci <[email protected]> * sdk(dotnet): more accurate schema representation Python SDK wants omitempty. Dotnet SDK doesn't. Python SDK wins. Signed-off-by: Alex Suraci <[email protected]> * update MultiSameTrace for new fancy exec name Signed-off-by: Alex Suraci <[email protected]> * tui: add 'OK', make TestChecks failure legit Signed-off-by: Alex Suraci <[email protected]> * tui: render dots ahead of status Signed-off-by: Alex Suraci <[email protected]> * checks: install toolchain modules, too this is either a blindingly obvious fix, or skirting around the issue of how toolchains are meant to be expressed at the schema level, depending on your perspective, and depending on the accuracy of my understanding of how toolchains work at the moment. but, in any case, it works, and it's a throw-away DagQL server anyway, so the fallout if I'm wrong is pretty narrow Signed-off-by: Alex Suraci <[email protected]> * checks: auto-sync syncable objects along the way, I simplified away from the span-hiding trick, but more than happy to go back, just seems not as necessary with the new roll-up mechanism Signed-off-by: Alex Suraci <[email protected]> * tui: split toggle/selection symbol out from status Solves two issues: 1. Sometimes the fact that the selection is indicated by inverting the foreground/background colors is confusing. 1. We lose the status indicator when a span has children or logs, which is a bummer. Signed-off-by: Alex Suraci <[email protected]> * tui: hide spans for _-prefixed calls Signed-off-by: Alex Suraci <[email protected]> * experimental: pretty-print many core APIs Signed-off-by: Alex Suraci <[email protected]> * .dagger: telemetry --update uses Changeset Signed-off-by: Alex Suraci <[email protected]> * tui: move 'expand completed' to max verbosity having this so low makes a few other of the verbosities kind of useless Signed-off-by: Alex Suraci <[email protected]> * fix MultiSameTrace assertion Signed-off-by: Alex Suraci <[email protected]> * tui: back to full bar for lines+dots now that it's not connecting to a status symbol, we can make the symbols touch again Signed-off-by: Alex Suraci <[email protected]> * tui: selected chevron matches status color easier to spot, and matches the rest of the highlighted bar when there are logs Signed-off-by: Alex Suraci <[email protected]> * checks -l: tidy up spans a bit Signed-off-by: Alex Suraci <[email protected]> * core: hide runtime loading + plumbing Signed-off-by: Alex Suraci <[email protected]> * fixup: remove doug toolchain not actually using this Signed-off-by: Alex Suraci <[email protected]> * core(checks): hide logs leading up to check Signed-off-by: Alex Suraci <[email protected]> * dagui: clean up revealing / auto-revealing * don't force-reveal errors; this pretty annoying to deal with, better to focus on error origin tracking * handle Reveal at TraceRow creation time, not TraceTree, so we can use the TraceTree data to render accurate context for errors * TODO: will break when something inside an internal span errors Signed-off-by: Alex Suraci <[email protected]> * rave: use moving average Signed-off-by: Alex Suraci <[email protected]> * modules: avoid giant error strings Signed-off-by: Alex Suraci <[email protected]> * parallel: stop using nop tracer provider Signed-off-by: Alex Suraci <[email protected]> * tui: show error origin logs in-tree w/ context Signed-off-by: Alex Suraci <[email protected]> * fix attr naming Signed-off-by: Alex Suraci <[email protected]> * add explicit Boundary attribute Encapsulate also hides its children by default, which we don't really want. Alternatively we could just get rid of that behavior since you can already just toggle things open now. Signed-off-by: Alex Suraci <[email protected]> * fix unnecessary truncation Signed-off-by: Alex Suraci <[email protected]> * tui: handle errors caused beneath internal spans Signed-off-by: Alex Suraci <[email protected]> * tui: clean up top-level err print, dedupe errors Signed-off-by: Alex Suraci <[email protected]> * update telemetry gold Signed-off-by: Alex Suraci <[email protected]> * regen Signed-off-by: Alex Suraci <[email protected]> * tui: retain color for args in summarized calls fixes awkward dimming of indent guides Signed-off-by: Alex Suraci <[email protected]> * dagql: update gold Signed-off-by: Alex Suraci <[email protected]> * update TestLLM gold Signed-off-by: Alex Suraci <[email protected]> * fix a couple rogue +checks Signed-off-by: Alex Suraci <[email protected]> * add +check for ReleaseDryRun Signed-off-by: Alex Suraci <[email protected]> * retire DB.CollectErrors web UI still uses it, need to retire it there too Signed-off-by: Alex Suraci <[email protected]> --------- Signed-off-by: Alex Suraci <[email protected]> Signed-off-by: Alex Suraci <[email protected]>
This PR switches checks to use the
@checkpragma/decorator, improving the developer experience.Also adds a new UI:
Usage Examples
Go
Add a
+checkpragma comment above functions that should be checks:Python
Use the
@checkdecorator on functions:TypeScript
Use the
@check()decorator on methods:Running Checks
Once you've marked functions as checks, run them with:
LLM
.contributingdocsAdding the pragma was heavily LLM assisted. Adding pragmas is in the category of 'known but still annoying and tedious amount of work'. I started by having it research previous commits, and then write a doc, and then follow the doc for this PR. The docs aren't perfect, since some of them focused on arg directives whereas this is a field directive, but they still help you get 95% there; in my case the LLM figured out the right way, and I just needed to fix a couple things in post.