[#238] feat: fence-aware skill ref rewrite + idempotent flatten/prefix#269
Conversation
Review InformationPR Number: #269 Review SummaryOverall Assessment
Key Changes SummaryAdds a token-based, fence-aware skill cross-reference rewriter ( Business Value ValidationRemoves the manual hand-repair burden on external KB maintainers documented in the issue's field-evidence note — the nested dataset stays nested at the source (D32) and the installer now handles flatten/prefix/rewrite end-to-end, idempotently. Code Review ChecklistFunctionality Review
Code Quality Assessment
Technical Standards Compliance
Security ReviewSecurity Checklist
Security Concerns
No security issues found that require action. Testing ReviewTest Coverage Assessment
Test Quality Review
Testing FeedbackPerformance ReviewPerformance Analysis
Performance MetricsNot benchmarked; no baseline exists for this pipeline and none was expected per the story's testing strategy. Documentation ReviewDocumentation Checklist
Documentation Quality
Detailed Review CommentsPositive FeedbackWhat's Done Well:
Issues to AddressCritical Issues
|
| Risk | Impact | Probability | Mitigation |
|---|---|---|---|
| Mirror cleanup deletes an un-nested top-level source file on next run | Medium | Low (no current dataset shape triggers it) | Track as debt; guard before reuse in another flatten+mirror registry |
| Rewriter corrupts markdown via a fence-detection edge case | Low | Low | Fence/inline-code-aware tokenizer + targeted regression tests already in place |
Business Risks
| Risk | Impact | Probability | Mitigation |
|---|---|---|---|
| External KB maintainers unaware the manual-repair workaround is no longer needed | Low | Medium | Docs site updated; awareness is a comms task, not a code task |
Deployment Considerations
Deployment Checklist
- Database Migration — N/A
- Configuration — no new config keys; existing
flatten/prefixnow fully honored - Feature Flags — N/A
- Rollback Plan — plain revert; new manifest file is additive and ignored by older CLI versions
- Monitoring — N/A (CLI, not a service)
- Documentation — updated
Follow-up Actions
Author Action Items
- Medium priority — track the
cleanupStaleTransformedEntriesroot-level-file gap as tech debt (see Tech Debt section) or add a guard/test before another registry combinesflatten+mirrorwith a dataset that may contain top-level files. - Low priority — confirm changeset requirement for this PR's packages.
Reviewer Follow-up
- Re-review — not required; no blocking issues.
Review Timeline
Review Process
- Review Started: 2026-07-07
- Initial Review Completed: 2026-07-07
Review Effort
- Time Spent Reviewing: ~45 minutes
- Complexity Level: Medium-High (markdown-aware rewriting + cross-run state reconciliation)
- Review Thoroughness: Deep
Adoption Compliance
| Area | Status | Notes |
|---|---|---|
| Tech stack | CONFORMANT | No new dependencies added; git diff main...HEAD shows no package.json dependency changes |
| Architecture | CONFORMANT | Extends existing asset-registry pipeline pattern (pure rewrite fn + async orchestrator), no new architectural decision requiring an ADR |
| Security | CONFORMANT | See Security Review above — no issues requiring action |
| Coding standards | CONFORMANT | Consistent naming/style with existing content-ops modules; quality-gate (lint + ts:check) passed |
| Infrastructure | CONFORMANT | No CI/CD or infra config changes |
| ADR/ADL | NOT APPLICABLE | No new architectural decision introduced; the token-based-rewrite-over-regex choice was already recorded in issue #238's Notes section at refinement time, not a new decision made during implementation |
Definition of Done Cross-Check (issue #238)
| DoD Item | Status | Notes |
|---|---|---|
| All acceptance criteria implemented and verified | ✅ Met | AC1–AC6 all have direct fixture coverage |
| Round-trip install/update fixtures (official + minimal external KB) | ✅ Met | idempotent-skill-registry.test.ts AC4/AC5 tests |
| Code review completed and approved | ✅ This review | See verdict below |
| Documentation updated — docs site | ✅ Met | skill-management.mdx |
| CLI updated | ✅ Met | apps/pair-cli handler + registry changes |
| Idempotency property test (install → update → update, byte-stable) | ✅ Met | AC4 test asserts byte-identical content across 3 runs |
| Installed skills validated on Claude Code/Cursor discovery rules | Validated only by inference from frontmatter/dir-name fixture assertions, not a live tool load — self-flagged honestly in the issue's own T5 notes, not a new gap introduced silently | |
| Regression: projects installed with current CLI update cleanly | ✅ Met | Covered by the update-path fixtures in the same suite |
Tech Debt Assessment
| Item | Category | Impact × Effort | Priority | Notes |
|---|---|---|---|---|
cleanupStaleTransformedEntries root-level-file gap |
Code (correctness edge case) | Medium impact × Low effort to fix | Track, not blocking | New code introduced by this PR; not exercised by the current .skills/<type>/<name>/ dataset shape, but unguarded for the general case |
Regex recompilation per line in rewriteSkillReferences |
Code (performance) | Low impact × Low effort | Low priority | Cosmetic; no measurable impact at current scale |
Non-atomic manifest write in reconcileSkillNameRegistry |
Code (robustness) | Low impact × Low effort | Low priority | Self-correcting due to rewrite idempotency |
No debt items rise to a level that should block merge.
Verdict: TECH-DEBT — Approve with tracking
Rationale: no critical issues, no missing ADR, no security holes, all quality gates and test suites pass. One Major-severity finding (cleanupStaleTransformedEntries root-level-file gap) is a real correctness gap in new code, but it is not triggered by any acceptance criterion or the current dataset shape and does not constitute a failing test or security hole — recommend merging with the finding tracked as debt rather than requesting changes.
a15c9c5 to
dbd56ff
Compare
dbd56ff to
c7a90f4
Compare
- skip fenced code blocks when rewriting /skill invocations (AC6) - persist skill name manifest (.pair/.skill-name-map.json) to make cross-reference rewriting idempotent across prefix changes, incl. add-behavior files never re-copied from source - warn (leave as-is) on invocations of removed/disabled skills - mirror-cleanup stale flattened dirs after a rename/prefix change - fixtures: install->update->update byte-stability, prefix change, orphaned skill, external KB via --source, name collision - docs: skill-management.mdx idempotency + fence-skip + collision notes Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- skill-reference-rewriter: backtick-fence info string must be backtick-free (CommonMark); a single-line ```inline `code` construct was misclassified as fence-open, suppressing rewrites for rest of file. Fixed + regression test. Documented blockquoted-fence gap (AC6 inverse, no real KB usage) as known limitation instead of fixing — needs blockquote-prefix-aware line stripping, not worth the risk for a case that doesn't occur. - reconcileSkillNameRegistry: kept the empty-skillNameMap short-circuit as-is rather than attempting orphan detection from the stale manifest alone. Judgment call: with an empty current map we can't tell "skill removed" apart from "skill still installed, just unprefixed now" (disabling flatten/prefix never produces a map entry), so orphan detection would misreport still-installed skills as removed — worse than the no-op. Locked in with an explicit test + inline comment.
c7a90f4 to
d6cdc11
Compare
- cleanupStaleTransformedEntries built its "expected" set only from dirMappingFiles, which copyFileWithTransform only populates for files under a subdirectory (dir !== '.'). A source file at the dataset root (no parent subdirectory) would be copied fine on the first run but silently deleted as "stale" on the next mirror run, since it was never registered as expected. Test-first: added a regression test that runs copyPathOps twice and asserts the root-level file survives (fails without the fix, passes with it). Fixed by tracking root-level file names in a new topLevelFiles set, threaded through copyFileWithTransform and merged into cleanupStaleTransformedEntries' expected set. Extracted copyAllFilesWithTransform to keep copyDirectoryWithTransforms under the lint's max-lines-per-function limit after the added parameter threading. - Hoisted buildReferenceRegex compilation in rewriteSkillReferences out of the per-line loop (was recompiling per line x per registry entry); reusing the compiled regex across lines is safe since String.replace() resets a global regex's lastIndex at the start of every call. - Documented the non-atomic manifest write in reconcileSkillNameRegistry (self-correcting on the next successful run, per its own idempotency). - No changeset: consistent with this implementation wave's policy (changesets added at release-batch time only, not per-PR). Refs: #238
Remediation: all review findings resolvedFollow-up to the TECH-DEBT review. All three findings fixed directly rather than only tracked — commit 1. Major —
|
Code Review — Re-review after remediationReview InformationPR Number: #269 Re-review after the remediation commit Review SummaryOverall Assessment
Key Changes SummaryThe remediation fixes the Major finding ( Code Review ChecklistFunctionality Review
Code Quality Assessment
Technical Standards Compliance
Testing ReviewTest Coverage Assessment
Testing FeedbackDocumentation ReviewDocumentation Quality
Detailed Review CommentsPositive FeedbackWhat's Done Well:
Issues to AddressNone — the Major finding is fixed and independently verified against the actual pushed diff, both Minor findings are resolved, and the changeset question was already a non-issue (established wave-wide policy). Risk AssessmentNo outstanding risks. The one risk named in the first review ("mirror cleanup deletes an un-nested top-level source file on next run") is eliminated by the fix, verified via a regression test that would fail again if the fix regressed. Follow-up ActionsNone for this PR. Review Timeline
Definition of Done |
…call-chain files Per the file-structure.md co-location rule (extended in this commit for the multi-module case), a test spanning multiple modules belongs in the test file already co-located with the root module of the call chain it verifies — not a standalone file. idempotent-skill-registry.test.ts tested both handleUpdateCommand (update/handler.ts) and handleInstallCommand (install/handler.ts) from neither's own sibling test file. - Moved the 3 update-focused cases (AC4 idempotency across repeated runs, prefix change, removed-skill reference) into update/handler.test.ts. - Moved the 2 install-only cases (AC5 external KB via --source, name collision) into install/handler.test.ts. - Deleted the standalone file. - Extended file-structure.md's Co-location Rules with the multi-module case and its two exceptions (E2E/page tests, content-validation tests). - Recorded the convention adoption as an ADL (2026-07-08-test-file-colocation-multi-module.md). All 5 moved tests pass unchanged in their new location; full @pair/pair-cli suite (770 tests, 65 files) green, re-run twice to confirm no order dependency was introduced by the move. Refs: #238
Follow-up: test file naming convention (post-approval)Additional commit Fixed by moving, not just renaming:
Also, since this convention had a real gap: Verification:
No behavior change to the tested code — this is a pure test-file reorganization plus a documentation gap it exposed. |
PR Information
PR Title: [#238] feat: fence-aware skill ref rewrite + idempotent flatten/prefix
Story/Epic: #238 (parent epic #213)
Type: Feature
Priority: P0 (Must-Have)
Assignee: N/A
Reviewers: N/A
Labels: feature
Summary
What Changed
Extends the skills asset-registry copy pipeline (flatten + prefix) with token-based cross-reference rewriting and idempotency across repeated install/update runs:
buildSkillNameMap) built from the directory renames the flatten/prefix copy already produces.rewriteSkillReferences/findSkillReferences) that rewrites/oldNameinvocation tokens to/newName, skipping fenced code blocks (inline code spans are still rewritten)..pair/.skill-name-map.json) recording the previous run's name map, enablingreconcileSkillNameRegistryto compute a rename-transition map and an orphaned-reference set across runs (handles prefix changes and skill removal without leaving stale content or corrupting already-installed files).cleanupStaleTransformedEntries) that removes stale flattened/prefixed top-level directories left over from a previous run (e.g. after a prefix change or a skill removed from source).Why This Change
Without this, an external KB had to flatten its skill tree at the source and hand-repair cross-references, because the installer never rewrote them. This story removes that class of manual work end-to-end: the dataset stays nested at the source (D32), and the installer's flatten/prefix/rewrite pipeline handles cross-references, relative links, and idempotent updates.
Story Context
User Story: As a KB maintainer (official or external), I want the CLI to install skills from the nested dataset layout by flattening, prefixing AND rewriting cross-references inside skill bodies (
/skillinvocations and relative links), idempotently on update, so that no KB ever needs to restructure its skills at the source to make them installable on AI tools.Acceptance Criteria: AC1–AC6 in issue #238 (flat installed layout with matching frontmatter; token-based invocation rewriting via registry; relative-link depth rewriting; byte-identical idempotent round-trip; external-KB parity; fenced-block exclusion).
Changes Made
Implementation Details
buildSkillNameMap(T1) — derives{shortName: installedName}from the dir-mapping state already collected during flatten/prefix copy; skips no-op renames.rewriteSkillReferences/findSkillReferences(T2) — boundary-aware, longest-name-first token rewrite/detection, routed through a newtransformOutsideFencesline-state-machine that tracks CommonMark-style fence delimiters (backtick and tilde, ≤3 leading spaces, backtick-free info-string check) so fenced example content is left untouched while inline code spans are still rewritten.skill-name-manifest.ts(T3) —readSkillNameManifest/writeSkillNameManifest/buildTransitionMap/findOrphanedInstalledNames/mergeSkillNameMaps, plusreconcileSkillNameRegistryanddetectOrphanedSkillReferencesinapps/pair-cli/src/registry/skill-refs.ts, wired into bothinstallandupdatehandlers (replacing the old conditionalapplySkillRefsToNonSkillRegistriescall).cleanupStaleTransformedEntriesincopyPathOps.ts(T3) — removes top-level flattened/prefixed entries no longer produced by the current source+config, invoked whendefaultBehavior === 'mirror'.Files Changed
apps/pair-cli/src/commands/install/handler.ts,apps/pair-cli/src/commands/update/handler.ts,apps/pair-cli/src/registry/skill-refs.ts,packages/content-ops/src/ops/skill-reference-rewriter.ts,packages/content-ops/src/ops/copyPathOps.ts,packages/content-ops/src/index.ts,apps/website/content/docs/reference/skill-management.mdxpackages/content-ops/src/ops/skill-name-manifest.ts(+ test),apps/pair-cli/src/commands/update/idempotent-skill-registry.test.tsDatabase Changes
API Changes
findSkillReferences,readSkillNameManifest,writeSkillNameManifest,buildTransitionMap,findOrphanedInstalledNames,mergeSkillNameMaps,SkillNameManifest) documented via docstrings in source; consumer-facing behavior documented inskill-management.mdx.Testing
Test Coverage
skill-reference-rewriter.test.ts(fence detection, boundary matching, inline-code rewriting),skill-name-manifest.test.ts(manifest read/write/transition/orphan, malformed-manifest tolerance),skill-refs.test.ts(reconcileSkillNameRegistry,detectOrphanedSkillReferences, manifest path resolution),copyPathOps.test.ts(mirror-behavior stale-entry cleanup).apps/pair-cli/src/commands/update/idempotent-skill-registry.test.ts— driveshandleInstallCommand/handleUpdateCommandend-to-end against an in-memory fixture dataset.Test Results
Testing Strategy
IO_ERRORwith a message matching/collision/i; malformed/missing manifest treated as "no previous install" rather than throwing.Quality Assurance
Code Quality Checklist
link-rewriter.ts)logger)Review Areas
Deployment Information
Environment Impact
pair.config.jsonflatten/prefixkeysDeployment Notes
flatten/prefixconfig keys now have full effect end-to-endRollback Plan
Revert the merge commit. No persisted state format is introduced outside the new
.pair/.skill-name-map.jsonmanifest file in consumer projects; if rolled back, that file is simply ignored by the previous CLI version (no migration needed).Breaking Changes
API Breaking Changes
Integration Breaking Changes
applySkillRefsToNonSkillRegistriescall sites replaced byreconcileSkillNameRegistry, but the latter is a strict superset (falls through to the same rewrite when there's no previous manifest).pair/.skill-name-map.jsonfile on next install/update; no action required from themMigration Guide
None required. Existing installed projects pick up idempotent rewriting on their next
pair-cli updaterun; the first such run has no previous manifest and behaves like a fresh reconciliation (equivalent to pre-#238 behavior plus the new manifest write).Documentation
Documentation Updates
apps/website/content/docs/reference/skill-management.mdx— four new sections (Collision Detection, Fenced Code Blocks Are Skipped, Idempotent Updates and Prefix Changes, Removed or Disabled Skills / External KBs)skill-reference-rewriter.tsandskill-name-manifest.tsexplain rationale and the two documented known limitationsKnowledge Sharing
reconcileSkillNameRegistry's empty-map no-op) are documented inline with rationale, not just flaggedPerformance Impact
Performance Metrics
Benchmarking Results
Monitoring
pushLog('warn', ...)), not a monitoring dashboardSecurity Considerations
Security Review
Security Testing
Accessibility
Accessibility Compliance
Accessibility Testing
Risk Assessment
Technical Risks
cff081d)type/name/nested) but untested for the general caseBusiness Risks
Reviewer Guide
Review Focus Areas
idempotent-skill-registry.test.tsgenuinely assert byte-level content, not just "no throw" (they do — see review comment).transformOutsideFencesstate machine and the two documented known limitations.skill-name-manifest.tsand its wiring inreconcileSkillNameRegistry.Testing the Changes
Key Test Scenarios
AGENTS.md, and manifest are byte-identical across the second and third run.prefixbetween two update runs; assert the old prefixed directory is removed and a stale reference in anadd-behavior file is rewritten via the recorded manifest mapping (not string-matching).Dependencies & Related Work
Blocking Dependencies
mainRelated PRs
pair updateignores--config(always uses base config, can’t disable skills flatten) #186 (flatten config honored)packages/content-ops/apps/pair-cliand are not yet rebased on top of this — merge ordering will be handled separately, out of scope for this PRFollow-up Work
Stakeholder Communication
Stakeholder Notification
Communication Plan