Summary
The IDS registry hooks rewrite .aiox-core/data/entity-registry.yaml on every commit and every push, wiping the usedBy reverse-dependency graph for entities the commit never touched. A commit touching 12 agent files silently dropped 191 of 542 entities' consumer lists — 35% of the graph — leaving usedBy: [].
The corrupted file is left unstaged in the working tree, so it is easy to sweep into the next git add -A without anyone noticing.
Reproduction
Deterministic, on a clean tree at any commit that touches registered entities:
# count entities carrying a non-empty usedBy
node -e "const y=require('js-yaml'),f=require('fs');const r=y.load(f.readFileSync('.aiox-core/data/entity-registry.yaml','utf8'));let t=0,n=0;for(const c of Object.values(r.entities))for(const e of Object.values(c)){t++;if((e.usedBy||[]).length)n++}console.log(t,n)"
# → 821 542
node .aiox-core/hooks/ids-post-commit.js
# → [IDS-Hook] Registry updated: 12 entities processed.
# same count again
# → 821 351
git diff --stat .aiox-core/data/entity-registry.yaml
# → 1 file changed, 230 insertions(+), 818 deletions(-)
.husky/pre-push → ids-pre-push.js reproduces it a second time, so a commit-then-push cycle corrupts the registry twice.
Sample of what is lost:
- usedBy:
- - aiox-master
- - analyst
+ usedBy: []
Root cause
Two problems compound in .aiox-core/core/ids/registry-updater.js:
1. The rebuild is global, but the trigger is incremental. _resolveAllUsedBy() (L632-639) resets usedBy on every entity in the registry, then recomputes the whole reverse graph from entity.dependencies via resolveUsedBy() (.aiox-core/development/scripts/populate-entity-registry.js L609-633). Any usedBy edge that is not derivable from a currently-resolvable dependencies entry is gone — even for the ~809 entities the commit never touched.
2. The second call discards the enrichment the comment says it is preserving. In processChanges() (L301-312):
this._resolveAllUsedBy(registry);
this._refreshDerivedDependencyFields(registry, changedEntities);
// NOG-8: Apply code intelligence enrichment AFTER resolveAllUsedBy
// so that code-intel usedBy data is merged on top of static graph
await this._applyCodeIntelEnrichments(registry);
this._resolveAllUsedBy(registry); // <-- wipes what line above just merged
this._refreshDerivedDependencyFields(registry, changedEntities);
_applyCodeIntelEnrichments() merges code-intel usedBy (L490-492), and the very next line resets usedBy = [] across the board and rebuilds from the static graph only. The stated intent of the NOG-8 comment is inverted by the call that follows it. On a host where the code-intel providers are unavailable the enrichment contributes nothing anyway, so the static rebuild is all that survives.
Impact
The versioned registry is the input to IDS impact analysis and gates G1-G6 (.claude/rules/ids-principles.md). With usedBy emptied:
*ids impact {entity} reports no consumers, so the usedBy BFS traversal returns nothing and ADAPT changes look consumer-free;
- G3/G5 duplication and reuse checks lose the signal they rely on;
- the damage is cumulative and committed by whoever runs
git add -A next, so it propagates to every clone.
Workaround
After any commit or push in this repo:
git checkout -- .aiox-core/data/entity-registry.yaml
Note that the same hook run also regenerates and stages .aiox-core/install-manifest.yaml — that part is correct and should be kept. Only the registry diff needs discarding.
Suggested direction (not implemented here)
- Drop the second
_resolveAllUsedBy() call at L308 so the code-intel merge survives, or make the enrichment run last.
- Make the reset incremental: instead of clearing every entity, clear only edges pointing at
changedEntities — the hook already computes that list and passes it to _refreshDerivedDependencyFields().
- Add a guard that refuses to write when the rebuild would drop more than N% of existing edges — a silent 35% loss should not be writable.
- Consider whether a post-commit hook should mutate a versioned file at all; a check that fails loudly may be safer than a rewrite that succeeds quietly.
Environment
🤖 Generated with Claude Code
Summary
The IDS registry hooks rewrite
.aiox-core/data/entity-registry.yamlon every commit and every push, wiping theusedByreverse-dependency graph for entities the commit never touched. A commit touching 12 agent files silently dropped 191 of 542 entities' consumer lists — 35% of the graph — leavingusedBy: [].The corrupted file is left unstaged in the working tree, so it is easy to sweep into the next
git add -Awithout anyone noticing.Reproduction
Deterministic, on a clean tree at any commit that touches registered entities:
.husky/pre-push→ids-pre-push.jsreproduces it a second time, so a commit-then-push cycle corrupts the registry twice.Sample of what is lost:
Root cause
Two problems compound in
.aiox-core/core/ids/registry-updater.js:1. The rebuild is global, but the trigger is incremental.
_resolveAllUsedBy()(L632-639) resetsusedByon every entity in the registry, then recomputes the whole reverse graph fromentity.dependenciesviaresolveUsedBy()(.aiox-core/development/scripts/populate-entity-registry.jsL609-633). AnyusedByedge that is not derivable from a currently-resolvabledependenciesentry is gone — even for the ~809 entities the commit never touched.2. The second call discards the enrichment the comment says it is preserving. In
processChanges()(L301-312):_applyCodeIntelEnrichments()merges code-intelusedBy(L490-492), and the very next line resetsusedBy = []across the board and rebuilds from the static graph only. The stated intent of the NOG-8 comment is inverted by the call that follows it. On a host where the code-intel providers are unavailable the enrichment contributes nothing anyway, so the static rebuild is all that survives.Impact
The versioned registry is the input to IDS impact analysis and gates G1-G6 (
.claude/rules/ids-principles.md). WithusedByemptied:*ids impact {entity}reports no consumers, so theusedByBFS traversal returns nothing and ADAPT changes look consumer-free;git add -Anext, so it propagates to every clone.Workaround
After any commit or push in this repo:
Note that the same hook run also regenerates and stages
.aiox-core/install-manifest.yaml— that part is correct and should be kept. Only the registry diff needs discarding.Suggested direction (not implemented here)
_resolveAllUsedBy()call at L308 so the code-intel merge survives, or make the enrichment run last.changedEntities— the hook already computes that list and passes it to_refreshDerivedDependencyFields().Environment
aiox-corev5.2.9,main@77265d53🤖 Generated with Claude Code