Move wizard composition to specify artifact CLI (blocked by spec-kit#4305) - #18
nicolehaugen wants to merge 46 commits into
Conversation
Complements artifact-cli.test.mjs (fixture round-trip) with three real-shape guards: * Live-CLI test invokes real `specify artifact list/info` on a scaffolded workspace and asserts wizard-contract fields (id/kind/stack, layer vocabulary, exactly-one-active). Skips when `specify` isn't on PATH. * Fixture-drift tests replay a committed snapshot of real CLI output and assert the field set the shape mapper reads is present. Guards against silent CLI shape changes without needing the binary in CI. * fixtures/README.md documents regeneration. Full suite: 211/211 pass.
On a warm cache the server's `bootAsync` reaches `phase: ready` before
the browser's first paint. The old flow relied on JS to (a) populate the
overlay content and (b) hide `main.app-body`, then almost immediately
flipped the overlay to `is-hidden` in the same microtask cycle. The
browser composited populate + hide into one frame and the user saw a
blank body flip straight to the loaded app with no boot indicator.
Three-part fix so the overlay is guaranteed to paint:
* Static markup in `index.html` — pre-render the overlay panel with
title + subtitle so it is visible from the very first paint, before
any module fetch/parse.
* CSS-level `main.app-body { visibility: hidden }` — no longer
depends on JS running to keep the app body hidden underneath.
* JS-side minimum visible time (`MIN_OVERLAY_MS = 450`) — even when
the state fetch resolves in a single frame, the hide is deferred via
`setTimeout` so the overlay stays up long enough to register.
Boot's `hydrateCatalogs` used to walk preset → extension → bundle serially, and each hydrator walked its 2–3 source URLs serially inside `hydrateFromCatalogSources`. That's ~8 GitHub GETs strictly serial on a cold cache, plus 3 sequential `specify <kind> list` shell-outs, for what is entirely disjoint state. Two changes: * Run `hydratePresetsForSources` / `hydrateExtensionsForSources` / `hydrateBundlesForSources` via `Promise.all` — they touch independent cache slices. * Inside `hydrateFromCatalogSources`, `Promise.all` the per-source `fetchCatalogJson` calls before folding into the items array. Order of items is preserved because we still iterate the resolved array in source order. All 211 tests pass.
This reverts commit bb5edf2.
The catalog boot step called specify artifact info once per artifact via �xecFileSync in a serial loop. With a real workspace stack (~70 artifacts across 4 presets + 1 extension), that's ~70 shell-outs, each one blocking the Node event loop for its full duration. Impact: /api/state and SSE could not be answered during boot, so the UI sat on 'Loading catalogs' for the full 108s wall time of the loop, even though the HTTP server was up. From the user's perspective the wizard 'hung'. Fix: - Swap the default runner to a promisified execFile so each shell-out yields the event loop instead of hard-blocking it. - Fan the info-per-id calls out with Promise.all — safe now that spawn is non-blocking. - Await the runner return so injected sync test runners (which return a Buffer/string) still work unchanged. Measured on the current workspace (68 artifacts): before: 108s serial sync, HTTP frozen throughout after: 13s parallel async, HTTP responsive throughout (~8x faster). All 211 tests still pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2d44fd06-6323-4654-8a80-b756561ef669
The catalog boot step was fragile in three separate ways beyond the
sync exec loop already fixed in the artifact-info fan-out:
1. fetchCatalogJson had NO timeout. A stalled socket (slow DNS, TCP
loss, CDN outage) would block the fetch forever and freeze boot on
'Loading catalogs' with no recovery path. Added AbortSignal.timeout
(15s per fetch).
2. specifyRun had NO timeout either. A wedged CLI (uv resolver stuck,
PATH resolution hang) had the same failure mode. Added a 20s kill
timer; on expiry we resolve with the partial stdout (callers already
tolerate empty output).
3. hydrateCatalogs awaited the three groups (presets, extensions,
bundles) serially, and hydrateFromCatalogSources awaited each source
inside a group serially. Both are independent I/O — swapped for
Promise.all at each level so total time is bounded by the slowest
single call, not the sum.
Measured on the current workspace (7 catalogs, 68 artifacts):
before: 108s serial sync, HTTP frozen throughout
after: ~14s parallel async, HTTP responsive throughout (~8x faster,
and now failure-bounded instead of unbounded)
All 211 tests still pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2d44fd06-6323-4654-8a80-b756561ef669
Boot was making 68 sequential `specify artifact info` shell-outs per artifact to reconstruct composition stacks — the flow that regressed catalog-loading to 27-60s on Windows. spec-kit PR #4305 makes `artifact list --json` return the full per-row stack, so the wizard needs exactly ONE shell-out to build composition.
Wizard-side changes:
- `buildCompositionFromCli` now consumes a single `list --json` call and feeds rows directly through `shapeArtifact`. Presets/extensions summaries are folded from artifact stacks (accepted edge case: a preset that contributes zero currently-active artifacts won't appear — living with it until upstream ships `preset list --json`).
- Removed `specifyArtifactInfo` — dead post-refactor. Don't reintroduce a per-artifact fan-out on the boot critical path.
- Deleted `project-scanner.mjs::scanComposition` and its `.specify/{presets,extensions}.json` reads. Neither file is written by any CLI version; the code was dead. Removed the now-unused `readBoundedJson` import.
- Governing principle: CLI is the source of truth for composition. No direct fs reads of `.registry`/`.yml` from the wizard, ever.
Test-side changes:
- Unit fixtures flattened: list rows now embed `stack` (no separate `info` map). `fakeRunner` simplified to only handle `list`.
- Fixture-drift test rewritten to require `stack` on list rows. Skips gracefully when the on-disk snapshot is pre-#4305 (regen once upstream ships).
- Live-CLI test skips when the installed CLI's `list --json` doesn't yet emit `stack` — same rationale.
- Deleted obsolete `live-cli-info.json` fixture; updated README.
- Deleted `scanWorkspace drops malformed composition entries` test — it exercised the deleted `scanComposition` path.
208 tests: 207 pass, 1 skip (drift, until fixture regen).
Real `specify artifact list --json` output now carries per-row `stack` (spec-kit#4305 has landed). Fixture-drift and live-CLI tests are now actively guarding (208/208 pass, 0 skips) instead of skipping under the pre-#4305 detector.
Boot overlay previously bundled two unrelated phases into one 'catalog' step: remote catalog JSON fetches (~150ms parallel) AND the composition CLI build (specify artifact list --json, ~2s cold). When boot felt slow, you couldn't tell which side was blocked. Split them so each shows independently in the overlay: - `catalog` (Loading catalogs) \u2014 hydratePresets/Extensions/Bundles, remote HTTPS + `specify <group> list` per group, all parallel. - `composition` (Building composition) \u2014 single `specify artifact list --json` call + shape mapping. Now a hang on either side is visible at a glance without log scraping.
Comments now describe current behavior only. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2d44fd06-6323-4654-8a80-b756561ef669
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2d44fd06-6323-4654-8a80-b756561ef669
- project-scanner.mjs: rewrite composition-block comment to describe current behavior only; drop 'used to look at' language and the fabricated AGENTS.md citation. - artifact-cli.mjs: drop the phantom 'AGENTS.md: CLI is the source of truth for composition.' line from the doc header. - pipeline-fast-path.mjs: rename 'LLM Stage 2' → 'LLM path' (there was no Stage 1) and 'Fast path' → 'Deterministic path' to match the actual code. - ui/index.html, ui/boot.js: drop '- Dev' suffix from the wizard title in all four spots to prepare for check-in. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2d44fd06-6323-4654-8a80-b756561ef669
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Moves wizard composition from filesystem inspection to the forthcoming specify artifact list --json API. This remains blocked by spec-kit#4305 and its CLI release.
Changes:
- Adds CLI-backed composition mapping, hook enrichment, and pipeline fast-path logic.
- Separates catalog/composition boot phases and parallelizes bounded catalog hydration.
- Updates boot UX, dependencies, and unit tests while removing the legacy assembler.
Show a summary per file
| File | Description |
|---|---|
ui/styles/boot.css |
Hides app content during boot. |
ui/index.html |
Adds initial boot markup. |
ui/boot.js |
Adds composition progress and minimum display time. |
test/state-and-scanner.test.mjs |
Removes obsolete scanner test. |
test/composition.test.mjs |
Removes legacy composition tests. |
test/boot-progress.test.mjs |
Covers the new boot step. |
test/artifact-cli.test.mjs |
Tests CLI composition mapping. |
project-scanner.mjs |
Removes filesystem composition scanning. |
package.json |
Updates js-yaml. |
package-lock.json |
Locks updated dependency. |
extension.mjs |
Splits catalog and composition boot work. |
composition/pipeline-fast-path.mjs |
Adds deterministic pipeline selection. |
composition/hooks.mjs |
Extracts hook metadata. |
composition/collect.mjs |
Removes legacy filesystem collector. |
composition/assembler.mjs |
Removes legacy assembler. |
composition/artifact-cli.mjs |
Adds CLI-backed composition source. |
catalog/sources.mjs |
Adds fetch timeouts. |
catalog/shared.mjs |
Parallelizes hydration and bounds CLI calls. |
canvas-runtime/composition-apply.mjs |
Integrates CLI composition and fast path. |
canvas-runtime/boot-progress.mjs |
Registers composition boot progress. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Files not reviewed (1)
- plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json: Generated file
- Files reviewed: 19/20 changed files
- Comments generated: 4
- Review effort level: Balanced
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json: Generated file
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:105
projectis a valid CLI layer, but the wizard renderers only recognizecore,preset, andextension. Preserving it here causes an active project override to be shown as Core/unchanged (for example,artifactPillOriginfalls through tocoreand contributor rows omit it). Add project-origin handling across the composition UI before accepting this layer.
return {
// CLI `null` layer = built-in; wizard code expects "core".
layer: layer.layer == null ? "core" : layer.layer,
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/composition-apply.mjs:302
dispatchKindPromptstill checks!fast.stage2Needed, so this renamed return field is never consumed. On every successful refreshstage2Neededisundefined, making the caller return early even whenpipelineFastPathis false; novel commands and stack directives therefore never reachinferPipeline. Update that caller to branch onfast.pipelineFastPathas part of this rename.
return { ok: true, reason, pipelineFastPath: fastPath.canSynthesize };
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:108
- The upstream CLI contract sets
presetId/presetNameto null for extension layers and identifies the extension throughsourceId. Passing those fields through unchanged breaks this file's own extension folds and ownership checks (accumulateProvidesCounts,activeExtensionIds, and hook-command suppression), all of which key extensions bypresetId; uncatalogued extensions disappear and hook commands can be duplicated. Add the wizard compatibility alias when normalizing extension rows.
return {
// CLI `null` layer = built-in; wizard code expects "core".
layer: layer.layer == null ? "core" : layer.layer,
presetId: layer.presetId ?? null,
presetName: layer.presetName ?? null,
sourceId: layer.sourceId ?? null,
- Files reviewed: 19/20 changed files
- Comments generated: 1
- Review effort level: Balanced
Co-authored-by: nicolehaugen <10600161+nicolehaugen@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json: Generated file
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:432
- The new CLI-backed path's hook enrichment is untested: the replacement tests cover core/preset mapping and pipeline decisions, while the deleted suite contained the only assertions for inline hook attribution, standalone hook artifacts, registration flags, and hook-command suppression. Add a temporary extension manifest plus
extensions.ymlfixture and verify this output throughbuildCompositionFromCli.
const { extensionHookInfo, hooksMap } = await collectHookMetadata(
workspaceRoot,
activeExtensionIds,
);
const artifacts = applyHookAttributions(artifactsRaw, extensionHookInfo, hooksMap);
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/composition-apply.mjs:302
dispatchKindPromptstill checks!fast.stage2Neededatcanvas-runtime/dispatch.mjs:100. Because this result now omits that property, the condition is true for every successful build, includingpipelineFastPath: false, so Refresh never falls through toinferPipelinefor novel commands or stack directives. Update that caller to branch onfast.pipelineFastPathand remove its stale Stage 2 naming.
return { ok: true, reason, pipelineFastPath: fastPath.canSynthesize };
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/extension.mjs:219
- A composition failure calls
tracker.fail, but this unconditionalready()immediately overwritesboot.phasewith"ready".ui/boot.js:112then hides the overlay, and generic composition failures are not rendered throughdepsError, so an unsupported CLI produces empty composition without the error the PR description promises to surface. Preserve the failed phase or surface a persistent in-app error before marking boot ready.
tracker.ready();
- Files reviewed: 19/20 changed files
- Comments generated: 0 new
- Review effort level: Balanced
The composition refresh flow still used the retired stage2Needed contract, incorrectly bypassing LLM pipeline inference. This aligns the runtime on pipelineFastPath while retaining LLM inference as the non-fast fallback.
Fast-path contract
Rename the deterministic decision helper to computePipelineFastPath.
Return pipelineFastPath: true only when inferredPipeline can be synthesized.
Refresh dispatch
Bypass LLM inference only for a successful deterministic pipeline:
if (fast?.ok && fast.pipelineFastPath) {
return { kind, fastComposition: true };
}
Fall through to LLM inference for novel commands, stack directives, or missing canonical anchors.
Terminology and coverage
Replace obsolete Stage 1/2 naming with “pipeline fast path” and “LLM inference.”
Update tests for deterministic synthesis and LLM fallback behavior.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Project override layers are misrepresented as Core, and runtime remains blocked on the unreleased upstream CLI contract.
Review tier: Balanced
Findings: 1
Pre-existing issues (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs — Providers absent from the hardcoded catalog sources are appended in counts insertion order, which… View comment |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs — sourceId is not a safe extension-directory name. The upstream artifact contract derives it from… View resolved comment |
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:108
- The upstream contract also emits
layer: "project", so passing non-null layers through introduces project overrides into the UI. Downstream origin logic only recognizes preset/extension and otherwise falls back to Core (ui/composition.js:120-129), and the layer label map has no project entry, so an active project override is presented as Core/default. Add explicit project-layer labeling/origin handling and a contract-shaped project override test.
Document that supported providers come from the wizard catalogs, provider summaries preserve payload order, and applied precedence belongs to each artifact stack. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
CLI rows can lose valid templates and extension display names, while the upstream dependency remains unmerged.
Review tier: Balanced
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs — Providers absent from the hardcoded catalog sources are appended in counts insertion order, which… View resolved comment |
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/composition-apply.mjs:304
- Switching to authoritative CLI rows exposes a legacy normalization bug:
buildCompositionFromClican return both a command and a template with the same name, butapplyCompositionstill removes the template innormalizeHookArtifactsInComposition. The upstream artifact contract explicitly preserves both kind-qualified rows, so mixed-kind overrides lose their template from cached composition. Remove that old command/template de-duplication and retain both artifacts.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:447 - CLI extension layers carry
sourceIdbut no display name. AlthoughextensionHookInfonow contains the manifest name, it is never attached to the original stack layers, sorenderStackLayerandlayerOwnerNamefall back to an ID such asqualityinstead ofQuality Extension. Enrich those layers before applying hook attribution.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/hooks.mjs:5 - The PR description says composition moves off direct filesystem inspection and that one artifact-list call provides everything needed for hooks, but this module still parses every extension manifest plus
.specify/extensions.yml. Either move hook metadata into the CLI contract or narrow the PR description to command/template/script composition and disclose this remaining filesystem dependency.
Remove the legacy command/template de-duplication now that the CLI emits authoritative kind-qualified rows. Document temporary hook enrichment and intentional extension ID labels. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Valid list-form hooks are dropped and omitted optional flags are incorrectly treated as required.
Review tier: Balanced
Findings: None
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/hooks.mjs:138
- Extension manifests allow an event to contain a list of hook mappings, but spreading an array here creates numeric properties and no
command; the filter then drops every hook for that event. Flatten array-valued event configurations so each declared command receives its own attribution.
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/hooks.mjs:146 - The extension manifest contract defaults an omitted
optionalfield totrue. Coercing it with!!instead marks valid hooks that omit the field as required, so the Composition UI incorrectly says they run unconditionally.
Clarify that manifest parsing preserves the wizard's existing hook behavior until native CLI hook artifacts replace the compatibility bridge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Its required upstream artifact CLI remains unmerged and unreleased, and stale references to deleted modules remain.
Review tier: Balanced
Findings: None
Previously missed findings (1)
In code that hasn't changed since last review
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/composition-apply.mjs:14
- Removing
assembler.mjsandcollect.mjsleaves the architecture documentation pointing at files and APIs that no longer exist. For example,README.md:280,env/deps-check.mjs:16,prompts/composition.mjs:42-52,canvas-runtime/wizard-phases.mjs:151-153, and the comment above this import still describe the assembler/collector andcomputeStage2Necessity. Update those references toartifact-cli.mjs,hooks.mjs, andcomputePipelineFastPathso maintainers are not directed to deleted code.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
PATH precedence and project-override links remain incorrect, while the required upstream CLI dependency is still unlanded.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/env/resolve-path.mjs — Preserve the caller's PATH precedence |
Previously missed findings (1)
In code that hasn't changed since last review
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:120
- The upstream artifact contract intentionally reports
sourcePath: nullfor project-override layers. Passing that through is correct, butartifactSourcePath()currently treats every null path as a core fallback, so an active project override renders a clickable core/materialized path and opens the wrong file. Restrict the conventional fallback to core/no-layer artifacts (or add a real project override path) so project-owned rows without a concrete path remain unlinked.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
Only prepend fallback directories that are absent so user-selected tool paths retain their original ordering. Add POSIX and Windows regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
PATH augmentation can select a stale fallback CLI over the caller-selected executable, and the required upstream CLI remains unreleased.
Review tier: Balanced
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/env/resolve-path.mjs — Preserve the caller's PATH precedence View resolved comment |
Previously missed findings (1)
In code that hasn't changed since last review
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/env/resolve-path.mjs:104
- Fallback directories should only make missing executables discoverable. Removing matching entries and reinserting them first changes an explicit PATH such as
/venv/bin:$HOME/.local/binso an older user-levelspecifyoverrides the selected virtualenv binary; the new artifact command can then fail even though a compatible CLI was first on PATH. Prepend only directories that are absent and otherwise preserve the caller's ordering.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Empty scanner composition permanently gates taskstoissues, and the required upstream CLI dependency remains unavailable.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner.mjs — Preserve provider data for taskstoissues gating |
Remove the obsolete provider-specific gate so taskstoissues follows the same setup and optional-phase behavior as clarify, analyze, and checklist. Refresh the entry-point composition documentation for the artifact CLI architecture. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf041796-b05b-4307-bef6-e6e54a544beb
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Runtime correctness depends on the still-open upstream PR and an unreleased compatible specify-cli version.
Review tier: Balanced
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner.mjs — Preserve provider data for taskstoissues gating View resolved comment |


Warning
Blocked by github/spec-kit#4305.
Do not merge until #4305 lands and a
specify-clirelease containingspecify artifact list --jsonis published to PyPI. Once that ships, bumpthe version floor note in
skills/speckit-cli-setup/SKILL.mdand un-draft.Replaces #17 — same commits, but branched directly in
github/spec-kit-copilotoffmaininstead of coming from a fork.Summary
Move the wizard's Composition tab off direct filesystem inspection for
commands, templates, and scripts and onto a single
specify artifact list --jsoncall. The CLI returns one row per artifactcarrying the full composition
stack: [...]. Until the CLI exposes nativehook metadata, hook attribution remains a temporary wizard-owned enrichment
from installed extension manifests and
.specify/extensions.yml.Depends on
specify artifactintrospection spec-kit#4305 — addsspecify artifact(list/info) with deterministiccontribution IDs and per-row stack. This PR does not work at runtime
without it.
What changes
composition/artifact-cli.mjs(new source of command/template/scriptcomposition stacks): calls
specify artifact list --jsononce at boot, mapsrows into the wizard's artifact / preset / extension shapes, temporarily
enriches hook attribution from installed extension metadata, and produces
the composition summary.
project-scanner.mjs: no longer reads.specify/{presets,extensions}.jsonfor composition. Starts empty and lets
overlayCachedCompositionapply theCLI-derived data after the scan.
composition/pipeline-fast-path.mjs: decides between the deterministicpipeline (canonical spine +
replace-only overrides) and the LLM pipeline(
prompts/composition.mjs::inferPipeline) needed when an extension adds anon-canonical command or uses
wrap/prepend/append.tracker steps so the user sees which phase is running.
test/artifact-cli.integration.test.mjs)and its fixture — that layer belongs in the spec-kit repo alongside the CLI
it exercises. Unit tests here inject a fake runner so CI is green regardless
of which
specify-cliversion is installed.Verification
239/239 unit tests pass (unit tests inject a fake
specifyrunner).Playwright DOM-diff matrix — captured the wizard side panel from a main-branch
plugin variant vs. this branch across five scenarios:
copilot-sub-agentspresetpirate-full-presetagent-contextextension onlyEach scenario snapshots five surfaces (Composition → Commands / Templates /
Scripts / Hooks, plus top-level Phases). 25/25 pairs are byte-identical
after normalizing internal
[ref=...]handles. No user-visible regressions.Runtime behavior when the CLI is too old
Right now the wizard will surface an error and empty composition if the
installed
specify-clilacksartifact list --json. That's acceptable whilethis PR is draft; when we un-draft, the floor version bump in
speckit-cli-setupguarantees users get a compatible CLI.