You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Feature]: Add --json output to preset info and extension info with fully-expanded per-contribution detail (per-pack detail view; complements list --json summary counts) #4213
Today specify preset info <id> and specify extension info <id> emit only text. speckit-wizard-canvas reads the raw preset.yml / extension.yml files with js-yaml and re-normalizes the shape itself in composition/collect.mjs::parseProvidesEntries and ::parseHookDeclarations — including strategy inference from shorthand keys (replaces vs. wraps vs. prepends vs. appends), hook-phase normalization (phase vs. trigger, command vs. targetCommand), and script-runtime inference by globbing scripts/{bash,powershell,python}/*.{sh,ps1,py}. All of this is server-side data being reconstructed on the client.
Downstream consumers need the structured shape returned by the CLI itself so they can stop reconstructing it.
How this differs from the companion preset list --json / extension list --json issue. The list variant operates on the collection of installed packs and returns a JSON array, one row per pack, where provides is just integer counts ({ commands: 4, templates: 2, scripts: 1, hooks: 3 }) — a summary/catalog view suitable for "here's every preset the project has". info operates on one pack, addressed by id, and returns a single JSON object where provides is fully expanded — every command, template, script, and hook enumerated with its full per-contribution schema (id, name, description, artifact, optional, handoffs, strategy, sourcePath, runtimes, etc.). A wizard rendering "here's what speckit.git contributes to my project" needs the info detail; list's counts are not enough. Both surfaces are required; neither is a subset of the other.
Extension commands/templates/scripts entries omit strategy (they are always replace, per the replace-only rule enforced at extensions/__init__.py:622-626).
id values use the stable-id scheme from the companion "stable id / lookupId" issue; the id on a per-contribution entry is what the companion specify artifact info --json stack's lookupId points at.
artifact, optional, handoffs on commands come from the companion "artifact/optional/handoffs" issue.
All shorthand-key normalization (replaces/wraps/prepends/appends → strategy: "replace"|"wrap"|"prepend"|"append") is done server-side.
Alternatives Considered
Fold everything into the companion specify artifact info --json command. Rejected — artifact info is per-artifact (walks one composition stack across all installed packs); info --json is per-source (walks one preset/extension across all its contributions). Both are needed and neither is a subset of the other.
Fold everything into the companion preset list --json / extension list --json. Rejected — that surface is per-collection with summary counts; info is per-pack with full expansion. Different shape, different call pattern, different use cases (see Problem Statement).
Emit YAML. Rejected — the whole point is to let the wizard drop js-yaml.
Leave hook-phase / strategy shorthand un-normalized. Rejected — every consumer would reproduce the client-side logic from composition/collect.mjs and drift over time.
Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
Not applicable
Use Cases
speckit-wizard-canvas deletes composition/collect.mjs::parseProvidesEntries and ::parseHookDeclarations, replacing them with JSON.parse(execFileSync("specify", ["preset", "info", "<id>", "--json"])) / specify extension info <id> --json. This is the change that removes the js-yaml dependency.
An IDE plugin renders hover-cards on command names by looking up the commands[] entry (description, artifact, handoffs).
A pre-commit check walks extension info --json's hooks[] to warn when two extensions register the same trigger at the same priority.
Acceptance Criteria
specify preset info <id> --json and specify extension info <id> --json emit a single JSON object with top-level fields matching the companion list --json issue (id, name, description, version, author, priority, enabled, source) plus fully-expanded commands, templates, scripts arrays (and hooks for extensions) — not integer counts.
Command entries include artifact, optional, handoffs (from the companion command-fields issue).
Script entries include runtimes for both presets and extensions.
Preset command/template/script entries include strategy; extension entries omit it (or set to "replace" for informational purposes).
Shorthand keys (replaces/wraps/prepends/appends) are normalized server-side into strategy.
All id values follow the stable-id scheme and are stable across reinstalls, matching the lookupId values emitted by specify artifact info --json.
Unknown id → non-zero exit + stderr JSON error.
Tests: schema round-trip, strategy normalization (all four shorthand forms), hook shorthand normalization, extension replace-only enforcement, handoffs frontmatter-merge, and id cross-reference with specify artifact info --json output.
Docs: preset info / extension info sections in the CLI reference show the --json shape and normalization rules, plus a note contrasting info --json (per-pack, full expansion) with list --json (per-collection, count summary).
Additional Context
Direct replacement for plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/collect.mjs::parseProvidesEntries and ::parseHookDeclarations in github/spec-kit-copilot. Depends on the companion "artifact/optional/handoffs" and "stable id / lookupId" issues; benefits from the companion "structured source provenance" issue.
Problem Statement
Today
specify preset info <id>andspecify extension info <id>emit only text.speckit-wizard-canvasreads the rawpreset.yml/extension.ymlfiles withjs-yamland re-normalizes the shape itself incomposition/collect.mjs::parseProvidesEntriesand::parseHookDeclarations— including strategy inference from shorthand keys (replacesvs.wrapsvs.prependsvs.appends), hook-phase normalization (phasevs.trigger,commandvs.targetCommand), and script-runtime inference by globbingscripts/{bash,powershell,python}/*.{sh,ps1,py}. All of this is server-side data being reconstructed on the client.Downstream consumers need the structured shape returned by the CLI itself so they can stop reconstructing it.
How this differs from the companion
preset list --json/extension list --jsonissue. Thelistvariant operates on the collection of installed packs and returns a JSON array, one row per pack, whereprovidesis just integer counts ({ commands: 4, templates: 2, scripts: 1, hooks: 3 }) — a summary/catalog view suitable for "here's every preset the project has".infooperates on one pack, addressed by id, and returns a single JSON object whereprovidesis fully expanded — every command, template, script, and hook enumerated with its full per-contribution schema (id,name,description,artifact,optional,handoffs,strategy,sourcePath,runtimes, etc.). A wizard rendering "here's whatspeckit.gitcontributes to my project" needs theinfodetail;list's counts are not enough. Both surfaces are required; neither is a subset of the other.Proposed Solution
Add
--jsonto bothinfocommands. Output shape:{ "id": "…", "name": "…", "description": "…", "version": "…", "author": "…", "priority": 100, "enabled": true, "source": { "…": "…" }, "commands": [ { "id": "…", "name": "speckit.plan", "description": "…", "artifact": "specs/{feature}/plan.md", "optional": false, "handoffs": [ { "to": "speckit.tasks", "when": "…", "message": "…" } ], "strategy": "wrap", "source": { "layer": "preset", "presetId": "…" }, "sourcePath": "commands/speckit.plan.md" } ], "templates": [ { "id": "…", "name": "…", "description": "…", "strategy": "replace", "source": { "…": "…" }, "sourcePath": "templates/…" } ], "scripts": [ { "id": "…", "name": "…", "description": "…", "strategy": "replace", "source": { "…": "…" }, "sourcePath": "scripts/bash/…", "runtimes": ["bash", "powershell", "python"] } ], "hooks": [ { "id": "…", "name": "…", "description": "…", "trigger": "before_speckit.plan", "targetCommand": "speckit.git.checklist", "sourcePath": "commands/speckit.git.checklist.md", "optional": false, "priority": 10 } ] }Notes:
hooks.commands/templates/scriptsentries omitstrategy(they are alwaysreplace, per the replace-only rule enforced atextensions/__init__.py:622-626).idvalues use the stable-id scheme from the companion "stable id / lookupId" issue; theidon a per-contribution entry is what the companionspecify artifact info --jsonstack'slookupIdpoints at.artifact,optional,handoffson commands come from the companion "artifact/optional/handoffs" issue.runtimeson scripts comes directly from the manifest (extensions post-[Feature]: Allow extensions to declare templates and scripts in their manifest #4010; add the same field to preset script entries for parity).replaces/wraps/prepends/appends→strategy: "replace"|"wrap"|"prepend"|"append") is done server-side.Alternatives Considered
specify artifact info --jsoncommand. Rejected —artifact infois per-artifact (walks one composition stack across all installed packs);info --jsonis per-source (walks one preset/extension across all its contributions). Both are needed and neither is a subset of the other.preset list --json/extension list --json. Rejected — that surface is per-collection with summary counts;infois per-pack with full expansion. Different shape, different call pattern, different use cases (see Problem Statement).js-yaml.composition/collect.mjsand drift over time.Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
Not applicable
Use Cases
speckit-wizard-canvasdeletescomposition/collect.mjs::parseProvidesEntriesand::parseHookDeclarations, replacing them withJSON.parse(execFileSync("specify", ["preset", "info", "<id>", "--json"]))/specify extension info <id> --json. This is the change that removes thejs-yamldependency.commands[]entry (description,artifact,handoffs).extension info --json'shooks[]to warn when two extensions register the sametriggerat the samepriority.Acceptance Criteria
specify preset info <id> --jsonandspecify extension info <id> --jsonemit a single JSON object with top-level fields matching the companionlist --jsonissue (id,name,description,version,author,priority,enabled,source) plus fully-expandedcommands,templates,scriptsarrays (andhooksfor extensions) — not integer counts.artifact,optional,handoffs(from the companion command-fields issue).runtimesfor both presets and extensions.strategy; extension entries omit it (or set to"replace"for informational purposes).replaces/wraps/prepends/appends) are normalized server-side intostrategy.trigger,targetCommand,sourcePath,optional,priority—phase/commandshorthand normalized.idvalues follow the stable-id scheme and are stable across reinstalls, matching thelookupIdvalues emitted byspecify artifact info --json.idcross-reference withspecify artifact info --jsonoutput.preset info/extension infosections in the CLI reference show the--jsonshape and normalization rules, plus a note contrastinginfo --json(per-pack, full expansion) withlist --json(per-collection, count summary).Additional Context
Direct replacement for
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/collect.mjs::parseProvidesEntriesand::parseHookDeclarationsingithub/spec-kit-copilot. Depends on the companion "artifact/optional/handoffs" and "stable id / lookupId" issues; benefits from the companion "structured source provenance" issue.