Skip to content

[Feature]: Add --json output to preset list and extension list #4211

Description

@nicolehaugen

Problem Statement

Both preset list and extension list emit human-formatted text only; there is no --json. Verified on main: preset_list() and preset_info() in presets/_commands.py take zero optional arguments (no flag anywhere in the file), same for extension _commands.py. Consumers (wizard UI, external tooling, CI checks) have to regex-scrape stdout. The speckit-wizard-canvas extension does exactly this in composition/preset-order.mjs::parsePresetListOutput, and the file's header comment explicitly notes:

"The CLI's specify preset list command prints installed presets in precedence order (first line = winner). That IS the precedence."

Beyond the missing --json, list_installed() in each registry omits fields consumers need:

  • Extensions (extensions/__init__.py:3399-3427) return command_count + hook_count only — no template or script counts, even though manifest.templates and manifest.scripts exist post-[Feature]: Allow extensions to declare templates and scripts in their manifest #4010.
  • Presets (presets/__init__.py:4049-4088) return template_count + tags only — no command_count, no script_count. The single template_count is misleading because presets contribute commands and scripts in separate manifest.commands / manifest.scripts lists.
  • author is dropped on the read path in both registries. The field exists in the manifest (and is even a filter parameter in the catalog search code path at extensions/__init__.py:4127), but neither list_installed() includes it in its return dict.
  • Neither surface exposes the structured source (see companion "Record structured provenance for installed presets and extensions" issue). The registry stores it; the read path drops it.

Proposed Solution

Add --json to both commands. The output is a JSON array of objects:

{
  "id": "speckit.git",
  "name": "SpecKit Git",
  "description": "Git-flow helpers",
  "version": "1.2.0",
  "author": "",
  "priority": 100,
  "enabled": true,
  "source": { "kind": "catalog", "catalog": "speckit-official" },
  "provides": { "commands": 4, "templates": 2, "scripts": 1, "hooks": 3 }
}

Preset objects omit hooks from provides (presets don't contribute hooks). The provides counts are integers, one per manifest kind. Ordering in the JSON array is the same precedence order the text output already uses (so parsePresetListOutput becomes JSON.parse(stdout) on the wizard side). Update list_installed() in both registries to compute the missing counts from the manifest fields landed by #4010 and to surface author.

Alternatives Considered

  • Emit YAML. Rejected — the whole point is to let the wizard drop js-yaml.
  • Emit only new fields via a separate list --extra flag. Rejected — every consumer would then need two calls.
  • Leave counts flat (command_count, hook_count) rather than grouping into provides. Rejected — mixes wire shape with the composition/stack view (filed separately as specify artifact), where the grouped shape is required.

Component

Specify CLI (initialization, commands)

AI Agent (if applicable)

Not applicable

Use Cases

  1. speckit-wizard-canvas replaces composition/preset-order.mjs regex parser with JSON.parse(execFileSync("specify", ["preset", "list", "--json"])).
  2. A CI job asserts "the speckit.constitution preset is installed and enabled at priority ≥ 100" with jq, no text parsing.
  3. External catalog UIs query extension list --json to reconcile installed vs. remotely-available extensions by id + source.catalog.
  4. Support triage: paste specify preset list --json output into a bug report for exact reproduction context.

Acceptance Criteria

  • specify preset list --json and specify extension list --json emit a JSON array to stdout with id, name, description, version, author, priority, enabled, source, provides for each entry.
  • Extension provides includes commands, templates, scripts, hooks integer counts. Preset provides includes commands, templates, scripts integer counts (no hooks).
  • list_installed() in both registries is updated to compute the missing per-kind counts from the current manifest schema, including entries landed by [Feature]: Allow extensions to declare templates and scripts in their manifest #4010.
  • source is the structured object from the companion provenance issue (or { "kind": "local" } for legacy records).
  • Array order matches the existing text-output precedence order (documented as authoritative in composition/preset-order.mjs).
  • Non-zero exit code and stderr JSON {"error": "..."} on failure so consumers can distinguish success from empty-array success.
  • Tests: JSON schema round-trip, count parity with the text output, missing-field defaults (author: null), legacy installed.json read.
  • Docs: preset list / extension list sections in the CLI reference show the --json shape.

Additional Context

Direct replacement for plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/preset-order.mjs in github/spec-kit-copilot. Depends on the companion "Record structured provenance for installed presets and extensions" issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementfeature-assessRun the Spec Kit idea-assessment pipeline on this feature requestfeature-goFeature assessment verdict: go — ready to hand off to /speckit.specify

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions