Problem Statement
Consumers of SpecKit sometimes need "the baseline set of commands, templates, and scripts baked into SpecKit itself — what a project has with zero presets and zero extensions installed." Command names are already enumerable via CORE_COMMAND_NAMES at extensions/__init__.py:119 — and, importantly, this is not a static list. It's populated by _load_core_command_names() at extensions/__init__.py:78-108, which already dynamically scans core_pack/commands (wheel-time) or templates/commands (source checkout) at import time, using the canonical _locate_core_pack() / _repo_root() asset resolvers. Current commands in the baseline (from that scan): analyze, checklist, clarify, constitution, converge, implement, plan, specify, tasks, taskstoissues.
There is no equivalent scan for the shipped templates or scripts, so consumers who want the baseline have to either:
- Enumerate the filesystem directly (fragile — internal directory layout isn't a stable contract), or
- Maintain their own hardcoded list.
speckit-wizard-canvas does exactly this — composition/collect.mjs::loadCoreInventory imports pipeline/canonical.mjs, which is a hand-maintained copy of what SpecKit ships. Every SpecKit release risks drift.
A companion issue (specify artifact info --json, filed separately) already surfaces core as layer: "core" rows inside each artifact's stack, but that's a per-artifact view. For "what does a bare-bones SpecKit look like?" a consumer needs an inventory view too.
Proposed Solution
Add a specify core inventory --json command that returns the full baseline inventory:
{
"commands": [
{ "id": "core:_:command:speckit.plan",
"name": "speckit.plan",
"description": "…",
"sourcePath": "<internal-path-relative-to-package>",
"artifact": "specs/{feature}/plan.md",
"optional": false,
"handoffs": [ "…" ] }
],
"templates": [
{ "id": "core:_:template:constitution",
"name": "constitution",
"description": "…",
"sourcePath": "…" }
],
"scripts": [
{ "id": "core:_:script:setup",
"name": "setup",
"description": "…",
"sourcePath": "…",
"runtimes": ["bash", "powershell", "python"] }
]
}
The entry shape mirrors the per-contribution schema returned by the companion preset info --json / extension info --json issue, minus strategy (core is always the base layer). Under the hood the command:
- Reuses
CORE_COMMAND_NAMES for the command list.
- Extends the existing file-scan pattern used by
_load_core_command_names() — same _locate_core_pack() + _repo_root() canonical asset resolvers — to scan core_pack/templates and core_pack/scripts (with the equivalent source-checkout fallbacks under templates/ and scripts/). No new asset-discovery paths.
- Runs a small frontmatter parser over the enumerated command / template markdown files (and reads script headers), extracting
name / description / artifact / optional / handoffs / (script) runtimes, and emits the JSON.
- Uses the same stable-
id scheme (core:_:kind:name) as the companion specify artifact issue so consumers can join this output to that command's layer: "core" stack rows via lookupId.
Alternatives Considered
- Fold entirely into the companion
specify artifact command's layer: "core" rows. Rejected — that command gives per-artifact stacks; this one gives the flat inventory needed for "here's what you get out of the box" listings. Both views point at the same data with the same id.
- Return only names (no
description / sourcePath / handoffs). Rejected — the wizard's pipeline/canonical.mjs needs the metadata, which is why it exists in the first place.
- Ship the inventory as a static JSON file inside the package. Rejected — still requires the wizard to know the package path; a CLI command is more stable.
Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
Not applicable
Use Cases
speckit-wizard-canvas deletes pipeline/canonical.mjs and replaces composition/collect.mjs::loadCoreInventory with JSON.parse(execFileSync("specify", ["core", "inventory", "--json"])).
- A "SpecKit tour" UI shows first-time users exactly which commands / templates / scripts they get with zero installs.
- A CI check asserts "no preset is shadowing a core artifact I depend on" by cross-referencing this inventory with the companion
specify artifact info --json stacks and the companion preset list --json installed set.
Acceptance Criteria
Additional Context
Direct replacement for plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/pipeline/canonical.mjs and composition/collect.mjs::loadCoreInventory in github/spec-kit-copilot. Depends on the companion "stable id / lookupId" issue; benefits from the companion "artifact/optional/handoffs" issue.
Problem Statement
Consumers of SpecKit sometimes need "the baseline set of commands, templates, and scripts baked into SpecKit itself — what a project has with zero presets and zero extensions installed." Command names are already enumerable via
CORE_COMMAND_NAMESatextensions/__init__.py:119— and, importantly, this is not a static list. It's populated by_load_core_command_names()atextensions/__init__.py:78-108, which already dynamically scanscore_pack/commands(wheel-time) ortemplates/commands(source checkout) at import time, using the canonical_locate_core_pack()/_repo_root()asset resolvers. Current commands in the baseline (from that scan):analyze,checklist,clarify,constitution,converge,implement,plan,specify,tasks,taskstoissues.There is no equivalent scan for the shipped templates or scripts, so consumers who want the baseline have to either:
speckit-wizard-canvasdoes exactly this —composition/collect.mjs::loadCoreInventoryimportspipeline/canonical.mjs, which is a hand-maintained copy of what SpecKit ships. Every SpecKit release risks drift.A companion issue (
specify artifact info --json, filed separately) already surfaces core aslayer: "core"rows inside each artifact's stack, but that's a per-artifact view. For "what does a bare-bones SpecKit look like?" a consumer needs an inventory view too.Proposed Solution
Add a
specify core inventory --jsoncommand that returns the full baseline inventory:{ "commands": [ { "id": "core:_:command:speckit.plan", "name": "speckit.plan", "description": "…", "sourcePath": "<internal-path-relative-to-package>", "artifact": "specs/{feature}/plan.md", "optional": false, "handoffs": [ "…" ] } ], "templates": [ { "id": "core:_:template:constitution", "name": "constitution", "description": "…", "sourcePath": "…" } ], "scripts": [ { "id": "core:_:script:setup", "name": "setup", "description": "…", "sourcePath": "…", "runtimes": ["bash", "powershell", "python"] } ] }The entry shape mirrors the per-contribution schema returned by the companion
preset info --json/extension info --jsonissue, minusstrategy(core is always the base layer). Under the hood the command:CORE_COMMAND_NAMESfor the command list._load_core_command_names()— same_locate_core_pack()+_repo_root()canonical asset resolvers — to scancore_pack/templatesandcore_pack/scripts(with the equivalent source-checkout fallbacks undertemplates/andscripts/). No new asset-discovery paths.name/description/artifact/optional/handoffs/ (script)runtimes, and emits the JSON.idscheme (core:_:kind:name) as the companionspecify artifactissue so consumers can join this output to that command'slayer: "core"stack rows vialookupId.Alternatives Considered
specify artifactcommand'slayer: "core"rows. Rejected — that command gives per-artifact stacks; this one gives the flat inventory needed for "here's what you get out of the box" listings. Both views point at the same data with the sameid.description/sourcePath/handoffs). Rejected — the wizard'spipeline/canonical.mjsneeds the metadata, which is why it exists in the first place.Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
Not applicable
Use Cases
speckit-wizard-canvasdeletespipeline/canonical.mjsand replacescomposition/collect.mjs::loadCoreInventorywithJSON.parse(execFileSync("specify", ["core", "inventory", "--json"])).specify artifact info --jsonstacks and the companionpreset list --jsoninstalled set.Acceptance Criteria
specify core inventory --jsonemits{commands, templates, scripts}arrays; each entry hasid,name,description,sourcePath, and the kind-specific fields (artifact/optional/handoffson commands,runtimeson scripts).idvalues follow thecore:_:kind:namescheme (matching the stable-id scheme used by the companionspecify preset info --json/specify extension info --json/specify artifact info --jsoncommands) so consumers can join this inventory to those commands' output vialookupId.CORE_COMMAND_NAMES; template and script lists come from a scan that reuses the existing_locate_core_pack()/_repo_root()canonical asset resolvers — notPath(__file__)arithmetic. (Path-arithmetic discovery has silently broken in this codebase once already; see refactor: move extension command handlers to extensions/_commands.py (PR-7/8) #3014 / Bug: extensions core-command discovery is dead — off-by-one paths after the #3014 module move #3274.) No filesystem paths outside the package leak into the output.CORE_COMMAND_NAMESfor commands; count parity with the shipped file set for templates/scripts;lookupIdcross-reference with the companionspecify artifact info --jsonoutput on a bare-bones project.specify core inventorysection in the CLI reference plus a note that it complements the companionspecify artifactcommand.Additional Context
Direct replacement for
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/pipeline/canonical.mjsandcomposition/collect.mjs::loadCoreInventoryingithub/spec-kit-copilot. Depends on the companion "stable id / lookupId" issue; benefits from the companion "artifact/optional/handoffs" issue.