Skip to content

feat(agent)!: align skill install with the Agent Skills standard#271

Open
powxenv wants to merge 3 commits into
TestSprite:mainfrom
powxenv:chore/agentskills-standard
Open

feat(agent)!: align skill install with the Agent Skills standard#271
powxenv wants to merge 3 commits into
TestSprite:mainfrom
powxenv:chore/agentskills-standard

Conversation

@powxenv

@powxenv powxenv commented Jul 22, 2026

Copy link
Copy Markdown

What does this PR do?

Aligns agent install with the Agent Skills open standard. Instead of hand-wiring a few agents with bespoke formats, the CLI now writes one canonical skill per project at .agents/skills/<skill>/SKILL.md (the standard's shared directory), and each agent reads it directly (universal agents) or via a symlink back to it. The bundled registry grows from 8 targets to the full standard set (72 agent ids), so any skills-compatible agent works out of the box.

This broadens adoption (works with 72 agents instead of 8), simplifies maintenance (one uniform code path instead of per-agent wrap()/mode/compact-body machinery), and follows a vendor-neutral standard already adopted by the agents users run.

Related issue

Closes #270

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behavior)
  • Documentation only
  • Build / CI / chore

Checklist

  • PR targets the main branch.
  • Commits follow Conventional Commits
    (feat(...), fix(...), docs(...), …).
  • npm run lint and npm run format:check pass.
  • npm run typecheck passes.
  • npm test passes and coverage stays at or above the 80% gate.
  • New behavior is covered by unit tests (mock-based; no network or
    credentials required).
  • No secrets, API keys, internal endpoints, or personal data are included.
  • User-facing changes are reflected in README.md / DOCUMENTATION.md where
    relevant.

Notes for reviewers

Breaking changes

This restructures the install model, so existing behavior changes for current users:

  • Per-agent bespoke formats removed. Codex no longer receives a managed section in AGENTS.md; Windsurf no longer receives a size-capped compact body. Both now read the canonical .agents/skills/ (codex directly, windsurf via symlink), like every other agent. The wrap() / own-file / managed-section machinery is gone in favor of one standard SKILL.md.
  • Install output schema changed. agent install results carry a new mode (canonical | symlink) field, and path now reflects the canonical-or-symlink model rather than each agent's bespoke location.
  • Existing installs go stale. Skills written in the old format will surface as stale/modified under agent status until refreshed — re-running agent install (with --force where needed) brings them in line.

What's preserved: the claude / kiro / copilot aliases still resolve, and agent install with no --target still defaults to claude-code — so the commands existing scripts use keep working; only the on-disk layout and output change.

Implementation notes

  • Model: canonical .agents/skills/<skill>/SKILL.md is the single source of truth. Universal agents (Codex, Cursor, Cline, Gemini CLI, Copilot, …) read it directly; every other agent gets a relative symlink from its own skills folder back to it — no drift between agents.
  • Path safety: both the canonical write and the symlink landing walk each path component with lstat and refuse to traverse a planted symlink (exit 5), so a malicious symlink can't redirect a write outside --dir.
  • Tests: the install/status/conflict/force/dry-run/path-safety paths are covered exhaustively by unit tests using an in-memory filesystem (deterministic, cross-platform, no real-symlink fragility); coverage on agent.ts is ~92% lines, with the remainder being defensive error handling and real-fs wrappers exercised by the e2e suite.

Summary by CodeRabbit

  • New Features

    • Added supported-agent documentation, target aliases, and universal versus symlinked installation guidance.
    • Agent skills are now installed from a shared canonical .agents/skills location, with symlink or copy fallback support.
    • Added clearer install, list, status, dry-run, conflict, backup, and force-update behavior.
    • Setup now defaults to claude-code and documents API-key verification and CI usage.
    • Added metadata for onboarding and verification skills.
  • Documentation

    • Updated contributor policies, onboarding instructions, command references, and supported-agent tables.

Replace the per-agent bespoke install formats with the Agent Skills open
standard: one canonical .agents/skills/<skill>/SKILL.md per project, read
directly by universal agents (Codex, Cursor, Cline, Gemini CLI, Copilot,
…) and via a relative symlink by every other agent. The bundled registry
grows from 8 hand-wired targets to the full standard set (72 agent ids).

BREAKING CHANGE: agent install no longer writes per-agent bespoke
formats. Codex no longer receives a managed section in AGENTS.md and
Windsurf no longer receives a size-capped compact body — both now read
the canonical .agents/skills/. The install result schema gains a `mode`
(canonical | symlink) field and `path` reflects the new model. Skills
written in the old format report stale/modified under `agent status`
until refreshed (re-run `agent install`, add --force where needed). The
legacy aliases (claude, kiro, copilot) and the claude-code default are
preserved, so existing commands keep working.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

The PR adopts canonical Agent Skills files under .agents/skills, classifies targets as universal or symlinked, adds alias resolution and marker-based status detection, updates setup defaults to claude-code, and revises tests and documentation for the new installation model.

Canonical skills and target registry

Layer / File(s) Summary
Canonical skills and target registry
src/lib/agent-targets.ts, src/lib/agent-targets.test.ts, skills/*
Defines skill metadata, target registry entries, aliases, canonical paths, frontmatter parsing, rendering, and provenance markers.

Canonical installation and status flow

Layer / File(s) Summary
Canonical installation and status flow
src/commands/agent.ts, src/commands/agent.test.ts, test/e2e/agent-install.e2e.test.ts
Writes canonical skill files once, creates symlink or copy-fallback landings, handles force/backups and safety checks, and reports install/list/status results.

Setup defaults and command integration

Layer / File(s) Summary
Setup defaults and command integration
src/commands/init.ts, src/commands/init.test.ts, test/e2e/setup.e2e.test.ts
Uses claude-code as the setup default and updates setup wiring and expectations for canonical skill paths.

Skill detection and doctor wiring

Layer / File(s) Summary
Skill detection and doctor wiring
src/lib/skill-nudge.ts, src/lib/skill-nudge.test.ts, src/commands/doctor.ts, test/e2e/skill-nudge.e2e.test.ts
Detects installed verification skills by supported landing-path existence and passes existsSync through doctor checks.

Documentation and contribution policy

Layer / File(s) Summary
Documentation and contribution policy
README.md, DOCUMENTATION.md, CONTRIBUTING.md
Documents target IDs, universal and symlinked installation, setup usage, status behavior, and supported-agent maintenance requirements.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AgentInstall
  participant CanonicalSkills
  participant AgentLanding
  User->>AgentInstall: agent install --target <id>
  AgentInstall->>CanonicalSkills: write or validate canonical SKILL.md
  AgentInstall->>AgentLanding: link target or copy fallback
  AgentLanding-->>User: install action and path
Loading

Possibly related PRs

Suggested reviewers: ruili-testsprite

🚥 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: aligning agent skill installation with the Agent Skills standard.
Linked Issues check ✅ Passed The changes implement canonical .agents/skills installs, universal vs symlinked targets, legacy aliases, status checks, backups, and path safety.
Out of Scope Changes check ✅ Passed The documentation, skill metadata, and test updates all support the install/setup overhaul; no unrelated changes stand out.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

✅ This PR is linked to an issue assigned to @powxenv — thanks! The needs-issue label has been removed.

@github-actions github-actions Bot added the needs-issue PR not linked to an issue yet — please open one first and claim it (see CONTRIBUTING) label Jul 22, 2026
powxenv added 2 commits July 24, 2026 14:07
…ehavior

- Revise CONTRIBUTING.md to describe universal vs symlinked agent targets and requirements for adding new targets
- Update DOCUMENTATION.md to clarify agent install command usage and differentiate universal and symlinked agents
- Expand README.md with a detailed supported agents table, including canonical ids, aliases, and skills folder locations
- Update src/lib/agent-targets.ts with added agents, corrections to skillsDir and universal flags, and alias mappings
- Modify unit tests and e2e tests to reflect renamed and updated agent targets, replacing deprecated aliases like gemini-cli with antigravity-cli and windsurf with devin-desktop
- Ensure single source of truth for skills at `.agents/skills`, symlink strategy for non-universal agents, and consistency across docs and code
@github-actions github-actions Bot removed the needs-issue PR not linked to an issue yet — please open one first and claim it (see CONTRIBUTING) label Jul 24, 2026
@powxenv
powxenv marked this pull request as ready for review July 24, 2026 12:09

@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: 3

🧹 Nitpick comments (1)
src/lib/agent-targets.test.ts (1)

131-165: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Coverage doesn't include adversarial alias tokens.

Tests confirm resolveTarget rejects a plain unknown token (line 156-158), but don't cover prototype-chain keys (constructor, __proto__, toString) that expose the bug flagged in agent-targets.ts (resolveTarget, lines 168-172). Once that's fixed, add a case here.

🤖 Prompt for 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.

In `@src/lib/agent-targets.test.ts` around lines 131 - 165, Add adversarial-token
coverage to the resolveTarget tests, specifically asserting that constructor,
__proto__, and toString are rejected as unknown tokens. Extend the existing
“resolveTarget rejects an unknown token” test in the TARGET_ALIASES +
resolveTarget suite, preserving the current null result expectation.
🤖 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 `@DOCUMENTATION.md`:
- Around line 111-118: Update the agent install examples in the documentation to
pass each agent name through the documented --target option instead of as a
positional argument. Preserve the existing install, list, and status examples
unchanged in purpose.

In `@src/commands/agent.ts`:
- Around line 714-728: Update runStatus around the TARGETS and DEFAULT_SKILLS
iteration to emit universal ok rows only for targets explicitly requested by the
user or targets with an installed actionable artifact. Preserve rows for
actionable states, while suppressing ok-only universal results for uninstalled,
unspecified targets.

In `@src/lib/agent-targets.ts`:
- Around line 168-172: Update resolveTarget’s alias lookup to verify raw is an
own key of TARGET_ALIASES before reading its value, returning null for inherited
keys such as constructor, __proto__, and toString. Preserve the existing TARGETS
lookup and canonical alias behavior, and add regression coverage in
agent-targets.test.ts for these tokens to ensure unknown-target validation is
reached instead of a crash.

---

Nitpick comments:
In `@src/lib/agent-targets.test.ts`:
- Around line 131-165: Add adversarial-token coverage to the resolveTarget
tests, specifically asserting that constructor, __proto__, and toString are
rejected as unknown tokens. Extend the existing “resolveTarget rejects an
unknown token” test in the TARGET_ALIASES + resolveTarget suite, preserving the
current null result expectation.
🪄 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.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 50a2a944-c182-480c-bf90-a91e921b1d6f

📥 Commits

Reviewing files that changed from the base of the PR and between fe07bc9 and 68f01d5.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
  • test/__snapshots__/help.snapshot.test.ts.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (18)
  • CONTRIBUTING.md
  • DOCUMENTATION.md
  • README.md
  • skills/testsprite-onboard.skill.md
  • skills/testsprite-verify.codex.md
  • skills/testsprite-verify.skill.md
  • src/commands/agent.test.ts
  • src/commands/agent.ts
  • src/commands/doctor.ts
  • src/commands/init.test.ts
  • src/commands/init.ts
  • src/lib/agent-targets.test.ts
  • src/lib/agent-targets.ts
  • src/lib/skill-nudge.test.ts
  • src/lib/skill-nudge.ts
  • test/e2e/agent-install.e2e.test.ts
  • test/e2e/setup.e2e.test.ts
  • test/e2e/skill-nudge.e2e.test.ts
💤 Files with no reviewable changes (2)
  • skills/testsprite-verify.codex.md
  • src/commands/doctor.ts

Comment thread DOCUMENTATION.md
Comment on lines 111 to 118
```bash
testsprite agent install claude # install the skill for Claude Code
testsprite agent install codex # install into AGENTS.md for Codex (managed-section)
testsprite agent install cursor # .cursor/rules/testsprite-verify.mdc
testsprite agent install cline # .clinerules/testsprite-verify.md
testsprite agent install windsurf # .windsurf/rules/testsprite-verify.md
testsprite agent install antigravity # .agents/skills/testsprite-verify/SKILL.md
testsprite agent install kiro # .kiro/skills/testsprite-verify/SKILL.md
testsprite agent install copilot # .github/instructions/testsprite-verify.instructions.md
testsprite agent list # list all 8 targets with status + mode + path
testsprite agent status # check installed skills against this CLI version
testsprite agent install claude-code
testsprite agent install codex
testsprite agent install cursor
testsprite agent install kiro-cli
testsprite agent list
testsprite agent status
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the documented --target option in install examples.

agent install accepts targets through --target; the supplied CLI definition does not declare a positional argument. These examples currently fail with an unexpected argument error.

Proposed fix
-testsprite agent install claude-code
-testsprite agent install codex
-testsprite agent install cursor
-testsprite agent install kiro-cli
+testsprite agent install --target claude-code
+testsprite agent install --target codex
+testsprite agent install --target cursor
+testsprite agent install --target kiro-cli
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```bash
testsprite agent install claude # install the skill for Claude Code
testsprite agent install codex # install into AGENTS.md for Codex (managed-section)
testsprite agent install cursor # .cursor/rules/testsprite-verify.mdc
testsprite agent install cline # .clinerules/testsprite-verify.md
testsprite agent install windsurf # .windsurf/rules/testsprite-verify.md
testsprite agent install antigravity # .agents/skills/testsprite-verify/SKILL.md
testsprite agent install kiro # .kiro/skills/testsprite-verify/SKILL.md
testsprite agent install copilot # .github/instructions/testsprite-verify.instructions.md
testsprite agent list # list all 8 targets with status + mode + path
testsprite agent status # check installed skills against this CLI version
testsprite agent install claude-code
testsprite agent install codex
testsprite agent install cursor
testsprite agent install kiro-cli
testsprite agent list
testsprite agent status
```
🤖 Prompt for 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.

In `@DOCUMENTATION.md` around lines 111 - 118, Update the agent install examples
in the documentation to pass each agent name through the documented --target
option instead of as a positional argument. Preserve the existing install, list,
and status examples unchanged in purpose.

Comment thread src/commands/agent.ts
Comment on lines +714 to 728
for (const target of Object.keys(TARGETS) as AgentTarget[]) {
const spec = TARGETS[target]!;
for (const skill of DEFAULT_SKILLS) {
const relPath = pathFor(target, skill);
results.push({
target,
skill,
path: relPath,
state: await classifyOwnFileState(
agentFs,
path.resolve(root, relPath),
target,
skill,
bodyForSkill,
),
});
const state = spec.universal
? await classifySkillFile(
agentFs,
path.resolve(root, canonicalSkillFile(skill)),
skill,
contentForSkill,
)
: await classifySymlinked(agentFs, root, target, skill, contentForSkill);
if (state === 'absent') continue;
results.push({ target, skill, path: pathFor(target, skill), state });
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'universal' src/lib/agent-targets.ts | head -50
rg -nP "runStatus" -C15 src/commands/agent.ts

Repository: TestSprite/testsprite-cli

Length of output: 8028


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Relevant agent-status implementation"
sed -n '637,750p' src/commands/agent.ts

echo
echo "## Agent target count and universal entries"
python3 - <<'PY'
from pathlib import Path
import re
text = Path('src/lib/agent-targets.ts').read_text()
entries = re.findall(r"([A-Za-z0-9-]+):\s*{\s*displayName:\s*'[A-Za-z0-9\s]+',\s*skillsDir:\s*[^,}]+,\s*universal:\s*(true|false)\s*},", text)
targets = [name for name, _ in entries]
universal = [name for name, flag in entries if flag == 'true']
print("targets:", len(targets), universal)
print("universal_count:", len(universal))
PY

echo
echo "## Search for runStatus result filtering/logging"
sed -n '748,798p' src/commands/agent.ts

Repository: TestSprite/testsprite-cli

Length of output: 6601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## locate install/status tests and agent command fixtures"
git ls-files | rg -n 'test|spec|jest|vitest|__tests__|agent|status|install' | sed -n '1,200p'

echo
echo "## focused search for agent status/install expectations"
rg -n "agent status|agent-install|install.*codex|runStatus|no TestSprite skill artifacts installed|Reports only agents that have an install|universal" -S --glob '*test*' --glob '!node_modules' --glob '!dist' --glob '!build' . | sed -n '1,240p'

Repository: TestSprite/testsprite-cli

Length of output: 8678


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## src/commands/agent.test.ts runStatus tests"
sed -n '689,900p' src/commands/agent.test.ts | cat -n | sed -n '1,220p'

echo
echo "## relevant e2e/status expectations"
sed -n '1,120p' test/e2e/agent-install.e2e.test.ts
sed -n '316,360p' test/e2e/agent-install.e2e.test.ts

echo
echo "## deterministic status behavior from source: install order and target loops"
sed -n '450,535p' src/commands/agent.ts
sed -n '544,625p' src/commands/agent.ts
sed -n '220,260p' src/commands/agent.ts

Repository: TestSprite/testsprite-cli

Length of output: 22347


Filter status output to installed artifacts only. After a single --target=codex install, runStatus currently prints 2 universal ok rows for every universal target in TARGETS, while the agent status contract says it reports only agents that have an install. Keep target rows that are actionable, but suppress ok-only universal rows that weren’t user-specified.

🤖 Prompt for 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.

In `@src/commands/agent.ts` around lines 714 - 728, Update runStatus around the
TARGETS and DEFAULT_SKILLS iteration to emit universal ok rows only for targets
explicitly requested by the user or targets with an installed actionable
artifact. Preserve rows for actionable states, while suppressing ok-only
universal results for uninstalled, unspecified targets.

Comment thread src/lib/agent-targets.ts
Comment on lines +168 to 172
/** Resolve a `--target` token (id or alias) to a canonical id, or null if unknown. */
export function resolveTarget(raw: string): AgentTarget | null {
if (Object.prototype.hasOwnProperty.call(TARGETS, raw)) return raw as AgentTarget;
return TARGET_ALIASES[raw] ?? null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

resolveTarget alias lookup is vulnerable to prototype-chain keys.

The TARGETS branch correctly guards with hasOwnProperty, but the alias branch falls back to a plain bracket lookup: TARGET_ALIASES[raw] ?? null. Since TARGET_ALIASES is a plain object literal, tokens like constructor, __proto__, or toString resolve to inherited, truthy values (e.g. the Object constructor) instead of undefined, so ?? null never triggers. That non-string "target" then gets pushed into targets/dedupedTargets in runInstall, and TARGETS[t]!.universal (agent.ts) throws on undefined instead of surfacing the documented "unknown target" validation error — a crash instead of the intended exit-code path.

As per path instructions, src/** requires "Correctness and clear error handling" and error paths that "map to the documented exit code" — this bug produces an unhandled crash instead of the validation error for those tokens.

🐛 Proposed fix
 export function resolveTarget(raw: string): AgentTarget | null {
   if (Object.prototype.hasOwnProperty.call(TARGETS, raw)) return raw as AgentTarget;
-  return TARGET_ALIASES[raw] ?? null;
+  return Object.prototype.hasOwnProperty.call(TARGET_ALIASES, raw) ? TARGET_ALIASES[raw]! : null;
 }

Consider adding a regression test in agent-targets.test.ts for tokens like 'constructor'/'__proto__'.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Resolve a `--target` token (id or alias) to a canonical id, or null if unknown. */
export function resolveTarget(raw: string): AgentTarget | null {
if (Object.prototype.hasOwnProperty.call(TARGETS, raw)) return raw as AgentTarget;
return TARGET_ALIASES[raw] ?? null;
}
/** Resolve a `--target` token (id or alias) to a canonical id, or null if unknown. */
export function resolveTarget(raw: string): AgentTarget | null {
if (Object.prototype.hasOwnProperty.call(TARGETS, raw)) return raw as AgentTarget;
return Object.prototype.hasOwnProperty.call(TARGET_ALIASES, raw) ? TARGET_ALIASES[raw]! : null;
}
🤖 Prompt for 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.

In `@src/lib/agent-targets.ts` around lines 168 - 172, Update resolveTarget’s
alias lookup to verify raw is an own key of TARGET_ALIASES before reading its
value, returning null for inherited keys such as constructor, __proto__, and
toString. Preserve the existing TARGETS lookup and canonical alias behavior, and
add regression coverage in agent-targets.test.ts for these tokens to ensure
unknown-target validation is reached instead of a crash.

Source: Path instructions

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.

[Hackathon] Support Agent Skills standard for install/setup

1 participant