Skip to content

feat: extension lifecycle hook for post-install setup #3359

Description

@rhuss

This builds on the discussion in #3303. The conclusion there was that agent-specific command specialization belongs in userland (presets, extensions) rather than in a core capability matrix, and I agree with the materialization invariant argument. What I'd like to propose here is the minimal plumbing that would make that userland approach practical.

The problem

Extensions that target multiple agent harnesses need to adapt their commands at install time. Today, the consuming project's init script has to centralize all that logic: detect the harness, then run per-extension, per-harness setup (select presets, adjust configuration, copy adapter scripts). This doesn't scale. Every new extension means more branches in the project's init script, maintained by someone who didn't write the extension.

The extension author is the right person to own that adaptation, but specify extension add currently gives them no hook to run it.

Proposal

Add an optional on_install lifecycle hook to extension.yml:

extension:
  id: spex-teams
  # ...existing fields...

hooks:
  on_install:
    script: scripts/on-install.sh
    description: "Select agent-appropriate preset for subagent dispatch"

When specify extension add processes an extension that declares on_install, it invokes the script after the extension is installed into .specify/extensions/<id>/. The script runs with the extension's directory as context and can do whatever it needs: select a preset, skip unsupported commands, copy adapter files, adjust configuration.

Harness context

The script needs to know which harness it's adapting for. Two options, not mutually exclusive:

  1. Extension detects it. The script runs its own detection logic (environment variables, directory presence, init-options). This works today, no core changes needed beyond the hook itself.

  2. specify passes it. If the active integration is already known at install time (from specify init --integration <name> or the stored .specify/init-options.json), specify could pass it as an environment variable (SPECKIT_INTEGRATION=claude) or a script argument. This would be a convenience, not a requirement. The extension could still override or extend the detection.

Option 2 is cleaner since specify already knows the integration. But the hook is useful even with just option 1.

What the hook would NOT do

This deliberately avoids the capability matrix from #3303. The hook doesn't introduce conditional template blocks, capability keys, or any shared vocabulary between core and extensions. The result is still fully materialized, auditable, self-contained project files. The hook is pure plumbing: "run this script after install."

Use case: cc-spex

cc-spex has 7 extensions with 30 commands. 24 are portable as-is. 6 need agent-specific adaptation (subagent dispatch patterns, interactive prompt tools). Today, spex-init.sh centralizes all of this in a 400-line script. With on_install, each extension would own its own adaptation:

  • spex-teams installs its Claude Code subagent preset or falls back to sequential execution
  • spex-deep-review selects the right dispatch section for the detected harness
  • spex core adjusts ship pipeline for available parallelism

The init script shrinks to just specify extension add calls. Each extension is self-contained.

Minimal implementation

  1. Parse on_install from extension.yml during specify extension add
  2. After installation, invoke the script with:
    • Working directory: the extension's installed path
    • Environment: SPECKIT_INTEGRATION (if known), SPECKIT_PROJECT_ROOT
  3. Non-zero exit from the script fails the installation (with the script's stderr as the error message)

An on_uninstall counterpart would be natural but isn't required for the initial implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions