Skip to content

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

@seiya-koji

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
    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