fix(#3751): provision the claude CLI in CI and cover agents/ in the plugin-validate fixture (#4229)

* test(#3751): the validation fixture must cover agents/ and CI must provision the CLI

* fix(#3751): cover agents/ in the plugin-validate fixture and provision the claude CLI in CI

* fix(#3751): wire the strict live-config guard into the plugin-validate job

* fix(#3751): job-level strict-guard env, where the guard derivation reads it

* chore(#3751): changeset for the CI-provisioned plugin-validate gate

* chore(#3751): backfill changeset pr number

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-09-03 06:59:30 -04:00
committed by GitHub
parent 3639ab0431
commit 8f013983e5
3 changed files with 79 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 4229
---
**`claude plugin validate --strict` now runs in CI and covers `agents/`** — a dedicated test.yml job provisions the claude CLI so the C2 tier is a real gate, and the validation fixture includes the agents/ tree the CLI validates by convention. (#3751)

View File

@@ -127,6 +127,33 @@ jobs:
- name: Lint — all (ESLint, skill deps, test-file count, command contract, PR checks, legacy name, regression-test names, resolution-provenance)
run: npm run lint:ci
# #3751 (decision 1): provision the claude CLI so plugin-manifest's C2 tier
# (`claude plugin validate --strict`) is a real CI gate instead of a
# local-only check. Dedicated job — the matrix lanes stay CLI-free.
plugin-validate:
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# #2665 round 4: every job that runs the suite wires the strict guard
# (job-level env, where the live-config-guard derivation reads it).
GSD_STRICT_LIVE_CONFIG_GUARD: '1'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
token: ${{ github.token }}
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
- name: Install dev dependencies
run: npm ci --ignore-scripts
- name: Install claude CLI (C2 gate dependency, #3751)
run: npm install -g @anthropic-ai/claude-code
- name: Run plugin-manifest suite (C1+C2+C3)
run: node scripts/run-tests.cjs --files plugin-manifest.test.cjs
test:
name: test (${{ matrix.os }}, ${{ matrix.node-version }}${{ matrix.shard && format(', shard {0}', matrix.shard) || '' }})
needs: [changes, preflight]

View File

@@ -421,12 +421,13 @@ describe('C: plugin.json schema validation', () => {
// symlinked. An earlier revision also copied agents/, on the (correct)
// observation that the CLI auto-validates it and a frontmatter-less
// agents/*.md exits 1. Dropped in review: it is a NEW gate the issue does not
// ask for, on the largest of the trees, and because C2 never runs in CI it
// would be red only on contributor machines with `claude` installed — the
// same worst-of-both-states #3613 exists to remove. agents/ coverage is worth
// having and is tracked as #3751, where "should CI provision the CLI" — the
// decision it actually turns on — can be answered for it.
const COMPONENT_DIRS = ['commands', 'hooks', 'skills'];
// ask for, on the largest of the trees. RESOLVED by #3751 (2026-09-02,
// options 1+3): CI provisions the claude CLI, agents/ is in the fixture, and
// the asymmetry #3613 existed to remove is gone — C2 runs in CI.
// #3751 (decision 1+3, 2026-09-02): agents/ is included — the CLI validates
// it by convention (measured on 2.1.239), and CI now provisions the claude
// CLI in a dedicated test.yml job, so C2 is a real gate over this tree.
const COMPONENT_DIRS = ['commands', 'hooks', 'skills', 'agents'];
/**
* One entry that must survive the copy into each component tree, so C3 catches
@@ -439,6 +440,9 @@ describe('C: plugin.json schema validation', () => {
commands: 'gsd',
hooks: 'hooks.json',
skills: 'gsd-add-tests',
// #3751: agents/ is undeclared in plugin.json (CLI-convention pickup), so
// the expected entry is a shipped agent file, not a manifest-declared path.
agents: 'gsd-executor.md',
};
/**
@@ -533,6 +537,43 @@ describe('C: plugin.json schema validation', () => {
}
);
// ── #3751: agents/ coverage (maintainer decision 2026-09-02: options 1+3) ────
//
// `claude plugin validate` auto-validates agents/ by CLI CONVENTION (the
// manifest does not declare it), measured live on CLI 2.1.239 in the issue.
// Decision: CI provisions the claude CLI (a dedicated test.yml job), so C2 is
// a real gate, and the fixture + C3 cover the tree everywhere else.
test('C3+#3751: the validation fixture covers agents/, the tree the CLI validates by convention', () => {
const pluginRoot = buildValidationPluginRoot();
try {
const agentsDir = path.join(pluginRoot, 'agents');
const stat = fs.lstatSync(agentsDir);
assert.ok(stat.isDirectory(), 'agents/ must be a real directory in the C2 validation fixture');
assert.equal(stat.isSymbolicLink(), false, 'agents/ must be copied, not symlinked');
const entries = fs.readdirSync(agentsDir);
assert.ok(entries.length > 0, 'agents/ is EMPTY in the C2 validation fixture');
assert.ok(
entries.some((e) => /^gsd-.*\.md$/.test(e)),
'agents/ must carry the shipped gsd-*.md files, not a stub'
);
} finally {
cleanup(pluginRoot);
}
});
test('#3751: CI provisions the claude CLI so C2 is a real gate, not a local-only tier', () => {
const workflow = fs.readFileSync(path.join(ROOT, '.github', 'workflows', 'test.yml'), 'utf8');
assert.ok(
/@anthropic-ai\/claude-code/.test(workflow),
'test.yml must install the claude CLI (npm i -g @anthropic-ai/claude-code) in a job'
);
assert.ok(
/plugin-manifest\.test\.cjs/.test(workflow),
'the provisioning job must run tests/plugin-manifest.test.cjs (the C2 gate)'
);
});
// ── C3: Unconditional fixture-construction guard ─────────────────────────────
//
// #3613 regression. C2 above is the test that actually shells out to the CLI,