Skip to content

#1107 Render rulebook links and path tokens per harness - #1124

Merged
williamthorsen merged 9 commits into
mainfrom
1107
Jul 30, 2026
Merged

#1107 Render rulebook links and path tokens per harness#1124
williamthorsen merged 9 commits into
mainfrom
1107

Conversation

@williamthorsen

@williamthorsen williamthorsen commented Jul 30, 2026

Copy link
Copy Markdown
Owner

What

Rulebooks can now address a file by linking to it: A Markdown link to a skill or script, or a home-directory placeholder anywhere in the body, reaches each harness with that harness's own absolute path filled in. A link target that names a place no harness creates is now rejected with an error naming the rulebook and the target. Links opening with a home-directory placeholder now resolve in skills and subagents as well.

Why

Guidance that has to name a harness-specific path had nowhere to live. The destination-tier doctrine holds that a harness-neutral file cannot carry a harness-scoped path and directs such content into an ambient rulebook instead, but a rulebook could not carry a path either, so the doctrine pointed at a destination that did not work. Content that should have moved stayed where it was, citing files in prose that a reader has no way to follow.

Relative paths are not a workaround: an agent cannot reliably resolve one, because it does not know the directory it was installed into. An absolute path produced by the delivery pipeline is the form that works, and it is what skills and subagents already receive.

Details

🎉 Features

  • Rulebook bodies pass through a per-harness render before delivery, at both delivery points: the consult-<slug> skill write loop and ambient region injection. A target under skills/ or scripts/ becomes the target harness's absolute path with any anchor fragment preserved, and {harness_home_dir} and {harness_id} expand per harness. A rulebook delivered to two harnesses yields the correct path in each.
  • Targets are validated before any rewriting. A target rooted outside the two linkable trees, or one escaping the content root, fails the run with every offending target reported together, naming the rulebook, the target as authored, and the reason it was rejected. Absolute, ~-prefixed, http(s), and anchor-only targets pass through untouched.
  • Validation runs in the same pre-write pass as declared-skill rendering, so a bad link fails a dry run with nothing written and nothing retired.
  • A link target that opens with a template token is now left for template expansion rather than resolved as a relative path first, which fixes the mangled output such a target produced in skills and subagents.
  • The codeassembly-content-specification rulebook gains a "Links in rulebook bodies" section: the authoring convention, the two linkable trees and why each other tree is excluded, what a rejected target reports, and the one limitation an author documenting the convention needs to know.

♻️ Refactoring

  • ResolvedRulebook and resolveRulebook move from the command layer into src/lib/rulebook-deploy.ts, matching how skill and subagent resolution already pair with their transform module.
  • Harness detection resolves once per domain and is threaded into ambient host resolution, prompts.yml reconciliation, and the new validation pass, replacing five separate detection calls that could each have seen a different filesystem.
  • The skill orphan scan carries the harness id of the target it was computed for, so the delivery loop reads directories, orphans, and render context from one list.
  • The ambient-host writability guard now tests the declaration rather than the rendered body. That guard exists to protect a half-written region from being appended to, and rendering it to answer "is anything declared?" would have coupled it to link validity once rendering could fail.
  • The Markdown link grammar is exported from path-rewriter.ts and shared, so the grammar and the passthrough predicate that together define what gets rewritten can no longer drift apart between the rewriter and the suites that inspect links.

🧪 Tests

  • New rulebook-transform.test.ts covers link rewriting under both linkable roots, anchor preservation, the four passthrough forms, template expansion, per-harness output, and five rejection cases including batch reporting and the anchoring depth of the rewrite.
  • sync.test.ts gains seven cases, among them a two-harness run asserting the correct absolute path in all four outputs (both SKILL.md files and both ambient hosts) and a dry run asserting a bad target writes nothing.
  • content-link-resolution.test.ts now treats guidance/rulebooks/ as a host root, so rulebook links are checked for dead files and unresolvable anchors alongside skills and subagents.
  • A new suite renders every shipped rulebook the way sync does, so a link target rooted outside a deployable tree fails the build rather than surfacing later as a broken sync. File existence alone cannot catch this: a link to an existing subagent file satisfies every other check and still fails every run.
  • Harness-specific test fixtures that shared bare names were renamed for clarity.

Closes #1107

A Markdown link target that opens with a template variable, such as `[describe-change]({harness_home_dir}/scripts/describe-change.sh)`, now expands to the harness path instead of resolving as a relative path to a location that does not exist.

Applies everywhere content is rendered: skills, subagents, and harness guidance files.
Rulebook resolution and the resolved-rulebook type now sit in the library layer beside skill and subagent resolution, rather than inside the sync command, so all three artifact kinds are resolved through the same layer.
A rulebook body can now carry a Markdown link or a `{harness_home_dir}` token and have it resolve. A relative link is authored the way the content tree is laid out, and each target harness receives that target as an absolute path it can follow.

A rulebook may link into `skills/` and `scripts/`, the trees that deploy under every harness home. A target anywhere else, or one escaping the content root, fails the run with an error naming the rulebook, the target as authored, and why it cannot be delivered.
A rulebook declared for more than one harness now reaches each one carrying paths that harness can follow, in both its `consult-` skill and its ambient guidance region. One authored link yields `~/.claude/...` for Claude Code and `~/.rovodev/...` for Rovo Dev.

An unusable link target fails the sync before anything is written, so `--dry-run` surfaces it with the tree untouched.
Rulebook bodies join skills and subagents in the installable-content link check, so a rulebook link to a file that has moved or been deleted, or an anchor naming no heading, fails the build rather than shipping.
The content specification now states where a rulebook link target is authored relative to, which two trees a target may name, and what a rejected target reports. It also records the limitation that rewriting covers the whole body, so a rulebook documenting link syntax describes a target rather than showing one.

Its account of the render pass is corrected: a rulebook body receives link and template rewriting, and only invocation tokens are reserved to skills and subagents.
The skill orphan scan now records which harness each entry belongs to, so rulebook-skill delivery reads the harness and its retractions from one list instead of rejoining two by directory name.
A rulebook link rooted outside `skills/` or `scripts/` now fails the test suite. Such a target can name a file that exists, so the existing resolution check passes it, and every later `sync` then rejects it -- breaking `pnpm run bootstrap` for anyone who pulls the merge.

The Markdown link grammar moves to a single exported definition, so the links a suite inspects and the links the rewriter acts on cannot diverge as that grammar widens.
@williamthorsen williamthorsen added feature Added or improved external functionality scope:agents labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown

Dependency audit

Production dependency audit passed.

@williamthorsen williamthorsen self-assigned this Jul 30, 2026
@williamthorsen
williamthorsen marked this pull request as ready for review July 30, 2026 17:47
@williamthorsen
williamthorsen merged commit 35aa58d into main Jul 30, 2026
3 checks passed
@williamthorsen
williamthorsen deleted the 1107 branch July 30, 2026 17:56
williamthorsen added a commit that referenced this pull request Aug 4, 2026
…e-v0.2.2 codeassembly-v0.4.0 factory-v0.2.2 codeassembly-mcp-v0.2.2

codeassembly-v0.4.0
- #1153 feat: Make codeassembly and kb CLI tools publishable (#1164)
- #1091 fix: Anchor a project-deployed link where its target deploys (#1159)
- #1156 refactor: Rename packages to publishable names (#1157)
- #1152 tooling: Run every test in the default gate, classified by what it reaches (#1155)
- #1110 tooling: Migrate Vitest to nmr's centralized model (#1154)
- refactor: Refine typescript-preferences
- #1150 fix: State doc-description form and make comment mood opt-in (#1151)
- deps: Upgrade first-party linting deps to latest version
- #1137 feat: Establish personal rulebooks for code layout and TypeScript preferences (#1144)
- #1126 feat: Check a package's own guidance content before it ships (#1138)
- refactor: Fix lint
- tooling: Remove redundant lint rules
- #1133 fix: Reject an anchor link that names no heading (#1135)
- #1122 feat: Capture lede decisions as an accumulating corpus (#1132)
- #1125 feat: Honor invocation tokens in rulebook bodies (#1129)
- #1107 feat: Render rulebook links and path tokens per harness (#1124)
- #1115 feat: Sync guidance at build and install so an upgrade cannot leave it stale (#1123)
- #1114 feat: Adopt a dependency's guidance by naming the package (#1121)
- #1088 feat: Deliver project ambient rulebooks per harness (#1113)
- #1087 refactor: Rename the authoring rulebook and mark its enforced rules (#1092)
- #1095 tooling: Move compilation out of the install lifecycle into a bootstrap step (#1102)
- #1094 refactor: Remove the ambient ripgrep dependency from the test suite (#1097)
- deps: Upgrade all deps to latest version
- tooling: Upgrade all deps to latest version & modernize configs
- refactor: Fix lint
- #1077 feat: Show the proposed edit above post-review menu options (#1078)
- #1068 feat: Deliver ambient rulebooks mechanically, retiring GLOBAL.md (#1075)
- #1069 feat: Add a no-second-person rule to the lede-voice doctrine (#1070)
- #1050 feat: Add a redundancy rule to the lede-voice doctrine (#1058)
- #1035 internal: Add lifecycle workspace with the canonical envelope, vocabulary & lane fold (#1049)
- #1006 internal: Extend lifecycle-event instrumentation to five high-traffic skills (#1034)
- #1028 feat: Rule out absence-of-removed-code tests (#1032)
- #114 feat: Let reviewers emit gated insights into review artifacts (#1031)
- #1027 internal: Retire input.received and redundant skill.progress emits (#1030)
- #1026 fix: Name the side effects an approval ask authorizes (#1029)
- #1022 fix: Normalize action and question label identifiers across asks blocks (#1025)

factory-v0.2.2
- #1156 refactor: Rename packages to publishable names (#1157)
- deps: Upgrade all deps to latest version
- #1152 tooling: Run every test in the default gate, classified by what it reaches (#1155)
- #1110 tooling: Migrate Vitest to nmr's centralized model (#1154)
- tooling: Remove redundant lint rules
- deps: Upgrade deps to latest version
- deps: Upgrade all deps to latest version
- deps: Upgrade all deps to latest version
- tooling: Upgrade all deps to latest version & modernize configs
- refactor: Fix lint
- deps: Upgrade all deps to latest version

kb-v0.3.0
- #1153 feat: Make codeassembly and kb CLI tools publishable (#1164)
- #1156 refactor: Rename packages to publishable names (#1157)
- #1152 tooling: Run every test in the default gate, classified by what it reaches (#1155)
- #1110 tooling: Migrate Vitest to nmr's centralized model (#1154)
- tooling: Remove redundant lint rules
- #1095 tooling: Move compilation out of the install lifecycle into a bootstrap step (#1102)
- tooling: Upgrade all deps to latest version & modernize configs
- refactor: Fix lint

codeassembly-lifecycle-v0.2.0
- #1153 feat: Make codeassembly and kb CLI tools publishable (#1164)
- #1156 refactor: Rename packages to publishable names (#1157)
- #1152 tooling: Run every test in the default gate, classified by what it reaches (#1155)
- #1110 tooling: Migrate Vitest to nmr's centralized model (#1154)
- tooling: Remove redundant lint rules
- #1095 tooling: Move compilation out of the install lifecycle into a bootstrap step (#1102)
- tooling: Upgrade all deps to latest version & modernize configs
- #1038 feat: Add the read-only git adapter for worktree and base-branch ground truth (#1059)
- #1051 feat: Bound fold memory and rescan cost with a retention window (#1057)
- #1035 internal: Add lifecycle workspace with the canonical envelope, vocabulary & lane fold (#1049)

codeassembly-mcp-v0.2.2
- deps: Upgrade all deps to latest version
- #1156 refactor: Rename packages to publishable names (#1157)
- #1152 tooling: Run every test in the default gate, classified by what it reaches (#1155)
- #1110 tooling: Migrate Vitest to nmr's centralized model (#1154)
- #1095 tooling: Move compilation out of the install lifecycle into a bootstrap step (#1102)
- tooling: Upgrade all deps to latest version & modernize configs
- refactor: Fix lint

codeassembly-run-core-v0.2.2
- #1156 refactor: Rename packages to publishable names (#1157)
- #1152 tooling: Run every test in the default gate, classified by what it reaches (#1155)
- #1110 tooling: Migrate Vitest to nmr's centralized model (#1154)
- #1095 tooling: Move compilation out of the install lifecycle into a bootstrap step (#1102)
- tooling: Upgrade all deps to latest version & modernize configs
- refactor: Fix lint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Added or improved external functionality scope:agents

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A link or path token in a rulebook body resolves to nothing

1 participant