Repository navigation
Description
APM integration — proposed target architecture (follow-up to #1015)
TL;DR
Re-opening from #1015. The five contract drifts I flagged are still verbatim in v0.9.4's packages/squad-cli/src/cli/commands/skill.ts (PR #1023 promoted PR #876 unchanged from insider to main; CHANGELOG [0.9.4] lists three unrelated fixes). This issue is paired with a PR I'm opening alongside it that mechanically realigns the existing squad skill ... integration to APM's documented contract — same command surface, same UX intent as PR #876, just routed through apm install / apm pack instead of a parallel reimplementation.
Note
Tone: this is from APM's maintainer. The framing in PR #876 ("Squad uses APM for skill distribution") is the right direction; this is just an offer to make the wiring match the framing. Take what's useful, push back on the rest, ignore freely — your project, your call.
Problem (WHY)
Two commits have ever touched packages/squad-cli/src/cli/commands/skill.ts in the v0.9.4 history (gh api repos/bradygaster/squad/commits?path=packages/squad-cli/src/cli/commands/skill.ts&sha=v0.9.4):
| SHA | Date | What it changed |
|---|---|---|
6e72c8a |
2026-04-08 | PR #876 — initial add (+568/-0) |
81f84b5 |
2026-04-21 | Cross-platform fixes only: await ghAvailable(), shell: IS_WINDOWS (+6/-5) |
PR #1023 ("promote insider to official") added skill.ts to main with diff +569/-0 — a verbatim copy of the insider file. CHANGELOG.md [0.9.4] lists three fixes (monorepo git init, ADO az shell on Windows, nap budget); none touch APM.
Each finding from #1015 against v0.9.4 skill.ts:
- Invalid top-level
apm.ymlkeys still emitted:skills:(L194),instructions:(L207),prompts:(L212). APM's manifest schema does not recognise these — they are silently ignored. - Custom
gh api+ regex YAML parser still present:gh apicalls at L317, L379, L422, L454; "Simple YAML parse" comment at L174;if (line.trim() === 'skills:')regex at L339.81f84b5only addedshell: IS_WINDOWSto those calls — it did not replace them withapm install. - Hand-rolled
apm.ymlregen instead ofapm pack --format plugin—publishSkillstill emitsapm.ymldirectly (L187–225); noapm packinvocation anywhere. - References to non-existent registry /
apm publishstill verbatim — L5, L162, L227, L539, L545. APM is git-native: "Any git repository is a valid APM package. Publish by pushing to a git remote — no registry required." .copilot/skills/as source-of-truth —resolveSkillsDirdefaults to.copilot/skills/(L82, L90);.apm/skills/is not referenced. APM's primitive-types doc says.apm/<primitive>/is the source location;.copilot/,.github/,.claude/,.cursor/are deploy outputs thatapm installpopulates.
Approach (WHAT)
Replace the parallel reimplementation with a thin adapter over the apm CLI. Same command surface as today plus a one-shot migrate for the legacy layout — no new flags, no new mental model for users.
| Verb | Today (v0.9.4) | Proposed |
|---|---|---|
squad skill install <source> |
gh api + regex YAML parse + raw write |
execFile('apm', ['install', source, '--target', 'copilot']) |
squad skill publish [<name>] |
Hand-rolled apm.yml regen |
Layout shim under .apm/skills/ then apm pack --format plugin |
squad skill list |
Reads .copilot/skills/ |
Reads .apm/skills/ + apm.lock.yaml |
squad skill migrate |
(does not exist) | One-shot move .copilot/skills/<n>/skill.md → .apm/skills/<n>/SKILL.md |
apm becomes an opt-in runtime dependency of the four squad skill ... verbs only. squad init, squad watch, and the rest work unchanged. Users who never run squad skill ... never see apm. A one-line install hint in the missing-binary error path covers first-run.
Diagrams
Architecture before — squad skill install owner/repo reimplements parts of APM:
flowchart TD
A[squad skill install] --> B["gh api repos/owner/repo/contents/apm.yml"]
B --> C["regex parse skills section"]
C --> D["for each skill: gh api contents"]
D --> E["write to .copilot/skills/n/skill.md"]
E --> F["write .apm-source.json"]
Architecture after — Squad delegates to apm and inherits its lockfile, integrity verification, path-security, and security scanning for free:
flowchart TD
A[squad skill install] --> B[ApmCli.ensureAvailable]
B -->|missing| X["fatal: apm not on PATH (install hint)"]
B -->|ok| C["execFile apm install source --target copilot"]
C --> D["stream stdout/stderr with apm prefix"]
D --> E["apm writes .copilot/, updates apm.lock.yaml"]
Module split — collapse the 569-line god-file into a small adapter plus per-verb files:
classDiagram
class SkillCommand {
+install(source)
+publish(name)
+list()
+migrate()
}
class ApmCli {
+ensureAvailable()
+install(source, target)
+pack(format)
+version()
}
class LegacySkillsLayout {
+detect()
+planMigration()
+execute()
}
class ApmCliError {
kind: missing | exit | versionTooOld
}
SkillCommand --> ApmCli : delegates
SkillCommand --> LegacySkillsLayout : opt-in migrate
ApmCli --> ApmCliError : throws
Trade-offs
- Adds an opt-in runtime dependency on the
apmCLI for the four skill verbs. Mitigation:squad init/watch/ etc. unaffected; missing-binary error includes the exact install command. Squad users who never publish or install skills never seeapm. - Replaces a working bespoke YAML parser with a subprocess call. Mitigation: APM's parser is schema-validated and gets supply-chain fixes upstream; the bespoke parser was flagged in #963 anyway.
- One-minor backward-compat window for legacy
.copilot/skills/<n>/skill.mdsource layout. Removed in the next minor. Consistent with APM's CHANGELOG philosophy of favouring shipping fast over long deprecation cycles. - One PR, not split. The five findings are tightly coupled (the manifest schema fix and the publish-path fix only make sense together). Splitting would create an awkward half-correct intermediate state.
Benefits
- Net-negative LOC. Estimated ~150 LOC removed (regex YAML parser, four
gh apicall sites,.apm-source.jsonwriter, hand-rolledapm.ymlemitter) vs ~80 LOC added (adapter, per-verb files, migrate command). - Closes four security surfaces with no new ones: bespoke parser, raw-blob trust without integrity check, path-traversal exposure on upstream-controlled paths, bypassed dependency security scanning. APM enforces lockfile content-hash verification, path-segment validation, and a security gate; Squad inherits all four by delegating.
- Truthful user-facing strings. No more "APM registry" / "Run
apm publish" pointing at things that don't exist. APM is git-native — any git repo is a valid package. - Lockfile + transitive deps + provenance for free.
.apm-source.jsonbecomes redundant;apm.lock.yamlalready tracks origin and integrity per dependency. - Cross-platform
apmshim handling done once in the adapter — noshell: IS_WINDOWSsprinkled across call sites.
What ships in the paired PR
- New module:
packages/squad-cli/src/cli/commands/skill/{index.ts, install.ts, publish.ts, list.ts, migrate.ts, apm-cli.ts}replacing today's single 569-line file. - Delete: regex YAML parser, four
gh apicontent-fetch sites,.apm-source.jsonwriter, hand-rolledapm.ymlemitter. - Replace: every "APM registry" /
apm publishstring with git-native phrasing. - Add:
squad skill migrate(one-shot, prompts for confirmation, dry-run summary first). - Tests: replace
gh apimocks withapmsubprocess mocks via the sameexecFilepatterns Squad already uses (core/gh-cli.ts); regression test asserts "APM registry" / "apm publish" absent fromskill/output. CHANGELOG.mdentry under[Unreleased]### Fixed.- Backward-compat: detect legacy
.copilot/skills/<n>/skill.mdsource layout; print a one-time deprecation pointer atsquad skill migrate. Reader removed in the next minor.
Important
If you'd rather take only items 1, 2, 4, 5 from #1015 (drop the apm pack --format plugin shift in publish), the PR is structured so that change is removable in a single commit — but flagging that without it, squad skill publish still emits invalid top-level keys, so finding #1 stays half-fixed.
How to engage
- The paired PR is opening alongside this issue and references it. Review either; happy to iterate or close if the direction doesn't fit your roadmap.
- Comment with concerns about scope, naming, or backward-compat — I'll adjust.
— Daniel
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
Activity
feat(skill): add apm CLI adapter with tagged-union error type
fix(skill): align squad skill verbs with APM CLI behaviour (github#1049)
danielmeppiel commentedon Apr 26, 2026
Closing this from my side.
After looking more carefully at
dev(the active branch), it's clearsquad skilland the APM wiring have already been removed there, and the direction is toward the open agentskills.io spec (PR #798) plus thesquad plugin marketplacemodel incommands/plugin.ts. The drifts I flagged only affect the v0.9.4 legacy onmainand will get resolved naturally on the next dev → main promotion, so a separate fix PR would just add noise.Thanks for the look — happy to keep an eye on things from a distance and reach out individually if anything APM-side ever becomes useful to you.