Skip to content

Cluster B: bundled skills workspace, idempotent installer, CLAUDE.md sentinel injection#33

Open
spaceshipmike wants to merge 6 commits into
mainfrom
feature/cluster-b-skills
Open

Cluster B: bundled skills workspace, idempotent installer, CLAUDE.md sentinel injection#33
spaceshipmike wants to merge 6 commits into
mainfrom
feature/cluster-b-skills

Conversation

@spaceshipmike

Copy link
Copy Markdown
Owner

Spec §2.17 "Bundled Claude Code Skills and CLAUDE.md Rule Injection" lands as code. Three chunks, six commits, three local tags (.19 → .20 → .21).

What's in this PR

B1 — packages/skills/ workspace + dual manifest + validator (S220, S221)

New private workspace packages/skills/ holding the two bundled skills with parallel host manifests over one skills/ tree:

  • skills/setlist-enroll-project/SKILL.md — walks the four-step onboarding (register_projectenrich_projectwrite_fieldsrefresh_project_digest) and post-enrollment register_capabilities. Calls vocab(field) before writing open-vocabulary values. Defers to setlist://docs/onboarding for full field semantics.
  • skills/setlist-portfolio-graph/SKILL.md — renders the area → project → capability tree using portfolio_brief, list_areas, list_projects, query_capabilities. Read-only by contract.
  • .claude-plugin/plugin.json + .claude-plugin/marketplace.json + .codex-plugin/plugin.json — same skills, two host manifests.

scripts/validate-skills-manifests.js cross-checks the manifests for version drift, skill-list drift, and orphan content. Silent on pass (S221 spec literal). Wired as npm run validate-skills-manifests. 10 unit tests.

B2 — Idempotent skills installer (S210, S211)

packages/skills/scripts/install.js (wired as npm run install:skills):

  • Honors \$CLAUDE_CONFIG_DIR for both the skills install dir and CLAUDE.md path
  • Copies skills with placeholder-token substitution (__NODE_BIN__, __SETLIST_PACKAGE_ROOT__, __SETLIST_CLI_BIN__)
  • Preserves user .local.md siblings byte-identical across re-runs
  • --no-skills and --no-rule flags compose
  • Idempotent: re-running produces byte-identical output for any skill whose source hasn't changed

17 unit tests cover copy + idempotency + .local.md preservation + flag combinations + path rewriting + CLAUDE_CONFIG_DIR routing.

B3 — CLAUDE.md sentinel-block injection (S198)

packages/skills/scripts/rule-injection.js implements the four-state idempotency contract:

  1. File missing → create with header + sentinel block
  2. Markers absent → append block, existing content byte-identical
  3. Markers present, content matches → no-op (no mtime bump)
  4. Markers present, content drifted → replace only between markers
  5. Exactly one marker (orphan) → REFUSE with one-line warning

The rule body is the textual companion to the MCP server's `ONBOARDING_INSTRUCTIONS` — coordinated framing, distinct surface. MCP instructions reach sessions over MCP; the CLAUDE.md rule reaches sessions where setlist's MCP isn't connected.

18 unit tests cover each state, end-before-begin marker order, an empty file, and `runInstaller` integration.

Versioning

  • `0.6.1-beta.18` (main baseline)
  • `0.6.1-beta.19` — chunk B1
  • `0.6.1-beta.20` — chunk B2
  • `0.6.1-beta.21` — chunk B3 (Cluster B complete)

`.fctry/config.json` `propagationTargets` extended with `packages/skills/package.json`, the two host plugin manifests, and the marketplace.json fields.

Scope guards

  • No modifications to existing core/mcp/cli/app code paths
  • No new runtime dependencies
  • All 777 tests stay green
  • `npm run typecheck` clean across all packages
  • Validator silent on pass against the real-repo manifests
  • Tags created locally only; merge will surface them

Test plan

  • `npm test` passes (777 tests)
  • `npm run typecheck` clean
  • `npm run validate-skills-manifests` exits 0 silently
  • `npm run install:skills` to a temp dir → skills present with substituted paths
  • Re-running installer is a no-op (no mtime bump on unchanged files)
  • CLAUDE.md gets the sentinel block injected; second run is no-op
  • Orphan marker (only ``) → installer refuses with warning, file untouched

🤖 Generated with Claude Code

…0, S221)

Add new private workspace packages/skills/ holding the bundled Claude Code
and Codex skills (setlist-enroll-project, setlist-portfolio-graph) with
parallel .claude-plugin/ and .codex-plugin/ manifests over a single skills/
tree. The skills walk an agent through the four-step onboarding workflow and
render the area to project to capability portfolio graph using only the 58
shipped MCP tools.

scripts/validate-skills-manifests.js cross-checks the dual manifests on:
- version drift between claude plugin.json and codex plugin.json
- version drift between marketplace.json (top + plugin entry) and plugin.json
- skill listing drift between claude and codex manifests
- orphan content on disk vs orphan listings in either manifest

Silent on pass (S221 spec literal); exits 1 on drift with stderr diagnostics;
exits 2 on config/IO error. Wired as npm run validate-skills-manifests at the
root. Ten unit tests cover every drift case plus the real-repo passing case.

Satisfies: S220, S221
Spec sections: #bundled-skills (2.17), #monorepo (5.1), #testing-discipline (4.5)
Add packages/skills/scripts/install.js — the installer that copies the bundled
skills from packages/skills/skills/ into the host's skills directory
($CLAUDE_CONFIG_DIR/skills/ or ~/.claude/skills/) with placeholder-token
substitution at copy time so installed files carry machine-specific paths.

Contract highlights:
- honors CLAUDE_CONFIG_DIR for both skills install dir and CLAUDE.md path
- copies all skill files; .md files get placeholder substitution
  (__NODE_BIN__, __SETLIST_PACKAGE_ROOT__, __SETLIST_CLI_BIN__)
- preserves user .local.md siblings byte-identical across re-runs and
  logs a one-line 'preserving local override' notice per preserved file
- --no-skills skips the copy phase; previously-installed skills are NOT
  deleted (contract is 'do not overwrite', not 'uninstall')
- --no-rule skips the CLAUDE.md sentinel-block injection (the injection
  itself lands in B3 — install.js calls into rule-injection.js which is
  a stub in this commit)
- idempotent: re-running produces byte-identical files for any skill
  whose source content hasn't changed

Wired as 'npm run install:skills' at the root. Seventeen unit tests cover
copy + idempotency + .local.md preservation + flag combinations + path
rewriting + CLAUDE_CONFIG_DIR routing.

Satisfies: S210, S211
Spec sections: #bundled-skills (2.17)
Flesh out packages/skills/scripts/rule-injection.js — the four-state
idempotent injection of the setlist proactive-use rule between
<!-- setlist:rule:begin --> / <!-- setlist:rule:end --> sentinel markers
in CLAUDE.md (honoring $CLAUDE_CONFIG_DIR or falling back to
~/.claude/CLAUDE.md).

The four states (per spec §2.17 'Idempotency contract for the rule
injection'):

  1. file missing → create with '# Claude Code instructions' header
     plus the sentinel block (single trailing newline)
  2. markers absent in existing file → append the block with a single
     leading blank line; existing content stays byte-identical
  3. markers present with matching content → no-op (no mtime bump,
     no rewrite)
  4. markers present with drifted content → replace ONLY the content
     between the markers; everything outside stays byte-identical
  4*. exactly one marker present (or end-before-begin) → REFUSE to
     write; log one-line warning naming the file and the missing
     marker. The user fixes the file and re-runs.

The rule body is the textual companion to the MCP server's
ONBOARDING_INSTRUCTIONS envelope — coordinated framing, distinct
surface. The MCP instructions reach sessions over MCP; the CLAUDE.md
rule reaches sessions in directories without setlist's MCP server
connected.

Eighteen tests cover each state, the orphan-refusal logger, idempotency
across consecutive runs, multi-line drift, end-before-begin marker
order, an empty file, and runInstaller integration (--no-rule opt-out,
CLAUDE_CONFIG_DIR routing, orphan refusal end-to-end).

Satisfies: S198
Spec sections: #bundled-skills (2.17)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant