Skip to content

[Feature]: Allow multiple commands per hook event with explicit priority ordering #2378

Description

@seiya-koji

Problem Statement

Today an extension manifest can declare at most one command per hook event:

hooks:
  after_plan:
    command: "speckit.my-ext.verify"

This forces extension authors to choose one of:

  1. Collapse multiple responsibilities into a single mega-command.
  2. Hide branching logic behind a single entry point.
  3. Ship multiple extensions just to register parallel hooks on the same event.

It also leaves no manifest-level way to control execution order when several extensions hook the same event — current order depends on install order, not on the extension author's intent.

Proposed Solution

Allow each hook event in extension.yml to accept either a single mapping (existing form) or a list of mappings, plus an optional integer priority field (default 10, lower runs first; ties preserve insertion order via stable sort).

hooks:
  after_plan:
    - command: "speckit.my-ext.verify"
      priority: 5
      optional: false
      description: "Verification step"
    - command: "speckit.my-ext.report"
      priority: 10
      optional: true
      prompt: "Generate report?"
      description: "Reporting step"

CLI side:

  • ExtensionManifest validates the list form and priority (positive int, bool rejected).
  • HookExecutor.register_hooks purges and replaces all entries owned by the extension on each event during reinstall, so shape changes (single↔list, list shrinkage) don't leak orphaned entries into the project config.
  • HookExecutor.get_hooks_for_event sorts entries by priority ascending via normalize_priority, tolerating corrupted on-disk values.

Backward compatibility: existing single-mapping manifests work unchanged.

Alternatives Considered

  • Keep one-command-per-hook forever: forces extension authors into mega-commands or extension fragmentation, with no priority control.
  • Encode order in command names (e.g. speckit.my-ext.10-verify): leaks ordering into public command names; brittle to refactors.
  • Out-of-band ordering config separate from extension.yml: splits the source of truth for hook behavior, making manifests harder to read and review.

Component

Specify CLI (initialization, commands)

AI Agent (if applicable)

All agents

Use Cases

  1. A single extension wants to run verification and reporting on after_plan as two independently optional / conditional steps, instead of one combined command.
  2. Two extensions hook the same event and the user wants verify to run before report regardless of install order.
  3. A safety-critical extension wants its check to run first via a low priority, ahead of less-critical extensions on the same event.

Acceptance Criteria

  • extension.yml accepts either a single mapping or a list of mappings under any hook event.
  • Each list entry supports an optional priority: int (>= 1, default 10) with validation (rejects non-int, bool, and < 1).
  • At fire time, entries are sorted by priority ascending; ties preserve insertion order via stable sort.
  • Reinstalling an extension whose hook shape changed (single↔list, list shrinkage) does not leave orphaned entries in the project config.
  • Existing single-mapping manifests continue to work unchanged (regression tests).
  • extensions/template/extension.yml documents the new form.
  • Tests cover validation, ordering, dedup across shape changes, and tolerance for corrupted on-disk priority values.

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions