Repository navigation
Releases
v4.2.0
Blueprint 4.2 reports executable gates, structural conformance, source reach, and semantic review as independent evidence. Passing machine checks does not establish correct ownership, presentation placement, or complete adoption.
Ordinary module roots now have one canonical index entry; direct root siblings are private. Layer-only modules and reserved app do not need an empty root barrel. Upgrading from an earlier release activates review-module-root-unit: review public exports, external imports, and presentation placement, then explicitly complete the operation. Blueprint does not choose the application's public API or move presentation code automatically. Existing module-first projects may need source and test changes to satisfy this contract.
Executable mustNot declarations, explicit external consumers, and direct module-dependency reconciliation add opt-in controls without turning review-only strings into machine gates. React and Vue presentation guidance follows responsibility, including feature-private and single-consumer presenters. Source discovery and assessment retain explicit evidence limits, and large piped reports and distinct Doctor prohibition probes preserve their complete results.
Minor Changes
-
78f4c1b: Report adoption as independent executable gates, structural conformance, source reach, and semantic review. Doctor and Inspect expose the shared assessment without treating green checks as proof of ownership or topology correctness. Preserve baseline debt, non-blocking violations, unavailable evidence, and bounded workflow scopes.
Add Doctor
gateVerdictand deprecateverdictas its gate-only compatibility alias for 4.2; preserve existing exit semantics. Update generated Agent guidance, CLI help, and English and Traditional Chinese documentation to distinguish verified evidence from remaining review. -
bdadd20: Add opt-in executable
mustNotconstraints for layer capabilities, package references, globals, and syntactic calls. ESLint enforces the declared checks while Rules, Inspect, Doctor, and generated guidance distinguish detector availability, file reach, wiring, and warning observations. Existing prose entries and presets remain review-only and introduce no new source failures. -
1f4db85: Add opt-in
architecture.consumerFilesfor explicit external consumers in module-first projects. Declared files outsidesourceRootmay consume governed source without an uncovered-source finding; governed source depending back on them remains an error. Omitted and empty declarations preserve existing behavior. No preset infers consumers from tool names. -
e1e05ac: Enforce one
indexentry for ordinary module roots with governed source. Direct siblings are private and Inspect reports proven unreachable root members. Layer-only modules and reservedappneed no root barrel. Existing adopters must manually review entries, exports, imports, and presentation placement; upgrade never chooses their public API. Reverse topology mapping retains original per-member LF identities and custom entries, and stops on unproved provenance. -
137ca74: Expose source-observed direct module dependencies, deterministic evidence, reconciliation, and importer-scoped uncertainty in deps and inspect. Add the opt-in Inspect moduleDependencySync gate while preserving transitive import permission. Existing configurations and presets gain observability without new synchronization failures until owners enable the gate; no config migration or automatic dependsOn edits are required.
-
3c2c26c: Bound module-first outside-source discovery to application-local Git visibility, explicit consumer search roots, and governed dependencies. Source-only commands no longer scan outside their source root. Inspect, Doctor, and transformation verification share scope evidence and refuse complete conformance when required import analysis is incomplete, even under a baseline. Non-Git applications retain an explicit complete filesystem fallback.
Patch Changes
- 1fe33e4: Keep large CLI reports complete when piped to another program, and preserve each executable-prohibition probe in Doctor assessments without conflicting evidence identities.
- 445c687: Classify React and Vue components by presentation responsibility, including feature-private and single-consumer presenters. Clarify local UI state and shared/public promotion in module-first authoring, both topology transformations, and the architecture guides.
v4.1.0
Blueprint 4.1 separates proven-empty greenfield adoption from brownfield adoption. A proven-empty React or Vue application, Next.js aside, can enter layer-first or module-first directly from the framework preset's complete canonical governance, and module-first opens with modules: [] instead of an invented domain model. Existing projects keep the source-sensitive adoption rules: measured brownfield source can enter authoring, while the existing layer-first preset paths remain available where their current conditions apply.
Adoption is no longer one-way. blueprint upgrade and blueprint remove are first-class lifecycle operations backed by explicit evidence: upgrade resolves the repository-wide lifecycle plan before it starts the upgrade, while remove computes the complete de-adoption plan before applying it. Both fail closed when required facts cannot be proven instead of guessing.
Deterministic migration still does everything Blueprint can prove, and the semantic judgment that remains is an explicit, recorded step for a person or coding Agent. An upgrade completes only when blueprint inspect --baseline and blueprint doctor pass in every adopted application, never because lifecycle state says it should.
Minor Changes
-
374582e: Distinguish greenfield from brownfield adoption. A proven-empty React or Vue application now starts from the framework preset's complete canonical governance under either topology:
init --topology module-firstscaffoldsreactPreset/vuePresetwithmodules: []instead of an authoring playbook, and invents no domain module; a Next.js application is not part of this runway and still receives the authoring playbook under module-first. Existing source keeps the conservative authoring path that translates house thresholds and ratchets pre-existing debt; as before, layer-first source below the 10-file brownfield threshold, or initialized with--preset, scaffolds the preset directly.architecture.modules: []is now a valid module-first runway. Topology follows the presence ofmodulesacross validation, resolution, lint emission,inspect,deps,doctor,impact, handbooks, Agent contracts, and guarded transformation.reactPreset()andvuePreset()accept amodulesoption that projects the same canonical governance onto module-first, with each module root at the container position and route composition in the reservedappmodule.Generated module-first handbooks and Agent contracts carry a module growth protocol for future requirements: a temporary layer-first projection for reasoning only, container/use-case seeds, merge and split decisions, then
Module → Layer → UnitwithdependsOnderived from real imports. The documentation explains greenfield and brownfield posture and includes a measurement-first prompt for tightening a brownfield config.The
@kekkai/blueprint/operational-contractrenderers stay source-compatible.renderFreshScaffoldNotealso accepts{ files, threshold, topology },renderAuthoringFlowBanneralso acceptsforcedExit, andrenderVerdictaccepts an optionalnext. The positionalrenderFreshScaffoldNote(files, threshold)form andforcedBelowThresholdstill work and are deprecated. -
51ade58: Add a first-class post-adoption lifecycle:
blueprint upgradeandblueprint remove.npx @kekkai/blueprint@latest upgradeuses the running package as the only target authority. It reads the repository's lifecycle checkpoint from the new.blueprint-lifecycle.json, or proves an exact source from the installed package on a repository adopted before lifecycle state existed, and resolves the structured upgrade operations of every release across the whole source → target interval before anything runs: canceled and superseded operations are removed, the rest are ordered by their requirements. An overlapping legacy config shape alone never proves an exact release. It then records the pending upgrade, moves@kekkai/blueprintto the target through the detected package manager, continues with that installed copy, runs deterministic migrations by reconciling every adopted application throughblueprint init, writes one resolvedblueprint-upgrade.mdplaybook when semantic work remains, and records the new lifecycle only afterblueprint inspect --baselineandblueprint doctorpass in every adopted application.--dry-runshows the complete plan without mutation,--complete <operation-id>records verified semantic work, and a pending upgrade resumes without repeating completed operations.Starting an upgrade requires a Git worktree without uncommitted changes, and no upgrade runs while an authoring or topology-transformation workflow is unfinished. Downgrades and sources before 3.2.0 are refused, and so is an application that resolves a newer package than the running release, or a mix of package versions. When the record already matches the target but an application resolves an older package or none,
upgradereinstalls the target instead of reporting it current: the lifecycle reads current only when the record and every install agree. Lifecycle state that is unreadable or points outside the repository stops every command that needs it, and so does state missing where the installed release always writes it, once it entered Git history or when that history cannot be read; Blueprint never rebuilds that history, and an upgrade that finds the state changed underneath it leaves the checkpoint where it was. Missing state that no commit reachable from any ref ever contained is not treated as lost, because the history Git can still show does not prove a lifecycle existed. The repository is dated only from an exact installed pre-lifecycle package; if a newer installed package sits beside the overlapping legacy 3.x shape, Blueprint refuses to guess and directs the adopter to establish a known 3.2 checkpoint first. Commit.blueprint-lifecycle.jsonso a later loss has history to restore from. A package install that stops part-way across several applications resumes on the next run without repeating the installs that landed, while any install mix the recorded upgrade could not have left is refused.initrecords ownership as soon as it writes but establishes the lifecycle checkpoint only when adoption completes, so a failed run, an install deferred by--no-install, or an authoring handoff leaves it unestablished until theinitrun that finishes. Every lifecycle state write replaces the file atomically, and completed operations stay readable history after a later release retires them from its supported window, where a newer operation can still supersede them. Re-planning a pending upgrade for a newer release keeps the operations it already completed, so the next run resumes the new plan without repeating them.npx blueprint removeplans the complete de-adoption before changing anything, and--dry-runstops at that plan.initnow records which files Blueprint generated or created, which managed sections it owns, and the exact shared-file edits it made, so removal deletes only proven Blueprint artifacts, strips only managed sections, reverses only recorded edits that are still intact, deletes a Blueprint-owned file such as a migratedblueprint.config.mjsinstead of restoring it from its recorded edits, keeps alias wiring that application source still imports, and uninstalls@kekkai/blueprintlast, together with the ESLint packages Blueprint recorded installing that nothing else still uses. A package script that holds neither what Blueprint wrote nor what it replaced, a recorded edit that now appears more than once or records only removed text, broken managed-section markers, and remaining Blueprint wiring stop the removal before any change, each reported once however Git and Node spell the repository root; a recorded edit whose inserted text is gone, or a script back at its original value, counts as already reversed, and a mention ofblueprint.config.mjsor@kekkai/blueprintinside a tool config's comment is not wiring; a shared Agent document is deleted after losing its managed section only when the lifecycle records prove Blueprint created it, and otherwise survives, even empty, with a note to review it; a file Blueprint created and later edited is reversed edit-first and then deleted when it matches what Blueprint created; a shared package stays installed while sibling applications remain adopted; removing part of a repository is refused while an upgrade is pending; repositories adopted before lifecycle records exist lose only provable artifacts and get a report of what could not be proven.Generated Agent contracts, the packaged agent contract, CLI help, and the English and Traditional Chinese documentation now route "upgrade Blueprint" and "remove Blueprint" requests to these commands.
Patch Changes
-
8132be4:
blueprint doctornow proves the Vite alias thatinitwrites. The bundler-runtime and test-runner alias checks read'<alias>': fileURLToPath(new URL('<dir>', import.meta.url))andpath.resolve(__dirname, '<dir>')/resolve(__dirname, '<dir>'), resolving<dir>from the configuration file's own folder. When Vitest is installed and novitest.config.*exists, the test-runner check reads the Vite config that Vitest uses instead of reporting the alias unverified. A check that still cannot prove an alias names the file it read, or says that no configuration file was found, and lists the alias forms it can prove. -
45a5d55:
defineBlueprint()and config loading now enforce two requirements the documentation and types already stated:frameworkmust be'vue','react', or'auto', and every layer needs a non-emptydoes. A hand-written JavaScript config that omitted either, or setframeworkto another value, used to load; it now fails with an error naming the field to fix.blueprint init --helpno longer tells a multi-application workspace to pass `--sour...
v4.0.0
Blueprint 4.0 promotes architecture topology from an implicit folder convention to an explicit repository architecture contract. Layer-first and module-first are both supported models, and module-first preserves the same layer discipline inside ordinary modules while keeping the reserved app surface focused on router composition.
Adoption, supported legacy migration, and topology transformation now start from repository facts Blueprint can prove. When semantic ownership or a safe transformation cannot be inferred, Blueprint fails closed and hands the judgment to a person or coding Agent instead of guessing.
Resolved architecture facts, mechanical enforcement, human guidance, and Agent operational guidance now have distinct authorities. They share the same resolved architecture without claiming that every product or public-information surface is unconditionally compiled from one config.
Major Changes
-
04a40ea: Require
architecture.aliasfor cross-layer and cross-module imports, and apply the same architecture checks to statically resolvable dynamic imports. Inspect and deps now disclose runtime-dependent targets they cannot prove. -
9e7af73: Add optional module-first topology through
architecture.modules, repeating the declared layers beneath every module while preserving layer-first projects.Move unit layout and entry configuration directly onto each layer, rename the
flatlayout tofile, and retirearchitecture.module,layers[].module, andmodule.privatewith targeted migration errors.Update lint, inspect, dependency reports, generated docs, and agent contracts to use Module → Layer → Unit identities consistently.
Declare direct module dependencies with optional
architecture.modules[].dependsOn. Blueprint rejects invalid module graphs, resolves transitive reachability, and requires governed imports to pass both the module DAG and the existing inner layer flow. -
0f29981: Require an explicit topology for first adoption, inherit one topology across repository applications, and coordinate configured topology changes repository-wide. Source-tree survey evidence no longer classifies unmanaged projects, and presets are limited to layer-first adoption.
-
dd085ce: Add
init --topology layer-first|module-firstand require an explicit choice for first repository adoption. Existing configurations remain authoritative, module-first initialization enters authoring, and unsupported or unsafe topology paths abort before writing.Align dependency reports with the canonical architecture resolver so governed recursive
app/**routes are never also reported as outside the architecture. -
22c3987: Let
init --topology module-firstguide a configured layer-first React, Vue, or Next.js App Router application through a Git-preflight-guarded Agent transformation. The generated playbook carries neutral container/page dependency evidence, requires semantic ownership decisions and trackedgit mvoperations, derives the final module DAG after movement, and reviews architecture debt before baseline regeneration. Next.js Pages Router continues to abort before mutation. -
c4d7c74: Reserve an optional module-first
appmodule for framework-independent router composition. Nestedappsource now uses container semantics instead of repeating the shared layer contract.
Minor Changes
- 3c40eef: Centralize Blueprint's CLI, authoring, transformation, Agent, handbook, diagnostics, and field
guidance under one runtime-backed operational contract. Add checked-in artifact verification and
require field adoption to select an explicit topology. - 0fb37ff: Let
init --topology layer-firstguide a configured module-first React, Vue, or Next.js App Router application back to layer-first. The Git-guarded playbook reports complete structural destinations, collisions, module/import evidence, router constraints, and baseline review steps while leaving semantic placement, tracked moves, import rewrites, and the synchronized cutover to the Agent. - 78d2b9e: Resolve nested application, workspace toolchain, and containing Git repository context consistently, require the normal lint entrypoint to reach ESLint before doctor reports complete adoption, and share structural pattern normalization between emission and doctor verification.
- b962241: Let
blueprint surveyexpose repeated direct-child folder names across sibling folder instances in text and JSON, including the measured parent and participation counts, without classifying or recommending an architecture.
Patch Changes
-
53945b5: Isolate release-time consumer verification from npm publisher authentication so packed Yarn 1 checks run without inheriting the publishing npmrc.
-
76bbc3a: Refresh Blueprint's public documentation with its new cyan-to-blue brand system and replace documentation tables with mobile-readable lists.
-
076decc: Keep Codex-only authoring and topology transformations free of Claude-specific command files. The neutral authoring playbook now follows the selected agent or configured
emit.agentspolicy across fresh, single-application, and repository-wide flows. -
35963d9: Make
blueprint doctorverify the safely reachable project-local ESLint leg live before reporting adoption complete. -
dcbed49: Keep
architecture.testFilesbehavior and guidance aligned across lint, inspect, dependency reports, CLI help, generated Agent contracts, and authoring playbooks. -
1c1d53c: Make adoption evidence fail closed: preserve ancestor ESLint policies for nested applications, scope emitted rules to the application, reject nested folder-unit relative escapes and conflicting alias identities, and report lint wiring and alias consumers independently. Impact reports unsupported ESLint versions as
unavailableand import parse failures aspartial, instead of implying complete verification.Retain layer-first to module-first transformation origins in application-scoped Git authority independently of source, configuration, and decision-file edits. Completion requires reserved app routing, consumed container seeds, one-to-one member mappings with Git content identity, matching requested topology, and final architecture errors resolved without baseline suppression; ordinary hand-authored module-first adoption remains separate.
Repair packed public declarations and verify external npm and Yarn 1 consumers, including pinned internal Vue and TypeScript parsers with Node 18-compatible dependency trees. Normalize Git inventory line endings and filesystem aliases across platforms.
-
947647c: Cover Vue JSX and TSX files in architecture nets, escape leading-hash aliases in emitted ESLint import patterns, and deduplicate overlapping alias restrictions. Clarify coverage output when files sit outside declared architecture nets.
-
eb51c2d: Parse JSX and TSX syntax inside Vue SFC script blocks when analyzing statically resolvable dynamic imports.
v3.2.0
This release improves adoption of existing projects with root-level or nested application
sources and makes generated rules and guidance follow the configured source scope more
consistently. It also closes enforcement gaps for bare layer imports and literal $
characters, and prevents malformed brace patterns from hanging commands.
The minor API addition is optional stack facts for document emitters; existing calls remain
compatible. Architecture ownership and the application scope of a multi-app workspace remain
owner decisions. Upgrading can expose previously missed imports; review the upgrade notes
below and regenerate contracts to receive the corrected enforcement guidance.
Minor Changes
-
72b684b: Add the exported
StackFactstype and optional stack facts toemitHandbookand
emitAgentFiles. Existing calls remain compatible.blueprint initnow supplies the detected TypeScript capability, so generated handbooks and
agent contracts no longer claim thatexplicitAnyis enforced in JavaScript-only projects.
TypeScript projects keep their existing output.
Patch Changes
-
8479e54: Preserve
$sequences literally when generating layer globs and updating an existing
package.jsonlint script. Configurations such asprice$$tagnow govern the intended
folder, andblueprint initno longer risks corrupting valid scripts containing$.After upgrading, projects with
$in a layer name may surface architecture findings that
were previously missed. If an earlier scaffold run modified a lint script containing$,
check that script once because upgrading cannot repair an already-written manifest. -
c4908ad: Enforce architecture bans against a layer's bare entry import, such as
~app/pages, in
addition to imports below that entry. This closes gaps in dependency-flow, same-layer, and
selfOnlyre-export enforcement across every configured alias.Upgrading may reveal imports that
inspectalready considered invalid but ESLint previously
missed. Projects that manually merged Blueprint's generatedno-restricted-importsgroups
should refresh those copied groups and runblueprint doctor. -
bad63ea: Prevent commands from hanging when a configured glob or
.gitignorepattern contains an
unmatched{; it is now treated as a literal brace.inspect,rules,doctor, anddepsnow also identifytestFilesentries that match no
files, whiledoctorreports ineffectivelayerFilesIgnoreentries. These diagnostics are
informational and do not change exit codes. JSON output gains the corresponding conditional
testExemptiondetails. -
70b463a: Discover root-level TypeScript applications and aliases declared in referenced configs during
brownfield survey, preserve conventionalsrc/scopes, require an explicit application scope
for multi-app workspaces, resolve aliases that target individual architecture layers, and use a
nested application's own TypeScript and Vite configuration whenarchitecture.sourceRoot
scopes Blueprint below the repository root. Keep authoring output and CLI help consistent with
the required application-scope decision. -
717d2ac: Align
inspectwith the generated ESLint scope: files matched by
architecture.layerFilesIgnoreno longer produce lint-backed findings or count toward lint
coverage. Inspect-only checks continue to include them, and coverage output now identifies
deliberately ignored layer files. -
be37bfa: Add an informational
owns-not-installedfinding when a layer declares ownership of a
package that is not installed. It names the package and layer without changing exit codes or
baseline contents. -
e4d1def: Describe
blueprint inspectas scanning the configured source root, withsrc/as the
default, and exportDoctorVerdictso consumers can name the public doctor result type. -
89905a2: Honor
architecture.sourceRootconsistently in init scaffolding, generated agent guidance,
dependency targets, relative-import and typedef-only enforcement, and inspect messages.
Projects using a source directory other thansrcnow receive paths and rules for the
configured root. -
32be831: Include the configured
architecture.testFilespatterns in full generated agent contracts,
so agents know that test-support files may be exempt from normal placement rules. Compact
CLAUDE.mdandAGENTS.mdcontracts are unchanged. -
2096a9d: Make full generated agent contracts describe enforcement accurately: unavailable
testFilenameanddeepWatchgates are no longer called hard lint gates, whilecyclesis
identified as enforced byblueprint inspect --baselinerather than ESLint. Regenerate the
contract to receive the corrected wording.
v3.1.0
What this release moved, and what it did not. Worth stating plainly, because most of the
entries below act on one axis and it is not the obvious one.
Adoption did not get more likely to succeed, because it was already succeeding. Every
entry here came out of live adoption runs — a real agent CLI taking a real repo through
init → inspect → impact → doctor — and across every one of those runs doctor
finished green. It finished green before these fixes too. Nothing here rescues a failing
adoption.
What moved is whether an adopting agent reaches a true conclusion and writes it into its
report. That is the axis: output an agent reads and acts on, where a confident sentence the
tool could not back turns into a claim in a handoff nobody re-checks. One measured example —
the build step used to assert that tsc -b type-checks your vite config edit; an agent
disproved it by injecting a type error there, and the next run's agent reported that the
rewritten passage stopped it from claiming an alias edit it had not verified.
Six were the tool doing the wrong thing rather than explaining itself badly, and those
are the ones to read first if you skim: an overwritten hand-written contract, a config path
reaching outside the repo, an interrupted install stranding the alias, testFiles: []
emitting a config ESLint refuses, impact demanding a plugin no gate would use, and a CRLF
tsconfig silently skipping the alias edit.
The largest change to how hard this is to break is not in this changelog. Mutation
testing arrived after 3.0.0 — that tag has no stryker.config.json — and the suite roughly
doubled under it. Most of that found places where a wrong edit to the source would have
shipped with every test green. It produced no behaviour you can see, so it has no entry, and
it is still the reason this version is sturdier than the last.
And the honest bound: the wording fixes are reasoned, not measured — with one exception,
which is also the reason to hold the rest loosely. The lint-merge instruction was reasoned
too, and one day later a field agent followed it into the wrong edit to its own config;
correcting it took an eslint --print-config probe per direction on each of the two ESLint
majors, and the answer was the opposite of what the paragraph advised. Several other fixes
repaired sentences this project had written one or two releases earlier, and those net close
to nothing — they closed gaps their predecessors opened. The per-item paper trail for all of
it, including what was judged not worth fixing and why, is in this repo's closed field-run
issues rather than here.
Minor Changes
-
5aa12b8: Action required, once: run
npx blueprint inspect --update-baseline.If you have a
.blueprint-baseline.json, this release refuses it and prints that
instruction. Re-keying records the same debt — nothing that was suppressed stops being
suppressed.Why it has to happen: baseline identity used to include the finding's message text,
the one part of a finding that changes while the violation does not. Rewording any
finding silently retired every baseline entry for that rule — the old debt came back as
fresh, the recorded entries counted asstale, and a brownfield CI went red on an
upgrade that changed no code. Identity is nowrule+path+subject, and the
message is prose the ledger keeps for whoever reads a diff.The old baseline is refused rather than reinterpreted: read under the new key it would
match nothing, which is the wall of red the ledger exists to prevent, arriving with no
stated cause.For
--jsonconsumers: every finding gainssubject(what insidepaththe finding is
about — the import specifier, a cycle's members, empty where the rule and path already
identify it), and the baseline file is"version": 2. -
5aa12b8: An interrupted install now leaves a tree that one command finishes.
initinstalls dependencies as part of its work, and the alias edits totsconfig/
vite.configused to sit below that step. On a machine that cannot reach the registry,
stopping the install left a config, an agent contract and an ESLint config with no alias
anywhere in the toolchain — anddoctorthen reported~app resolves nowhere, which reads
as a broken tool rather than as an install that never finished. Two toolchain files had to
be hand-edited to clear the final gate.Every filesystem effect now lands above the install, so what an interrupted run leaves
behind is a complete tree minusnode_modules.And the install announces itself before it runs. It is the one step that can sit for
minutes — a package manager with no route to the registry retries in silence — so the line
above it now carries the command it is about to run, that quiet is normal, that minutes of
quiet means stopping it and running that line yourself or re-running with--no-install,
and what stopping leaves behind:→ install: eslint, @kekkai/blueprint, … npm install -D eslint @kekkai/blueprint … This is the one step that needs the registry. Silence while it works is normal; minutes of silence means it cannot get there — stop it and run the line above yourself, or re-run init with `--no-install`. … Stopping is safe: this is the last step, so every file above is already on disk. What stopping omits is these packages in `package.json` — this line is the only thing that records them there, so until it runs, a failure naming one of them is that gap and not a broken adoption.No version list to go find: these are your project's dependencies. Which ESLint major
that resolves to, and what backs it, is its own entry. -
5aa12b8:
doctorhas three outcomes now, not two — and a check that could not run is no longer
counted as one that passed.The merge-survival check — the only one proving your emitted rules are alive in the config
ESLint actually resolves — skips rather than fails when that config will not resolve,
because a red you cannot appease is worse than no check. It used to ride in the pass
count anyway, so the output read✓ … (skipped)above✓ Adoption complete — all 7 checks passed.What you see instead:
⊘ emitted rules survive the merged eslint config (skipped — could not resolve …) ⊘ Adoption unverified — 6 of 7 checks passed, 1 could not run (⊘ above). Nothing failed, and nothing here proves what those checks cover.Exit status is unchanged — a skip is not a failure. Gate on the JSON instead of the
exit code when the difference matters:{ "ok": true, "verdict": "unverified", "summary": "⊘ Adoption unverified — 6 of 7 checks passed, 1 could not run …", "counts": { "total": 7, "passed": 6, "failed": 0, "skipped": 1 }, "checks": [{ "label": "…", "ok": true, "skipped": "why it could not run" }] }verdictiscomplete/unverified/incompleteand is onrunDoctor's return value
too.okkeeps the meaning the exit code needs — nothing FAILED — socounts.skippedor
verdictis what a CI gate should read.Three more things
doctornow says out loud:- A green banner on a repo with no version control says what it leaves out — nothing
adoption wrote is committed, and a ratchet living only in an uncommitted working tree is
not installed. - The survival check states its reach: it compares config text and never executes
ESLint, one probe per layer, and it does not compare thresholds, package ownership, or a
merged entry covering only part of a layer. Its ✓ says so. - When it cannot resolve your config it quotes the loader, so the missing package is on
screen instead of onenpm run lintaway — and it names the case where that package is
also absent frompackage.json, which means an install step never finished.
- A green banner on a repo with no version control says what it leaves out — nothing
-
5aa12b8: More of what you need to author a config is now something the tool computed, not
something you have to go and check.blueprint surveynames the specifiers anownsclause can be verified with.owns
takes{ package: 'react', imports: ['createContext'] }, and the survey's evidence was
package-granular —reactreads as "half the layers use it" whichever specifier the clause
names, so there was nothing to check such a clause against. There is now a section for
specifiers that appear in exactly one folder, from a package that appears in several:Named imports in ONE folder, from a package in several (specifier-level ownership candidates — `owns: [{ package, imports: […] }]`; the rows above cannot support one). Read from brace clauses only: a member reached through `import * as` is invisible here, so a folder using one is not counted against the "only": react → createContext — contexts only react → useContext — hooks onlyblueprint rules --jsoncarriesjsLiteralbesideselectors. The selectors you are
told to copy when folding blueprint'sno-restricted-syntaxentry into a house one escape
their path separators — and JavaScript resolves that same escape when it parses a string
literal, so pasting the rendered value into'…'produced a regex that ends early. No parse
error, lint still green, and the ban silently matching nothing.jsLiteralis the selector
as JS source, quotes included; the text output prints that form, since that line exists to
be copied.selectorsis unchanged for programs that build config rather than paste it.One
inspectrun inventories every import cycle, not just the first one it meets — one
per strongly connected component, ordered by content so an unrelated module does not
reshuffle the report. Enforcement never changed (one cycle fails the gate), but "how many"
is the question anyone sizing a migration is asking.**
inspect's cove...
v3.0.0
Major Changes
-
a2107fd: Folder layout is entry-only.
../Sibling— one module reaching another
inside the same layer by its public entry — is legal now, on both gates.
Reaching past that entry (../Sibling/internals) is not, and the alias
spelling (~app/{ownLayer}/Sibling) stays banned, so a same-layer edge has
exactly one shape.What changed is a reading, not a principle. Folder layout previously banned
the entry too, which left a folder layer with no legal way to share at all:
not relatively, not by alias. The only advice the output had left was
"extract shared code to a lower layer" — and a shared unit sunk with nothing
to name it lands in the folder that names nothing, one honest decision at a
time. That message now points at../Siblinginstead.It also closes a name collision that cost a real adoption.
structure-lint's
moduleLayout: 'folder'always meant entry-only; blueprint'sfoldermeant
the neighbour is untouchable. An agent migrating between them carried the
stricter reading across on the strength of the shared word, and filed fifteen
imports that had always worked as pre-existing debt. The two now mean the
same thing.Under the hood the two gates stopped being two implementations.
inspect's
relative-escapefinding and the embeddedblueprint/relative-escaperule
both call onerelativeVerdict, because they claimed to agree by sharing
resolution primitives and did not — the same../Siblingcould be legal to
one and illegal to the other, with no test positioned to see it. The rule
also receives each layer's entry filename, so a layer whose entry is not
indexno longer reads every entry import as reaching past one.Same-layer edges now exist, so cycles among them are possible. Nothing new is
emitted for that: cycles are a property of the graph andinspectwalks it
once, where a per-file lint rule re-walks it for every file. A project that
wants the cycle red at edit time can addimport-x/no-cyclewith
ignoreExternal: true— the carrier already ships. -
35a7923:
importBlocknow rideseslint-plugin-import-x. The emitted rule ids
change fromimport-lite/first/import-lite/no-duplicatesto
import-x/*— a merged config that overrides either one by name stops
matching, which is the whole of the breaking surface.import-litewas chosen when the adoption baseline still included repos that
import-xcould not install into: it peers on@typescript-eslint/utils@^8.56
for its resolvers, and a repo pinned below that failed the install as a whole
(field issue #41), while the originaleslint-plugin-importcaps its eslint
peer at 9 (#37). On an ESLint 10 baseline that trade reverses. No tree reaches
ESLint 10 while holding typescript-eslint below 8.56 — older typescript-eslint
refuses ESLint 10 as its own peer — so the population the guard protected is
now the repos already installing with--legacy-peer-deps, whose installs do
not abort on peer conflicts in the first place.ALLOWED_CARRIER_PEERSrecords
that as a deliberate entry rather than a widened hole, and says which
conformance fixtures would prove it wrong.What the resolvers buy is the whole-graph family a resolver-free plugin
structurally cannot express,no-cycleabove all. This release does not emit
it: cycles are a property of the graph, andinspectalready walks that graph
once where a per-file rule re-walks it for every file. But a project that wants
the cycle red at edit time can now reach for it without adding a plugin — and
withignoreExternalset, since the rule's default walks intonode_modules
and a project with no cycles pays the most, having no early exit to find.
Patch Changes
-
deb61a4: The early-exit checklist says where version control stops. It closes with
doctor, and said nothing about committing — while the full method has the
clause: commit what adoption wrote, and where that is impossible, leave the
files and say so rather than initializing version control for the owner.A field agent on that path went and found the clause in the full method, then
followed it correctly. It got the right answer by leaving the checklist that
had just told it "nothing else in this file applies" — a handoff you have to
go looking for is one the next reader can miss, and the miss here is an agent
runninggit initinside somebody's directory.The checklist now carries it, and says it is a repetition and why.
-
e6696af:
ruleshands over the whole entry, not just its selectors. Flat config
replaces same-key entries rather than merging them, so the merge guidance asks
an adopter to fold blueprint'sno-restricted-syntaxselectors into their own
entry, and points atrules --jsonfor the exact strings. That output had the
selectors and nothing else — while the emitted block also carries
ignoresexempting test files. An entry rebuilt from selectors alone has no
exemption, so it starts governing tests.A field run lost it and spent a debug cycle on 34 errors in one test file. That
was the loud version, and only because the adopter's own rule happened to
collide on the same layer. Where nothing collides, the entry that quietly
reaches test files is blueprint's own selfOnly ban, behind a lint that stays
green — and doctor compares selectors, not scope, so nothing downstream
notices.rules --jsonnow carriestestExemptionsbeside the selectors on every
layer, the text output prints theignoresline to paste under the selectors
to copy, and the playbook's merge section states that an entry is more than
its selectors, naming both how the loss shows up and how it hides.
v2.2.0
Minor Changes
-
4caf8fd: ESLint now owns formatting in the emitted config, and
maxLinesfinally has a unit. Five new gate ids, all riding a caller-injected plugin so the library keeps zero runtime dependencies. All land aterrorin every preset.The load-bearing one is
statementsPerLine→@stylistic/max-statements-per-lineat a hard-wired{ max: 1 }.maxLinescounts code lines with blanks and comments skipped, so a line budget with no cap on line content is satisfiable by collapsing statements onto one line instead of splitting the file — the evasion an agent under budget pressure reaches for first.codeStylecloses the rest of that route:@stylistic's owncustomize()set (read from the factory, not hand-listed — a hand-picked subset is what left zero-indentation legal) plusmax-lenat 90,linebreak-style: unix, and corecurly, which matters because without itif (x) return;counts as one statement and slips past the line gate. Knobs:indent,quotes,semi,maxLen.Also
explicitAny→@typescript-eslint/no-explicit-any(the mechanical half of the narrow-interfaces principle —anyis the hole that lets illegal states be expressed),statementPadding→@stylistic/padding-line-between-statementswith a fixed 17-entry list, andimportBlock→import-lite/first+import-lite/no-duplicates, which catches the two import mistakes an incrementally-editing agent makes routinely and which no formatter can fix.emitLintgrowsoptions.stylisticandoptions.importsbesideoptions.typescript; the generated config passes all three,initinstalls@stylistic/eslint-pluginandeslint-plugin-import-lite, andimpactloads both from the project so its per-rule counts cover the new gates instead of reporting a silent zero. A gate whose carrier plugin is absent emits nothing while lint still passes — indistinguishable from a clean merge — so the merge snippets, the gate catalog, the playbook and the generated config each state that the options object travels whole.codeStylethrows rather than emitting an empty bundle when handed a plugin withoutconfigs.customize().Three behaviors worth knowing before adopting. The shape family is the one gate family test files are not exempt from — a metric threshold screams on a test, but a duplicate import or a collapsed line is no easier to read there.
codeStyleis nearly all auto-fixable (about 5 of ~68 rules are not), soeslint --fixclears most of a first run: the playbook asks for that pass as its own commit, since it rewrites whitespace repo-wide, and notes it cannot push a file overmaxLines. What--fixcannot clear is named too —max-lenhas no fixer and deliberately does not exempt plain strings, because a cap a line escapes by containing a string is not a cap; and alinebreak-stylered usually means git'sautocrlfor a missing.gitattributes, not the file, so fixing the source just gets undone by the next checkout. Two rules stay rejected on their own merits, and the carrier that ships does not carry them anyway:import/no-cycle(a per-file re-check of a graphinspectalready walks, measured at 92s on 850 files) andimport/no-unused-modules(cannot run under flat config). Both need a resolver, which is exactly what the shipped plugin leaves out. On a JavaScript projectexplicitAnyleaves inspect's coverage denominator entirely —anyis a TypeScript construct, and a gate nobody can open is not a gate, the ruledeepWatchalready follows on React.
Patch Changes
-
cbd8ae8: Three field-run corrections to what the tool says about itself. No behavior changes — but each of these sent an adopting agent somewhere it did not need to go.
The playbook no longer invites
impactinto the drafting loop. "Let the tools correct you:inspectandimpactare read-only and cheap" sat above the Method as a licence to run both while drafting, and both brownfield agents of the run took it —impactlints with the emitted config, so it needs the pluginsinitinstalls and greeted them with a load error instead. One agent filed it as a self-contradiction, the other blamed itself; the same wall twice is the playbook's, not theirs. The paragraph now namesinspectalone as the config-only loop tool and states whereimpactjoins (Method step 9, after init) and why it cannot come earlier. The load error's own pointer toblueprint init— the thing that got both agents unstuck in one hop — is now asserted, not incidental.A deletion in
init's output no longer wears the writes'✓.− rm:marks it instead. The note always carried the cause (pristine preset scaffold — removed; the playbook authors the real one), but a destructive line stamped with the success mark and buried among writes skims past as one more thing created: a field agent filtered init's output forwriteand missed its own config being reclaimed, then spent commands hunting the disappearance. The mark says which direction the effect went; the note still says why.And the install line stops stuttering its own kind —
✓ install: install eslint …is now✓ install: eslint …. -
c420e2a: A finding now names the rule that carries it, and the
--agentflag says which path launches anything. Two field observations, both cases of a correct state reading as a broken one.One violation went by three names.
inspectreported[deep-import],impactand the lint run called itno-restricted-imports, and an adopter verifying the merge searched the resolved config forblueprint/deep-importand found nothing — because that ban, likeflow-violationandpackage-ownership, folds into the singleno-restricted-importsentry rather than standing alone. Inspect's migration steps now carry both halves: each names its finding and the ESLint rule that enforces it, and the findings no lint run will ever show —cycle,undeclared-folder,no-entry— say so instead of leaving the reader to wonder where they went.relative-escapeis called out as the one structural ban that is a standalone rule, since a../escape cannot be written as a literal pattern.The playbook's
--print-configguidance gains this as its fourth "know this before reading the output": finding names are not rule ids. It sits beside the three added last release — prefixed keys, rules absent because their layer holds no files, and selfOnly resolving on the importer layer.Separately, the README's security bullet was imprecise about
--agentand two field runs stopped to check. It presented the flag as the one explicit opt-in that runs an agent, without saying that this is true only on the authoring path; on the preset path (init --preset --agent claude) nothing is launched and the flag only narrows which contract file is written. Both meanings are stated now, withinit --helpand--dry-runnamed as the authoritative check — which is what both agents reached for. -
21eeb2f: The generated handbook stops promising a gate that does not exist. Its Rules table printed a tier beside every declared rule under one legend — "
errorfails lint" — which is true of most rows and false of two kinds.cyclesisinspect's finding and deliberately emits no ESLint line;deadCodeis documentation-only and asks for knip. A preset declares both aterror, so a stock handbook told its reader that two rules gate their lint run when neither does.A field agent caught it by cross-checking against
blueprint rulesand trusted the catalog — at no cost to itself, but it named the real problem: the handbook is the artifact meant to outlive the adoption, read later by people who will not have the catalog open beside it. Two generated artifacts from one source disagreeing is the thing the README claims cannot happen.The table gains an Enforced by column —
lint,`blueprint inspect`, ordocumentation only— and the legend now separates the tier (what the enforcing machine does with a violation) from which machine that is. Unknown ids resolve to documentation, matching how the agent contract already treats them.The classification comes from the catalog rather than a second hand-kept list:
GateSpecgains an optionalruntimemarker,cyclescarries it, andenforcedBy()derives the three-way answer. Adding a runtime-backed gate needs no edit in the handbook, and a test asserts that correspondence rather than the current membership. -
c423c4e: The last carrier stops constraining the adopter's tree, and doctor's merge check stops over-promising. Two field findings, both about a green that meant less than it said.
importBlocknow rideseslint-plugin-import-lite(import-lite/first,import-lite/no-duplicates). The two predecessors each walled a real repo:eslint-plugin-importcaps its eslint peer at 9, andeslint-plugin-import-xpeers on@typescript-eslint/utils@^8.56for resolvers these two rules never use — an optional peer is still version-checked when the package is present, and no nested copy can satisfy a peer, so a repo pinned at 8.47 could not install it at all. Since npm resolves the required-deps list as a unit, either one failed the whole install. import-lite is those same rules ported without the resolvers: zero dependencies,eslintas its only peer, and behaviour verified identical across eleven cases including the TypeScript one that matters (import typebeside a value import from the same module stays legal). No release ever carried the old ids.The peer guard widened with it. It already asserted every carrier admits each supported ESLint major; it now also asserts no carrier peers on anything else the adopter owns, since that is the constraint that actually failed.
typescript-eslint↔typescriptis allowed by name; a new peer is a test failure to review, not a surprise in someone's adoption.Doctor's merge-survival check no longer walks past the failur...
v2.1.0
Minor Changes
-
003cacc: Drop the "backend boundary" posture from the emitted handbook. The
respect-backendcore belief and the whole "Data integrity & backend boundary" playbook section (no-fake-fallback,drift-guard-framing,no-fe-workaround,preserve-locale-shape) no longer ship in the presets, the handbook, or the agent contract.No API surface changes —
PrincipleDefandPlaybookSectionare untouched. The preset data is simply nine beliefs and a three-section playbook now (was ten and four). Re-runinitto regenerate a handbook and agent contract without the section; a repo that authored its ownprinciples/playbookis unaffected. -
7dcbe82: Drop CI scaffolding. Blueprint no longer emits
.github/workflows/blueprint-ci.yml, and theemitCiexport, theCiOptionstype, and theemit.ciconfig field are removed.Verification strategy is the adopter's call — a git hook, GitHub Actions, GitLab CI, whatever you already run. Blueprint still ships the verification commands (
inspect --baseline,doctor) that exit non-zero on new findings; wire them into your own gate.Migration: remove
emit.cifrom anyblueprint.config.mjs(it is now an unknown key, soinitrejects it), and move the two steps the emitted workflow ran —eslintandblueprint inspect --baseline— into your existing pipeline.Shipped as a minor by the owner's call: strictly this removes public surface, but that surface existed for days on 2.0.0, nothing in the field ever used it, and a config still carrying
emit.cifails loud with the pointed unknown-key message above — not silently. -
ab134f4:
architecture.moduleis optional — the playbook's "flat default" is now real (field issue #23): omitting the block, or any of its keys, resolves to{ layout: 'flat', entry: 'index' }through one shared reader, and an emptyentrystill fails loud with the default named as the way out. Validation used to demandmodule.entrywhile Method step 5 said plain files take the flat default — the field agent burned two edit-run cycles proving the tool contradicted itself.
Patch Changes
-
ab134f4: Judgment calls agents kept making unaided, now stated where they arise (field issue #23):
rulessays the fold's message text is yours to write (doctor verifies selectors, never messages); a house rule under a different KEY with the same semantics never collides — keep one gate per semantic; anownscandidate the intent documents never mention is a proposal for the report, not a clause to encode; annotating a bare disable's reason is a comment edit inside the adoption boundary; everything the adoption produced — config, artifacts, both ledgers — is meant to be committed, and initializing VCS is never the agent's call; the CLAUDE.md discipline pointer notes the contract ships inside the package. -
ab134f4: init's vite alias instruct respects a tsconfig-paths bridge plugin (field issue #25): with vite-tsconfig-paths in the vite config, the tsconfig side init already wires covers the bundler, and doctor's alias check passes that state — init no longer demands a redundant
resolve.aliasthe check never asks for, and the instruct itself names the bridge-plugin escape hatch when it does fire.Contract references ship WITH their
BLUEPRINT:START/ENDmarkers (field issue #26): the reference's header said "init rewrites only between the markers" while the file carried none — the agent probed raw bytes, guessed headings were markers, and the integrated block went permanently stale (a wrong layer flow in CLAUDE.md, invisible to doctor). Pasting the reference verbatim now keeps the block refreshable by any later init; both integration instructs state the trade — keep the markers and init refreshes the block, strip them and the file is yours to update by hand, forever. Also from the same batch (#24–#26): the build-verification artifacts are named as the build's normal output, not adoption leftovers; ongoing enforcement is stated as deliberately unscaffolded (the gate commands are the deliverable, the pipeline is the owner's); linearization prefers the smallest-relaxation position among equally legal ones; a skipped parser block's package is dependency wiring to keep, not waste. -
ab134f4: Closing-round polish from field issues #27–#28 (zero blockers across four scenarios): the survey's "Same-folder imports via the alias" section always prints — an explicit
0 (none found)instead of a silently absent row the playbook cites; the one-gate-per-semantic rule now spans gate layers (a houseimport/no-cycleand the inspect-sidecyclesgate are one semantic — pick one detector); theownssketch states that repeating the same entry across layers IS the shared-allowance syntax (same-signature entries merge). -
049b1fa: The playbook states doctor's leftover criterion — exact file families (the playbook, the command file,
*.blueprint.*references, marker-bearing contracts outsideemit.agents), never other files whatever their names. Two field runs verified this experimentally by writing a file and re-running doctor (issues #27, #32); the answer now sits in the semantics section instead. -
325379e: The anti-bypass guard's files glob scopes to the detected stack's extensions, like the parser blocks — a react repo's guard used to carry
.vue, and four field runs trimmed it by hand (field issue #30); an unknown stack keeps the full set, and the glob honorssourceRoot. The guard's comment also states that its position relative to the emitLint spread does not matter (the rule sets never intersect) — the one merge-order doubt agents kept resolving themselves. -
ab134f4: The merge instruction meets the agent at the point of need (field issues #21–#22): an existing
eslint.config.tsgets the TS7016 caveat — importing./blueprint.config.mjsneedsallowJson the tsconfig covering the config, or a one-lineblueprint.config.d.mts;defineConfig([...])arrays are named spread-equivalent; the generated jsx parser block carries its own skip criterion (dormant on a TS-only repo)."Only a real run proves it" now covers the alias wiring (both runs added the build check by hand): the early-exit checklist and the merge step order one build run after init edits tsconfig/vite, stating why — doctor's alias check reads wiring as text, never as a compile. The contract header now says hand-written notes live outside the marker block, the convention an agent had to infer when the generated file became the repo's only CLAUDE.md.
-
ab134f4: An additional alias whose target sits above the source root now really joins the bans (field issue #29):
'~root': '.'used to emit~root/<layer>patterns no real import ever used — the whole~rootleg of every structural ban was a silent no-op, inspect was equally blind to~root/src/<layer>imports, and a closing report almost claimed a protection that did not exist. Emit and inspect now derive each alias's layer base from one shared helper with the target offset baked in (~root/src/views/**, selector^~root\u002Fsrc\u002F…), so patterns and findings cannot disagree; an alias into a subfolder has no layer surface and carries no layer bans, and the playbook says so. -
ab134f4: Playbook verdicts agents had to invent, now stated (field issues #18–#20): the report is a message — the closing reply or the PR description, never a committed file — and the early-exit path says its one-line verdict IS the report; the early-exit wiring step closes by running the project's own lint once (doctor's wired check reads config text and never executes eslint, so a load-time crash sailed through all seven checks); the
ownssketch notespattern: trueglobs match any import specifier, internal alias paths included; init's preset hint carries--agent, matching the checklist it hands off to. -
ab134f4: selfOnly re-export selectors encode
/as the\u002Fregex escape: esquery below 1.7 has no\/escape in its regex literal, so the emitted selector truncated to a broken pattern and crashed ESLint — andblueprint impact— on every file of the selfOnly importer's layer, wedging doctor's survival check against the lint gate (field issue #19). Doctor's survival red now names both possible causes (a replacing flat-config entry, or a hand-folded copy that drifted from this version's output) and points atrules --jsonfor the exact selectors, instead of asserting a merge collision that may not exist.rules(text and--json) carries the exactno-restricted-syntaxselector strings a selfOnly merge fold needs, per layer and target: the playbook demands "combine both option sets into ONE entry", and the only source for the selectors used to be an emitLint dump — exactly the bundle archaeology the same playbook forbids (field issue #20).
v2.0.0
Major Changes
-
33ed541: 2.0.0 — the field-hardened release. Fifteen autonomous adoption rounds
(closedfield-runissues #1–#15) drove every change since 1.14; the
loop converged when a full round reported zero tool defects, with every
suspicion an agent raised verified against the channels and retracted.Breaking changes:
- Config validation rejects unknown keys across the structural path
(blueprint, architecture, layers, allowedImporters entries, module,
owns entries, emit). A key nothing reads was a silently dead
declaration — a field repo shipped aselfOnlyban that never existed
because the key sat one level too high. Configs carrying stray keys
now fail loud with a pointed message. architecture.flowandemit.lint.pathare removed (both were
inert), along with theretirecommand, the--frameworkflag on
impact/doctor/rules, and the never-documented entry exports
emitAgentContract/AgentContractOptions/injectBetweenMarkers/
StructuralRule.doctoris stricter: leftover authoring artifacts
(blueprint-authoring.md, the command file) fail the leftover check —
"Adoption complete" and the playbook now define done identically; a
marker-bearing contract outsideemit.agentsalready failed since 1.14.- The emitted CI workflow gates with
inspect --baseline(a missing
ledger is an empty one) — locked brownfield debt no longer turns the
tool's own CI permanently red. architecture.module.privateis optional (omitted = none) — the
one loosening.
- Config validation rejects unknown keys across the structural path
Minor Changes
-
4fe2d55: Product-wide chicken-rib sweep — dead surface is gone, not deprecated:
architecture.flowremoved. It was a required field that nothing ever
read — the layer ORDER is the flow. Existing.mjsconfigs keep working
(the runtime ignores unknown properties); TypeScript-typed configs delete
one line.emit.lint.pathremoved for the same reason: never consumed, while its
doc claimed a consumer.emitAgentContract(andAgentContractOptions) left the package entry:
the supported agent targets are the onesemit.agentsnames, and a
render-it-yourself hatch for unsupported tools is surface without a
mission.emitAgentFilesremains the one distribution API.injectBetweenMarkersleft the package entry — an internal merge utility
with no documented story.pluginstays, and its story is now stated in the reference: the escape
hatch for hand-wiring ablueprint/*rule withoutemitLint.
-
538578b:
doctorand the playbook now define "done" identically (field issue #13):
the leftover-files check also flagsblueprint-authoring.mdand
.claude/commands/blueprint-author.md— "Adoption complete" over a live
playbook was a second, contradicting authority, and a careless agent
stopped there. The check's detail says a mid-authoring doctor run is
EXPECTED to fail on it; the early-exit checklist and acceptance gates now
order cleanup BEFORE the final doctor run. Gate turns stricter: repos
that finished wiring but skipped the playbook's cleanup step go red until
the two files are deleted.Also: the
missing-layernote carries the keep-is-default verdict in
place ("runway, not a todo … slimming is the owner's call") — six of
these read as a todo list and pointed an agent at dismantling the preset
skeleton the playbook says to keep. -
6c5af1c: Field issue #4 (489-file brownfield run, structure-lint retired per the
playbook): unknown CLI flags now fail loud instead of being silently ignored
—inspect --verboseread as a broken no-op to an agent; init re-includes
its gitignored artifacts by appending!negations to .gitignore itself
(with the parent-directory caveat stated in the note) instead of handing the
fix back; the rule catalog now states what two agents had to eval the bundle
to learn —ownscovers named-import granularity (a house "only composables
may inject" rule maps to it),no-restricted-syntaxis emitted only when a
selfOnly importer exists, andadditionalAliasesjoin every structural ban;
the playbook's retirement clause says to DELETE the retired tool's config
and that source-comment pointers may outlive the sweep under no-source-edits;
impact's echoes block leads with "NOT blueprint findings, NEVER counted". -
f7bc5d9: Field issue #5:
init --agent claudenow PERSISTS the choice — a scaffolded
config carriesemit: { agents: ['claude'] }, so the first run emits one
contract and the next plain init cannot grow the second one back; the
chicken-and-egg (config must exist before you can narrow it) is gone. The
catalog closes the last bundle-eval of the round:unusedVarsis named as
TWO rule keys on TypeScript, every optional gate is stated to scope to the
layer globs only, and merge guidance says collisions are decided by rule
KEY (blueprint rules --jsonnames them), not by hit count. -
5337f77: Field issues #2–#3 (automated harness runs): doctor's leftover check now
also flags stale agent contracts — a marker-bearing CLAUDE.md/AGENTS.md/…
outside the emittedemit.agentsset cannot hide behind green (init removes
a wholly-generated one itself; a hand-touched one only got an instruct, and
nothing gated the orphan afterward). A preset that introduces the repo's first import
alias names it as a decision instead of letting a new convention pass
silently. init --help stops promising layer folders unconditionally and says
plainly that --agent never launches anything on the preset path. -
a3aa5a8: Field issues #7–#8:
blueprint rulesgains the resolved per-layer view —
what each layer may not import, which owned packages (down to named imports)
and globals are banned there — ending theeslint --print-config
archaeology behind "0 hits: wired-and-clean, or not applying at all?". The
playbook's early-exit verdict becomes a complete self-contained checklist
("following this verdict IS executing the playbook fully"; impact's zero
gates the suppress-all run; trivially-true acceptance gates are named as
such; the now-empty command directory goes too), the config schema's full
owns/additionalAliasesshapes live in the playbook so nothing exists
only in dist, the test-file exemption is named as a deliberate relaxation
versus tools that police tests, and a below-threshold--authoringrun
says up front that the playbook's own verdict will be the early exit. All
adoption prompts state that an early exit the playbook prescribes counts
as full execution. -
99a0727: Init now lives by its own doctrine (field batches 10–11): empty layer folders
are scaffolded only into an empty source tree — where code already lives, an
unbuilt layer's absence is its true state, no.gitkeepshells. Narrowing
emit.agentsmakes the next init remove a stale agent contract that is
wholly its own output (nothing outside the marker block; own-strategy rule
files by construction) — a hand-edited file only gets told. And
validateBlueprintnow returns the blueprint instead ofvoid, so a passing
call is visible at runtime. -
1e077f1: A config key nothing reads now fails validation instead of dying silently
(field issue #14): a 489-file field repo declaredselfOnly: trueon the
layer object — where nothing reads it — and the intended re-export ban
never existed while every gate stayed green. Validation now rejects
unknown keys across the structural path (blueprint, architecture, layers,
allowedImporters entries, module, owns entries, emit), and the misplaced
selfOnlygets a pointed error naming its real home
(allowedImporters: [{ layer: 'views', selfOnly: true }]).blueprint rulesalso answers "does THIS config emit it?" for the
structural family: each rule carries a per-config✓ emits / · not emittedannotation (activein--json), so nobody has to probe
emitLint to learn whether theirno-restricted-syntaxwill collide with
a house rule. A test pins the annotation to emitLint's real output. -
514eb53: Presets take
emitdirectly and merge it over their day-1 default
(ci: 'github'). Declaring the agent tool in use is the first customization
nearly every adoption makes — it no longer costs the one-line preset form,
and no longer silently drops the CI workflow the way a spread-levelemit
override does. -
62ddb7e:
architecture.module.privateis now optional — omitting it means no
private parts ([]). A draft-first config that never mentions private
sub-parts validates instead of failing with "must be an array" (field
issue #11). Explicitprivate: []keeps working unchanged; a non-array
value still fails loudly. The playbook's schema sketch marks the field
optional. -
0ee89bf: The tool now answers for itself (field batch 12):
blueprint rulesprints
the emitted-rule catalog — always-on structural rules, optional gates with
metric defaults, documentation-only ids — annotated with the config's
declared tiers and whether each gate emits today.inspectgains the tenth
finding,declaratory-self-only: a selfOnly ban protecting a layer with no
files is named as a blank round until code lands. The authoring playbook
additionally licenses draft-first authoring — write the config early and let
read-only inspect/impact runs correct it, instead of studying the archive
first — and carries the retirement sweep for a consolidated-away tool.
Patch Changes
impact's zero-hit line states its own reach: "no red today" is an
emitLint claim, and the anti-bypass guard rides in the generated config
outside impact's scope — its findings (bare eslint-disables) surface in
the project's own lint. The playbook's "zero hits → skip
--suppress-all" carries the same carve-out (field issue #17: 0 hits,
then five guard findings in the real lint run).- e59c662: Init...
v1.14.0
Minor Changes
- bc55a63: Doctor's merge-survival check no longer goes blind on empty repos — the anti-false-green gap field batch 7 called ironic. A layer with no files yet gets a synthetic probe derived from its own globs (
calculateConfigForFileresolves by pattern and never touches the filesystem), so a gutted config turns red with zero files on disk; globs the synthesis cannot honor simply yield no probe. The authoring playbook gains an early-exit clause: on a repo at or below the preset threshold whose shape a preset fits,init --presetis a legitimate verdict — walking the full method on a starter is ceremony, not judgment. Docs show the{ ...reactPreset(...), emit: {...} }spread, sinceemitis a top-level blueprint field, not a preset option.
Patch Changes
- 5d45120: Layer names carrying glob or path characters are rejected at validation — a layer literally named
*(field batch 9's root-files workaround) widened every file glob into a wildcard and scaffolded a literalsrc/*/folder. The playbook states the doctrine the workaround violated: an empty net on a root-only app is the true state, not a failure — never invent a layer to make coverage non-zero; root-file hygiene belongs to the project's own lint. - 9a5b15a: Batch-8 field fixes, all with repros.
usePrefix: 'off'no longer validates its target layer (an off rule has nothing to target — it used to throw on repos without ahookslayer).impactstops contradicting itself:parse-errorandunused-disable-directivemove out of the wiring-red total into an "Isolation caveats" section that says which kind vanishes after the merge and which survives. The handbook diagram renders order-only spine edges dotted and declared importer relations solid with inline labels — consecutive leaf layers chained solid read as dependencies they never were. The playbook's overlapping-tool guidance gains its missing exception: when the existing tool sets the same ESLint rules emitLint emits, coexistence is mechanically impossible and consolidation becomes a wiring precondition, not a scope decision to flag. - 6464b43: Scanned paths normalize to forward slashes at birth — on Windows every downstream glob net (inspect, coverage, survey, survival probes) silently matched nothing. Layer-name validation also rejects characters that corrupt the emitted mermaid diagram (whitespace, quotes, parens,
&,%,;) while keeping real conventions like@corevalid; pipe-bearing edge descriptions render as/instead of breaking the inline-label syntax; and the playbook's deliverables and acceptance gates go zero-debt-conditional — a clean repo's absent baseline is the correct outcome, not a stalled checklist item.