Guidance for any AI agent working in this repository. CLAUDE.md is a symlink to this file.
A pnpm workspace monorepo hosting AI agent plugins developed at Unic. Today it contains Claude Code plugins; the structure supports plugins for other agents (GitHub Copilot, etc.) in the future.
apps/
├── claude-code/ # Claude Code plugins — one dir per plugin
│ ├── pr-review/
│ ├── auto-format/
│ ├── unic-confluence/
│ ├── unic-archon-dlc/
│ ├── unic-pr-review/
│ └── unic-spec-review/
└── copilot/ # GitHub Copilot plugins (future)
packages/
├── biome-config/ # @unic/biome-config
├── tsconfig/ # @unic/tsconfig
├── release-tools/ # @unic/release-tools (bump / sync-version / tag / verify-changelog)
└── tracker-streams/ # @unic/tracker-streams (generates the published streams page)
docs/
├── adr/ # Architectural Decision Records
├── agents/ # Agent skill documentation
├── inbox/ # Retired idea-capture notes (historical)
├── issues/ # Grilled and scoped feature issues
├── process/ # Process and workflow guides
└── research/ # Research notes and explorations
- Plugin manifests:
apps/<agent>/<plugin>/.claude-plugin/plugin.jsonandmarketplace.json - Shared release scripts:
packages/release-tools/scripts/ - Architectural decisions:
docs/adr/ - Process templates:
docs/process/
pnpm install # install all workspace deps
pnpm check # Biome + Prettier check (whole tree)
pnpm format # Biome + Prettier fix (whole tree)
pnpm ci:check # same as check, non-interactive (for CI)
pnpm test # run tests across all packages
pnpm typecheck # type-check across all packages
# Per-plugin operations
pnpm --filter <name> bump patch # bump plugin version
pnpm --filter <name> verify:changelog # check changelog- Runtime: Node.js ≥ 22.
.nvmrcis the source of truth for local dev (currently24.15.0) and is consumed byactions/setup-nodein CI. - Package manager: pnpm 10 (workspace mode, catalog pinning)
- Module system: ESM (
"type": "module") throughout - Linter/formatter: Biome 2 for code/JSON; Prettier for Markdown only
- Type checking:
tsc --checkJs --noEmiton.mjsfiles; no compilation step - Test runner:
node:testbuilt-in
Every plugin must work on macOS, Windows, and Linux. Use Node.js APIs (node:path, node:fs, node:os) instead of shell commands. CI runs all three OSes × Node 22 and 24.
- Tabs for indentation in
.mjs/.js/.tsfiles; spaces (2) for.json/.yml/.yaml - Single quotes, no semicolons, trailing commas ES5-style (enforced by Biome)
- Line width 120 (Biome)
- Prettier for Markdown only
- No TypeScript compilation —
// @ts-check+ JSDoc for type safety
Plugins are versioned independently. plugin.json is the source of truth. Use pnpm --filter <name> bump <patch|minor|major> — never hand-edit marketplace.json.
On a 0.x plugin a breaking change is a minor bump, and major is reserved for the 1.0.0 release itself. SemVer §4 makes the 0.x API unstable by definition, and 1.0.0 is reached by meeting that plugin's own release bar — never by making a breaking change. So the ### Breaking changelog entry is what warns a consumer there, not the version number: write the entry, and never reach for bump major to signal it. Read the plugin's current version off its plugin.json to know which rule applies. Above 1.0.0 the ordinary contract holds. See ADR-0022.
Tag scheme: <plugin-name>@<version> (e.g. auto-format@0.5.5).
CHANGELOG version headers must use the format ## [X.Y.Z] — YYYY-MM-DD (em dash, then ISO date). pnpm bump writes this format and verify:changelog (in packages/release-tools) structurally enforces — YYYY-MM-DD on every versioned header. Do not change the separator or the date format: CI and the release flow depend on it.
Use package scope: feat(auto-format): …, fix(pr-review): …, chore(release-tools): …, chore(unic-archon-dlc): …, feat(unic-pr-review): ….
| Branch | Purpose |
|---|---|
main |
Production. Only receives merge commits from develop (or hotfix/*). The release workflow fires here and creates tags. |
develop |
Integration. Default target for all feature PRs. CI runs on every push and PR. |
feature/<name> |
Day-to-day work. Branch from develop, PR back to develop. |
hotfix/<name> |
Urgent fixes only. Branch from main, PR to both main and develop. |
Never commit directly to main or develop. Always go through a PR.
PRs merge with a merge commit, never a squash — the release flow reads develop → main merges.
The Archon worktrees under ~/.archon/workspaces/<org>/<repo>/worktrees/ are worktrees of your clone, not a separate checkout. They share its ref store, its config and its origin, so an autonomous run can move develop or main directly and bypass the PR gate above. .githooks/pre-push refuses a push of either branch when the push comes from a path under .archon/workspaces/. It leaves your own pushes alone, so read a refusal as "an Archon worktree tried this", never as "develop is protected".
Archon also keeps a default_branch of its own per repository, set from whatever was checked out on its first run there and never re-read from the host. worktree.baseBranch in .archon/config.yaml overrides it, and --from <branch> overrides that. A bare top-level baseBranch: has no reader — the nesting is the whole setting. So fix a wrong fork point in that file, never in ~/.archon/archon.db, which is one machine's row.
.git/hooks is not version-controlled, so install it once per clone:
ln -sf ../../.githooks/pre-push "$(git rev-parse --git-common-dir)/hooks/pre-push"Bugs are not a separate prefix: a bug issue that targets develop uses feature/ (the prefix encodes PR topology, not change kind). Archon-dispatched branches add a scope sub-namespace: feature/<scope>/<issue#>-<slug>, where <scope> is the area label with its tier stripped (app:unic-pr-review → unic-pr-review, repo → repo). The /archon-rollout command owns the full derivation rule.
To ship a new plugin version:
- On a feature branch, bump the version:
pnpm --filter <name> bump <patch|minor|major> - Add a dated entry to the plugin's
CHANGELOG.mdunder the new version. - Open a PR targeting
develop. CI runsverify:changelogon all PRs — it will fail if the changelog entry is missing or malformed. - After the PR merges to
develop, open a release PR fromdevelop→main. - After the release PR merges, the release workflow on
maindetects that<name>@<version>has no tag yet and creates it automatically.
CI summary:
| Event | Root checks | Package tests | verify:changelog |
|---|---|---|---|
| PR (any branch) | ✓ | ✓ (changed packages) | ✓ |
Push to develop |
✓ | ✓ (changed packages) | — |
Push to main |
✓ | ✓ (changed packages) | — |
New work enters through the issue tracker as Features. Plan with /wayfinder when the work is too big for one agent session, or /grill-with-docs when it fits in one; then /to-spec → /to-tickets → /archon-rollout. /to-tickets iterates the breakdown with you and applies ready-for-agent once you approve it — that in-session approval is the checkpoint, and nothing re-checks it before dispatch. So keep the ready queue short: grill late, dispatch soon, and re-grill a ticket that has sat for more than a few days rather than trusting it. Use /tdd and /implement for individual issues. See docs/process/ai-development.md for the mental model.
unic-archon-dlc is not installed here — this repo builds it, it does not run it. See ADR-0033.
This repo's product is prose — commands, skills, AGENTS.md files, ADRs. So a criterion names an observable outcome: what a document must state, checked by reading it. It does not name a command carrying its pasted output, and no file:line citation inside a criterion is binding — the next merge moves the line, so a criterion written around one rots on a schedule nobody controls.
Adding a module plus a test so that prose becomes testable is a defect, not rigour. unic-archon-dlc's lib/slopcheck.mjs was the worked example: it existed only to be tested, while apps/claude-code/unic-archon-dlc/.archon/workflows/unic-dlc-build.yaml inlines its own copy and imports nothing. #381 deleted it with the whole of that plugin's lib/ and test/, which is what the next passage is the bar for.
Four reads catch what a criterion-by-criterion pass cannot. Run them while writing the criteria, inside /to-tickets, because nothing downstream runs them for you:
- Read the criteria as one set. Individually reasonable criteria can be collectively impossible, and the dangerous shape is a gap between two of them that an implementer fills with a defensible-sounding decision — a green PR that faithfully implements the wrong thing. Amend the ticket and re-dispatch; do not merge it, because it becomes the precedent the next agent reads.
- Turn the diagnosis on the cure. When a ticket says a thing rots because it is written by hand, read the fix back and ask what is still written by hand.
- Ask which criteria an implementer satisfies by changing nothing. A vacuous criterion is a finding. It reads as coverage, buys none, and costs an audit round to discover after the code exists.
- Close a grilling by listing what it did not decide. "Either is acceptable" and "whichever fits" are where an unanswered question hides. Write each one down as an open question, or decide it there.
- External runtime deps to plugins unless truly essential (
auto-formathas zero; that's the bar) - Turborepo or other build orchestrators — plain pnpm workspaces is the current choice
- Features not tracked in the issue tracker — open a Feature first
- Per-plugin
pnpm-lock.yamlfiles — the root lockfile is canonical; sub-package lockfiles should never be committed
Never create, copy, or delete LICENSE files. The maintainer manages these manually in every package and plugin directory. If an acceptance criterion requires a LICENSE file to exist, warn the maintainer to add it themselves before continuing.
A Box is prose, so its bar is a run and a read, never a test. Three parts:
- It runs where it ships. Install the plugin into a Consumer through the marketplace — no
node_modules, no hand-set environment variable — and run the Box far enough to see the step under review succeed. This is the only check that sees what this repository cannot: every command defect ofunic-archon-dlc0.22.0 was invisible topnpm testand toarchon workflow listhere, and visible on the first command run inDXP-DesignSystem. - Its rules are stated where they are needed. A rule that lives only in an
AGENTS.mdis invisible at run time, so every prompt that must honour a rule carries it inline. Read for those rules when you touch a Box, and again in review. - What it depends on is written once. One list, in prose, with no mirror and no generator — because a mirror is what drifts, and a generator is the module this bar exists to avoid.
What the bar gives up, plainly:
- A
git add -A, or a repository derived from a remote URL, now merges green if nobody reads the diff.test/box-staging-and-repo-pinning.test.mjsgrepped every Box YAML for both patterns; #381 deleted it and nothing replaces it. Both patterns are visible on the page, and both rules are stated inline in every prompt that stages a path or names a repository. That is the whole guard, and an unread diff defeats it. - An upstream rename is not caught in this repository.
test/command-methods.test.mjswas the tripwire for a Method upstream had renamed; it compared two hand-written surfaces here and never watched upstream, so it could not have caught the rename wave it was written for. Nothing replaces it either, but the moment of the check is now named rather than left to chance: upgrading a vendored Method Bundle means diffing the vendored tree against the new upstream tag by hand, in the same commit that moves the pin. The plugin version is the pin. Between two upgrades, a rename surfaces on the next Consumer run.
Issues live in GitHub Issues at unic/unic-agents-plugins (planned migration to Azure DevOps). See docs/agents/issue-tracker.md.
8-state vocabulary: needs-triage → needs-info → needs-specs → ready-for-agent / ready-for-human → resolved → closed (or rejected). See docs/agents/triage-labels.md.
Type (kind of issue): feature, bug, spike, tech-debt, docs, release. See docs/agents/labels.md, which is now the only home for the repo-local release type.
Area (which app/package): app:<plugin> one per app, pkg:<package> one per workspace package, and repo for monorepo-wide / cross-cutting work. Hand-applied. See CONTEXT-MAP.md and ADR-0032.
Multi-context repo: Per-plugin CONTEXT.md files live under apps/claude-code/<plugin>/. Root CONTEXT-MAP.md at repo root. See docs/agents/domain.md.
Matt Pocock's skills (mattpocock/skills) are the agent-skill driver for this repo, installed via npx skills and pinned in skills-lock.json. See ADR-0033.
- issue-tracker.md —
ghconventions, PR triage surface, wayfinding operations - triage-labels.md — canonical triage role → this repo's state label
- labels.md — four-tier label taxonomy: state, type, priority, area
- domain.md — multi-context layout,
CONTEXT.mdand ADR locations - feature-runner.md — AFK invocation of the feature runner
| Path | Owner | Rule |
|---|---|---|
.agents/skills/** |
Upstream mattpocock/skills |
Never hand-edit. Every npx skills add overwrites it; edits die silently |
.claude/skills/<name> symlinks |
npx skills |
Managed. skills remove leaves the .agents/skills/ source directory behind, so pair it with git rm -r |
.claude/skills/{archon,new-plugin,verify-spec} |
This repo | Real directories, repo-authored. npx skills does not manage them — never remove them while pruning vendored skills |
skills-lock.json |
npx skills |
Never hand-edit — the hashes are computed |
docs/agents/*.md |
This repo | Hand-maintained, no generator. Do not run /setup-matt-pocock-skills: it reverts triage-labels.md to a five-role wontfix vocabulary |
Selection policy: all of skills/engineering/ and skills/productivity/, skills/misc/ by explicit justification, never skills/in-progress/.
Two entries the policy needs to name explicitly:
misc/git-guardrails-claude-codeis the onemisc/entry installed. Justification: it is the only vendored skill that installs a repo-local safety hook, so it belongs where the repo is. Read the caveat below before wiring it up.setup-matt-pocock-skillsstays installed but must never run. It is the referencedocs/agents/*.mdwas hand-authored from, which is why it is kept. A run revertsdocs/agents/triage-labels.mdto the five-rolewontfixvocabulary. Four installed skills tell an agent to invoke it when a tracker or label mapping looks missing —triage,wayfinder,to-spec,to-tickets. Those files exist and are correct here, so that condition is never met: if a skill asks for them, readdocs/agents/, do not run the setup skill.
npx skills@latest add mattpocock/skills -a claude-code -y -s <name> -s <name> …
npx skills@latest remove -s <name> -s <name> … -a claude-code -yThree traps the CLI sets:
- Target
-a claude-code, never-a '*'. The wildcard installs a second, frontmatter-rewritten copy of every skill into a top-levelagent/skills/tree for foreign agents, which then drifts from.agents/skills/.removerejects-a '*'outright. -stakes repeated flags, not a comma list.-s a,b,creports "no matching skills found" and exits 0.removeis 2-for-3. It cleans the.claude/skills/<name>symlink and the lockfile entry but leaves.agents/skills/<name>/behind. Pair every removal withgit rm -r .agents/skills/<name>.
Upstream renames and deletes skills between releases, and nothing prunes. After upgrading, diff the installed set against the upstream tree and remove what no longer exists there.
.agents/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh reads its PreToolUse payload with jq and does not check that the read worked. Without jq on the hook runner's PATH it exits 0 — which the hook protocol means as allow — so a git push --force passes while the transcript looks identical to a successful block. Its own verification step tests only the matching-command path, so installing it appears to confirm protection that is conditional on jq.
Before wiring this hook into .claude/settings.json, confirm jq resolves for the hook runner, then test the negative path: pipe a payload in with jq off the PATH and expect a non-zero exit. The fix belongs upstream — .agents/skills/** is never hand-edited here, so a local patch dies on the next npx skills add.