## [2026-05-16T19:18:57.383Z]
Initial population: pure graph-query helpers extracted from context-builder and when-evaluator. Reason: establish a canonical home so future graph queries land in one place. Three helpers move here in this task — traversal (collectAncestors, collectDescendants), flows (collectParticipatingFlows), dependencies (collectDependencyAncestors + DependencyAncestorInfo). Two more files move in subsequent tasks (effective-aspects, context-files). Aspect locking the location is added after the moves.
## [2026-05-16T19:31:38.504Z]
Added aspects.ts (computeEffectiveAspects + getAspectSource). Previously in core/effective-aspects.ts; moved here in this task and exported through the barrel. dependencies.ts updated to import from sibling. Internal imports rewired (../model -> ../../model, ./when-evaluator -> ../when-evaluator, etc).
## [2026-05-16T19:44:31.717Z]
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-26T09:35:08.837Z]
Add language-registry.ts as phase 1 stub. Three languages with extension mapping, comment types, override getter. Pure data + pure functions per engine aspect compliance. Layering pin: validator imports from this module (precedent via core/graph/aspects.js). Phase 3 expands to 35 tier 1 languages, populates grammar pins.
## [2026-05-26T10:44:58.503Z]
Rewrote aspect single-source-graph-queries against raw tree-sitter API. Verified behavior-identical via ast-test diff.
## [2026-05-27T10:20:09.808Z]
Added tier-identity synthetic hash entries in collectTrackedFiles: for each LLM aspect effective on a node, a tier-identity:<aspectId> entry is folded into the drift hash. Changing the resolved tier config (model, provider, consensus, temperature) now triggers source drift for all affected nodes, ensuring re-approval after tier configuration changes.
## [2026-05-27T13:54:36.655Z]
Renamed the synthetic drift-hash entry used when an LLM aspect has no resolved reviewer configuration from a schema-versioned marker to a behaviour-named one. The marker semantics remain identical (the synthetic hash is consumed only by collectTrackedFiles); the rename removes a stray schema-version label from runtime code so only the migration module knows about historic schemas.
## [2026-05-28T08:12:43.196Z]
Extended collectTrackedFiles to include aspect references in drift tracking. LLM aspects may declare references (supporting lookup files) that the reviewer reads during approval. These files must now participate in drift detection so that a change to a reference file triggers re-approval of all affected nodes. The implementation computes the node mapping path set once (reused for both the SOURCE step and the new reference-skip guard) and, inside the LLM aspect branch, iterates aspect.references, adding each path as graph/aspects unless that path already belongs to the node own mapping (where the SOURCE step takes precedence). AST aspects are excluded by the existing reviewer.type !== llm guard, which also prevents any stray references field on AST aspects from leaking into tracking.
## [2026-05-28T11:52:27.600Z]
Add computeEffectiveAspectStatuses — phase-1 resolver that walks aspect-status channels 1 through 6 (own, ancestor, own-architecture-type, ancestor-architecture-type, flow, port) and returns a Map<aspectId, status> using strictest-wins across channels. Each attach site may declare an explicit status that overrides the aspect's default. Implies expansion (phase-2) is intentionally deferred to a later task; this function intentionally runs in parallel to computeEffectiveAspects rather than replacing it, so the existing id-only consumers remain undisturbed.
## [2026-05-28T12:01:15.895Z]
Added getAspectStatusSources (per-channel provenance walker covering channels 1-6) and hasNonDraftEffectiveAspects (single-source helper) to the aspect resolver. AttachSource is exported as a new public type. These helpers are consumed downstream by validator downgrade detection, approve/check short-circuits, and the yg context/impact display paths. Channel 7 (implies) is intentionally excluded from AttachSource because implies is structural propagation, not a direct attach declaration.
## [2026-05-28T12:09:59.351Z]
Added phase-2 implies fix-point resolution to computeEffectiveAspectStatuses. The status-aware effective-aspects pass now propagates statuses through implies edges using a monotone (max-only) fix-point. Two propagation behaviors are honored per implies edge: 'strictest' (default) takes the maximum of the implier's effective status and the implied aspect's own default; 'own-default' always uses the implied aspect's own default regardless of the implier's status. Aspects with effective status 'draft' do not propagate at all — they are treated as inactive carriers, so an implied aspect reached only via a draft implier does not appear in the result. The loop is bounded by aspects.length+1 iterations and throws if it overshoots, providing a cycle/non-convergence guard distinct from the existing acyclic check in expandImpliesFiltered (which serves the parallel id-only path). Per-edge and global 'when' predicates filter implied entries the same way they filter direct contributions in phase-1.
## [2026-05-29T07:17:58.084Z]
Added structure-identity synthetic hash entry inside the aspect loop for structure-type aspects, mirroring the existing tier-identity pattern for LLM aspects. The hash captures kind and language so a language-list change triggers drift while a status toggle does not. Also added a baseline-driven structure-touched block after the aspect loop: when the caller passes a stored drift baseline, the map of per-aspect touched files is injected as 'structure-touched' layer entries plus a path-set digest synthetic hash, enabling drift to fire when the set of files observed by a structure aspect changes between runs. The collectTrackedFiles signature gains an optional third parameter (baseline?: DriftNodeState); all existing call sites continue to work unchanged.
## [2026-05-29T17:08:10.235Z]
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-29T17:58:58.554Z]
Absorbed the blast-radius and reverse-dependency graph algorithms (direct/transitive reverse dependents, indirect-dependent chains, and the cross-component structure-read cascade scan) that previously lived inside the change-impact command handler. These are pure derivations over the loaded graph with no presentation concerns, so their canonical home is the side-effect-free graph-query layer alongside ancestor/descendant traversal, flow membership, and dependency views. The cross-component cascade scan reads each component's stored approval baseline through the persistence adapter to decide precise versus potential reads; this filesystem read verifies graph-declared state and stays deterministic relative to that state. Iteration and sort order were preserved so hashed and rendered outputs remain byte-stable.
## [2026-05-30T04:16:52.732Z]
Made the mapping-ownership check used during drift classification prefix-aware so a path located inside a directory mapping is recognized as owned by that node, not as an upstream dependency. Previously ownership was tested by exact path equality, so an aspect-reference file or a structure-touched path sitting under a directory mapping was misclassified as upstream drift; that let an edit to an own source file be recorded as a cascade and skip the requirement to record a justification for the change. Both ownership guards now share a single prefix-aware helper.
## [2026-05-30T04:57:35.253Z]
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-30T05:28:34.848Z]
Unified the aspect-cascade channel walk. The four cascade functions (effective-aspect set, effective-status map, single-source provenance, and per-channel status provenance) previously each re-walked the same six attachment channels — own, ancestor node, own architecture type, ancestor architecture type, flow, and consumed port — with subtly different policies. They now all reduce over one generator that yields each attachment once in channel order, while each function keeps its own applicability-predicate policy (the human-readable provenance deliberately ignores the predicate; the others filter by it), aggregation, and origin formatting. This removes the duplicated traversal that had drifted between the functions and makes channel order and membership defined in exactly one place; behavior is unchanged, locked by a characterization snapshot over a graph exercising every channel plus implication, predicate filtering, status overrides, and the via-parent flow case.
## [2026-05-30T06:29:11.817Z]
Centralize drift-layer classification so the check path and the approve path cannot disagree about what a changed file means.

Whether a modified file counts as a direct source change or as an upstream cascade decides two things: whether a fresh business-context log entry is required, and whether the file is re-reviewed against its aspects or merely re-hashed. Those two consumers each carried their own copy of the path-to-layer lookup, and the copies had drifted: one expanded directory-style mappings into their contained files, the other did not. A divergence there silently mislabels an edit — skipping a required re-review or forcing an unnecessary one. The classifier now lives in one place that both consumers call, with directory-mapping expansion handled identically. Exact-path matches win over directory-prefix matches, and the first layer recorded for a path wins, mirroring how the tracked-file set itself is assembled.
## [2026-05-30T14:09:01.566Z]
The per-aspect language declaration was removed from the system. An aspect that ships a parsed-tree check used to carry a `language:` list naming the languages it targeted, and the system validated that list with four dedicated checks. Nothing in the runtime ever read it: the engine already determines each source file's language from its file extension through one shared registry, then loads the matching grammar. The declaration was therefore inert metadata that could silently disagree with what the engine actually parsed — an authoritative-looking field that governed nothing.

This change deletes the field, the validators that policed its shape, and the documentation and tests that described it, and promotes the extension-to-grammar registry to the single authority for matching a file to its parser. The motivation is to remove a confusing mismatch between what an aspect claimed about languages and what the engine did, and to collapse the duplicated extension-knowledge that had drifted into more than one place.

The language a parsed-tree check sees for a given file is now derived solely from that file's extension; an unrecognized extension yields no parsed tree rather than a per-aspect error. The drift identity of a graph-aware structural check is deliberately held stable across this change so existing approvals are not invalidated by metadata that never affected behavior.
## [2026-05-30T15:43:16.515Z]
Re-verification was made selective. When a component needs re-approval because one of its rules changed, only that rule is re-checked and the component's other rules keep their prior verdict — instead of re-checking every rule on the component on every re-approval. The motivation is cost: re-checking a rule against a non-deterministic reviewer is the expensive operation, and a change to one rule's definition (or a cheap deterministic identity shift) previously forced a full, repeated re-review of every rule on every affected component, including expensive rules that could not possibly have been affected by that change.

A source-code edit still re-checks every rule on that component, because every rule reads all of the component's source — that case is component-global by nature. The optimization applies only to changes cleanly attributable to a specific rule (a change to that rule's own definition, content, reference material, or identity). Any change that cannot be attributed to a single rule conservatively re-checks everything, so a documented verdict is never silently preserved when the thing it judged has actually moved. A re-approval where nothing attributable changed performs no reviewer work at all and leaves every prior verdict intact.

This is the enabler for an upcoming change that re-stamps the identity of a large set of rules at once: without selective re-verification that re-stamp would have forced a full, expensive re-review across nearly the whole graph; with it, each re-stamped rule re-checks for free and the components' unrelated expensive verdicts carry forward.
## [2026-05-30T18:08:09.776Z]
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-30T20:06:10.407Z]
The way a rule's verification is declared collapsed from three kinds — a human-language reviewer, a single-file programmable check, and a graph-aware programmable check — down to two: the human-language reviewer and one unified deterministic programmable check. The two programmable kinds were never a real choice, since the graph-aware kind is a superset of the single-file one; keeping both forced authors into a false up-front decision and made the tooling carry two parallel surfaces for one concept. Collapsing them removes that false choice. This change consolidates the remaining user-facing surface that still exposed the old split.

Specific to this node: a synthetic drift-identity key that existed only for the former graph-aware kind became dead once the two programmable kinds merged and was removed; persisted baseline tokens that still carry the historical kind names are documented as retained for baseline compatibility rather than renamed, because renaming them would invalidate every stored baseline with no migration path.
## [2026-05-30T21:54:03.613Z]
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-31T06:24:46.315Z]
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.
## [2026-05-31T07:08:06.181Z]
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:42.155Z]
Fixed a correctness gap where a dormant rule that points at another rule would still drag that second rule into force on a component. A rule in the draft phase is meant to be completely inert — it is skipped by the reviewer and contributes nothing — but the routine that computes which rules apply to a component was following the dormant rule's onward references and pulling the referenced rule into the active set, where it could then block acceptance. The routine that computes enforcement levels already treated a dormant source as inert; this aligns the set computation with it, so a dormant rule neither acts itself nor activates anything it references. A referenced rule still applies whenever it is reached through any non-dormant path.
## [2026-05-31T09:59:01.674Z]
Made a rule's enforcement level not count toward the drift fingerprint. Previously the entire rule-definition file, including the line stating its enforcement level, was hashed into each component's drift fingerprint, so merely changing a rule from advisory to enforced (or back) made every component using that rule look changed and demand re-verification — even though the rule's actual content and the component's code were untouched. The fingerprint now covers the rule's definition with the enforcement level excluded, so a level change is correctly treated as not-a-change for drift purposes: the recorded verdict carries forward and only how it is displayed changes. A genuine content change to the rule still re-fingerprints and cascades as before, and promoting a rule out of its dormant phase is still surfaced through the separate not-yet-verified signal.
## [2026-05-31T10:05:51.504Z]
Condensed several explanatory comments in the drift-tracking module so the component stays within the reviewer's context-size budget after the enforcement-level-stability change added code. No behavior change — the comments were shortened or made accurate to the new tracking (a rule's definition metadata, excluding its enforcement level, is what is fingerprinted), and stale references to the previously-tracked raw rule file were removed.
## [2026-05-31T10:10:32.074Z]
Made the impact-listing sort locale-independent. The function that lists impacted components ordered its results with a locale-aware string comparison, which depends on the runtime's default locale — implicit environment state — so the same graph could produce different orderings on machines with different locale settings, and any consumer relying on that order (or on a hash of it) would see spurious differences across environments. The sort now uses a plain code-unit comparison, deterministic everywhere, consistent with every other sort in this module. Surfaced by the architecture reviewer enforcing the determinism rule on this engine.
## [2026-05-31T13:40:20.253Z]
The deterministic reviewer can now parse source files in many languages, not only the TypeScript and JavaScript family. The language registry — the single source of truth mapping a file extension to its tree-sitter grammar and to the node-type names and delimiters used for comment-based rules — was expanded to cover Python, Go, Rust, Java, C#, C, C++, PHP, Ruby, and JSON alongside the existing TypeScript, TSX, and JavaScript. Each grammar's comment node-type names and delimiters were determined by parsing a real sample with that grammar rather than assumed, because a wrong value would silently make comment-based rules and inline suppression markers stop working for that language while structural rules kept passing; Rust and Java expose separate line and block comment node types, PHP recognizes three comment styles, and JSON has none. Grammar version pinning for the determinism contract remains a later enhancement.
## [2026-05-31T16:03:33.361Z]
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-06-01T06:20:25.463Z]
Relocated the typed-identity diff and cause-token/layer helpers out of the file-tracking module into a dedicated sibling, importing what remains. The graph-query node had crossed the per-node reviewer-context budget after the drift baseline adopted a typed upstream-identity block; an oversized reviewer prompt risks context-window truncation that falsely rejects unchanged code. The remaining file-tracking code still builds the typed identity during context collection; only the diffing/description helpers moved. Pure code move, no behavior change.
## [2026-06-01T07:03:40.471Z]
Updated stale comments in impact-graph.ts that referred to the old flat checkTouchedFiles field. The comments now reference identity.aspects[id].checkTouched, which is the actual location in the typed drift-state schema. No behavior changed; this is a documentation-only fix to keep comments consistent with the data structure.
## [2026-06-03T07:05:21.855Z]
Effective-aspect computation now expands an aggregating aspect by attaching its implied children onto every node where the aggregate is effective, while the aggregate itself contributes no own reviewer and no own verdict. This lets one named attach point stand for a multi-rule contract backed by atomic child aspects.
## [2026-06-08T15:11:09.676Z]
Graph-level file ownership now matches mapping entries by glob pattern in addition to exact path and directory prefix. A node can therefore own precisely the files matching a naming pattern within a directory, which is needed when a rule should apply to only some files in a folder (for example repository classes but not co-located helpers) rather than the whole folder.
## [2026-06-08T17:36:19.914Z]
The effective-aspect-status computation has its iteration backstop documented honestly as a defensive non-convergence guard, not a cycle detector: the status fix-point is monotone (max over a finite lattice) and saturates rather than diverging on a cyclic graph, so a structural implies-cycle cannot be surfaced by exceeding the bound. This computation intentionally does NOT throw on a cycle, because it is invoked by validation-path checks that run before the authoritative cycle detector; rejecting the cyclic graph is that detector job. The parallel id-only effective-aspect computation does throw on a cycle, but only on the drift/approve paths whose callers wrap it. The two diverge on a structurally-invalid graph by design.
