Repository navigation
Description
Summary
Allow users to define custom WatchCapabilities in their repo without forking squad-cli. The watch system already has a clean plugin interface (WatchCapability with name, phase, preflight(), execute()), a typed registry, and phase-based execution. The only missing piece is a loader that discovers and registers user-defined capabilities from outside the package.
Motivation
Users want to define deterministic, code-enforced workflows that run as part of the watch loop — for example, an issue-pipeline capability that enforces: issue > branch > implement (fresh copilot session) > PR > review (separate session, different prompt) > check approval > merge. Today this requires forking because createDefaultRegistry() only registers built-in capabilities.
The watch architecture is the right place for this: each LLM invocation is a fresh session (no drift), the pipeline between steps is TypeScript (not prompt), and the capability interface is already typed and tested.
Proposed Design
Loading mechanism
After createDefaultRegistry() in runWatch(), scan for user capabilities:
- Local repo:
.squad/capabilities/*.js— ES module default-exporting aWatchCapability - npm packages: Convention TBD (e.g.
squad-capability-*or@squad-capability/*)
Each file/package exports a class or object implementing WatchCapability. The loader does import() + registry.register().
Config integration
User capabilities are enabled/disabled the same way as built-in ones — via config.json keys or --<name> / --no-<name> CLI flags.
Sample capability
Ship a sample issue-pipeline capability (in .squad/templates/ or as a docs example) that demonstrates the pattern:
- Phase:
post-triage - Behavior: For each triaged issue without a PR:
- Create branch (code)
- Spawn copilot session with implement-only prompt (LLM)
- Open PR via
gh pr create(code) - Spawn separate copilot session with review-only prompt (LLM, different context)
- Check review decision (code)
- If approved + CI green → merge (code)
- If rejected → loop back to step 2 with different prompt context
What's NOT in scope
- Changing the WatchCapability interface (it's already good)
- Moving built-in capabilities to external loading (they stay hardcoded)
- Runtime hot-reloading (capabilities are loaded once at watch startup)
Implementation estimate
~50-100 lines in runWatch() for the loader + docs + sample capability.
Context
This came from a conversation with @tamirdresher and a colleague (Yoni) about enforcing issue>PR>review>merge pipelines without LLM drift. The watch architecture already solves the drift problem (fresh session per invocation), but lacks the extensibility hook for users to define their own pipeline steps.
Activity
feat(watch): load external WatchCapabilities from .squad/capabilities/ (
feat(watch): external capability loading + PID tracker (#918, #921) (#…
feat(watch): external capability loading + PID tracker (#918, #921) (#…