## [2026-05-15T09:13:17.308Z]
Add to-4.4.0.ts migration: pre-checks current version is 4.3.0, writes 4.4.0 to yg-config.yaml, returns warnings describing new 4.4.0 validation rules (type-when predicates, organizational types, enforce:strict). Register in migrations/index.ts.
## [2026-05-15T09:17:13.402Z]
Fix migrateTo44: remove strict version pre-check (was checking for exactly 4.3.0). The orchestrator doesn't update yg-config.yaml between migrations, so migrateTo43 leaves the version at 4.0.0 when called from that version. Now only check that version field exists; also use dynamic current version in the actions message.
## [2026-05-15T19:37:33.718Z]
Add migration-bumps-version AST aspect to enforce that migration files reference their target version; fix to-4.3.0.ts to call updateConfigVersion('4.3.0') — it previously modified architecture YAML without bumping the schema version in config
## [2026-05-15T19:39:59.328Z]
Make updateConfigVersion call graceful (try/catch) and update test fixture to include yg-config.yaml so the migration properly exercises the version bump path in tests
## [2026-05-16T06:51:33.685Z]
Fix non-idempotent rm calls in to-4.0.0.ts: added force:true to rm() in processNodesRecursive and resetDriftStateRecursive so re-runs don't throw ENOENT on already-deleted files.
## [2026-05-16T07:16:25.354Z]
Fix to-4.0.0.ts: remove inline version bump from cleanConfig, call updateConfigVersion() at end of migrateTo4() conditioned on writes having occurred (schema-bump-bookkeeping). Fix to-4.4.0.ts: wrap updateConfigVersion() in try/catch, push failure to warnings instead of throwing.
## [2026-05-16T13:40:04.206Z]
Flatten 4.3.0+4.4.0 into single 4.3.0 release: merged to-4.4.0.ts (version-bump-only migration) into to-4.3.0.ts (adds log_required:false + when-predicate warning). Removed to-4.4.0.ts file and its MIGRATIONS entry. Updated index.ts description to reflect combined migration.
## [2026-05-26T10:31:31.810Z]
Rewrote aspect migration-bumps-version against raw tree-sitter API. Replaced ast.within() traversal with walk() + early return (false) on string and template_string nodes. Verified behavior-identical via ast-test diff.
## [2026-05-27T11:59:10.017Z]
Added to-5.0.0.ts migration: transformConfigReviewer converts v4 reviewer format (provider keys + active selector directly under reviewer:) to v5 tiers structure; transformAspectReviewer converts reviewer: string shorthand to reviewer: { type: ... } mapping. Registered in migrations/index.ts as version 5.0.0 migration.
## [2026-05-27T13:19:06.829Z]
Made to-5.0.0.ts version bump unconditional — previously the migration only wrote version '5.0.0' to yg-config.yaml when at least one content transformation occurred (reviewer config or aspect YAML). A repo with no reviewer section and no aspects would run the migration but keep the old version string. The unconditional bump ensures the schema version always reflects the highest migration that has run, which is required for correct migration-pipeline behavior when multiple migrations are chained in sequence.
## [2026-05-27T13:23:07.807Z]
Corrected to-5.0.0.ts version bump to be conditional: uses detectVersion to check the current config version, and only calls updateConfigVersion when the version is not already '5.0.0'. This ensures accurate MigrationResult reporting (no phantom 'version bump' action on idempotent re-runs) while still guaranteeing that repos with no reviewer section and no aspects get their schema version bumped to 5.0.0 when the migration pipeline processes them.
## [2026-05-27T13:54:40.639Z]
Reviewer-tier migration rewritten. Every legacy provider key now becomes a named tier (tier name equals provider name) rather than being collapsed into one tier; the legacy active field is renamed to default; default is omitted when only a single tier results. When warnings are emitted (unrecognized aspect reviewer value, mapping with no type, multi-provider config lacking active, non-credential field in the secrets file) MigrationResult.bumpVersion is false so the runner withholds the version bump until the user fixes the listed files. Multi-provider configs without active STOP migration before touching aspects so the repo stays parseable. A new inspect-only secrets step audits the secrets file for non-credential fields. Aspect recursion handles nested aspect directories.
## [2026-05-28T06:03:27.489Z]
Import path of KNOWN_PROVIDERS updated — the constant moved from io to utils to match the utility node-type which already covers source/cli/src/utils/*.ts. No behavioural change in the migration itself.
## [2026-05-28T09:25:23.319Z]
Aspect references dogfood: added reference on migration-idempotent aspect pointing to source/cli/src/core/migrator.ts. The reference gives the LLM reviewer access to the canonical helpers (detectVersion, updateConfigVersion, MigrationResult) that the migration content.md rule mentions abstractly. Validates the full feature end-to-end: parser accepts the entry, validator confirms file exists, drift cascade triggered on the single affected node, reviewer prompt included the file content, approve succeeded with 5 aspects satisfied, and yg context displays the reference under read: with description truncated at the 80-char word boundary.
## [2026-05-28T14:30:01.418Z]
Added inspect-only addAspectStatusDefaults pass to to-5.0.0.ts that detects two v5-breaking patterns in pre-5 graphs and emits warnings withholding the version bump. First, escalation: an aspect with default enforced status implying another aspect whose own default is lower (advisory or draft) via a bare reference or object form without status_inherit. Under the new strictest propagation mode the implied aspect will silently run at the higher status when reached via the implier. Second, downgrade: an explicit per-attach-site status that is strictly below the cascading anchor (max of aspect-default and all other channels contributing the same aspect onto the same node). Both patterns are surfaced from raw YAML parsing without instantiating the full graph — keeps the migration robust to pre-v5 parse rules. Wired after the secrets pass so warnings accumulate alongside the existing reviewer/secret checks.
## [2026-05-28T15:09:21.065Z]
Extracted the aspect-status defaults pass into a dedicated helper module to keep to-5.0.0.ts focused on the original reviewer-tier and aspect-reviewer transforms. The new helper exposes a single async function that takes the yggdrasil root and a warnings array. The migration entry point wires it in after the secrets pass so any escalation or downgrade findings flow into the same warnings list that gates the runner's version bump. Moving the helper out also keeps each migration source file small enough for the reviewer to evaluate as a whole; with all the helper code inlined into to-5.0.0 the file became long enough that the reviewer hallucinated about it being absent from its own prompt.
## [2026-05-28T15:19:33.765Z]
Clarified in the aspect-status defaults helper's leading comment that it is a sub-routine called by migrateTo50, not itself a migration. The aspect that enforces version bookkeeping is scoped to migration entry files (named to-X.Y.Z.ts); the helper module does not own version bookkeeping, does not call updateConfigVersion, and does not return a MigrationResult. Putting that distinction in the header keeps a reader (or LLM reviewer) from treating the helper as if it had to satisfy the migration-bumps-version / schema-bump-bookkeeping rules in isolation.
## [2026-05-29T18:22:43.091Z]
The v4-to-v5 config migration now validates the legacy global reviewer.consensus before copying it into per-tier blocks. In v4 the field had no constraint, so an even value (e.g. 2 or 4) was legal. In v5 each tier requires a positive odd integer for consensus. Silently copying an even value produced a structurally invalid v5 config that the parser rejected on load; because no warning was emitted the version was bumped and the migration could not re-run — forcing a manual hand-edit to recover. The fix mirrors the file's existing warning-prevents-migration convention: if reviewer.consensus is present and is not a positive odd integer, a descriptive warning is pushed and the function returns with changed: false and value: undefined, leaving both the config file and the version untouched so the user can correct the value and re-run the upgrade. An absent consensus or an odd value (including 1) continues to migrate normally as before.
## [2026-05-29T22:37:52.575Z]
The migrations were separated into a current-generation group (the latest transform, its helpers, and the registry) and a legacy group (older version transforms), so the set a reviewer inspects at once stays small as more migrations accumulate over time. No migration logic changed; only which node owns which file.
## [2026-05-30T18:08:09.948Z]
The vocabulary for how a rule is verified was reduced from three kinds to two. Previously a rule was checked by one of: a human-language reviewer, a single-file programmatic check, or a graph-aware programmatic check. The two programmatic kinds are now a single "deterministic" kind, leaving just deterministic-or-reviewer.

The motivation: the three-way split was drawn on the wrong axis. It described HOW a programmatic check reached its context (one file at a time, versus the whole graph), but the distinction that actually matters to a rule author and to cost is whether verification is local-and-free or requires the paid, non-deterministic reviewer. The single-file kind was already a strict subset of the graph-aware kind — every input the former could see, the latter also provides — so maintaining two of them forced rule authors to make a false choice up front and forced the engine to carry two parallel handling paths for one concept. Collapsing them removes that false choice and the duplicated handling, and routes every programmatic check through the one graph-aware path.

The language a programmatic check infers for a source file is determined solely from that file's extension, so a check no longer declares which languages it targets. A rule's verification kind being deterministic is also no longer carried as a separate synthetic identity signal — a deterministic rule's identity is fully covered by the files it already tracks — which keeps re-verification of such rules free.
## [2026-05-31T16:03:33.873Z]
Replaced the hand-inlined path-separator normalization with calls to a single shared helper. The same small idiom — convert backslash separators to forward slashes, and in most places also strip a trailing slash — had been copied across many modules, so the normalization rule lived in dozens of places at once and any change to it risked drifting them out of step. Consolidating it behind one well-named helper means the rule lives in exactly one spot and each call site reads by intent instead of by a repeated regex. Behavior is unchanged: the helper bodies are byte-for-byte equivalent to the expressions they replace, and the full test suite passes identically.
## [2026-05-31T21:53:51.823Z]
Relocated format-detect.ts (legacy-shape predicates) from core/ into migrations/. Runtime parsers no longer import or depend on these predicates — a 5.0-version graph with an old reviewer shape now yields a generic validation error from the parser. The migration remains the only code that understands and transforms old shapes, so keeping the predicates here prevents accidental reuse in runtime paths while preserving the migration's ability to detect and transform pre-tier configs and aspects.
## [2026-05-31T22:09:56.531Z]
Updated to-5.0.0.ts import from core/format-version.ts to core/format-detect.ts (renamed during the legacy-detection relocation). The format-detect module now has its own engine node; the migration uses it via a declared dependency.
## [2026-06-01T07:20:40.430Z]
The 5.0 schema introduced a typed drift-state baseline format (a structured identity block plus a required per-aspect verdict map), replacing an older flat shape that stuffed upstream-identity information into synthetic string keys inside the file map. Adopters upgrading across this boundary carry on-disk baselines in the old flat shape. Earlier major upgrades simply deleted all baselines, which forced a full re-approval of every node — costly and noisy for adopters with large graphs.

This migration instead re-keys each baseline losslessly: the same logical inputs are reshaped into the typed format and the canonical hash is recomputed over them, so a clean check over unchanged source sees no drift and no re-approval is needed. Deleting baselines is reserved for the safe-degradation path only.

Three deliberate choices, all derivable from the baseline file alone (no graph load, keeping the migration cheap and side-effect-free):

### Pre-verdict synthesis

A baseline written before per-aspect verdicts existed has no verdict map at all. Treating that as "nothing approved" would flood the first post-upgrade check with every effective aspect appearing newly active. Instead, when the original baseline carried no verdict map, each aspect already present in the re-keyed identity is recorded as approved — reproducing "the aspects effective at the last approve were approved." When the baseline already carried a verdict map (even an empty one), it is preserved verbatim; synthesis must never overwrite a recorded refusal or an intentional empty map. When a pre-verdict baseline has no per-aspect identity at all, the verdict map is left empty and a warning is emitted, because there is nothing to mark approved.

### Safe degradation

A baseline that cannot be parsed or cannot be reshaped is deleted rather than partially rewritten, and a warning names the file. The owning node then surfaces as drift so the adopter re-approves exactly that one node. A half-transformed baseline must never be written, because the runtime read path rejects malformed current-format baselines and would break the build mid-upgrade.

### Idempotency

A baseline already at the current schema is owned by the runtime and is skipped untouched, so re-running the upgrade (including after an interruption) changes nothing. The reshape transform only understands the old shape, so re-applying it to an already-typed baseline would be meaningless; the schema-version guard prevents that.

The reshape transform itself stays a pure, I/O-free helper; the approved-synthesis, the corrupt-file deletion, and the version-bookkeeping all live here at the migration boundary, where warnings withhold the version bump until the adopter has re-approved any nodes whose baselines could not be carried forward.
## [2026-06-01T07:57:57.299Z]
Removed a dead try/catch that wrapped the drift-state baseline scan during the 5.0 migration. The scan helper already returns an empty list when the drift-state directory is absent, so the surrounding catch for the not-found case could never fire; the scan now runs directly. This migration does not, and never did, write the project version itself — version bookkeeping is delegated to the migration runner, which is the sole writer of the version field. The migration only signals completion (or withholds it via the no-bump flag when it emits recoverable warnings the user must fix first).
