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
9 changes: 5 additions & 4 deletions .github/workflows/pr-issue-linkage.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: pr-issue-linkage

# Validates the PR body carries a native closing keyword (Closes/Fixes/Resolves
# #N, including owner/repo#N, or the literal "No linked issue" when the PR closes
# nothing) and a non-empty `## Related` section, via the shared
# #N, including owner/repo#N, or a no-issue marker when the PR closes
# nothing) and four non-empty contract sections — `## Summary`, `## Fix`,
# `## Verification`, `## Related` — via the shared
# pr-issue-linkage reusable from ci-workflows. `pull_request_target` runs the
# base-branch definition, so a head-branch edit cannot bypass the gate — safe
# here because the reusable reads PR body metadata from the event payload only
Expand Down Expand Up @@ -34,6 +35,6 @@ jobs:
uses: melodic-software/ci-workflows/.github/workflows/pr-issue-linkage.yml@7107b34832a7b6db5d08d3b132621c599fbe5e50 # v0.14.2
with:
runner: ubuntu-24.04
# dependabot PR bodies cannot carry the closing-keyword + `## Related`
# markers this gate requires; exempt the login so its PRs are mergeable.
# dependabot PR bodies cannot carry the closing keyword + four contract
# sections this gate requires; exempt the login so its PRs are mergeable.
exempt-authors: 'dependabot[bot]'
27 changes: 18 additions & 9 deletions docs/MIGRATION-PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,13 @@ that could silently regress — how it triggers, how it routes an ambiguous requ
or the shape of what it emits. A skill is an explicit **skip** when it is pure-reference (answers
from a knowledge corpus with no decision contract — `playbooks:fable-5`, `tdd`, …). A **hook** plugin
is a skip only in the case its rationale actually describes — deterministic, silent-always-on,
guarded by `.test.sh`, **no model-facing skill at all**. A hook plugin that ships a `setup` skill has
a skill with a judgment-bearing contract, and the plugin's shape does not exempt it: a `setup`
skill *is* warrantable — it makes interview and write-config decisions (the
`codebase-health/setup` eval is the model). Gray-zone skills (thin mechanical wrappers, reference-ish
guarded by `.test.sh`, **no skill carrying a judgment-bearing contract**. The condition is the
absence of that contract, not the invocation mode. Stating it by invocation mode does not work: a
`setup` skill sets `disable-model-invocation: true`, so "no model-invoked skill" is satisfied by a
plugin that ships one — admitting as a skip the very plugin the rest of this rule excludes. A `setup` skill makes interview and write-config decisions that can
silently regress, which is precisely the contract the skip exists to excuse the absence of. The
plugin's shape does not exempt it: a `setup` skill *is* warrantable (the `codebase-health/setup`
eval is the model). Gray-zone skills (thin mechanical wrappers, reference-ish
routers) are **author-confirm**: re-check the warrant against the live `SKILL.md` at authoring time
and record an explicit skip verdict if it dissolves — a satisfied "looks covered" is not a warrant.
This section is the policy; current coverage is verified on demand — a live glob of
Expand Down Expand Up @@ -1354,12 +1357,17 @@ Reintegration (below) covers a repo that already ran an in-repo copy and now swi
3. **Install and seed config.** Pass every option on the install command: `claude plugin
install <plugin>@<marketplace> --scope project --config KEY=VALUE …` (repeatable, schema-validated).
Non-sensitive options land in the **user** `settings.json` `pluginConfigs` regardless of the enable
scope; a sensitive value still routes to secure credential storage (smoke-tests A and C).
scope — documented behavior, not an observation: seam 1 above records that non-sensitive values
**store** in user settings, and that a sensitive value routes to secure credential storage
instead.
Re-running that command later against an already-installed plugin prints `already installed`
**and still writes the value** (smoke-test C), so a headless reconfiguration is another `--config`
install rather than an uninstall/reinstall — verified for a **non-sensitive option at `user`
scope** on Claude Code 2.1.240 and **not** at the `--scope project` this step uses, so read the
stored value back there rather than assuming the write landed. Interactively, `/plugin configure` owns personal
stored value back — for a non-sensitive option, from the **user** `settings.json` `pluginConfigs`
per the storage rule above, not from the project settings this command names; a `sensitive` value
is absent from settings entirely (smoke-test A) and cannot be verified this way — rather
than assuming the write landed. Interactively, `/plugin configure` owns personal
`userConfig`; an explicit setup skill owns any separate tracked project configuration declared by
the plugin.
4. **Headless prompting caveat.** Install never prompts non-interactively — a required `userConfig`
Expand Down Expand Up @@ -1412,9 +1420,10 @@ surface to a published plugin for a single consumer's low-value nicety.
so a headless reconfiguration is another `--config` install, not an uninstall/reinstall. That was
verified for a **non-sensitive option at `user` scope** on Claude Code 2.1.240 and is **untested at
the `project` scope this step uses**, so read the stored value back before reporting a
project-scope reconfiguration as applied — from the **user** `settings.json` `pluginConfigs`, where
step 3 above records non-sensitive options landing irrespective of enable scope (itself observed
only at `--scope local`, smoke-test A), not from the project settings this command names.
project-scope reconfiguration as applied — for a non-sensitive option, from the **user**
`settings.json` `pluginConfigs`, where such options land regardless of enable scope (seam 1 above
records that they **store** there), not from the project settings this command names; a
`sensitive` value is absent from settings entirely (smoke-test A) and cannot be verified this way.
**Exception:** a `directory`/`file` relative-path entry in checked-in project settings resolves
against the repo checkout (cloud sessions included) — see
[`docs/CLOUD-SESSIONS.md`](CLOUD-SESSIONS.md). Otherwise the marketplace is known but the plugin is absent, and step 3's
Expand Down
35 changes: 22 additions & 13 deletions docs/PLUGIN-PHILOSOPHY.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,31 @@ content*, not only runtime behavior: the publishing organization's name, its mar
repository names, and publisher-prefixed configuration keys do not appear in a plugin's skill, agent,
or schema content. One use is sanctioned — a citation that *names a source rather than a target the
plugin acts on*: a documentation URL, or a cross-plugin reference to this marketplace's own published
files, cited for a reader to consult. The line is what the content does with it, not where it points:
prose citing such a URL is conforming, while a skill instructed to fetch, poll, or write to it has
made the publisher a runtime dependency and is not. (`plugin.json` publisher metadata sits outside
files, cited for a reader to consult. Whether that sanctioned citation is forfeited turns on the
target's owner: a skill instructed to fetch, poll, or write a **publisher-owned** file has made the
publisher a runtime dependency and is not conforming. A third-party documentation URL creates no such
dependency, so fetching one does not forfeit the citation — this rule reaches publisher-owned targets
only. For publisher-owned targets, distinguishing
an instruction to fetch from a citation offered for a reader remains genuinely hard, and this
statement does not settle it; `plugins/architecture/reference/topic-docs.md` is an open case.
(`plugin.json` publisher metadata sits outside
this rule entirely, being neither skill, agent, nor schema content — identifying the source is what
the manifest is for.)

Like the setup contract below, **this is a normative target, not a description of the fleet**, and
enforcement reaches a strict subset of it. `scripts/validate-plugin-contracts.mjs` gates the
enforcement reaches a strict subset of it.
`scripts/validate-plugin-contracts.mjs` gates the
marketplace id, `melodic-software/github-iac`, and `MELODIC_*` keys across every plugin's skill
content, and holds the `autonomy` plugin to a stricter token set;
`plugins/github/github.test.sh` runs a wider sweep over its own plugin's prose as its "agnostic
conformance" check — a sibling of that file's D4 zero-vendored-knowledge checks, not one of them. The
`plugins/github/github.test.sh` sweeps a wider token set over a narrower scope — its own plugin's
prose only — as its "agnostic conformance" check, a sibling of that file's D4 zero-vendored-knowledge
checks, not one of them. The
bare organization name in skill prose is gated nowhere *fleet-wide* — only inside `autonomy` and
`github`, each by its own narrower sweep — and agent content is gated nowhere at all, so shipped
skills predating this statement are nonconforming until brought into conformance rather than absolved
by a green build. A fleet-wide edit answers to two independent mechanisms, both steps of the same
`plugin-gate` CI job and neither aware of the other; consolidating them behind this statement, and
`github`, each by a sweep scoped to that one plugin — and agent content is gated nowhere at all, so
shipped skills predating this statement are nonconforming until brought into conformance rather than
absolved by a green build. A fleet-wide edit answers to two independent mechanisms, each running in
its own step of the same `plugin-gate` CI job and neither aware of the other; consolidating them
behind this statement, and
settling that conformance gap deliberately, is tracked in issue #3136.

Keep plugins horizontally decoupled:
Expand Down Expand Up @@ -392,7 +400,7 @@ is closed. Setup must be:
- transparent about what it inferred, changed, skipped, or could not verify;
- limited to configuration the plugin owns;
- safe for existing files, preserving unrelated user content;
- evidence-bearing: after making or routing a change, it reports the effective value it *observed*,
- evidence-bearing: after making or routing a change, it reports the stored value it *observed*,
and says plainly where it could not observe one — never an unobserved change; and
- non-interactive when complete arguments are supplied, so automation and headless use remain possible.

Expand All @@ -405,8 +413,9 @@ edit left to the operator — the rule is unchanged.
behaves. They can legitimately disagree, so a naive readback reports false failures — and reporting
one as a failed write is the specific error this clause exists to prevent. Verify the effective value
by re-checking in a **fresh session**, and never claim an unobserved change. A same-session `check`
therefore satisfies the bullet above by reporting what it observed *and* naming it as possibly stale,
not by pretending the running session already reflects the write.
therefore satisfies the bullet above by reporting the stored value it observed *and* naming the
running session's behaviour as not yet established, not by pretending that session already reflects
the write. The stored value read in-session is current; it is the behaviour that lags.

Two mechanisms are offered across the fleet as the reason the two diverge: that a `${user_config.*}`
value is substituted into skill content at load, and that a hook's `CLAUDE_PLUGIN_OPTION_*` mirror
Expand Down
Loading