Repository navigation
Agent references nodes as [Title](nodespace://id); chat shows the live title as a plain link - #3467
Conversation
Pragmatic Code Review: PR #3467 (issue #3267)Review Type: Initial Review (no prior reviews or comments on the PR) SummaryThe change does what the issue asks and is a net improvement: the guidance, the listing rule and the golden agree, the normalizer no longer undoes the link form, and the chat reference takes its text from Two things should be handled in this PR. The "missing" style never applies in the real chat, and the new unlink step is broader than the case it was written for: it also keeps replies that name a node the model invented. Checked by running: the three chat component test files (22 passed) and the targeted Requirements Check
The removal of the icon, type badge and task status is per the user's direction during implementation ("a simple hyperlink"), so it is not scope creep. Code Review Findings🟡 Important1. The muted "missing" style loses to the chat's link colour, so a missing reference looks like a live link Compiled selectors: The parent's
2. The unlink step also keeps replies that name an invented node, which the fabricated-id guard used to replace The doc comment's premise is "the sentence around such a link is still a true answer". That holds for the case in the eval (a type name linked to
Before this PR the same reply was replaced by
🟢 Suggestions3. A label with inline markdown shows as literal HTML while loading or missing
4.
5. The grounded set and the link target are read with different rules Grounded ids are extracted with Nitpicks
SecurityNo new exposure found. The title and label are rendered through Svelte text interpolation, never as HTML. CLAUDE.md adherenceNo compatibility code, no lint suppression, no raw RecommendationAPPROVE, with findings 1 and 2 to be addressed in this PR before merge. Neither is a blocker on its own: finding 1 is a small visible defect in a stated behaviour, and finding 2 is a narrowing of an existing safety guard that should be an explicit, tested decision. |
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Review addressed (commit 72e5813)Addressed
Nits: the two stale "card" comments are fixed, and Not taken
Checks: Re-Review DecisionDecision: RE-REVIEW NEEDED Rationale: Finding 2 changed which replies the fabricated-id guard replaces. That is a behaviour change in a safety guard and should be checked by a second read. |
Pragmatic Code Review: PR #3467 (issue #3267)Review Type: Re-Review (commit Previous Review Summary
Finding 2 is addressed as the first review worded it, but the id-shape test it introduced is too exact, so part of the original hole is still open. That is the one Important finding below. Checked by running
Not run
Previous findings
Do the new tests fail without the change?
Code Review Findings🟡 Important1. The id-shape test only matches a well-formed id, so a malformed invented id is unlinked and the claim is kept
Nitpicks
Attribute escaping checkCorrect, with no double-escaping. marked 16 keeps token text raw, so
DOMPurify keeps both attributes in every case, including a label containing CLAUDE.md adherenceNo compatibility code, lint suppression, raw RecommendationAPPROVE, with new finding 1 to be fixed in this PR before merge. The commit is a net improvement on |
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review (re-review): APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Re-review addressed (commit 754bd02)Addressed
Not taken
Checks: Re-Review DecisionDecision: NO RE-REVIEW NEEDED Rationale: One predicate changed, in the direction the re-review recommended and further. It can only send more replies to the existing guard, never fewer, and a table test pins it. |
…title (closes #3267) The agent guidance and listing rule now tell the model to link every node it names as a markdown link. The response normalizer no longer rewrites a titled node link into bold text plus a bare URI. The chat card takes its title from pluginRegistry.getNodeTitle, so headers lose their leading #, title-template types show their computed title, and the agent's label shows only while the node loads or when it does not exist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
No icon, type badge or task status: the link text is the node's live title, and a reference to a node that does not exist is muted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Told to link every node it names, the model also links names it has no id for (a type, say) and invents the target. The fabricated-id guard replaced the whole reply. A titled link to an id no tool result produced is now reduced to its label; a bare invented id still trips the guard. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lain-text labels - Unlink only a titled link whose target is a name, not an id. A titled link to an ungrounded id-shaped target is left for the fabricated-id guard, so an invented node named in link form is still replaced. - The missing-link colour moves to chat-markdown.svelte, where it outranks the chat link colour; in the component it never applied. - A link label renders as plain text, so inline markup in a label no longer shows as HTML while the node loads or is missing. - Not taken: formatTabTitle for the link text. It cuts to tab width, and this text is inline in a message. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The id-shape test recognised the dangerous case by an exact shape, so a miscounted, undashed or short invented id was treated as a name and its claim kept. The repair now recognises the safe case instead: a target of lowercase letters and underscores, optionally behind schema:. Everything else ungrounded goes to the fabricated-id guard. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
754bd02 to
68b3128
Compare
Closes #3267
What changed
Guidance.
NODE_REFERENCE_FORMATand the listing rule now tell the agent to link every node it names as[Title](nodespace://id). The resident-prompt golden is regenerated; its diff is those two lines.Response normalizer.
response_processing.rsused to rewrite[Title](nodespace://id)into**Title** (nodespace://id), which would have undone the new guidance. A titled link now passes through. A link whose label is its own URI is still collapsed to the bare URI, because the renderer wraps bare URIs itself and a URI inside a label would nest.Ungrounded links (not in the issue). Told to link every node, E4B also links type names and invents the target (
[event_venue](nodespace://schema:event_venue)). The fabricated-id guard then replaced the whole reply: 4 replies in the first eval run. A titled link whose target is a type name (lowercase letters and underscores, optionally behindschema:) is now reduced to its label and the reply is kept. Every other ungrounded target still trips the guard, bare or inside a titled link.Chat reference. The inline reference is a plain hyperlink: no icon, type badge or task status. Its text is
pluginRegistry.getNodeTitle(node), so a header shows without its#and a person shows its computed title. The agent's label shows only while the node loads or when the node does not exist (then muted). A resolved node with no title shows "Untitled", not the label.Sweep for the old bare-URI rule
packages/skill/SKILL.md, its generator andreferences/carry no node-reference rule, so nothing changed there. The seeded agent-guidance nodes are built fromNODE_REFERENCE_FORMAT, so they follow the constant.Eval spot-check (
eval:decisions, E4B, one rep each, purged database, rebuilt daemon)schema-on-menu-company,declarative-fact-report-resolvedThe same two scenarios fail under both wordings; the scored counts differ because different scenarios were excluded for empty generations in each run.
Replies use the link form:
The record for Northwind Trading already exists as a [company_sold_to](nodespace://9ab0f34c-…)against the baseline's… as a company sold to (nodespace://353b597e-…). Two limits showed up. The label there is the type name, not the title; the rendered link shows the live title regardless. And in the first run one reply named a record with no link at all ("I've updated the booking date for Northwind Trading to April"), so the model does not link every node it names.Tests
bun run test:changedpasses: frontend, scripts, browser and Rust tiersNot done here
nodespace-docs/architecture/acp-integration.mdstill describes the bare-URI rule. It is updated in the docs repo after this merges.🤖 Generated with Claude Code