Skip to content

feat: add --json to preset info and extension info - #4833

Open
nefayran wants to merge 1 commit into
github:mainfrom
nefayran:feat/4213-info-json
Open

nefayran wants to merge 1 commit into
github:mainfrom
nefayran:feat/4213-info-json

Conversation

@nefayran

@nefayran nefayran commented Oct 4, 2026

Copy link
Copy Markdown

Description

Part of #4213, following the scope in #4213 (comment).

Adds --json to specify preset info <id> and specify extension info <name> for installed packs. Each command writes one JSON object to stdout:

  • id, name, description, version, author, priority, enabled and source come from installed_list_item, the helper behind list --json, so the two views share one definition. The provides counts are replaced by arrays.
  • commands, templates and scripts entries have name, description, source and sourcePath (the manifest file). Preset entries add strategy as the manifest validates it (lowercased, default replace). Extension entries have no strategy, since extension-provided files always replace. Extension scripts add runtimes when the manifest declares them.
  • Extension hooks entries have trigger, targetCommand, optional and priority, with the defaults hook registration applies (true and 10). If an event declares the same command twice, the last declaration wins, as it does in HookExecutor registration.
  • Lookup matches the existing human-readable commands: presets by ID, extensions by ID and then by a unique display name, ignoring case.
  • Errors follow the list --json contract: one {"error": "..."} object on stderr. A pack that is not installed, an ambiguous name or a missing project exits 1; parse errors keep their exit code through InstalledListJSONCommand. extension info --json --versions exits 2.

The new helpers are in src/specify_cli/_installed_info_json.py, next to _installed_list_json.py. Without --json, both commands behave as before. The CLI reference pages for presets and extensions describe the new output.

With the bundled git extension and lean preset installed through --dev:

$ specify extension info git --json | jq -c '{id, commands: (.commands | length), hooks: .hooks[0:2]}'
{"id":"git","commands":5,"hooks":[{"trigger":"before_constitution","targetCommand":"speckit.git.initialize","optional":false,"priority":10},{"trigger":"before_specify","targetCommand":"speckit.git.feature","optional":false,"priority":10}]}
$ specify preset info lean --json | jq -c '{id, commands: [.commands[0] | {name, sourcePath, strategy}]}'
{"id":"lean","commands":[{"name":"speckit.specify","sourcePath":"commands/speckit.specify.md","strategy":"replace"}]}
$ specify preset info missing --json; echo "exit=$?"
{"error": "Preset 'missing' is not installed"}
exit=1

Not in this PR

These are the open questions from the scope comment, left for follow-ups:

  1. Per-entry id: [Feature]: Add deterministic contribution IDs and stack lookup IDs for resolved artifacts聽#4210 tracks ids for these surfaces.
  2. artifact, optional and handoffs on commands: they depend on [Feature]: Add artifact, optional, and handoffs to the command manifest schema聽#4209.
  3. runtimes on preset scripts: this needs a preset manifest schema change.
  4. Shorthand keys: the output reports the validated strategy. The loader does not read replaces: (used in presets/lean/preset.yml), so lean reports the default replace. Mapping replaces/wraps/prepends/appends would change the loader, which seems better as its own PR.
  5. Catalog-only packs: --json returns the not-installed error instead of catalog metadata.

Hook entries have no sourcePath, because a hook can target a core command or a command from another pack, and then there is no file in this pack to point to.

#4776 also changes presets/command_info.py; I'll rebase on whichever lands first.

Testing

The agent ran these on macOS with Python 3.12:

  • uv run pytest tests/test_installed_info_json.py: 12 passed. The same file against main (ae5ade7): 12 failed with No such option: --json.

  • uv sync --extra test, then uv run pytest -n 4 (pytest-xdist added to the local venv only): 9312 passed, 264 skipped.

  • uvx ruff@0.15.0 check src tests: all checks passed.

  • specify preset info --help and specify extension info --help list --json; the sample-project commands above were run as shown.

  • Tested locally with uv run specify --help

  • Ran existing tests with uv sync && uv run pytest

  • Tested with a sample project (if applicable)

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: Opened by @nefayran. Implemented with Claude Code (model: Claude Opus 5.5, max reasoning effort) in autonomous mode: the agent wrote the code, tests, docs and this description, and ran the checks listed above. The commit carries an Assisted-by trailer.

Write one JSON object for an installed pack: the list --json fields plus
commands, templates and scripts arrays (and hooks for extensions) in place
of the provides counts. Errors follow the list --json stderr contract.

Part of github#4213.

Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
@nefayran
nefayran requested a review from mnriem as a code owner October 4, 2026 06:50
@mnriem mnriem added the triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate label Oct 4, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants