Files
2026-10-14 12:34:57 +02:00

221 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture — Matt Pocock Skills → OpenCode v2
Phase-0 analysis, 2026-10-14. Sources: `mattpocock/skills@49dd158` (v1.3.1),
`fastcrw-opencode-websearch` (working V2 plugin + Gitea release pipeline), `mcdays94/pocock-agents`
(proven V1 orchestrator/worker pair, tracks upstream v1.3), OpenCode v2 docs + `anomalyco/opencode@dev`
source (verified via Context7). Per-skill classification: `docs/skill-mapping.md`.
## 1. Proposed architecture
One npm package, three delivery channels, one generator pipeline:
```
mattpocock/skills (upstream, git)
│ scripts/sync-upstream.ts (nightly)
▼
upstream/ pristine snapshot (skills/ subtree + UPSTREAM.json) — never hand-edited
│ scripts/generate.ts (inputs: upstream/ + .opencode/state/classification.json + agent-templates/ + overlay/)
▼
skills/ + manifest.json OpenCode skill tree (verbatim content, stripped of other-harness files)
agents/*.md V2-native agent files (orchestrator, worker, ...)
│
▼
package @haylan/opencode-pocock (name provisional)
├── dist/index.js plugin: injects skills via ctx.skill.transform; optional guardrails hook;
│ warns when agents are missing/stale
├── bin init copies agents/*.md → .opencode/agents/ (or ~/.config/opencode/agents/ with -g)
└── files: dist, skills, agents, manifest
```
Why this split (decision D2): V2 `SkillEditor.add()` exists, `AgentEditor.add()` does not. Skills can
be injected at runtime by the plugin with zero user file management; agents must exist as markdown
files in a discovered directory, so a tiny `init` copy step is unavoidable. The plugin detects
missing/version-mismatched agents and prints the one-line fix instead of failing or silently
writing into the user's repo.
User-facing install:
```jsonc
// opencode.json
{ "plugins": ["@haylan/opencode-pocock"] }
```
```bash
npx @haylan/opencode-pocock init # once per project (or -g for global agents)
```
Fidelity rule (D1): skill text ships verbatim. V2 natively honors `disable-model-invocation`
(user-invoked skills stay hidden from auto-selection but loadable via `/name` or by id), skill
`name`/`description`, and supporting-file paths relative to SKILL.md (we keep each skill's whole
directory, so `DEEPENING.md`, `template.sh`, etc. resolve). OpenCode-specific wiring lives only in
generated agents. If a skill ever needs a textual deviation, it goes through a tracked `overlay/`
patch input — never a hand-edit of `upstream/` or `skills/`.
Generator scope (deliberately small):
- strip non-OpenCode harness files (`agents/openai.yaml`, Claude plugin metadata) from the output tree
- parse each SKILL.md frontmatter → `manifest.json` entry: `{ id, name, description, autoinvoke,
slash, path, files[] }`; `autoinvoke=false` when upstream sets `disable-model-invocation: true`
- render `agents/*.md` from templates + `classification.json` (skill allow/deny lists, version stamp)
- regenerate `docs/skill-mapping.md` from `classification.json` (docs never drift from data)
## 2. Skill inventory (summary)
38 active skills at `49dd158`: engineering 20, productivity 7, misc 4, in-progress 7 (excluded per
D6), deprecated 0. Full table with classification + rationale: `docs/skill-mapping.md`.
## 3. Plugin API requirements
Verified against V2 (`@opencode/plugin` ^2.x, `Plugin.define`):
| Requirement | V2 mechanism |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Stable identity | `Plugin.define({ id: "haylan.pocock", setup })` |
| Inject all skills | `await ctx.skill.transform(editor => editor.add({ id, name, description, path, content, autoinvoke, slash }))` per manifest entry; `path` = absolute SKILL.md location inside the installed package so supporting-file references resolve |
| Options | `ctx.options` — validate and fail loudly (fastcrw pattern) |
| Dangerous-git guardrails (D7) | `await ctx.tool.hook("execute.before", ...)` on shell tool; opt-in `{ guardrails: true }` |
| Agent staleness hint | `await ctx.agent.list()` in setup; one-line console warning if `pocock`/`pocock-worker` missing or stamped version ≠ package version |
| Packaging | `type: module`, `exports["."]`, dependency `@opencode/plugin`; installed via bun by OpenCode at startup |
Not needed: custom tools, MCP, websearch, auth. Keep the plugin surface minimal.
## 4. Agent architecture
Hierarchy (D5) — two core agents, one likely addition, rest rejected unless the matrix proves value:
| Agent | Mode | Role | Key V2 config |
| ----------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pocock` | primary | Orchestrator: routes via `ask-matt`, walks grill→spec→tickets→build→review→retro, gates user-invoked skills behind explicit proposals, runs the `implement-spec` dispatch loop (integration branch + one worktree per ticket + frontier scheduling from its own merge record) | `permissions`: edit/shell allow; shell deny for destructive git on the main checkout; `skill` allow `*`; task allow |
| `pocock-worker` | subagent | Builds one ticket test-first inside its prepared worktree, merges integration tip into its branch, reports; never pushes/stashes/worktrees/PRs; asks nothing | `permissions`: skill deny `*` + allow-list (tdd, codebase-design, diagnosing-bugs, stack skills); shell deny push/stash/reset --hard/clean/worktree/switch, `gh pr`/`gh issue close`; edit allow |
| `pocock-reviewer` (candidate) | subagent | Read-only `code-review` fan-out over the integration branch; findings go back to the orchestrator, fixes run in a fresh worker | `permissions`: edit deny; shell allow git read commands only; skill allow code-review only |
| `pocock-researcher` (likely reject) | subagent | Exploration notes | Built-in `explore`/`general` subagents already do this; a branded one adds drift risk, no capability |
Design requirements coverage (per mission): purpose/scope/IO contract in each agent's system body;
skills via `action: skill` permission patterns; primary vs subagent via `mode`; spawn rights — only
`pocock` gets `task`; model — left to user default (pocock-agents pinned opus; we ship without a
`model` line and document how to pin, since provider availability varies); context/duration — phases
1–4 stay in one window (~150k guidance from `ask-matt` PHASE-BOUNDARIES), workers get bounded
`steps` only if testing shows runaway loops; git/worktrees — orchestrator-owned `/tmp/pocock-workers`
convention (documented, `external_directory` permission guidance in README); failure modes — worker
reports instead of repairing bad worktrees, orchestrator never merges failed workers; validation —
smoke + drift tests below.
Explicitly rejected: one giant agent containing all skills; a 12-agent hierarchy mirroring phase
names (phases are conversation steps, not roles — splitting them shreds the shared context the
methodology depends on, e.g. grilling facts feeding `to-spec`).
## 5. Synchronization strategy
- `upstream/` is vendored by script only: shallow-clone/fetch `mattpocock/skills`, resolve `HEAD`,
copy the `skills/` subtree, write `upstream/UPSTREAM.json` `{ repo, commit, commit_date, version }`
and mirror it to `.opencode/state/upstream.json`.
- Diff old vs new snapshot → added/removed/changed skill list → written into the changelog entry and
`status.md`; changed skills are matched against `classification.json` to flag affected agents
(regeneration is full but cheap and deterministic; "regenerate only what is necessary" is satisfied
by determinism — unchanged inputs produce byte-identical outputs, so the git diff _is_ the
affected-set report).
- New upstream skills appear in the manifest automatically but land unclassified:
`classification.json` defaults them to `skill` + `status: "needs-review"`, and the drift test fails
until a human confirms the classification. Upstream renames/removals fail the drift test when a
generated agent still references the old name (pocock-agents `check-skills.sh` pattern, ported to TS,
run against the vendored snapshot — no network in tests).
- Locally maintained artifacts (`agent-templates/`, `overlay/`, `src/`) are never touched by sync.
## 6. Gitea Actions design
`.gitea/workflows/check.yml` — on PR + push to main: `npm ci` → `npm run check` (prettier + tsc) →
`npm test` → `npm run build`. (fastcrw pattern.)
`.gitea/workflows/nightly.yml` — `schedule: cron` nightly + `workflow_dispatch`, `if: gitea.ref ==
'refs/heads/main'`, node 24:
1. checkout, `npm ci`
2. `node scripts/sync-upstream.ts` — no change → log + exit 0 (success)
3. `node scripts/generate.ts`
4. `npm run format` → `npm run check` → `npm test` (unit + integration + drift/regression) →
`npm run build` — any failure aborts before publish; drift-test failure for unclassified new
skills aborts too (human review required; publish waits for the next green run)
5. `node scripts/version.ts` — prepend `## [<minor-bump>] - <date>` + upstream sync entry to
CHANGELOG.md unless this exact upstream SHA was already released (state check → idempotent)
6. `node scripts/release.ts` — fastcrw release script adapted: publish packument+tarball to
`${SERVER_URL}/api/packages/${owner}/npm` with `Bearer ${PACKAGE_TOKEN}`, skip if version exists,
ensure tag `v<version>` at `gitea.sha`, link package to repo
7. commit-back: `sync: update mattpocock skills to <short-sha> [skip ci]` (upstream/, skills/,
agents/, docs/skill-mapping.md, state, CHANGELOG) pushed to main via token-embedded URL;
job fails loudly if push is rejected (no force-push, ever)
Secrets: only `secrets.PACKAGE_TOKEN` (write:package + write:repository). Nothing else, nothing
committed. Release/tag = publish step 6; no separate release job.
Smoke workflow (`workflow_dispatch` only): `scripts/smoke.ts` boots `opencode serve` in a fixture
project whose config points at the local package path, polls `GET /api/plugin` (status ok),
`GET /api/skill` (contains `tdd`, `to-spec` hidden-from-model flag), `GET /api/agent` (contains
`pocock`, `pocock-worker`). No model calls, no tokens spent. Optionally a model-driven acceptance
run (pocock-agents Docker pattern) stays manual-only.
## 7. Testing strategy
`node --test` directly on `.ts` (Node ≥ 22.18 type stripping; fastcrw precedent — zero test deps).
- **Unit**: frontmatter parser (name/description/disable-model-invocation/slash extraction incl.
quoted YAML values); manifest generation (fixture skill tree → expected manifest); classifier
(classification.json schema: every snapshot skill classified exactly once, enum values valid,
in-progress/deprecated excluded); agent generation (frontmatter keys ⊆ V2 allowlist — fails on any
legacy key like `prompt`/`permission`/`maxSteps`; permission arrays well-formed; worker skill
allow-list ∩ user-invoked skills = ∅); version bump calc; release helpers (`latestVersion`,
`publishBody` shasum/integrity, `isPublished`, `ensureTag` with fake fetch — fastcrw's tests
already prove this shape).
- **Integration**: plugin `setup` against a fake ctx (fastcrw's fake-editor pattern) → every manifest
skill added once, ids unique, `path` points at an existing file inside the package, user-invoked
skills get `autoinvoke: false`; bad options throw and register nothing; `init` bin copies agents to
a temp dir, `-g` to a fake home, refuses to overwrite modified files without `--force`; guardrails
hook denies `git push --force`, allows `git status`; drift/regression suite (agents reference only
skills present in the snapshot; every agent-referenced skill is in the manifest).
- **Smoke**: dispatch-only workflow above; representative execution (one worker ticket build) stays
manual/local, documented in `docs/runtime-checks.md` style.
## 8. Versioning
Semver; CHANGELOG is the source of truth (D3/D4). Package version in `package.json` stays
`0.0.0-dev`; release stamps the staged copy. Upstream tracking is orthogonal:
`.opencode/state/upstream.json` + changelog entries naming `mattpocock/skills@<short-sha>` (from →
to) + skill add/remove/change list. Upstream content change ⇒ minor bump (automated); generator/
plugin/agent behavior change ⇒ human-written changelog entry decides the bump; major never automatic.
## 9. Phased implementation plan
**Phase 1 — plugin package (quality gate: install → load → skills work → publish → nightly green)**
1.1 scaffold: package.json (`@haylan/opencode-pocock`, type module, exports, bin, files), tsconfig,
prettier, `.gitignore`, LICENSE (MIT — matches upstream; include upstream NOTICE/attribution), README
1.2 vendor snapshot: sync-upstream.ts + first `upstream/` commit + state files
1.3 generator: manifest + skills tree + tests
1.4 plugin: src/index.ts (skill injection, options validation, agent-staleness warning) + fake-ctx tests
1.5 init bin + tests
1.6 agents v1: hand-port pocock + pocock-worker to V2-native frontmatter as `agent-templates/`,
generate `agents/*.md`, drift tests
1.7 release.ts + version.ts (adapted from fastcrw) + unit tests
1.8 workflows: check.yml, nightly.yml (secrets documented for the repo admin), smoke.ts + dispatch workflow
1.9 local runtime verification: `opencode serve` against a fixture project — skills visible,
`/tdd` loads, agents appear after init, worker permission denies hold (`opencode debug`-style checks
or HTTP API); record results in docs/runtime-checks.md
1.10 first manual release `0.1.0` to Gitea registry; verify `plugins: ["@haylan/opencode-pocock"]`
installs from the registry (the one thing fastcrw left unverified — do it here)
**Phase 2 — classification-driven conversion (quality gate: every skill classified, conversions
tested, rejections documented, sync doesn't break agents)**
2.1 per-skill deep analysis (Step 1–3 of the conversion process) → fill `classification.json` +
`docs/skill-mapping.md` rationale/dependencies
2.2 implement `plugin` classification: guardrails hook (D7) + tests
2.3 evaluate `pocock-reviewer`; implement or document rejection
2.4 wire orchestrator flow to V2 realities (background subagents? batch fallback; worktree
permissions guidance; slash-command UX for user-invoked skills)
2.5 regression suite per converted skill; orchestration smoke (manual, token-spending, documented)
2.6 docs: README usage guide, skill-mapping complete, runtime-checks updated
2.7 let nightly run a full week; fix fallout; declare stable
## 10. Persistent TODO
Lives in `TODO.md` at the repo root (that file is the checklist; this section is the pointer).