Skip to content

feat(compat): model-invocable Skill tool — progressive disclosure via index block + on-demand body (#120)#172

Merged
OGtwelve merged 2 commits into
mainfrom
feat/skill-tool-120
Jul 13, 2026
Merged

feat(compat): model-invocable Skill tool — progressive disclosure via index block + on-demand body (#120)#172
OGtwelve merged 2 commits into
mainfrom
feat/skill-tool-120

Conversation

@OGtwelve

@OGtwelve OGtwelve commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What & why

Closes #120. The skills subsystem stopped at discovery: loadSkills parsed SKILL.md files but nothing let a subagent invoke a skill and no builder rendered a skill index for the system prompt. This ships the two missing tiers of progressive disclosure plus the parser fixes they depend on.

Mechanism only — zero content, zero aitm wiring, so nothing changes at runtime when it lands (per the issue's scope note).

Changes

  • skill-tool.ts (new)
    • skillIndexBlock(skills) — the always-visible tier: the blocking invocation contract as a preamble plus one <name>: <description> line per skill. Bodies never appear here; multi-line descriptions collapse to one line. Returns '' when nothing is invocable.
    • skillTool(skills) — the Skill tool a subagent calls to pull a matched skill's body + absolute path into context on demand, rendered as plain text (toModelOutput, per feat(compat): plain-text tool-result rendering — toModelOutput on the file/search/bash tools so the model stops seeing JSON-escaped file contents #127) so it reads as instructions rather than a JSON-escaped blob. Unknown names return an error result naming the available skills — never throws.
    • Skills with disable-model-invocation: true are excluded from both the index and the tool's matchable set.
  • frontmatter.ts — block-scalar support (| literal / > folded, with - chomping). Multi-line skill descriptions previously parsed as the bare marker character and silently dropped their content. Stays hand-rolled and dependency-free.
  • skills-loader.tsSkillDefinition gains extra: Record<string, FrontmatterValue> carrying every frontmatter key other than name/description, so callers can honor allowed-tools, disable-model-invocation, etc.
  • index.ts — export the new surface.

Scope boundaries (from the issue)

The caller passes the skills array, deciding what a role may mount. Target-repo .claude/skills are untrusted input — Worker-only, behind an explicit opt-in, never Planner/Reviewer — and that mounting is a separate aitm-side issue this API deliberately does not preclude. Skipped here: plugin namespaces, the slash-command surface (no interactive surface in aitm), and directory-scoped resolution (blocked on #104's file-touch tracking).

Tests / gate

  • skill-tool.test.ts (new): index preamble + one-line-per-skill + disable-model-invocation exclusion; valid lookup returns body/path and plain-text rendering; unknown name returns the available-names error without throwing; args echoed.
  • frontmatter.test.ts: | / > / |- / >-, folded paragraph breaks, block ends at the next top-level key, and key: >text staying a plain scalar.
  • skills-loader.test.ts: extra passthrough + a folded description round-tripping through loadSkills.
  • Green: typecheck (both packages) + typecheck:tests (compat) + test:node (247 pass / 0 fail) + biome all clean.

Part of #100.

Summary by CodeRabbit

  • New Features
    • Added support for YAML-style folded (>) and literal (|) block scalars (including optional chomping) in skill frontmatter descriptions.
    • Preserved additional, unrecognized frontmatter metadata via a new extra field on loaded skills.
    • Introduced model-facing “skills” tooling: skill indexing plus a lookup tool that returns name, path, body, and formatted results (excluding disabled skills).
  • Bug Fixes
    • Improved frontmatter parsing for CRLF, blank-line folding behavior, and correct trailing-newline/chomp semantics. Also ensured unsupported block-scalar markers are treated as plain text.
    • Expanded test coverage for these parsing and skills-tooling behaviors.

… index block + on-demand body (#120)

Skills discovery (loadSkills) previously stopped at parsing; nothing let a
subagent invoke a skill and no builder rendered a skill index. Add the two
missing tiers of progressive disclosure plus the parser fixes they need.

- skill-tool.ts (new): `skillIndexBlock(skills)` renders the always-visible
  tier — the blocking invocation contract plus one `<name>: <description>`
  line per skill; `skillTool(skills)` is the AI-SDK `Skill` tool that pulls a
  matched skill's body + absolute path into context on demand, rendered as
  plain text (toModelOutput) so it reads as instructions. Unknown names return
  an error naming the available skills — never throw. Skills with
  `disable-model-invocation: true` are excluded from both the index and the
  tool's matchable set.
- frontmatter.ts: block-scalar support (`|` literal / `>` folded, with `-`
  chomping). Multi-line skill descriptions previously parsed as the bare
  marker and dropped their content. Stays hand-rolled, dependency-free.
- skills-loader.ts: `SkillDefinition` gains `extra: Record<string,
  FrontmatterValue>` carrying every frontmatter key other than
  name/description, so callers can honor `allowed-tools`,
  `disable-model-invocation`, etc.
- index.ts: export the new surface.

Mechanism only: zero content, zero aitm wiring, so nothing changes at runtime
when it lands. The caller passes the `skills` array, deciding what a role may
mount; target-repo skills are untrusted (Worker-only) and their wiring is a
separate aitm-side issue.

Part of #100.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 4999c3c2-ed9e-460b-b474-78287932ae82

📥 Commits

Reviewing files that changed from the base of the PR and between 5f6490c and 8185287.

📒 Files selected for processing (2)
  • packages/ai-claude-compat/src/frontmatter.test.ts
  • packages/ai-claude-compat/src/frontmatter.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ai-claude-compat/src/frontmatter.test.ts
  • packages/ai-claude-compat/src/frontmatter.ts

📝 Walkthrough

Walkthrough

Adds YAML block scalar support to frontmatter parsing, preserves additional skill metadata, and introduces exported tooling for indexing and invoking model-accessible skills with exact-name lookup, disabled-skill filtering, argument echoing, and plain-text results.

Changes

Skills frontmatter and invocation

Layer / File(s) Summary
Block scalar parsing and coverage
packages/ai-claude-compat/src/frontmatter.ts, packages/ai-claude-compat/src/frontmatter.test.ts
Supports folded and literal block scalars, CRLF normalization, optional trailing-newline removal, indentation-based termination, and blank-line folding behavior.
Skill metadata propagation
packages/ai-claude-compat/src/skills-loader.ts, packages/ai-claude-compat/src/skills-loader.test.ts
Adds extra metadata to SkillDefinition and preserves unrecognized frontmatter keys while round-tripping folded descriptions.
Model-invocable skill tooling
packages/ai-claude-compat/src/skill-tool.ts, packages/ai-claude-compat/src/skill-tool.test.ts, packages/ai-claude-compat/src/index.ts
Adds the invocation contract, eligible-skill index, exact-name tool lookup, argument handling, formatted results, tests, and package exports.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Model
  participant skillIndexBlock
  participant skillTool
  Model->>skillIndexBlock: Read eligible skill names and descriptions
  skillIndexBlock-->>Model: Return invocation contract and skill index
  Model->>skillTool: Invoke exact skill name with optional args
  skillTool-->>Model: Return formatted skill instructions with body and path
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a model-invocable Skill tool with progressive disclosure and the linked issue reference.
Linked Issues check ✅ Passed The changes align with #120: they add skill indexing/tooling, block-scalar frontmatter support, and extra metadata passthrough with tests.
Out of Scope Changes check ✅ Passed I don't see meaningful out-of-scope code; the edits stay within #120's skill tooling, parsing, exports, and test coverage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 30.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-tool-120

Comment @coderabbitai help to get the list of available commands.

@OGtwelve

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-claude-compat/src/frontmatter.ts`:
- Around line 83-96: Update parseBlockScalar to remove trailing carriage returns
from each input line before collecting or slicing it, while preserving the
existing indentation and blank-line handling for LF input. Ensure both folded
and literal block-scalar assembly produces values without embedded CR characters
when parseBlock receives CRLF content.
- Around line 65-79: Restrict BLOCK_SCALAR to the documented chomping indicators
by removing “+” from its accepted pattern. Keep parseBlockScalar’s existing “-”
and default clip behavior unchanged, allowing headers such as >+ to follow the
plain-scalar/no-op path instead of being silently misparsed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b170e067-70e4-4f31-99c7-58a22f07bf38

📥 Commits

Reviewing files that changed from the base of the PR and between 3ddbf27 and 5f6490c.

📒 Files selected for processing (7)
  • packages/ai-claude-compat/src/frontmatter.test.ts
  • packages/ai-claude-compat/src/frontmatter.ts
  • packages/ai-claude-compat/src/index.ts
  • packages/ai-claude-compat/src/skill-tool.test.ts
  • packages/ai-claude-compat/src/skill-tool.ts
  • packages/ai-claude-compat/src/skills-loader.test.ts
  • packages/ai-claude-compat/src/skills-loader.ts

Comment thread packages/ai-claude-compat/src/frontmatter.ts Outdated
Comment thread packages/ai-claude-compat/src/frontmatter.ts
…mping (#120)

CR review on PR #172:
- Major: CRLF line endings leaked `\r` into block-scalar values. Root cause is
  broader than the block scalar — `raw.split('\n')` left a trailing `\r` on every
  line, and the key-line regex `(.*)$` never matches a line ending in `\r`, so on a
  CRLF-authored SKILL.md NO field parsed at all. Fixed generally by splitting on
  `/\r?\n/` in parseBlock, which neutralizes CR for every value path.
- Minor: `+` (keep) chomping was accepted by BLOCK_SCALAR but behaved like clip.
  Dropped it from the regex so `>+` falls through to the plain-scalar path;
  corrected the comment to document `-`-only support.

Tests: CRLF folded/literal round-trips + `>+` plain-scalar fallback.
@OGtwelve
OGtwelve merged commit 2dbb187 into main Jul 13, 2026
4 checks passed
@OGtwelve
OGtwelve deleted the feat/skill-tool-120 branch July 13, 2026 10:39
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.

feat(compat): model-invocable Skill tool — progressive disclosure via index block + on-demand body

1 participant