Skip to content

RFC-007 Phase 3: wiki-link + composes-with edges with slugify() resolution and dangling tracking (fixes #589)#617

Merged
lantiscooperdev merged 5 commits into
mainfrom
feat/rfc-007-p3-wiki-link-composes-with
Jul 26, 2026
Merged

RFC-007 Phase 3: wiki-link + composes-with edges with slugify() resolution and dangling tracking (fixes #589)#617
lantiscooperdev merged 5 commits into
mainfrom
feat/rfc-007-p3-wiki-link-composes-with

Conversation

@lantisprime

Copy link
Copy Markdown
Owner

Implements RFC-007 Phase 3. Closes #589.

What ships

Two rule-to-rule edge types on em-graph.mjs, both opt-in (registered in EDGE_TYPES, absent from DEFAULT_EDGES):

  • wiki-link[[name]] in rule bodies, fenced blocks and inline backticks excluded
  • composes-with — top-level bullets under ## Composes with, nested bullets ignored

Plus the shared target resolution both depend on, per-edge-type dangling tracking, and a 31-case suite wired into CI.

The issue understated the work

#589 described two parsers and a fixture. Adding edges to rule nodes broke three assumptions Phase 2 had made explicit in its own comments:

Site Before After
--orphans reported every rule unconditionally reports a rule only at degree 0
--from <rule-slug> early-returned a single node falls through to the BFS, making good on the comment left at em-graph.mjs:290
--hubs degree map was episode-only rule ids seeded, so rules can rank

Every line anchor #589 cited was also stale by +10 to +25 lines, drifted by Phase 2 (#604). Several pointed at blank lines; two pointed at the wrong JSON object entirely. All corrected.

Target resolution needed a decision

Built literally, the spec resolves almost nothing. Measured against the live corpus before writing any code:

Edge type Spec as written With filename-alias index
wiki-link 13 of 132 122 of 132
composes-with 0 of 76 51 of 76

Node ids are slugify(frontmatter name) per RFC-007:142, but authors link by filename. RFC-007:151 separately requires composes-with markdown links to resolve "by file basename", which needs an index :142 disclaims. The RFC contradicted itself, functionally rather than cosmetically.

Resolution now tries the name-derived id first, then a filename-basename alias index; ambiguous basenames are dropped rather than guessed. Node ids are unchanged and :142 is untouched. Documented in a new Target resolution subsection.

Two RFC contradictions settled

  • Dangling bucket is wiki-link everywhere. The prose said wiki-link-dangling, a spelling appearing nowhere else in the 737-line file and disagreeing with both the contract and the kind enum.
  • The wiki-link regex was stated twice, non-equivalently. The prose form is canonical. Verified programmatically: the contract JSON-unescaped, the prose span, and the literal compiled into em-graph.mjs are now character-identical.

Verification

Claim Command Observed
New suite node tests/test-em-graph-links.mjs 31 passed, 0 failed
Phase 1 regression node tests/test-em-graph.mjs 10 passed, 0 failed
Phase 2 regression node tests/test-em-graph-nodes.mjs 28 passed, 0 failed
Suite genuinely CI-wired node tests/test-ci-suite-registration.mjs 16 passed, 0 failed
Negative control node tests/test-em-graph-links.mjs --break-wikilink exit 1, 11 failures
REQ-8 default output worktree at 8e6e699, cmp on --hubs and --orphans byte-identical
RFC anchors sed -n '239p;251p;314p;320p;332p' all point where claimed

REQ-8 was checked against the baseline commit directly rather than via the suite's own before/after comparison, which is the proxy layer one short of the claim.

Live corpus: 81 rule nodes carry wiki-link edges (9 dangling), 31 carry composes-with edges (1 dangling). --from pi-minimax-m3-builder-seat --depth 2 returns 5 nodes and 4 edges where 8e6e699 returned count: 1.

Review

Two rounds, both HOLD, all 12 findings dispositioned.

Round 1 on the plan caught a blocker: a step referenced a symbol a later step defined, so verbatim application would have thrown ReferenceError. Round 2 on the frozen diff caught a MAJOR: CRLF line endings silently dropped every composes-with edge — zero edges, zero dangling, exit 0. Latent on this corpus but the same silent-zero signature REQ-6 traps for, and inconsistent with lib/rule-nodes.mjs:67 which already handles CRLF. Fixed with a regression test proven to fail without the fix.

Round 2 also caught a REQ-12 miss: the RFC's Scope "Unbuilt" list still enumerated Phase 2 and Phase 3.

Pre-existing stalenesses fixed in passing

  • The mermaid diagram labelled Phase 2 UNBUILT while the phases table and ledger both recorded it SHIPPED.
  • slugify._shipped_use claimed the function was unused by the shipped script, though scanRuleNodes has called it since Phase 2.

grep -c UNBUILT on the RFC: 17 before, 10 after.

Deliberately not included

No Implementation ledger row. Its provenance must come from git log --follow on the merge commit, which does not exist yet. Writing a branch-tip SHA there is the defect #582 was filed for and #605 had to repair. It will be filled post-merge.

🤖 Generated with Claude Code

lantisprime and others added 5 commits July 26, 2026 06:54
Plan for RFC-007 Phase 3, reviewed round 1 by a pi/GLM-5.2 seat
(HOLD, 7 findings, all dispositioned in section 19.1; 6 applied).

Corrects the stale line anchors #589 inherited from before Phase 2,
and records three operator decisions: prose regex at RFC-007:150 is
canonical, the dangling bucket is wiki-link, and #603 is not folded.

Adds a filename-basename alias index for target resolution after
measuring that the literal spec resolves 13/132 wiki-links and
0/76 composes-with bullets against the live corpus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gration

Adds the wiki-link edge type (opt-in, not in DEFAULT_EDGES), the shared
target-resolution path, and per-edge-type dangling tracking. slugify is
imported from lib/rule-nodes.mjs rather than reimplemented (P2).

Integrates rule nodes into the three modes that assumed they had no edges:
--orphans now reports a rule only at degree 0, --hubs seeds rule ids into
the degree map, and --from falls through to the BFS when the root has
adjacency, making good on the comment Phase 2 left at em-graph.mjs:290.

Target resolution tries the name-derived id first, then a filename-basename
alias index. Ambiguous basenames are dropped rather than guessed.

Live corpus: 81 of 127 rule nodes now carry edges, 9 links dangle.
Default output verified byte-identical to 8e6e699 by cmp on --hubs and
--orphans, not by proxy.

Also corrects the plan's EC5, which claimed an unclosed fence swallows the
remainder. stripFenced is non-greedy and needs a closing fence, matching
shipped cites at em-graph.mjs:207; parity is the requirement, so the test
now asserts the real behavior.

Tests: tests/test-em-graph-links.mjs, 21 cases. Negative control
--break-wikilink exits 1 with 11 failures.

Refs #589

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Parses top-level bullets under a "## Composes with" heading, reusing S1's
target resolution and dangling scaffold. Nested bullets ignored, section
terminated by the next "## " or EOF.

The caller passes stripFenced() only and deliberately NOT stripInline():
real bullets write their target inside backticks, so stripping inline code
here would delete every target and emit zero edges across all 21 files in
the corpus that use the section. RFC-007:149 scopes the inline-backtick
exclusion to wiki-link and cites, so this matches the spec.

A bullet that is neither a markdown link nor backtick-led names no target
and is skipped rather than slugified, which avoids manufacturing garbage
dangling refs from prose. Only the first backtick run is used.

Live corpus: 31 rule nodes carry composes-with edges, 1 link dangles.

Tests: 8 new cases, suite now 29 passed, 0 failed. Phase 1 and Phase 2
suites unchanged and green.

Refs #589

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flips the wiki-link and composes-with edge rows and both machine-readable
status fields to SHIPPED, with verified em-graph.mjs anchors.

Resolves the two internal contradictions #589 asked to settle:
- The dangling bucket is "wiki-link" everywhere. The prose at :103 said
  "wiki-link-dangling", a spelling that appeared nowhere else in the file
  and disagreed with both the contract at :677 and the kind enum at :237.
- The machine-readable regex now matches the prose form character for
  character. Verified by JSON-unescaping the contract and comparing against
  both the prose span and the literal compiled into em-graph.mjs; all three
  agree.

Adds a Target resolution subsection documenting the filename-alias index,
which reconciles the "resolved by file basename" clause with the
frontmatter-name node-id rule. The node-id rule itself is untouched.

Also corrects two pre-existing stalenesses found while working here: the
mermaid diagram still labelled Phase 2 UNBUILT while the phases table and
the ledger both recorded it SHIPPED, and slugify._shipped_use still claimed
the function was unused by the shipped script although scanRuleNodes has
called it since Phase 2.

Wires the Phase 3 suite into tests.yml; test-ci-suite-registration passes
16/16, so the suite is genuinely step-wired rather than silently skipped.

grep -c UNBUILT on the RFC: 17 before this PR, 10 after.

No Implementation ledger row yet. Its provenance must come from
git log --follow on the merge commit, and writing a branch-tip SHA is the
defect #582 was filed for and #605 had to repair. Filled post-merge.

Refs #589

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, RFC coherence)

Round 2 on the frozen diff returned HOLD with 5 findings, all accepted.

CRLF line endings silently dropped every composes-with edge: zero edges,
zero dangling, exit 0. The heading matched because that test trims, but the
bullet kept a trailing \r and the bullet regex did not match it. Same
silent-zero signature REQ-6 traps for empty sections, reachable through a
portable line ending, and inconsistent with lib/rule-nodes.mjs:67 which
already tokenizes frontmatter CRLF-aware. Split on /\r?\n/.

A markdown-link target carrying a #fragment slugified the fragment into the
target and dangled; the fragment is now stripped before basename.

The section start test trimmed while the terminator used a raw anchor, so an
indented "## Composes with" could start a section that an indented "## Next"
could not end, wrongly absorbing later bullets. Both now test the trimmed
line.

RFC Scope's "Unbuilt" list still enumerated Phase 2 and Phase 3 work,
contradicting the phases table, the edge-status table and the contract block
in the same file. That was a REQ-12 miss: REQ-12 covers every place the RFC
makes a claim, and the first sweep only covered the places enumerated in the
plan.

The two new contract entries lacked the shipped_at field all five older
entries carry.

Tests: t_composes_crlf_line_endings and
t_composes_markdown_link_fragment_stripped. Suite 31 passed, 0 failed.
The CRLF guard is not vacuous: the bullet regex returns false against a
trailing \r, so the test fails without the split fix.

Refs #589

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@lantiscooperdev lantiscooperdev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiered multi-agent review — RFC-007 Phase 3, two rounds, ACCEPT

Covers the full #617 diff (5 files, +1638/-34) across a scout stage, a plan review and a frozen-diff review. Both review rounds returned HOLD; all 12 findings are dispositioned and the actionable ones applied.

Verdict trail

Round Scope Seat Verdict Blockers
Scouts RFC anchors, em-graph.mjs runtime state, test/CI wiring, governance + disposition sweep 4 parallel Sonnet n/a n/a
1 The plan, with Appendix A frozen pi / neuralwatt glm-5.2 HOLD → folded 1 BLOCKER, 2 MAJOR, 4 MINOR
2 Frozen diff main...HEAD pi / neuralwatt glm-5.2 HOLD → fixed in f76dc6b 1 MAJOR, 4 MINOR

Seat spend for the whole arc: $2.71. No Fable seats, no openrouter.

Round 1 findings, closed before any code was written

# Class Resolution
1 BLOCKER Step 1.8's payload called danglingOut(), defined by step 1.9. Verbatim application would have thrown ReferenceError and dropped Phase 2 to 27/28 with no clean in-step fix. Stray call removed.
2 MAJOR Step 1.4's verify asserted a dangling key nothing emits until 1.9. Rewritten to a loop-presence grep.
3 MAJOR Step 1.9 expected grep -c 'danglingOut()' → 4 counting the definition, but the definition reads danglingOut = () so the substring never matches it. Corrected to 3.
4 MINOR Step 3.5's relative UNBUILT count was ambiguous. Restated absolutely: 17 at 8e6e699, 15 after 3.2, 10 after 3.5.
5 MINOR composesTargets slugified whole prose bullets into garbage refs. Now yields no target.
6 MINOR Two backticked spans on one bullet: first wins, made explicit.
7 MINOR Presence-only greps, assessed as back-stopped by the functional suites. No change.

Round 2 findings, closed in f76dc6b

# Class Resolution
1 MAJOR CRLF line endings silently dropped every composes-with edge — zero edges, zero dangling, exit 0. The heading matched (that test trims) but the bullet kept a trailing \r and the bullet regex did not match. Same silent-zero signature REQ-6 traps for, and inconsistent with lib/rule-nodes.mjs:67 which already handles CRLF. Fixed by splitting on /\r?\n/.
2 MINOR A markdown-link target with a #fragment slugified the fragment into the target. Stripped before basename.
3 MINOR Section start trimmed, terminator did not, so an indented ## Composes with could start a section an indented ## Next could not end. Both now test the trimmed line.
4 MINOR RFC Scope's "Unbuilt" list still enumerated Phase 2 and Phase 3, contradicting three other places in the same file. A REQ-12 miss: the first sweep only covered the sites the plan enumerated.
5 MINOR The two new contract entries lacked the shipped_at field all five older entries carry.

RFC contract changes

  • Edge-status rows :84-85 and both contract status fields → SHIPPED, with verified anchors.
  • Dangling bucket unified to wiki-link; the wiki-link-dangling spelling appeared nowhere else in the file.
  • Machine-readable regex rewritten to match the prose form. Verified programmatically: the contract JSON-unescaped, the prose span, and the literal compiled into em-graph.mjs are character-identical.
  • New Target resolution subsection defining the filename-alias index, reconciling the "resolved by file basename" clause with the frontmatter-name node-id rule. :142 itself untouched.

Orchestrator re-verification (independently executed, not taken from seat reports)

  • Suites: links 31/31, phase1 10/10, phase2 28/28, ci-suite-registration 16/16. PASS.
  • Negative control --break-wikilink → exit 1, 11 failures, and the passing tests are exactly the ones not dependent on wiki-link extraction. PASS.
  • REQ-8 against the real baseline: worktree at 8e6e699, cmp on --hubs and --orphans output → byte-identical. The suite's own before/after check is the proxy layer one short of this. PASS.
  • RFC anchors 239, 251, 314, 320, 332 each verified to point where claimed, re-checked after the S4 line shifts. PASS.
  • CRLF guard proven non-vacuous: the bullet regex returns false against a trailing \r, so the new test fails without the fix. PASS.
  • Payload fidelity: every §A.5 block diffed against the plan, verbatim including comments. PASS.

Live corpus behavior

Edge Nodes with edges Dangling
wiki-link 81 of 127 rule nodes 9
composes-with 31 1

--from pi-minimax-m3-builder-seat --depth 2 returns 5 nodes and 4 edges; 8e6e699 returned count: 1.

Follow-ups filed (Rule 18 step 9)

  • #618 — fill the Implementation ledger row post-merge from git log --follow. Deliberately omitted here: the provenance SHA does not exist until merge, and a branch-tip SHA is the defect #582 was filed for and #605 had to repair. Carries all 5 DEFER fields.
  • #619em-console.mjs:114 flag whitelist omits --nodes/--edges, so rule/rfc nodes and Phase 3 edges are unreachable through the console. Pre-existing since Phase 2, recorded as an explicit non-goal.
  • #620 — an unclosed code fence does not suppress extraction, affecting shipped cites as well as new wiki-link. Design question surfaced by the build, deliberately not decided inside this slice.

CI

8/8 green: validate ×3, substrate, gates-classifiers, p12-invariant-gate, recall-activation, clerk-consolidation.

What user approval is checking

Per Rule 17, your approval in the UI is the review-required gate. Substantive review is complete and both rounds are closed. Worth a specific look before approving: the filename-alias index is a genuine spec extension, adopted because the literal spec resolved 13/132 wiki-links and 0/76 composes-with bullets against the live corpus. It does not change node ids. If you disagree with that call, it is the one decision here that is architectural rather than mechanical.

🤖 Posted by lantiscooperdev (bot account) — Claude Opus 5 (1M context).

@lantiscooperdev
lantiscooperdev merged commit dafc3c8 into main Jul 26, 2026
10 checks passed
lantiscooperdev pushed a commit that referenced this pull request Jul 26, 2026
…622)

PR #617 shipped without an Implementation ledger row, deliberately: the row's
SHA must be the merge commit, which did not exist until it landed.

Provenance derived from git against the files the row claims, not from an
adjacent row and not from a branch tip:

  git log --follow --oneline scripts/em-graph.mjs   -> dafc3c8
  git log --follow --oneline tests/test-em-graph-links.mjs -> dafc3c8

That is the #582 / #605 class this defers to avoid.

CI anchor cited as tests.yml:407-408, verified against the merged file.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC-007 Phase 3: wiki-link + composes-with edges with slugify() resolution and dangling tracking

2 participants