Skip to content

feat(skills): canonical Skill IR, token registry, and per-host lowering (#108) - #185

Merged
ScriptedAlchemy merged 1 commit into
mainfrom
wave4/108-skill-ir
Sep 1, 2026
Merged

feat(skills): canonical Skill IR, token registry, and per-host lowering (#108)#185
ScriptedAlchemy merged 1 commit into
mainfrom
wave4/108-skill-ir

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Summary

  • Parse one Skill source into a canonical IR that keeps portable Agent Skills metadata, typed Claude/Cursor/Codex extensions, sidecars, resources, runtime placeholders, provenance, and capability-facing diagnostics.
  • One plugin-surface token registry classifies arguments, skill root, plugin root, plugin data, project root, and session identity per host: semantically portable (lowers to documented syntax), target-namespaced, or honestly-no-equivalent (AB3008). Build-time never resolves runtime values; one host's syntax does not leak into another (AB3009).
  • Static lowering reuses the existing SKILL.tsx/SKILL.ts rendered-skill path (no live Flight client). The artifact planner still owns destinations, staging, manifests, and provenance. Portable SKILL.md stays a byte-stable copy pass-through when no extension or placeholder requires target-specific output.

Fixes the Wave 4 design slice of #108. Real-install proofs stay on #100 stage 3.

Token lowering (Skill Markdown)

Token Claude Codex Cursor Portable
arguments $ARGUMENTS no equivalent no equivalent no equivalent
skillRoot ${CLAUDE_SKILL_DIR} no equivalent no equivalent no equivalent
pluginRoot ${CLAUDE_PLUGIN_ROOT} no equivalent in SKILL.md (${PLUGIN_ROOT} in hooks/plugin config) no equivalent in SKILL.md (${CURSOR_PLUGIN_ROOT} in plugin config) no equivalent in SKILL.md (${PLUGIN_ROOT} in plugin config)
pluginData ${CLAUDE_PLUGIN_DATA} no equivalent in SKILL.md (${PLUGIN_DATA} in hooks/plugin config) no equivalent no equivalent in SKILL.md (${PLUGIN_DATA} in plugin config)
projectRoot ${CLAUDE_PROJECT_DIR} no equivalent no equivalent in SKILL.md (${workspaceFolder} in plugin config) no equivalent
sessionIdentity ${CLAUDE_SESSION_ID} no equivalent no equivalent no equivalent

Evidence is pinned to the in-repo host revisions (Claude Code 2.1.250, Codex 0.147.0, Cursor 2026-08-28 / cursor/plugins@070189284e702e8a4d2e3cc8913994b204c5337a) and the Agent Skills schema at 69ef37e9424c0a7ea9dd2293b559e43ec8176379. Closed per-host output schemas live in src/schemas/skill-hosts/ and reject unknown fields.

Rendered-form decision

Keep SKILL.tsx / SKILL.ts. Do not add SKILL.mdx or a second Markdown-AST compiler.

  • The convention already compiles at config-load time through jiti and renderElementToMarkdown, with watch/digest participation via skill.source.
  • MDX would add a second toolchain without a win on watch, source maps, or multi-target lowering.
  • A Markdown-AST transform cannot express the defineSkill + token-component composition the issue needs.
  • Compile target-aware Skills with host frontmatter and interpolation #108 forbids a live Flight client for static skills; the existing path already satisfies that.

defineSkill and Skill.* token components are identity helpers over the registry. Host syntax is applied only during lowering.

Shared vs per-host skills/ tree

Inspect-visible only (skillTreeLayout.decision: shared | per-host-required, feeds: '#101'). Not hard-committed. Standalone targets still write skills/<name>/SKILL.md in that target's artifact root. The composite plugin target writes the portable intersection when host documents differ, and inspect records that #101 must select at install time.

Diagnostics

  • AB3006 unknown source field
  • AB3008 required token has no host equivalent
  • AB3009 foreign host syntax leaked into lowered Markdown
  • AB3010 closed-schema failure on a lowered document

AB3000AB3005 and AB4735 are unchanged.

Test plan

  • packages/agent-bundle/tests/skill-ir.test.ts — IR, tokens, pass-through, per-host fixtures, schema pins, planner copy path
  • rendered-skills, agent-skills-schema, skill-document-service, config, normalization, cursor-adapter, manifest, provenance
  • scoped rslint on the touched files
  • real Claude / Codex / Cursor installs — rides Compile capability-aware host component bundles #100 stage 3

Coordination

Adapter edits are the shared skill emission tail in adapters/types.ts only. Event/hook files are untouched so Wave 4 Lane A (wave4/97-event-routes) stays disjoint. Evidence capabilityRevision / capabilitySha256 fields are unchanged.

Give authors one Skill source that lowers to closed Claude, Cursor, and Codex
documents, keep portable SKILL.md byte-stable when nothing requires a
target-specific rewrite, and surface shared-vs-per-host layout as inspect
evidence for #101 instead of guessing an install tree.
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dbbe347

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T20:02:57.546295Z dbbe347 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ScriptedAlchemy
ScriptedAlchemy merged commit 6baf597 into main Sep 1, 2026
5 of 9 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbbe347f3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +839 to +845
diagnostics.push(...validateAgentSkillsFrontmatter({
...(ir.portable.allowedTools === undefined ? {} : { 'allowed-tools': ir.portable.allowedTools }),
...(ir.portable.compatibility === undefined ? {} : { compatibility: ir.portable.compatibility }),
...(ir.portable.description === undefined ? {} : { description: ir.portable.description }),
...(ir.portable.license === undefined ? {} : { license: ir.portable.license }),
...(ir.portable.metadata === undefined ? {} : { metadata: ir.portable.metadata }),
...(ir.portable.name === undefined ? {} : { name: ir.portable.name }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate the authored portable fields before sanitizing them

Validate the original portable subset rather than this reconstructed IR object. portableFrom drops invalid optional values (for example license: 42, compatibility: 42, or non-string metadata entries), so agent-bundle validate now reports these sources as valid even though the Agent Skills schema previously rejected them; a pass-through skill can then retain the invalid raw frontmatter until artifact validation runs during a build.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in #205 (merged as 079a77d): the pinned Agent Skills frontmatter validation now runs on the raw authored portable fields before Skill IR sanitization, so invalid optional values fail with the AB4007-family diagnostics instead of surviving to build. Regression test in normalization.test.ts.

Comment on lines +145 to +146
...(iface === undefined ? {} : {
interface: Object.freeze({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unknown nested Codex extension fields

Reject unknown keys inside targets.codex before projecting the recognized properties. For example, targets.codex.interface.display_nmae creates an empty interface object here, lowers to a schema-valid agents/openai.yaml, and completes without any diagnostic while silently discarding the intended display name; the current unknown-field check only covers keys directly under targets.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in #205 (merged as 079a77d): unknown nested keys under targets.codex (and the same gap under targets.claude / targets.cursor, including codex interface, policy, dependencies, and tool entries) now emit the AB3006 unknown-field diagnostic instead of being silently dropped. Regression test in skill-ir.test.ts covers a typo under every host target.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Heads-up from Lane B (#102): dev-services.test.ts :: creates an exact deeply frozen root-independent project context fails on pristine main since this landed — modelDigest now differs across checkout roots. canonicalizeNormalizedModel (core/project-context.ts) does not canonicalize the new NormalizedSkill.skillIr field, so any absolute path inside the Skill IR (skill dir/source/resource paths) makes the model digest root-dependent. Reproduced at 3ae7721 in a clean worktree (Lane B's #186 merged over it without touching that seam). The fix belongs with the Skill IR: either relativize the IR's paths during normalization or teach the canonicalizer about skillIr like it handles skills[].dir/source/resources.

ScriptedAlchemy added a commit that referenced this pull request Sep 1, 2026
PR #185 added skillIr and hostDocuments to the normalized model without
extending canonicalizeNormalizedModel, so equivalent projects in different
temp directories produced different modelDigest values.
ScriptedAlchemy added a commit that referenced this pull request Sep 1, 2026
Fixes dev-services modelDigest regression from #185 by canonicalizing skillIr/hostDocuments paths in canonicalizeNormalizedModel.
ScriptedAlchemy added a commit that referenced this pull request Sep 1, 2026
…arget keys (#205)

Post-merge review findings from #185: run the pinned Agent Skills
frontmatter validation on the authored values before Skill IR
sanitization so invalid optional fields fail with AB4007-family
diagnostics instead of surviving to build, and report AB3006 unknown-field
diagnostics for unrecognized keys nested under targets.claude,
targets.cursor, and targets.codex (including codex interface, policy,
dependencies, and tool entries) instead of silently dropping typos.
@ScriptedAlchemy
ScriptedAlchemy deleted the wave4/108-skill-ir branch September 3, 2026 05:29
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.

1 participant