Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ accepts that expected skip.

## Cache behavior

The workflow installs Bun with `oven-sh/setup-bun`, pinned per the supply-chain rule below. The action reads the Bun version from `packageManager` in `package.json`, needs no version input, and caches only the Bun executable. A separate official `actions/cache` step caches `~/.bun/install/cache`; its exact key binds runner identity, `bun.lock`, and `package.json`. Caches are hints only: frozen installs and `ci.mjs` gates decide correctness. `resolve-shards` restores no cache and installs no dependencies; it needs only a checkout and Node. (verify: `grep -n "setup-bun@\|actions/cache@" .github/workflows/ci.yml`)
The workflow installs Bun with `oven-sh/setup-bun`, pinned per the supply-chain rule below. The action reads the Bun version from `packageManager` in `package.json`, needs no version input, and caches only the Bun executable. A separate official `actions/cache` step caches `~/.bun/install/cache`; its exact key binds runner identity, `bun.lock`, and `package.json`. Caches are hints only: frozen installs and `ci.mjs` gates decide correctness. `resolve-shards` restores no cache and installs no dependencies; it needs only a checkout and Node, because it runs `node scripts/ci-target.mjs shards` and installs no Bun. (verify: `grep -n "setup-bun@\|actions/cache@" .github/workflows/ci.yml`)

## Hosted cost capture

Expand All @@ -106,7 +106,7 @@ Mitigations against npm and GitHub Actions supply-chain attacks. Each is load-be
- **Dependency caches use the official `actions/cache`**, with the Bun key bound to `bun.lock` and `package.json`. Bun itself comes from `oven-sh/setup-bun`, which reads the `packageManager` pin and caches only the executable.
- **`claude-code` is pinned rather than installed globally**: it is an exact-version devDependency in `package.json` and hash-locked in `bun.lock`, including its platform-binary optional dependencies. `minimumReleaseAge` in `bunfig.toml` quarantines fresh publishes (verify: `grep -n minimumReleaseAge bunfig.toml`); bump the dependency only to a version older than that quarantine.
- **Lifecycle scripts are denied in `package.json` with `"trustedDependencies": []`.** The empty array is the only spelling that denies every lifecycle script. Omitting the field instead trusts Bun's built-in list, which includes `@anthropic-ai/claude-code`.
- **Every PR validation shard, the targeting-contracts job, and every non-PR `validate` execution** run `bun install --frozen-lockfile`, then `node node_modules/@anthropic-ai/claude-code/install.cjs` to materialize the CLI binary whose lifecycle script was denied, and put `node_modules/.bin` on PATH so `ci.mjs`'s `claude plugin validate` resolves. The PR `validate` job is only the authoritative lane-result join. Node is the validator runtime (version: root `AGENTS.md` Commands); Bun is the package manager and script runner.
- **Every PR validation shard, the targeting-contracts job, and every non-PR `validate` execution** run `bun install --frozen-lockfile`, then `node node_modules/@anthropic-ai/claude-code/install.cjs` to materialize the CLI binary whose lifecycle script was denied, and put `node_modules/.bin` on PATH so `ci.mjs`'s `claude plugin validate` resolves. The PR `validate` job is only the authoritative lane-result join. Bun runs the gate and the author tooling, and it is the package manager. Node runs the shipped-file contracts, `install.cjs`, and `npm audit signatures`, so every job keeps setup-node. Versions: root `AGENTS.md` Commands.
- **`npm audit signatures`** remains the non-blocking signature check after every install because Bun has no signature-verification equivalent.
</constraint>

Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ jobs:
printf '{"step":"add node_modules/.bin to PATH (so ci.mjs finds the pinned claude)","at_ms":%s}\n' "$(date +%s%3N)" >> "$RUNNER_TEMP/docks-hosted-timings.jsonl"
- name: "run validation lane"
run: |
node scripts/ci.mjs --lane "${{ matrix.lane }}" --timings-json "$RUNNER_TEMP/docks-ci-timings.json"
bun scripts/ci.mjs --lane "${{ matrix.lane }}" --timings-json "$RUNNER_TEMP/docks-ci-timings.json"
printf '{"step":"run validation lane","at_ms":%s}\n' "$(date +%s%3N)" >> "$RUNNER_TEMP/docks-hosted-timings.jsonl"
- name: "publish hosted timing artifact"
if: always()
Expand Down Expand Up @@ -207,7 +207,7 @@ jobs:
printf '{"step":"add node_modules/.bin to PATH (so ci.mjs finds the pinned claude)","at_ms":%s}\n' "$(date +%s%3N)" >> "$RUNNER_TEMP/docks-hosted-timings.jsonl"
- name: "run non-unit plugin-targeting contracts"
run: |
node scripts/tests/ci-plugin-targeting.mjs
bun scripts/tests/ci-plugin-targeting.mjs
printf '{"step":"run non-unit plugin-targeting contracts","at_ms":%s}\n' "$(date +%s%3N)" >> "$RUNNER_TEMP/docks-hosted-timings.jsonl"
- name: "publish hosted timing artifact"
if: always()
Expand Down Expand Up @@ -242,7 +242,7 @@ jobs:
- name: resolve CI target
id: target
if: github.event_name == 'push'
run: node scripts/ci-target.mjs release-tag "$GITHUB_REF_NAME" --github-output "$GITHUB_OUTPUT"
run: bun scripts/ci-target.mjs release-tag "$GITHUB_REF_NAME" --github-output "$GITHUB_OUTPUT"
- name: "start hosted step timing"
if: github.event_name != 'pull_request'
run: |
Expand Down Expand Up @@ -292,10 +292,10 @@ jobs:
# leg runs the repo-wide shard too — once, here, over the exact bytes being
# released — and the targeted plugin gate still has the last word.
if [ "${{ github.event_name }}" = "push" ]; then
DOCKS_CI_MEMO=0 node scripts/ci.mjs --lane repo
node scripts/ci.mjs --plugin "${{ steps.target.outputs.plugin }}" --timings-json "$RUNNER_TEMP/docks-ci-timings.json"
DOCKS_CI_MEMO=0 bun scripts/ci.mjs --lane repo
bun scripts/ci.mjs --plugin "${{ steps.target.outputs.plugin }}" --timings-json "$RUNNER_TEMP/docks-ci-timings.json"
else
node scripts/ci.mjs --timings-json "$RUNNER_TEMP/docks-ci-timings.json"
bun scripts/ci.mjs --timings-json "$RUNNER_TEMP/docks-ci-timings.json"
fi
printf '{"step":"run the authoritative gate (scripts/ci.mjs)","at_ms":%s}\n' "$(date +%s%3N)" >> "$RUNNER_TEMP/docks-hosted-timings.jsonl"
- name: "publish hosted timing artifact"
Expand Down
16 changes: 8 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ This root file stays **repo-wide**. Per-area authoring details - skill/agent fro

```bash
bun install --frozen-lockfile # one-time setup
node scripts/ci.mjs --plugin <name> # authoritative gate for one plugin and its owned release tooling
node scripts/ci.mjs # full gate for repo-wide, shared, or multi-plugin changes
bun scripts/ci.mjs --plugin <name> # authoritative gate for one plugin and its owned release tooling
bun scripts/ci.mjs # full gate for repo-wide, shared, or multi-plugin changes
```

Node is the validator runtime; its version is the `node-version` in `.github/workflows/ci.yml`. Bun is the package manager; its version is the `packageManager` pin in `package.json` (verify: `grep -n node-version .github/workflows/ci.yml; grep -n packageManager package.json`).
Bun runs this repository's tooling and is the package manager; its version is the `packageManager` pin in `package.json`. Node runs the shipped plugin files that the gate tests, because consumers run them with `node`; its version is the `node-version` in `.github/workflows/ci.yml` (verify: `grep -n node-version .github/workflows/ci.yml; grep -n packageManager package.json`).

## Repository scope

Expand Down Expand Up @@ -46,7 +46,7 @@ Per-area conventions load lazily from nested `AGENTS.md` nodes. A node is a sing
| `scripts/AGENTS.md` | validators, edit→release workflow, double-layer gating, versioning |
| `.github/AGENTS.md` | CI trigger model, keep-in-sync with `ci.mjs` |

The table must name every `AGENTS.md` below the root, and every row must resolve. `scripts/tree/guard.mjs` enforces both, plus the node contract (size cap, no legacy `CLAUDE.md`) (verify: delete one row from a scratch copy, then run `node scripts/tree/guard.mjs <copy>`; it must fail naming the node). The `context-tree` skill (`plugins/docks/skills/productivity/context-tree/`) scaffolds, audits, and refreshes these nodes.
The table must name every `AGENTS.md` below the root, and every row must resolve. `scripts/tree/guard.mjs` enforces both, plus the node contract (size cap, no legacy `CLAUDE.md`) (verify: delete one row from a scratch copy, then run `bun scripts/tree/guard.mjs <copy>`; it must fail naming the node). The `context-tree` skill (`plugins/docks/skills/productivity/context-tree/`) scaffolds, audits, and refreshes these nodes.

## Authoring agents

Expand All @@ -62,8 +62,8 @@ The `agents/` folder deliberately carries **no context-tree node** (hence its ab
- **Description (CSO):** lead with "Use when …" AND include a "Not …" exclusion clause (both required by `scripts/agents/guard.mjs`); ≥80 and ≤500 chars; concrete triggers; no slop words.
- **Frontmatter:** `name` (required, kebab-case, matches filename, no `anthropic`/`claude` substring); `description` (required, with the "Not …" clause); **no `model` key** - `scripts/agents/guard.mjs` rejects any value, `inherit` included. Claude Code resolves an omitted `model` through its subagent model order (ending at the main conversation model), and omp falls back to the parent session model, so omission is the only spelling both runtimes agree on; every literal (`inherit`, `sonnet`, `claude-*`) is handed to omp as a model ID, resolves to nothing, and kills the spawn with "No model selected" (a plugin agent carrying `model: inherit` fails to spawn; the identical file without the key runs). Consumers pin per-agent models their own way - Claude via `CLAUDE_CODE_SUBAGENT_MODEL` (a default below frontmatter; add `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1` to override every subagent), omp via `task.agentModelOverrides.<agent>`. `tools` (allowlist; omitted = inherit all). For plugin-shipped agents, `hooks`/`mcpServers`/`permissionMode` are silently ignored for security - define the agent in the consuming project's own .claude/agents folder when you need those.
- **Body:** hard cap 500 lines (`scripts/agents/guard.mjs`); scorer sweet spot 60–300 lines (`scripts/agents/score.mjs`). Same patterns as skills (`<constraint>` blocks - up to 2 rewarded - lookup tables, BAD/GOOD, gotchas, validation loop); structure as context-acknowledgment (step 1), then `## Workflow`, `## Output Format`, `## Anti-Hallucination Checks`, `## Success Criteria`.
- **No author-script refs (consumer-safety):** a plugin-shipped agent body must not name docks plugin-author scripts (`scripts/ci.mjs`, `scripts/skills/*`, `scripts/agents/*`, `scripts/tree/*`, …) as a step - they don't ship to a consumer's project. Refer to "the project's CI / validators, if present", or make the check self-contained. `scripts/skills/no-author-scripts.mjs` scans agent bodies alongside shipped skills (verify: append a line naming `node scripts/ci.mjs` to a non-allowlisted skill body → the guard run must FAIL naming that file; revert).
- **Validators:** `node scripts/agents/guard.mjs` (structural) + `node scripts/agents/score.mjs --per-file` (quality score; the rubric maximum is defined in `scripts/agents/score.mjs`). The per-file floor lives in `scripts/config/scoring.json` (verify: `node scripts/config/read-floor.mjs agents`). Use up to two `<constraint>` blocks when they express distinct load-bearing invariants, not to manage score slack. Both validators run inside `scripts/ci.mjs`; the validator table lives in `scripts/AGENTS.md`.
- **No author-script refs (consumer-safety):** a plugin-shipped agent body must not name docks plugin-author scripts (`scripts/ci.mjs`, `scripts/skills/*`, `scripts/agents/*`, `scripts/tree/*`, …) as a step - they don't ship to a consumer's project. Refer to "the project's CI / validators, if present", or make the check self-contained. `scripts/skills/no-author-scripts.mjs` scans agent bodies alongside shipped skills (verify: append a line naming `bun scripts/ci.mjs` to a non-allowlisted skill body → the guard run must FAIL naming that file; revert).
- **Validators:** `bun scripts/agents/guard.mjs` (structural) + `bun scripts/agents/score.mjs --per-file` (quality score; the rubric maximum is defined in `scripts/agents/score.mjs`). The per-file floor lives in `scripts/config/scoring.json` (verify: `bun scripts/config/read-floor.mjs agents`). Use up to two `<constraint>` blocks when they express distinct load-bearing invariants, not to manage score slack. Both validators run inside `scripts/ci.mjs`; the validator table lives in `scripts/AGENTS.md`.
- **Sources:** [sub-agents](https://code.claude.com/docs/en/sub-agents) · [plugins-reference](https://code.claude.com/docs/en/plugins-reference).

## Plans
Expand Down Expand Up @@ -95,8 +95,8 @@ CI always runs `scripts/ci.mjs`: in full, per `--lane <shard>`, or per `--plugin

## Tool-agnostic rules

- Run focused checks while implementing. Before committing, pushing, or releasing a change owned by exactly one plugin, run `node scripts/ci.mjs --plugin <name>`; that selected-plugin gate is authoritative for the plugin payload and its descriptor-owned author, source, and release-contract tooling.
- Run full `node scripts/ci.mjs` for repo-wide validation/tooling, shared infrastructure used by multiple plugins, registry or CI-topology changes, changes spanning multiple plugins, and manual full-gate requests. Do not run it merely because a single-plugin release is imminent.
- Run focused checks while implementing. Before committing, pushing, or releasing a change owned by exactly one plugin, run `bun scripts/ci.mjs --plugin <name>`; that selected-plugin gate is authoritative for the plugin payload and its descriptor-owned author, source, and release-contract tooling.
- Run full `bun scripts/ci.mjs` for repo-wide validation/tooling, shared infrastructure used by multiple plugins, registry or CI-topology changes, changes spanning multiple plugins, and manual full-gate requests. Do not run it merely because a single-plugin release is imminent.
- Reuse a green gate only while its validated implementation bytes are unchanged. A relevant source change requires rerunning the same smallest authoritative gate; plan-issue-only lifecycle changes do not.
- Don't loosen validator floors to pass; fix the file instead
- Manifest version numbers stay in lockstep per plugin; `scripts/AGENTS.md` (Versioning) owns the rule, its enforcement, and its verify command
Expand Down
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,19 @@ Four validators mirror the kit-side conventions:

```bash
bun install --frozen-lockfile
node scripts/skills/guard.mjs # Codex + Claude skill compatibility + reference hygiene
bun scripts/skills/guard.mjs # Codex + Claude skill compatibility + reference hygiene
node plugins/docks/skills/productivity/write-skill/scripts/skill-guard.mjs score --per-file # skill quality score (max 16)
node scripts/agents/guard.mjs # frontmatter, "Use when…" / "Not…" CSO, no `model` key, tools declared, Workflow + Success Criteria
node scripts/agents/score.mjs # quality score (max 15) - no `model` key, tools declared, Workflow + Success Criteria
bun scripts/agents/guard.mjs # frontmatter, "Use when…" / "Not…" CSO, no `model` key, tools declared, Workflow + Success Criteria
bun scripts/agents/score.mjs # quality score (max 15) - no `model` key, tools declared, Workflow + Success Criteria
```

Node 24 remains the validator runtime and matches CI's `node-version`; Bun 1.4.0 is the package manager pinned through `packageManager`.
Bun runs this repository's own tooling: `scripts/`, `tests/`, and the plan-lifecycle selftest. Bun is also the package manager. Its version is the `packageManager` pin in `package.json`.

`--per-file` on a scorer prints one `<name> <score>` line per item - useful for spotting drift after an edit. `node scripts/ci.mjs` runs the full local gate (guards + scorers + manifest + idempotency); `ci.yml` runs that same file on CI.
Node runs the shipped plugin files, also inside the gate, because consumers run them with `node`. The `skill-guard.mjs` scorer line above is one of them. Node's version is the `node-version` in `.github/workflows/ci.yml`.

On a PR to `main`, CI runs only the shards the changed paths resolve to - the repo-wide checks always, plus the lane owning any plugin you touched. On a `<plugin>--v<version>` release tag (docks and plan-lifecycle tag independently), CI resolves the tag to one registry plugin, runs `node scripts/ci.mjs --lane repo` once over the released bytes, then runs `node scripts/ci.mjs --plugin <name>` as the authoritative gate. The `--plugin` gate alone skips repo-wide checks. See `.github/workflows/ci.yml`; full trigger model below.
`--per-file` on a scorer prints one `<name> <score>` line per item - useful for spotting drift after an edit. `bun scripts/ci.mjs` runs the full local gate (guards + scorers + manifest + idempotency); `ci.yml` runs that same file on CI.

On a PR to `main`, CI runs only the shards the changed paths resolve to - the repo-wide checks always, plus the lane owning any plugin you touched. On a `<plugin>--v<version>` release tag (docks and plan-lifecycle tag independently), CI resolves the tag to one registry plugin, runs `bun scripts/ci.mjs --lane repo` once over the released bytes, then runs `bun scripts/ci.mjs --plugin <name>` as the authoritative gate. The `--plugin` gate alone skips repo-wide checks. See `.github/workflows/ci.yml`; full trigger model below.

## Versioning + releases

Expand All @@ -153,13 +155,13 @@ Versions are per plugin. Each plugin's `version` in `plugins/<name>/.claude-plug
`scripts/release.mjs` releases one plugin per run and wraps the full dance in one command (`--plugin` defaults to `docks`; `--dry-run` previews the bump + manifest diff without tagging):

```bash
node scripts/release.mjs patch # docks: 0.1.0 → 0.1.1
node scripts/release.mjs minor # docks: 0.1.0 → 0.2.0
node scripts/release.mjs --plugin plan-lifecycle major # plan-lifecycle: 0.1.0 → 1.0.0
node scripts/release.mjs --plugin docks 0.2.0 # explicit
bun scripts/release.mjs patch # docks: 0.1.0 → 0.1.1
bun scripts/release.mjs minor # docks: 0.1.0 → 0.2.0
bun scripts/release.mjs --plugin plan-lifecycle major # plan-lifecycle: 0.1.0 → 1.0.0
bun scripts/release.mjs --plugin docks 0.2.0 # explicit
```

The script runs `node scripts/ci.mjs --plugin <name>` as a preflight, bumps that plugin's Claude and Codex manifests plus its entry in the versioned Claude marketplace catalog, commits + pushes, runs `claude plugin tag --push` for the `<name>--v<version>` tag, **waits for the tag-CI run to pass** (`.github/workflows/ci.yml` is triggered by tag pushes), then calls `gh release create` with notes generated from `git log` since the previous tag. If CI fails, the GitHub Release is NOT created - the tag stays as a marker that the release was attempted, and the script prints recovery steps. Released versions appear at https://github.com/DocksDocks/docks/releases.
The script runs `bun scripts/ci.mjs --plugin <name>` as a preflight, bumps that plugin's Claude and Codex manifests plus its entry in the versioned Claude marketplace catalog, commits + pushes, runs `claude plugin tag --push` for the `<name>--v<version>` tag, **waits for the tag-CI run to pass** (`.github/workflows/ci.yml` is triggered by tag pushes), then calls `gh release create` with notes generated from `git log` since the previous tag. If CI fails, the GitHub Release is NOT created - the tag stays as a marker that the release was attempted, and the script prints recovery steps. Released versions appear at https://github.com/DocksDocks/docks/releases.

CI runs only on (a) PRs to main, (b) tag pushes matching `<plugin>--v<version>`, and (c) manual `workflow_dispatch`. Pushes to main don't re-trigger CI - PR validation gates merges, tag-CI gates releases.

Expand Down
Loading
Loading