refactor(draft): the drafting core is a package, not a block of package main - #133
Merged
Conversation
…ge main The tests in this repository are moving off committed blueprint artefacts and onto blueprints derived at test time, and internal/generate cannot reach into package main to derive one. The only two ways out were writing a second deriver for the tests or moving the real one somewhere both can call. A second deriver is worse than no test at all: it drifts from the pipeline silently, and the day it does, the tests are asserting against output the generator never produces while still reporting green. So the drafting core moves to internal/draft, and the command keeps what a command owns. The split is along the line between deciding what a blueprint contains and presenting a run to a person. Inference, SDK pruning and the scenario worksheet scaffold decide content and move; candidate selection moves with them because choosing which families to draft is part of drafting, and a caller deriving a single family in a test needs it as much as the CLI does. Flag parsing, snapshot resolution and the candidate table stay behind, as does the notes report -- but it is passed in as a hook rather than left where the loop could no longer reach it, because where that report appears relative to the progress lines is itself the reporting order, and moving it either side of the summary would have been a visible change to what a run says. The seven positional arguments become an Options struct. That is not tidying: three of the seven were strings whose meaning came only from their position, and the tests this refactor exists to enable will call it directly and often. The progress lines are preserved verbatim, including their spacing, because they are the run's only account of the families a document offered and this did not take. The sentinel is the one deliberate seam: the package states the drafting fact it knows -- nothing could be inferred -- and the command wraps it in the "nothing to do" vocabulary every other subcommand shares, so both the exit code and the message a user reads are unchanged. Redrafting the Jamf Pro blueprints leaves the committed set byte-identical. Four further drafting configurations were captured before the move and again after -- both kiota pilots, the resty dialect with and without pruning, and a tag-filtered run against the older snapshot -- and all 263 written files, every progress line, every exit code and the notes report match exactly. Two of those configurations are diagnostic rather than reproductions: blueprints/thousandeyes and blueprints/thousandeyes-kiota carry merged probe facts and curated renames, so no bare draft run has ever reproduced them, and only the Jamf Pro set is a pure drafting artefact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pure refactor. The blueprint-drafting core moves out of
package mainintointernal/draft, so something other than the CLI can call it.Why
We are migrating this repo's tests off committed blueprint artefacts and onto blueprints derived at test time.
internal/generate's tests cannot callpackage main, and writing a second deriver for tests would be worse than useless — it would drift from the real pipeline, and the tests would then validate output the pipeline never produces.What moved
inferAll(nowdraft.Run),pruneAgainstSDK,writeScenarioDraft,FilterCandidates,Matches. Flag parsing, path resolution and presentation stay incmd/tfpfgen.Three judgement calls worth a look:
printNotesstayed, as a hook. It cannot simply be hoisted to the caller: it fires between the writing loop and the summary line, so lifting it would print notes after"N blueprint(s) written"— a visible reordering.Options.Notes func([]openapi.Caveat)keeps the formatting in the CLI and the order exact.errNothingToDois the seam.internal/draftreturnsErrNothingInferred; the command wraps it. Message and exit code verified identical:tfpfgen: nothing to do: nothing could be inferred.return errsites keep//nolint:wrapcheck..golangci.ymlonly lintsinternal/*.go, so moving the code brought previously-unlinted lines into scope. Wrapping them would change error text, which a refactor must not. The repo already uses that convention in 66 places.The verification — and a correction to what I asked for
I specified "redraft all three committed blueprint sets, require zero diff". That premise was wrong for two of the three.
Only
blueprints/jamfprois a pureblueprint draftartefact.blueprints/thousandeyesandblueprints/thousandeyes-kiotaare draft plusblueprint mergefolding probe facts, plus curation. All 23 ThousandEyes blueprints carrybehaviourblocks; they contain curation prose ("Required-by-API by curation: the sweeper's field, unprobeable by omission"),docRefUrlvalues inference never emits, and manual key renames a fresh draft still spells the old way.docs/generated-boundary.md:14says so explicitly. There is no invocation that reproduces them, and there never was.So a stronger gate was substituted: capture complete output of five drafting configurations before and after, requiring byte-identity of files, stderr, stdout and exit codes.
-scenario-drafts(the only run exercisingwriteScenarioDraft)-tag tags,alertsagainst the older 7.0.97 snapshot (the selection path)Result: all five trees, all five stderr logs (507/359/258/445/12 lines), all five exit codes, all stdout — identical.
Independently re-confirmed on this branch: redrafting jamfpro gives
120 blueprint(s) writtenandgit status -- blueprints/reports 0 changed files.A pre-existing nondeterminism, found by the harness
Running the baseline twice gives 263 identical written files but differing
prunedlog lines:sdkbind.Prunereports whichever failing operation it reaches first, so the same removal is explained asbinding.read.chainon one run andbinding.update.chainon the next. The set, order and count are stable and it never reaches a written blueprint — so it is cosmetic, not a drift risk. Not introduced here, but worth knowing it exists.Gates
go build·go vet·go test ./...(18 packages) ·gofumpt -l .·golangci-lint run ./internal/...0 issues ·TestUnit_CLI_DocsMatchTheBinaryPASS, so the CLI surface is unchanged.Only
TestUnit_CLI_Matcheschanged, to calldraft.Matches.🤖 Generated with Claude Code