Skip to content

T-R4XL-project-local-skill-extension-mechanism

Status: closed/done · Impact: high · Complexity: medium

Provide a clean way for a consuming project to add project-specific behavior to an upstream SDLC skill (or any plugin skill) without modifying the upstream skill’s prose. Today, friction observed during SDLC-on-SDLC development tends to land as PRs against plugin/skills/<name>/SKILL.md that hard-code SDLC-specific path patterns into a skill every downstream consumer also uses (e.g. task-work learning to scan ## Files to touch for plugin/skills/*/SKILL.md). That contaminates the general skill with behavior only ever exercised by the SDLC plugin’s own dev repo. Closing this gap gives such friction a project-local home so the upstream skills stay general.

  • plugin/skills/<name>/SKILL.md is loaded by the Claude Code runtime from ${CLAUDE_PLUGIN_ROOT} (i.e. the installed plugin path). The consuming project has no documented way to add behavior on top of an upstream skill — modifications mean a PR against the plugin’s repo.
  • The project-check skill at .claude/skills/project-check/ is the one precedent for a fully project-local skill (its own SKILL.md declares “not part of the distributed sdlc plugin”). But it’s a standalone skill, not an extension OF an upstream skill.
  • Claude Code already supports project-local hooks via .claude/settings.json (PreToolUse, PostToolUse, Stop, etc.). These can fire scripts at well-known events but don’t have a documented integration with the skill flow.
  • Recent post-mortem-spawned tasks that exhibit the contamination pattern (now parked pending this mechanism):

Define a convention that lets a consuming project (today: the SDLC plugin’s own dev repo) add behavior to an upstream skill without editing the upstream skill’s prose. Evaluate two complementary design directions:

  • (A) Hook-based extension points. Each major SDLC skill declares named extension points in its SKILL.md prose (e.g. “after Step 1, any project-local hook at .claude/hooks/task-work-step1-post.sh is invoked with the task path as argument”). Consuming projects add scripts at those paths. The skill’s prose explicitly enumerates the hook contract (inputs, outputs, exit-code semantics) so the hook is a stable contract, not implicit composition.

  • (B) Project-local skill extension SKILL.md. A consuming project drops .claude/skills/<upstream-name>-extension/SKILL.md. When the upstream skill runs, it checks for this file and — if present — treats it as additional procedural prose layered on top of the base steps. The extension file declares which step(s) it augments. Closer to “inheritance” than to “hooks”; better for prose-shaped extensions that don’t fit a single shell-out hook.

Either direction (or both, used at different layers) must satisfy: upstream skill prose remains general, project-specific friction lives under .claude/, and the two together produce the augmented behavior at runtime.

  1. Survey the friction examples currently parked on this task (task-work-detects-self-modifying-skill, task-define-prompts-for-executable-shadow). For each, sketch what the project-local extension would look like under both (A) and (B). The sketches inform which mechanism fits which kind of extension.
  2. Pick (A) vs (B) vs both. Document the convention in plugin/conventions/project-local-skill-extension.md (per the factor-shared-prose rule in plugin/skills/CLAUDE.md).
  3. For (A): document the hook-event taxonomy each consuming project can rely on (which skills declare hooks at which steps, what the input/output contract is). The hook contract goes in each affected SKILL.md’s Notes section, referencing the convention doc.
  4. For (B): document the SKILL.md extension-file shape (what frontmatter the extension file carries, how the upstream skill discovers and composes it). The discovery rule goes in each affected SKILL.md.
  5. Land one of the parked carve-out tasks (likely task-work-detects-self-modifying-skill as the simpler case) using the new mechanism, as a working example that both proves the convention and serves as a reference for the other.
  • plugin/conventions/project-local-skill-extension.md (new) — the convention doc covering whichever mechanism(s) get adopted.
  • plugin/skills/CLAUDE.md — cross-reference the new convention.
  • plugin/skills/<affected>/SKILL.md — at least one upstream skill that declares extension points / discovery, to validate the convention.
  • .claude/skills/<affected>-extension/SKILL.md OR .claude/hooks/<...> (new, this repo) — the working-example extension that closes the parked carve-out task.
  • AC-1: plugin/conventions/project-local-skill-extension.md exists and unambiguously specifies at least one mechanism (hooks OR SKILL.md extension) including the contract for inputs, outputs, and discovery.
  • AC-2: At least one upstream skill’s SKILL.md references the new convention and declares its extension points / discovery rule.
  • AC-3: One of the parked carve-out tasks (task-work-detects-self-modifying-skill is the simplest target) ships its behavior via the new mechanism — implemented under .claude/ in this repo, NOT by editing the upstream plugin/skills/task-work/SKILL.md. The behavior is observable from a real /sdlc:task-work run.
  • Migrating existing .claude/skills/project-check/ (which is a standalone skill, not an extension of an upstream one) to the new convention. project-check’s shape doesn’t need it.
  • Designing a generic “skill inheritance” framework that handles arbitrary skill composition. The scope is project-local augmentation of upstream skills, not arbitrary skill mixins.
  • Building a hook registry / orchestrator. The hooks fire via Claude Code’s existing settings.json mechanism (if mechanism A is adopted); no new harness is required.
  • none (depends on Claude Code’s existing project-local .claude/ infrastructure, which already supports hooks and project-local skills)

Filed 2026-05-21 during a /sdlc:task-review walkthrough that surfaced multiple post-mortem-spawned tasks proposing plugin/skills/*/SKILL.md carve-outs in general skills. The user named the pattern explicitly: “If it’s a fix that just improves task-work for how it works against the SDLC project itself — that seems wrong.” This task creates the home such fixes should live in.

Captured by /sdlc:task-work on 2026-05-21. PR: pending.

  • AC-1: auto — plugin/conventions/project-local-skill-extension.md exists and specifies the hook-based mechanism with explicit path shape, input contract (env var TASK_FILE), exit-code semantics (informational, ignored), and discovery rule ([ -x <path> ] test). Verified by reading the committed file.
  • AC-2: auto — plugin/skills/task-work/SKILL.md Step 1a declares the extension point and references the convention; a Notes-section bullet at the bottom cross-links it (per the factor-shared-prose rule in plugin/skills/CLAUDE.md). Verified by reading the committed diff.
  • AC-3: agent-manual — ran .sdlc/skill-ext/task-work/step1-post.sh against three task files: (a) the parked carve-out 2026-05-20-task-work-detects-self-modifying-skill.md (has plugin/skills/task-work/SKILL.md in Files to touch) — emitted the runtime/worktree note as expected; (b) 2026-05-20-task-work-ancestry-helper-one-liner.md (also touches a SKILL.md) — emitted the note; (c) 2026-05-20-headless-bases-syntax-parser-for-eval.md (no SKILL.md reference) — emitted nothing, exit 0. The behavior observed lives entirely under .claude/; no edit to the upstream task-work SKILL.md was needed.
  • The “shell-out hook with documented contract” shape converged quickly — no design back-and-forth needed once the alternative (project-local SKILL.md layering) was dismissed for being fuzzier-contracted than a script.
  • Closing the parked carve-out task (2026-05-20-task-work-detects-self-modifying-skill) as closed/done in the same PR makes the working example legible: one PR demonstrates both the mechanism and the first user.
  • Quality checks (7/7) and skill-prose invariants both passed first try; the convention doc didn’t break any existing check.
  • .claude/ was gitignored except for an explicit allowlist (!.claude/hooks/, !.claude/skills/, etc.). Adding the new .sdlc/skill-ext/ tree required editing .gitignore to add another carve-out, which is easy to miss. A /sdlc:setup-style scaffold step for “register a new project-local extension tree” would automate the un-ignore step so the next consumer doesn’t silently commit nothing. → T-ZZD1-setup-generalizes-claude-subtree-unignore
  • task-work Step 6 says “delegate implementation to a sub-agent” but the Agent tool was not available in this run (not in allowed-tools, not surfaced as a deferred tool). The skill proceeded by doing the implementation inline, which worked for a small task but would have scaled poorly. Either soften the prose to “delegate IF the Agent tool is available, otherwise implement inline” or ensure the Agent tool is wired into the harness for skills that name it as required. → T-PZW5-task-work-step-6-inline-impl-fallback
  • The hook contract specifies “working directory: the project root”, but task-work’s Step 1a prose doesn’t show the actual invocation shape that sets cwd correctly. A small reference invocation in the convention doc (e.g. (cd "$PROJECT_ROOT" && TASK_FILE=... ./hook.sh)) would make the contract executable rather than declarative. → T-ZQ3M-project-local-extension-doc-shows-invocation

← Back to Tasks