Compare commits
6 Commits
main
...
feat/66-wi
| Author | SHA1 | Date | |
|---|---|---|---|
| 644a77b0cb | |||
| f1fa65ebc2 | |||
| dda7f8d3dd | |||
| df575129a1 | |||
| 8516305b91 | |||
| ac41326ba8 |
@@ -15,6 +15,8 @@ This file carries only what applies to **every** session. Setup, prerequisites,
|
|||||||
|
|
||||||
Not everything in a plugin root is generated. `README.md`, `docs/`, `bin/`, `sources.md`, `.mcp.json` and per-plugin extras are hand-authored there with no `.apm/` source — edit those in place. The rule is per-path, not per-directory. But a file placed *inside* a mirrored directory is deleted on the next sync (`sync_dir` runs `rm -rf` before every copy), so plugin-root documentation goes in `docs/`, never in `hooks/` or `skills/`.
|
Not everything in a plugin root is generated. `README.md`, `docs/`, `bin/`, `sources.md`, `.mcp.json` and per-plugin extras are hand-authored there with no `.apm/` source — edit those in place. The rule is per-path, not per-directory. But a file placed *inside* a mirrored directory is deleted on the next sync (`sync_dir` runs `rm -rf` before every copy), so plugin-root documentation goes in `docs/`, never in `hooks/` or `skills/`.
|
||||||
|
|
||||||
|
`.mcp.json` is hand-authored but it is **not** outside apm. MCP is a first-class apm primitive, and a plugin's `.mcp.json` is how this repo declares one: apm reads the `mcpServers` pointer in the generated `.github/plugin/plugin.json`, resolves it to `.mcp.json`, and injects the result into that package's `dependencies.mcp` when a consumer installs it. Declare MCP servers there and **never** in the plugin's own `apm.yml` — that arms a per-package gate this repo cannot satisfy (`LESSONS.md`, 2026-09-12).
|
||||||
|
|
||||||
Full model: `docs/spec/architecture.md`.
|
Full model: `docs/spec/architecture.md`.
|
||||||
|
|
||||||
## Prefer plugin skills over raw shell
|
## Prefer plugin skills over raw shell
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ Patterns observed during development of this repo. Three or more entries on the
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-09-12 — "Not an `.apm/` primitive" was read as "not an apm primitive", and the review that followed was wrong
|
||||||
|
|
||||||
|
`AGENTS.md` and `docs/spec/architecture.md` both listed `.mcp.json` alongside `README.md`, `docs/` and `bin/` as plugin-root material "hand-authored with no `.apm/` source". That is literally true — nothing under `.apm/` produces MCP config — but it reads as "apm has no MCP concept", and a review of PR #132 drew exactly that conclusion and recommended moving the declaration into the plugin's `apm.yml` under `dependencies.mcp`. The recommendation was wrong twice over. It arms `lockfile-exists` in the per-package `apm audit --ci` that the `apm-audit-ci` hook runs in every `plugins/*/`, which then demands the package's whole deployed tree inside the package directory: 93 missing files and 79 drifted paths on `plugins/gitea`. And it was unnecessary, because the `.mcp.json` route already reaches `dependencies.mcp` through the `mcpServers` pointer in the generated Copilot manifest, env references intact.
|
||||||
|
|
||||||
|
Two process lessons, not one. First, when a doc says a file is not a primitive **of a specific subsystem**, say which subsystem and what the file actually is instead — the negative claim alone invites the wrong generalisation. Second, the three scratch installs that produced the wrong conclusion all used local `./path` dependencies, where apm skips the plugin-normalisation step that injects `.mcp.json`. The repo consumes its plugins as `git:` + `path:` objects. A scratch test that does not reproduce the real dependency form can invert the result, so reproduce the form, not just the shape.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-05-17 — Workflow documents should prescribe sub-agent usage, not just allow it
|
## 2026-05-17 — Workflow documents should prescribe sub-agent usage, not just allow it
|
||||||
|
|
||||||
When writing workflow documents (like `docs/notes/skill-implementation-workflow.md`), the natural tendency is to describe steps at a high level and leave sub-agent usage as an implementation detail. But if the workflow doesn't explicitly prescribe "spawn a sub-agent here," practitioners default to doing everything in the main context — accumulating token cost and losing the isolation benefit. Fix: make sub-agent usage a named step in the workflow, specifying what the agent receives, what it returns, and why it's isolated. This makes the workflow reproducible rather than dependent on the practitioner remembering to use agents.
|
When writing workflow documents (like `docs/notes/skill-implementation-workflow.md`), the natural tendency is to describe steps at a high level and leave sub-agent usage as an implementation detail. But if the workflow doesn't explicitly prescribe "spawn a sub-agent here," practitioners default to doing everything in the main context — accumulating token cost and losing the isolation benefit. Fix: make sub-agent usage a named step in the workflow, specifying what the agent receives, what it returns, and why it's isolated. This makes the workflow reproducible rather than dependent on the practitioner remembering to use agents.
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -35,6 +35,17 @@ Install all of these before setting up. Each one is a hard dependency of a git h
|
|||||||
| `python3` + PyYAML | Required by `scripts/skill-size-check.sh` (the `skill-size-check` pre-commit hook), which reads folded YAML frontmatter | `python3` is usually present — pre-commit is itself a Python application. `pip install pyyaml` if the hook reports PyYAML missing |
|
| `python3` + PyYAML | Required by `scripts/skill-size-check.sh` (the `skill-size-check` pre-commit hook), which reads folded YAML frontmatter | `python3` is usually present — pre-commit is itself a Python application. `pip install pyyaml` if the hook reports PyYAML missing |
|
||||||
| `vale` | Required by the `vale-audit-prefilter-skill` / `-agent` pre-commit hooks and the `check-vale-style-sync` pre-push hook | `brew install vale` (macOS), `snap install vale` (Linux), `choco install vale` (Windows), or https://vale.sh/docs/vale-cli/installation/ |
|
| `vale` | Required by the `vale-audit-prefilter-skill` / `-agent` pre-commit hooks and the `check-vale-style-sync` pre-push hook | `brew install vale` (macOS), `snap install vale` (Linux), `choco install vale` (Windows), or https://vale.sh/docs/vale-cli/installation/ |
|
||||||
| `claude` CLI | Required by the `validate-plugins` and `validate-marketplace` pre-push hooks | Claude Code |
|
| `claude` CLI | Required by the `validate-plugins` and `validate-marketplace` pre-push hooks | Claude Code |
|
||||||
|
| `go` toolchain | The gitea MCP server runs as `go run gitea.com/gitea/gitea-mcp@v1.7.0`, resolved from `PATH`. Without it the server fails to start and every `gitea-*` skill loses its tools | https://go.dev/dl/ — verify with `go version` |
|
||||||
|
|
||||||
|
The gitea MCP server additionally needs two environment variables in the shell that launches your agent — referenced as `${GITEA_ACCESS_TOKEN}` and `${GITEA_HOST}` in `plugins/gitea/.mcp.json`, with apm passing those references through to the deployed config unexpanded so the values are resolved at server startup and never committed. Copy `plugins/gitea/.env.example` to `.env` at the repo root, fill in real values, then export it — nothing in this repo auto-loads a `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp plugins/gitea/.env.example .env
|
||||||
|
$EDITOR .env
|
||||||
|
set -a; source .env; set +a
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the token in Gitea under Settings, Applications. Scope it to the repositories you want the agent to reach. If the server starts but every call returns an authorization error, that token is the first thing to check.
|
||||||
|
|
||||||
Two notes worth reading before you skip one:
|
Two notes worth reading before you skip one:
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ Two compilers produce the plugin roots you see in the tree:
|
|||||||
|
|
||||||
`.apm/` is the sole hand-edited authoring source for plugin content. An edit made in the flat mirror is discarded by the next sync and is reported as drift by the `check-plugin-content-sync` pre-push hook. Hand-authored material that is not an `.apm/` primitive — `README.md`, `docs/`, `bin/`, `sources.md`, `.mcp.json`, and per-plugin extras such as `plugins/git/config.example.json`, `plugins/gitea/references/` and `plugins/bin/evals/` — lives at the plugin **root** and is untouched by either compiler.
|
`.apm/` is the sole hand-edited authoring source for plugin content. An edit made in the flat mirror is discarded by the next sync and is reported as drift by the `check-plugin-content-sync` pre-push hook. Hand-authored material that is not an `.apm/` primitive — `README.md`, `docs/`, `bin/`, `sources.md`, `.mcp.json`, and per-plugin extras such as `plugins/git/config.example.json`, `plugins/gitea/references/` and `plugins/bin/evals/` — lives at the plugin **root** and is untouched by either compiler.
|
||||||
|
|
||||||
|
`.mcp.json` is the one entry in that list that is still load-bearing for apm rather than merely ignored by it. MCP is a first-class apm primitive — `dependencies.mcp` sits beside `dependencies.apm` in the manifest schema, and apm tracks deployed servers in `apm.lock.yaml` under `mcp_servers`, `mcp_configs` and `mcp_config_provenance`. A plugin reaches that primitive indirectly. `apm pack` writes the string `".mcp.json"` into the generated `.github/plugin/plugin.json` as its `mcpServers` value, and on install apm resolves the plugin manifest in the order `plugin.json`, `.github/plugin/plugin.json`, `.claude-plugin/plugin.json` — so the Copilot manifest wins, the pointer is followed, and `.mcp.json` is injected into the package's `dependencies.mcp` with any `${VAR}` env references intact. Verified against the real remote: a git-sourced install of `plugins/gitea` deploys the gitea server with both env references unexpanded.
|
||||||
|
|
||||||
|
Two consequences follow, and both have bitten already:
|
||||||
|
|
||||||
|
- **Do not declare `dependencies.mcp` in a plugin's own `apm.yml`.** It is the schema-correct place and it breaks the build. The `apm-audit-ci` pre-push hook runs `apm audit --ci` inside every `plugins/*/`, so a declared dependency arms `lockfile-exists` there, which then demands an `apm.lock.yaml` in the package plus every file of that package's own deployed tree present inside the package directory. Measured on `plugins/gitea`: 93 missing deployed files and 79 drifted paths.
|
||||||
|
- **`.claude-plugin/plugin.json` carries an env-stripped copy.** `apm pack` inlines `.mcp.json` there, and its sanitiser drops `env` and `headers` blocks unconditionally at any depth, `${VAR}` indirection included. That copy is inert under apm, which never reaches it, but a native Claude Code plugin install reads exactly that file and would launch the server with no credentials. Anything installed natively rather than through apm needs its MCP env supplied by the host.
|
||||||
|
|
||||||
That immunity is positional, not by filename. Anything placed *inside* a mirrored directory is destroyed regardless of what it is: `sync_dir` runs `rm -rf "$dst"` before every copy, and `sync_hooks_json` does the same to `hooks/`. A hand-written `README.md` under `plugins/<name>/hooks/` or `plugins/<name>/skills/` is deleted by the next sync with no drift report, because a file with no `.apm/` counterpart is simply absent from the regenerated tree. This has already cost the repo one document — `plugins/kyberforge/hooks/README.md`, since restored to `plugins/kyberforge/docs/hooks.md`. Plugin-root documentation belongs in `docs/`.
|
That immunity is positional, not by filename. Anything placed *inside* a mirrored directory is destroyed regardless of what it is: `sync_dir` runs `rm -rf "$dst"` before every copy, and `sync_hooks_json` does the same to `hooks/`. A hand-written `README.md` under `plugins/<name>/hooks/` or `plugins/<name>/skills/` is deleted by the next sync with no drift report, because a file with no `.apm/` counterpart is simply absent from the regenerated tree. This has already cost the repo one document — `plugins/kyberforge/hooks/README.md`, since restored to `plugins/kyberforge/docs/hooks.md`. Plugin-root documentation belongs in `docs/`.
|
||||||
|
|
||||||
## Governance layer
|
## Governance layer
|
||||||
|
|||||||
@@ -17,5 +17,17 @@
|
|||||||
"milestones",
|
"milestones",
|
||||||
"releases",
|
"releases",
|
||||||
"branches"
|
"branches"
|
||||||
]
|
],
|
||||||
|
"mcpServers": {
|
||||||
|
"gitea": {
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"gitea.com/gitea/gitea-mcp@v1.7.0",
|
||||||
|
"-t",
|
||||||
|
"stdio"
|
||||||
|
],
|
||||||
|
"command": "go",
|
||||||
|
"type": "stdio"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
plugins/gitea/.env.example
Normal file
20
plugins/gitea/.env.example
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Environment variables required by the gitea MCP server declared in
|
||||||
|
# plugins/gitea/.mcp.json. apm passes these ${VAR} references through to the
|
||||||
|
# deployed MCP config unexpanded; Claude Code resolves them from the process
|
||||||
|
# environment at server startup. Neither value is ever committed to this repo.
|
||||||
|
#
|
||||||
|
# Nothing in this repo auto-loads a .env file -- apm has no dotenv support.
|
||||||
|
# Copy this file to .env at the repo root, fill in real values, then export it
|
||||||
|
# into your shell before starting Claude Code, e.g.:
|
||||||
|
#
|
||||||
|
# set -a; source .env; set +a
|
||||||
|
#
|
||||||
|
# Or skip the file and export the two variables directly in your shell
|
||||||
|
# profile. Either way, never commit .env -- .gitignore already excludes it.
|
||||||
|
|
||||||
|
# A Gitea access token, scoped to the repositories this agent should reach.
|
||||||
|
# Generate one in Gitea under Settings > Applications.
|
||||||
|
GITEA_ACCESS_TOKEN=<your-gitea-access-token>
|
||||||
|
|
||||||
|
# The base URL of your Gitea instance, including scheme.
|
||||||
|
GITEA_HOST=https://<your-gitea-host>
|
||||||
5
plugins/gitea/.github/plugin/plugin.json
vendored
5
plugins/gitea/.github/plugin/plugin.json
vendored
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "gitea",
|
"name": "gitea",
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"description": "Skills and agents for working with a Gitea forge through its HTTP API \u2014 the forge's own objects, as distinct from the local git clone.",
|
"description": "Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Defame1297",
|
"name": "Defame1297",
|
||||||
"email": "defame1297@rkdr.net",
|
"email": "defame1297@rkdr.net",
|
||||||
@@ -17,5 +17,6 @@
|
|||||||
"milestones",
|
"milestones",
|
||||||
"releases",
|
"releases",
|
||||||
"branches"
|
"branches"
|
||||||
]
|
],
|
||||||
|
"mcpServers": ".mcp.json"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {}
|
"mcpServers": {
|
||||||
|
"gitea": {
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"gitea.com/gitea/gitea-mcp@v1.7.0",
|
||||||
|
"-t",
|
||||||
|
"stdio"
|
||||||
|
],
|
||||||
|
"command": "go",
|
||||||
|
"env": {
|
||||||
|
"GITEA_ACCESS_TOKEN": "${GITEA_ACCESS_TOKEN}",
|
||||||
|
"GITEA_HOST": "${GITEA_HOST}"
|
||||||
|
},
|
||||||
|
"type": "stdio"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user