Skip to content

Commit 7e6fca0

Browse files
ci(scaffold-e2e): build the skills-boundary expected set from the whole curated skills/ catalog (#26285)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi97RDQRRsHagYpbF6jeYVbtq)_ Fixes #26204 Clause-②: no Metadata-protocol: none ⛔ **Tier H** (`.github/workflows/**`): this PR stays a draft until a `GOVERNED_APPROVERS` approval. No seat readies it, queues it or arms auto-merge before that. ## 维护者速读(草稿) - **改了什么**:`Scaffold E2E` 里「Skills boundary」这一步的期望技能集合,从「只认 `skills/objectstack-*`」改成「`skills/` 下凡是带 `SKILL.md` 的目录都算」。另外加了一道保护:若 `skills/` 读出来是空集,这一步直接失败。两条防泄漏断言(安装集合相等、仓根发现数相等)一字未动。 - **为什么改**:objectui 技能并入根 `skills/` 之后,这一步把它当成「泄漏」,所有动到 `packages/create-objectstack/**` 的 PR 都挂红。`@objectstack/skills` 发布包本来就是把整个 `skills/` 原样打包,所以 `skills/` 本身就是策展目录;在工作流里再维护一份白名单,等于以后每加一个技能都要改一次受管文件。 - **风险与代价(含回滚)**:只改一个 CI 步骤,不影响任何发布包。代价是:往 `skills/` 新放一个普通技能目录会被当成策展技能放行(它本来也会随 npm 包发布);内部技能(`metadata.internal`)放进 `skills/`、或放在 `skills/` 之外却没标 internal 的技能,仍然会让这一步失败(本地已实测,见下)。回滚就是 revert 这一个提交。 - **席位意见**: - **你要做的**:看一眼 diff,同意的话批准这个 PR。批准后由席位负责转 ready 并入队。 ## What changed `.github/workflows/scaffold-e2e.yml`, step "Skills boundary: only the curated skills/ catalog installs": - `expected` was `ls -d objectstack-*/` inside `skills/`. It is now every `skills/` subdirectory that holds a `SKILL.md`, read with the same loop shape as the later "Skills: the scaffolder installed exactly the @objectstack/skills catalog" step. - An empty `expected` now fails the step loudly instead of comparing two empty sets. - `expected_count`, and therefore the `Found N skill` check, follows the new set. Both assertions (installed set equality, repo-root discovery count, and the `dogfood-verification` grep) are unchanged. **Why a directory read and not an allow-list.** The card allowed either. `@objectstack/skills` ships the whole `skills/` tree (`packages/skills/scripts/sync-catalog.mjs` copies `skills/**` and `--check` refuses any difference), so `skills/` is the curated catalog by definition. An allow-list in this file would be a second registry of the same fact, and every new curated skill would red this step until a Tier H edit of the workflow. That is the same failure this card fixes. With the directory read, what the boundary still catches is anything that is not curated: a skill outside `skills/` without the internal marker, an internal-marked skill placed in `skills/`, and a nested skill inside a curated directory. All three are rehearsed below. ## Rehearsal The step's `run:` block was extracted verbatim from the workflow (the YAML parsed with the `yaml` package) and run with `bash -e`, the runner's default shell for a step with no `shell:`. `GITHUB_WORKSPACE` was the checkout, or a `git archive HEAD` copy for the controls. `RUNNER_TEMP` was a fresh temp dir. skills CLI 1.7.4 via `npx -y skills`. `create-objectstack` was built from this tree. The final head's run block is byte-identical to the rehearsed one (`cmp`). | run | workspace | exit | what the log shows | |:---|:---|:---|:---| | old step (`origin/main` e05e0de) | checkout | **1** | installed = ten `objectstack-*` + `objectui`, expected = ten `objectstack-*`, `::error::scaffolded skill set != curated skills/ catalog`. The card's failure, reproduced. | | new step | checkout at a9585ae | **0** | installed = expected = the 11 skills, `Found 11 skills` twice | | new step | archive copy, unmodified | **0** | control leg for the three negatives below | | negative A: unmarked skill at `.claude/skills/leak-probe/` | archive copy | **1** | `Found 12 skills`, `::error::repo-root discovery does not match the curated catalog (expected 11 skills)` | | negative B: `metadata.internal: true` skill at `skills/internal-probe/` | archive copy | **1** | expected lists `internal-probe`, installed does not, `::error::scaffolded skill set != curated skills/ catalog` | | negative C: nested `skills/objectui/guides/nested-probe/SKILL.md` | archive copy | **1** | installed lists `nested-probe`, `::error::scaffolded skill set != curated skills/ catalog` | | control D: plain new skill at `skills/new-curated-probe/` | archive copy | **0** | `Found 12 skills`. A new curated skill passes without a workflow edit, by design. | | new step after removing every probe | archive copy | **0** | restore leg | Negative B shows the skills CLI hides `metadata.internal` skills even under `--all` today, so an internal skill in `skills/` trips assertion 1. If an older CLI did install it, assertion 2's count would trip instead. The step's comment says both. ## Gates At head a9585ae, all 38 commands from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (change set: this one file, 14 changed lines) were run one by one with stdin from `/dev/null`, and each exit was captured before any pipe. Every one exited 0, including `pnpm check:pm-dispatch-gates` (fast tier, 1899 cases). The `--ran` reconciliation reads `38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN`, a derived zero because every line recorded its exit code. The families the derivation prints as NOT MEASURED (workflow-valued argv, the `Test Core` shard steps, the type-check lanes) are CI's. Their verdicts are this PR's check-runs. Changeset: none. The diff is one CI workflow and publishes nothing, so `skip-changeset` rides the create. ## Acceptance notes - The card's negative control reads "an extra non-curated skill directory". Under the directory-read rule, a plain directory added to `skills/` is curated by definition (control D passes) because it also ships in `@objectstack/skills`. The non-curated shapes are A, B and C, and each fails. - The workflow's `pull_request.paths` lists `.github/workflows/scaffold-e2e.yml` itself, so this PR runs `Scaffold E2E`. The card's first acceptance line is read from this PR's `Scaffold with repo dist` check-run, which is a real CI run of the edited step against unchanged skills. --- _Generated by [Claude Code](https://claude.ai/code/session_014kunMthReAU7eVraV1qLsb)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f60f4ea commit 7e6fca0

1 file changed

Lines changed: 13 additions & 1 deletion

File tree

‎.github/workflows/scaffold-e2e.yml‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,23 @@ jobs:
248248
# 2. Interactive `skills add …/framework` (no --all) relies on
249249
# metadata.internal hiding — assert repo-root discovery surfaces
250250
# exactly the curated set and nothing internal.
251+
# The curated catalog is every skills/<dir>/ holding a SKILL.md — the
252+
# same tree @objectstack/skills ships whole (packages/skills/scripts/
253+
# sync-catalog.mjs), read the same way as the package-catalog step
254+
# below. ⛔ Never a name glob or an allow-list here: a skill added to
255+
# skills/ would red this step until a governed edit of this file.
256+
# An internal skill placed in skills/ still fails: the CLI hides
257+
# metadata.internal skills, so the installed set (assertion 1) or the
258+
# listed count (assertion 2) falls short of the directory set.
251259
run: |
252260
cd "$RUNNER_TEMP"
253261
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" skills-probe --skip-install --skip-skills
254262
cd skills-probe
255-
expected=$(cd "$GITHUB_WORKSPACE/skills" && ls -d objectstack-*/ | tr -d '/' | sort)
263+
expected=$(for d in "$GITHUB_WORKSPACE"/skills/*/; do if [ -f "${d}SKILL.md" ]; then basename "$d"; fi; done | sort)
264+
if [ -z "$expected" ]; then
265+
echo "::error::skills/ holds no <skill>/SKILL.md — the curated catalog this step compares against is empty"
266+
exit 1
267+
fi
256268
expected_count=$(echo "$expected" | wc -l | tr -d ' ')
257269
258270
npx -y skills add "$GITHUB_WORKSPACE/skills" --all --copy

0 commit comments

Comments
 (0)