Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: richer edge type inference reduces D-rate 61%→47%

inferEdgeType had gaps: CONTEXT↔non-REQ, INVARIANT↔CONSTRAINT, and all
same-type pairs fell through to 'relates_to'. Now covers all cross-type
pairs and uses tag overlap (≥50% containment) to infer 'refines' for
same-type pairs. Composite score: 0.8861→0.9061.

+30 -14
+1
experiments/results.tsv
··· 15 15 2026-03-26T22:23:04.147Z 0.8861 100.0 94.4 95.5 60.7 100.0 6.2 s0w5pd 16 16 2026-03-26T22:23:36.322Z 0.8791 100.0 91.7 95.5 60.7 100.0 6.2 rgwg4j 17 17 2026-03-26T22:23:50.652Z 0.8861 100.0 94.4 95.5 60.7 100.0 6.2 qwrndy 18 + 2026-03-26T22:46:38.390Z 0.9061 100.0 94.4 95.5 47.4 100.0 6.2 nq75ia
+26 -12
src/resolution.ts
··· 227 227 } 228 228 229 229 function inferEdgeType(from: CanonicalNode, to: CanonicalNode): EdgeType { 230 - // Constraint → Requirement = constrains 231 - if (from.type === CanonicalType.CONSTRAINT && to.type === CanonicalType.REQUIREMENT) return 'constrains'; 232 - if (from.type === CanonicalType.REQUIREMENT && to.type === CanonicalType.CONSTRAINT) return 'constrains'; 230 + const types = new Set([from.type, to.type]); 233 231 234 - // Invariant → Requirement = invariant_of 235 - if (from.type === CanonicalType.INVARIANT && to.type === CanonicalType.REQUIREMENT) return 'invariant_of'; 236 - if (from.type === CanonicalType.REQUIREMENT && to.type === CanonicalType.INVARIANT) return 'invariant_of'; 232 + // Definition ↔ anything = defines 233 + if (types.has(CanonicalType.DEFINITION)) return 'defines'; 237 234 238 - // Definition → anything = defines 239 - if (from.type === CanonicalType.DEFINITION) return 'defines'; 240 - if (to.type === CanonicalType.DEFINITION) return 'defines'; 235 + // Constraint ↔ Requirement = constrains 236 + if (types.has(CanonicalType.CONSTRAINT) && types.has(CanonicalType.REQUIREMENT)) return 'constrains'; 241 237 242 - // Context → Requirement = refines 243 - if (from.type === CanonicalType.CONTEXT && to.type === CanonicalType.REQUIREMENT) return 'refines'; 244 - if (from.type === CanonicalType.REQUIREMENT && to.type === CanonicalType.CONTEXT) return 'refines'; 238 + // Invariant ↔ Requirement = invariant_of 239 + if (types.has(CanonicalType.INVARIANT) && types.has(CanonicalType.REQUIREMENT)) return 'invariant_of'; 240 + 241 + // Invariant ↔ Constraint = constrains (invariants constrain constraints) 242 + if (types.has(CanonicalType.INVARIANT) && types.has(CanonicalType.CONSTRAINT)) return 'constrains'; 243 + 244 + // Context ↔ any non-context = refines (context provides framing) 245 + if (types.has(CanonicalType.CONTEXT) && types.size > 1) return 'refines'; 246 + 247 + // Same-type pairs: use tag overlap to distinguish refines vs relates_to 248 + if (from.type === to.type) { 249 + const fromTags = new Set(from.tags); 250 + const toTags = new Set(to.tags); 251 + let shared = 0; 252 + for (const t of fromTags) if (toTags.has(t)) shared++; 253 + const smaller = Math.min(fromTags.size, toTags.size); 254 + 255 + // If one node's tags are largely a subset of the other's, the smaller 256 + // one refines a broader concept — that's a refinement relationship 257 + if (smaller > 0 && shared / smaller >= 0.5) return 'refines'; 258 + } 245 259 246 260 return 'relates_to'; 247 261 }
+3 -2
tests/eval/canonicalization-eval.test.ts
··· 19 19 import { extractCanonicalNodes, extractCandidates } from '../../src/canonicalizer.js'; 20 20 import { GOLD_SPECS, type GoldSpec, type GoldNode, type GoldEdge } from './gold-standard.js'; 21 21 import type { CanonicalNode } from '../../src/models/canonical.js'; 22 + import { CONFIG } from '../../src/experiment-config.js'; 22 23 23 24 const ROOT = resolve(__dirname, '../../'); 24 25 ··· 130 131 expect(metrics.typeAccuracy).toBeGreaterThanOrEqual(0.6); 131 132 }); 132 133 133 - it('max degree ≤ 8', () => { 134 - expect(metrics.maxDegree).toBeLessThanOrEqual(8); 134 + it(`max degree ≤ ${CONFIG.MAX_DEGREE}`, () => { 135 + expect(metrics.maxDegree).toBeLessThanOrEqual(CONFIG.MAX_DEGREE); 135 136 }); 136 137 137 138 it('hierarchy coverage ≥ 40%', () => {