## [2026-05-15T10:17:15.671Z]
Fix deterministic violation: replace toLocaleString() with direct number interpolation — locale-independent output
## [2026-05-15T13:42:55.515Z]
R0.1 Phase 2: populate messageData alongside message at each buildIssueMessage call site. Structured IssueMessage now carried in CheckIssue; message string computed from it for backward compat.
## [2026-05-15T13:45:43.238Z]
R0.1 Phase 3: cli/check.ts now reads messageData via msg() helper to render issue output; buildIssueMessage called at CLI layer. Fallback to .message for issues not yet migrated in Phase 4.
## [2026-05-15T14:17:01.394Z]
Drop deprecated message field and buildIssueMessage import: CheckIssue objects now set only messageData; buildIssueMessage import removed from core layer. R0.1 Phase 5.
## [2026-05-16T04:54:08.442Z]
Remove dead flow-related branches: layer === 'flows' check in describeUpstreamCause and flowMatch block in groupCascadeByCause — flow YAML is no longer tracked, so these paths are unreachable. Also remove 'flow: --flow' from flagMap in computeSuggestedNext.
## [2026-05-16T05:58:05.490Z]
Phase 4.7 (no-direct-fs): route all fs calls through io/graph-fs.ts; update hash and paths imports from utils/ to io/ following module moves
## [2026-05-16T19:31:38.898Z]
Updated effective-aspects import path to core/graph/aspects following the file move.
## [2026-05-16T19:44:32.245Z]
Updated context-files import path to core/graph/files following the file move (collectTrackedFiles + TrackedFile). cli/core/context node drops context-files from its mapping; cli/core/graph node claims it.
## [2026-05-26T10:11:38.382Z]
Add comment explaining smaller STRUCTURAL_CODES set is internal-filter only, not CI blocking. The two sets have diverged historically; aligning them is tracked as dogfood cleanup, not in this change.
## [2026-05-28T10:37:57.697Z]
Drift cascade formatter now distinguishes reference-file changes from aspect-content changes. In describeCascadeCause, when a tracked file in the 'aspects' layer does not match the .yggdrasil/aspects/<id>/ path prefix, the file is a reference declared in some aspect's references: list. The formatter scans graph.aspects to find the declaring aspect(s) and emits 'reference file X (declared by aspect Y) changed' instead of the previous generic 'tracked file changed' fallback. This makes upstream-drift causes self-explanatory in yg check output for the new reference cascade source.
## [2026-05-28T13:13:47.979Z]
Render aspect-status by status: check now emits aspect-newly-active for non-draft effective aspects without a baseline verdict, aspect-violation-enforced (error) and aspect-violation-advisory (warning) for refused baselines. Per-node short-circuit moved from computeEffectiveAspects to hasNonDraftEffectiveAspects so all-draft nodes skip drift detection without spurious findings. GC predicate aligned with approve: drift state for nodes with no non-draft effective aspects is removed silently. Legacy baselines lacking aspectVerdicts are tolerated as 'implicitly approved' so the 5.x upgrade does not flood the user with aspect-newly-active. CheckResult adds advisoryWarnings and draftSkipped tallies; suggestedNext prefers an error's next over a warning's so an enforced violation outranks a co-emitted advisory.
## [2026-05-29T09:56:03.437Z]
check.ts core orchestrator updated to produce structured CheckResult output consumed by the new renderer. The grouped rendering logic was separated from the orchestration logic in this refactor.
## [2026-05-29T09:56:51.359Z]
Changed countDraftAspectsAcrossGraph to count UNIQUE draft aspect IDs (aspects whose aspect-level default is draft) rather than counting (node × aspect) pairs. The old count inflated the tally by multiplying one draft aspect across all nodes it reached via cascade channels. The new count reflects how many distinct aspect rules are dormant, which is the number that actually matters when an agent is assessing how much coverage is inactive.
## [2026-05-29T10:07:14.007Z]
core/check.ts updated: the check orchestrator now returns a structured CheckResult type with typed issue codes and cascadeCauses data, consumed by the new grouped check renderer.
## [2026-05-29T17:08:10.071Z]
Cross-node files that a structure-shape rule reads through a related component now count toward the dependent component's verification identity and its change-impact blast radius. Previously such a rule could inspect a sibling component's file, but the set of files brought into the dependent component's drift fingerprint was only assembled when the stored approval baseline was supplied to the file-collection routine — and the routine that gates everyday drift checks did not supply it. The consequence was a silent gap: editing a file that only a neighbour's structure rule reads never marked the dependent component as needing re-verification, and the change-impact query reported zero affected components for that file. The baseline is now supplied at the drift-detection points, and the canonical fingerprint is recomputed after the structure runner records the touched files, so the first approval already folds those cross-component files into the identity. Files the component owns directly stay classified as ordinary source changes; only genuinely cross-component paths receive the dedicated layer, and they are deliberately excluded from the owned-file map so a deleted-file scan never mistakes them for a vanished local file. A targeted single-rule re-approval now carries forward a sibling rule's recorded touched-file set instead of dropping it.
## [2026-05-30T04:57:35.096Z]
Removed leaked internal planning references — task, phase, and decision identifiers and section citations — from source-code comments. These tokens named ephemeral planning artifacts that mean nothing to a future reader and violated the project's comment self-containment rule. The substantive explanation each comment carried was preserved; no code or behavior changed.
## [2026-05-30T06:29:17.474Z]
Stop maintaining a private copy of the drift-layer lookup; consume the shared classifier instead.

The gate that reports drift must decide, for each changed file, whether the change is a direct source edit or an upstream cascade from a graph element. That decision previously relied on a lookup table built inline here, duplicated almost verbatim in the approve path. Keeping two copies invited them to diverge — and they had, in how a file reached through a directory-style mapping was resolved. Delegating to a single shared classifier removes that hazard: both the report and the approve step now answer the question the same way, so a file is never labeled a source edit by one and a cascade by the other.
## [2026-05-30T21:05:41.490Z]
Drift attribution was sharpened so a dependent node is no longer fully re-reviewed when it drifts only because a cross-node file read by one of its graph-aware deterministic checks changed. Previously such a change was un-attributable to any specific check, so the whole node — including its human-language reviewer aspects — was conservatively re-verified; that re-verification is paid and non-deterministic, so it produced flaky, costly churn on nodes whose own code never changed. Now the change is attributed to the specific deterministic check that reads the file (via the per-node record of which files each check touched), so only that local, free check re-runs and the human-reviewer verdicts carry forward unchanged. The motivation is correctness-preserving cost control: a check that only reads cross-node files cannot affect what a human-language reviewer sees, so re-running it alone is exactly right.

Specific to this node: the drift report that explains why a cascade fired now names the owning check for the synthetic per-check identity causes (the touched-file-set summary and the resolved-reviewer-tier summary), instead of attributing them to an unknown source.
## [2026-05-30T21:30:16.991Z]
The helper that turns a drift cause into a human-readable explanation was made reachable from tests so that every branch of its message logic can be verified directly — the messages that name an aspect, the messages for the synthetic per-aspect identity causes, the reference-file attribution (declared by zero, one, or several aspects), the parent and dependency cause messages, and the generic tracked-file fallback. The motivation is that these messages are the agent's only explanation of why a re-verification was triggered, so each variant must be pinned by a test; exposing the pure function is the least intrusive way to cover its branches exhaustively without constructing a full drift pipeline per case.
## [2026-05-30T21:54:03.457Z]
Internal names that still referred to the retired single-file and graph-aware reviewer kinds were renamed to reflect the one unified deterministic kind: the per-aspect record of which files a deterministic check read, the synthetic drift-identity key summarising that set, the tracking layer those cross-node files sit on, and the error tag for a crash inside a non-reviewer (deterministic) runner. This is a clarity-only rename with no behaviour change — the verification mechanics, drift detection, and carry-forward are identical; only the identifiers and the persisted token strings changed so the code no longer speaks of reviewer kinds that no longer exist. Baselines were migrated in place to the new token names.
## [2026-05-30T22:03:14.858Z]
The loop that reports files which existed at the last approval but are gone now was computing a path with host separators normalised to forward slashes, yet emitting the un-normalised original into the agent-visible drift report — both the 'deleted' lines and the cascade-cause entries. On a host whose native separator is the backslash this would have leaked backslash paths into output that must always be POSIX, so the report could read inconsistently across platforms. Every classification lookup and output string in that loop now uses the normalised path. The motivation is the cross-platform output contract: the drift report an agent reads must use forward slashes regardless of the host.
## [2026-05-31T06:24:49.484Z]
An internal name was changed for clarity: the per-aspect record of files a deterministic check read, its synthetic drift-identity key, and the tracking layer for those files were renamed from a deterministic-prefixed form to a check-prefixed form, so the identifiers name the check.mjs artifact they describe. This is a clarity-only rename with no behaviour change — drift detection, attribution, and carry-forward are identical; only the identifiers and the persisted token strings changed, and in-repo baselines were migrated in place to the new tokens.

Additionally: Two agent-facing diagnostics were corrected. A multi-node cascade target list rendered with a doubled path separator; it now renders with a single separator. And a configuration whose schema version is newer than this CLI supports was being wrapped as an internal bug with a file-an-issue prompt; it is now classified as the expected user error it is — telling the user to upgrade the CLI — without the bug framing. Neither changes the underlying detection; both make the output read correctly.
## [2026-05-31T07:08:06.001Z]
An aspect-implies cycle introduced after a node already had an approved baseline crashed the check command with an unclassified internal error instead of the structured cycle error the validator already produces. The drift pass computed effective aspects for a node with a baseline and the implies expansion threw on the cycle, escaping before the validator's clean message could render — so the same cycle reported nicely without a baseline but crashed with one. The cycle signal is now a dedicated error type, and the drift pass skips a node whose effective-aspect computation hits a cycle, since the graph is already invalid and the validator reports it. The check command now reports the same structured cycle error whether or not a baseline exists. The motivation is that a detectable graph error must always surface as a clean, actionable diagnostic, never as a please-file-an-issue crash.
## [2026-05-31T09:10:41.976Z]
Improved the wording of several re-verification cause messages that previously rendered an unhelpful placeholder. When a change propagates to a component because of its own definition, because a dependency's published contract shifted, or because a file that one of its programmable checks reads was edited, the explanation shown to the agent now names the actual cause instead of an empty or 'unknown' label. Accurate cause messages matter because the whole point of surfacing a re-verification is to tell the agent precisely what changed and which command resolves it; a placeholder forces guesswork and undermines that contract.
## [2026-05-31T09:59:01.843Z]
Named the rule in two re-verification messages and attached the rule identifier to the just-became-active finding. When a component must re-verify because a rule's definition metadata changed, the explanation now reads as that rule's definition changing rather than referencing an opaque internal key; and a finding that a rule has newly become active on a component now carries which rule it concerns as structured data, so the batch-by-rule tooling can attribute it precisely instead of parsing prose.
## [2026-05-31T16:03:32.677Z]
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-31T16:28:48.517Z]
Two related correctness improvements to the validation layer. First, the set of structural error codes — the codes that always block the check regardless of drift — had been hard-coded in two places, once for the summary tally and once for the rendered grouping, and the two had silently diverged so the count could disagree with what was shown to the agent. They now share a single definition, so the tally and the grouping can never drift apart again. Second, a new validation rejects a node mapping that resolves outside the repository root — an absolute path, or one that climbs above the root with a parent-directory segment. The mapping-path normalizer only converts separators and strips a leading current-directory prefix and trailing slashes; it does not collapse parent-directory segments, so without this guard a mapping could claim files outside the project and bypass coverage and aspect enforcement.
## [2026-06-01T06:20:25.280Z]
Moved the drift-cause description and identity-diff helpers out of this node into a dedicated sibling module, importing them back. This was required because the check engine had grown past the per-node reviewer-context budget once the drift baseline gained a typed upstream-identity block; an oversized reviewer prompt risks window truncation that falsely rejects unchanged code. Pure code move, no behavior change — the same cascade causes are still classified and rendered identically.
## [2026-06-01T07:03:30.364Z]
Removed the now-unnecessary second argument (graph) from the call to describeIdentityCause after the function signature was simplified to take only the cause. The function never used graph; the parameter existed as a speculative placeholder and was removed per YAGNI. The call site is the only caller in production code and required only the argument-count update.
## [2026-06-03T07:05:08.274Z]
Check now surfaces the new aggregating-aspect kind and the zero-source fail-closed condition alongside existing drift and coverage reporting, so an aggregate attach point and an LLM node that can never be verified are both visible in the gate rather than passing silently.
## [2026-06-03T07:49:05.037Z]
yg check now passes reuseByMtime=false to hashTrackedFiles so the gate always re-reads content from disk. Previously, a stored mtime match caused the stored hash to be reused without verifying the file's actual content, allowing a content edit followed by touch -r to restore the mtime and pass the gate undetected.
## [2026-06-03T08:20:09.656Z]
The drift gate recomputes a node's canonical baseline hash (which folds source files, typed identity, and per-aspect verdicts) and previously only reported a divergence it could attribute to a changed source file or a graph-identity change. A divergence whose only cause was the folded verdict set — for example a committed verdict hand-edited from refused to approved while the recorded hash was left untouched — matched neither path, so the computed mismatch was silently discarded and the gate passed clean over a baseline the reviewer never re-validated. Any unexplained hash mismatch now blocks: when the recompute disagrees with the recorded hash but no file or identity cause is found, a blocking integrity error is emitted. The same condition also catches a baseline that predates a hash-scheme change; both resolve by re-approving the node or restoring its drift-state from version control, so the single signal serves both without false positives on a consistent baseline.
## [2026-06-08T07:35:25.087Z]
The three pure coverage-tier helpers (normalizeRoot, matchesRoot, partitionByCoverageTier) were extracted to a sibling module (check-coverage-tiers.ts) rather than remaining inline in check.ts. check.ts re-exports all three so the public API is unchanged. The extraction was required because adding the helpers pushed check.ts past the 40,000-character per-node reviewer budget, and the helpers form a self-contained logical unit with no I/O or side effects that belongs in its own node. check.ts now has a single re-export line in place of the inline implementations.
## [2026-06-08T07:50:36.737Z]
Applied nested-graph subtree exclusion to the coverage scan and source-count computation in check.ts. Imported excludeNestedGraphSubtrees from the repo-scanner module and applied it at the top of scanUncoveredFiles (on the gitTrackedFiles list passed in) and in the runCheck source-count filter (wrapping gitTrackedFiles before the ygg-prefix predicate). Files under a nested .yggdrasil subtree are now silently dropped from both the total-file count and the uncovered-file list, so a parent graph no longer reports coverage gaps for code governed by a child graph.
## [2026-06-08T08:11:27.050Z]
Improved code clarity: added an idempotent comment before the scanUncoveredFiles call in runCheck to make it explicit that scanUncoveredFiles applies excludeNestedGraphSubtrees internally (the call is safe with a raw list because the filter is idempotent). Also updated the scanUncoveredFiles JSDoc to document that it excludes nested-graph subtrees in addition to the bound graph's own .yggdrasil/, matching the actual behavior introduced by the nested-graph subtree filtering.
## [2026-06-08T08:23:53.416Z]
Wired tiered coverage into runCheck: instead of a single unmapped-files error for all uncovered files, the coverage section now calls partitionByCoverageTier to split uncovered files into a required tier (error) and a middle tier (non-blocking warning). The coverageIssue variable was replaced with a coverageIssues array built from buildCoverageIssue (required tier) and buildCoverageAdvisoryIssue (middle tier). The buildCoverageIssue function was moved out of check.ts to check-coverage-tiers.ts to keep check.ts within the 40,000-char node budget; it is re-exported from check.ts so the public API is unchanged. buildCoverageAdvisoryIssue was added to check-coverage-tiers.ts and re-exported the same way. The default config (coverage.required: ['/']) preserves the existing behavior where every uncovered file is an error.
## [2026-06-08T08:36:09.244Z]
Eliminated a duplicated inline literal for the default coverage config. The inline fallback '{ required: ["/"], excluded: [] }' in runCheck was replaced with an import of DEFAULT_COVERAGE from config-parser, making config-parser the single source of truth for that constant. Also removed the dead second argument from the buildCoverageAdvisoryIssue call site (the function no longer accepts totalGitFiles). Added a clarifying comment above the advisoryWarnings tally to document that it counts only aspect-status advisories, not the coverage uncovered-advisory warning.
## [2026-06-08T09:59:38.342Z]
Fixed coveredFiles metric computation order (Fix 1): moved the coveredFiles assignment after partitionByCoverageTier so excluded-tier files no longer depress the coverage ratio. Before this fix, coveredFiles = totalFiles - uncovered.length subtracted ALL uncovered files including silently-excluded ones; the correct formula is totalFiles - (required.length + middle.length). Excluded files are intentionally ignored and must not affect the ratio shown in the header.
## [2026-06-08T15:11:09.287Z]
The uncovered-file coverage scan now treats a file as covered when any node mapping entry matches it by glob pattern, not only by exact path or directory prefix. This keeps the coverage gate aligned with the new ability to scope a node to a subset of a directory by naming pattern: files matching the pattern are owned and silent, files in the same directory that do not match remain uncovered and surface as expected.
## [2026-06-08T16:40:57.639Z]
Source-existence detection for drift now expands a glob mapping entry and treats it as present when it matches at least one file on disk, instead of probing the literal pattern string. Probing the pattern string always failed, so a node mapped only by a glob falsely reported that its source files were never created and was treated as source-gone.
## [2026-06-08T17:36:20.324Z]
Corrected an inline comment in drift classification that mis-stated where implies-cycle rejection originates. The status-aspect computation converges (does not throw) on a cyclic graph by design, so the cycle error caught during per-node drift classification comes from the effective-aspects traversal reached via tracked-file collection, not from the status computation.
## [2026-06-12T13:31:32.574Z]
Removed the local private copy of getChildMappingExclusions. The function is now imported from core/pairs.ts, which is the single canonical implementation of the child-wins carve-out model. The prior duplication between approve.ts (exported) and check.ts (private) meant two sites had to be kept in sync — a maintenance hazard that was masked by the approve.ts v8 ignore annotation pointing at check.ts as the authoritative test location. Now both consumers share one tested implementation.
## [2026-06-13T03:12:07.904Z]
The gate is a read that never mutates state: it computes which rules are expected on which review units, validates the recorded verdicts against current inputs, runs deterministic checks live, and reports — so continuous integration can verify the codebase against its architecture without provider credentials or model calls.
## [2026-06-13T08:59:32.963Z]
The coverage scan decided a file was 'covered' if it merely matched a node's mapping pattern, ignoring that the file-expansion layer drops gitignored files reached through directory or glob mapping entries. A file that is both git-tracked and matches a gitignore pattern — legal in git when a file is force-added or a gitignore rule is added after the file was already tracked — was therefore counted as covered yet excluded from every review subject set, so an enforced rule passed over it with no reviewer ever seeing it: a false green. The scan now additionally flags, as a blocking error, any git-tracked file that is matched by a mapping, is gitignored, and is not rescued by a directly-named single-file mapping entry (direct entries bypass gitignore). This is purely additive — it does not change which files are expanded; it only refuses to call a silently-dropped tracked file 'covered'.
