Skip to content

Move wizard composition to specify artifact CLI (blocked by spec-kit#4305) - #18

Draft
nicolehaugen wants to merge 18 commits into
mainfrom
nicolehaugen-wizard-composition-artifact-cli
Draft

Move wizard composition to specify artifact CLI (blocked by spec-kit#4305)#18
nicolehaugen wants to merge 18 commits into
mainfrom
nicolehaugen-wizard-composition-artifact-cli

Conversation

@nicolehaugen

Copy link
Copy Markdown
Contributor

Warning

Blocked by github/spec-kit#4305.
Do not merge until #4305 lands and a specify-cli release containing
specify artifact list --json is published to PyPI. Once that ships, bump
the version floor note in skills/speckit-cli-setup/SKILL.md and un-draft.

Replaces #17 — same commits, but branched directly in github/spec-kit-copilot off main instead of coming from a fork.

Summary

Move the wizard's Composition tab off direct filesystem inspection and onto a
single specify artifact list --json call. The CLI returns one row per
artifact carrying the full composition stack: [...], which is everything the
wizard needs to render commands, templates, scripts, and hooks.

Depends on

What changes

  • composition/artifact-cli.mjs (new sole source of composition): calls
    specify artifact list --json once at boot, maps rows into the wizard's
    artifact / preset / extension shapes, and produces the composition summary.
  • project-scanner.mjs: no longer reads .specify/{presets,extensions}.json
    for composition. Starts empty and lets overlayCachedComposition apply the
    CLI-derived data after the scan.
  • composition/pipeline-fast-path.mjs: decides between the deterministic
    pipeline (canonical spine + replace-only overrides) and the LLM pipeline
    (prompts/composition.mjs::inferPipeline) needed when an extension adds a
    non-canonical command or uses wrap / prepend / append.
  • Boot UX: "Loading catalogs" split into distinct catalog + composition
    tracker steps so the user sees which phase is running.
  • Removed: the live-CLI integration test (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-cli version is installed.

Verification

  • 207/207 unit tests pass (unit tests inject a fake specify runner).

  • Playwright DOM-diff matrix — captured the wizard side panel from a main-branch
    plugin variant vs. this branch across five scenarios:

    1. baseline (0 presets / 0 extensions)
    2. copilot-sub-agents preset
    3. pirate-full-preset
    4. agent-context extension only
    5. all three stacked

    Each 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-cli lacks artifact list --json. That's acceptable while
this PR is draft; when we un-draft, the floor version bump in
speckit-cli-setup guarantees users get a compatible CLI.

nicolehaugen and others added 13 commits August 25, 2026 13:07
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.
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copilot AI review requested due to automatic review settings August 26, 2026 20:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • project is a valid CLI layer, but the wizard renderers only recognize core, preset, and extension. Preserving it here causes an active project override to be shown as Core/unchanged (for example, artifactPillOrigin falls through to core and 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

  • dispatchKindPrompt still checks !fast.stage2Needed, so this renamed return field is never consumed. On every successful refresh stage2Needed is undefined, making the caller return early even when pipelineFastPath is false; novel commands and stack directives therefore never reach inferPipeline. Update that caller to branch on fast.pipelineFastPath as 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/presetName to null for extension layers and identifies the extension through sourceId. 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 by presetId; 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

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/extension.mjs Outdated
Co-authored-by: nicolehaugen <10600161+nicolehaugen@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 26, 2026 20:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.yml fixture and verify this output through buildCompositionFromCli.
    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

  • dispatchKindPrompt still checks !fast.stage2Needed at canvas-runtime/dispatch.mjs:100. Because this result now omits that property, the condition is true for every successful build, including pipelineFastPath: false, so Refresh never falls through to inferPipeline for novel commands or stack directives. Update that caller to branch on fast.pipelineFastPath and 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 unconditional ready() immediately overwrites boot.phase with "ready". ui/boot.js:112 then hides the overlay, and generic composition failures are not rendered through depsError, 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>
Copilot AI review requested due to automatic review settings August 26, 2026 20:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Files not reviewed (1)
  • plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json: Generated file
Suppressed comments (5)

Previously missed (3) — in code that hasn't changed since the last review.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/composition.test.mjs:1

  • Deleting this file wholesale also removes the still-applicable regression tests for pipeline/canonical.mjs, pipeline/effective-phases.mjs, and ui/phase-runtime.js; those modules remain in use, and no equivalent tests exist elsewhere in test/. Move those suites to focused test files while deleting only the obsolete assembler/collector coverage.
    plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:318
  • This rebuilds each hook artifact from scratch even though byId contains the CLI-provided command row, and the original row is filtered out below. As a result the hook loses its CLI description and full stack provenance (including sourcePath). Seed the hook artifact from the existing command artifact, changing only its kind and binding metadata.
            const hookArtifactId = `commands/${hookCommand}`;
            let hookArtifact = hookArtifactsById.get(hookArtifactId);
            if (!hookArtifact) {
                hookArtifact = {
                    id: hookArtifactId,
                    kind: "hook",
                    description: "",
                    stack: [],
                    hookBindings: [],
                };
                hookArtifactsById.set(hookArtifactId, hookArtifact);

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/catalog/shared.mjs:61

  • Returning partial stdout on timeout is not safely malformed: a timed-out preset list/extension list can end after complete early rows, so the parsers accept that prefix and incorrectly mark every later installed item inactive. Treat a timeout as a failed query (null) rather than as a complete listing.
        const timer = setTimeout(() => {
            try { child.kill(); } catch { /* best-effort */ }
            done(stdout || null);
        }, timeoutMs);

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/composition-apply.mjs:306

  • The prior review decision was to replace the Stage 2 terminology with pipelineFastPath, but this exposes both names and dispatch.mjs still consumes the legacy inverse flag. Complete the rename atomically: return only pipelineFastPath, and have the refresh dispatcher fast-exit when that flag is true.
            pipelineFastPath: fastPath.canSynthesize,
            stage2Needed: !fastPath.canSynthesize,

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:114

  • sourcePath is part of the blocked upstream list-row stack contract, but this mapper drops it. The UI uses the active layer's sourcePath to open the actual preset/extension artifact (composition-artifacts.js:600-601 and phase-runtime.js:890-898); without it, overridden commands fall back to guessed core paths or become non-clickable. Preserve the field here and add it to the contract fixture.
        manifestPath: layer.manifestPath ?? null,
        lookupId: layer.lookupId ?? null,
    };
  • Files reviewed: 19/20 changed files
  • Comments generated: 1
  • Review effort level: Balanced

"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.3.tgz",
"integrity": "sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==",
"version": "5.3.0",
"resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/js-yaml/-/js-yaml-5.3.0.tgz",
The issue is actually addressed by this: github/spec-kit#4305 (comment) => please review to confirm if it does address the issue and report back your conclusion.  don't make any changes.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 26, 2026 20:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Files not reviewed (1)
  • plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json: Generated file
Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/artifact-cli.mjs:105

  • The upstream contract includes an active project layer for project-local overrides, but the wizard consumers only recognize core, preset, and extension: project overrides fall back to a Core pill, a Preset band, or no contributor label. Add a Project origin/label across the composition and phase renderers (or adapt this layer to an explicit supported UI representation) before passing it through.
    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/composition/hooks.mjs:118

  • The extension manifest contract allows a hook event to contain a list of mappings, but spreading an array here creates numeric properties and no command, so every hook in that supported form is filtered out. Flatten each event's value as Array.isArray(cfg) ? cfg : [cfg] before adding phase.
    if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
        hooks = Object.entries(hooks).map(([phase, cfg]) => ({
            phase,
            ...(cfg && typeof cfg === "object" ? cfg : {}),
        }));

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/artifact-cli.test.mjs:132

  • This replacement suite removes coverage for still-live pipeline/canonical.mjs, pipeline/effective-phases.mjs, and ui/phase-runtime.js; repository search finds no remaining tests for canonicalTemplateIds, effectivePipelinePhases, or resolvePipelineEntry. Only the assembler/collector cases became obsolete, so move the unaffected tests to focused suites rather than dropping them.
describe("buildCompositionFromCli", () => {

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/composition/hooks.mjs:73

  • Spec Kit extension metadata lives under the top-level extension: mapping, so reading doc.name, doc.description, and doc.version always falls back for current manifests. Hook artifacts consequently show the directory id instead of the declared display name and lose version/description metadata. Read these fields from doc.extension (with a legacy top-level fallback).
        name: doc.name ?? id,
        description: doc.description ?? "",
        version: doc.version ?? null,
        priority: typeof doc.priority === "number" ? doc.priority : null,
  • Files reviewed: 19/20 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +126 to +130
if (kind === "command") {
const activeLayer = stack.find((layer) => layer.active);
const skillName = wizardId.slice("commands/".length).replace(/\./g, "-");
if (activeLayer && skillName) {
activeLayer.sourcePath = `.github/skills/${skillName}/SKILL.md`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3ba4d51. The normalizer now preserves each layer’s authoritative CLI sourcePath, and the guessed active-command skill path has been removed. Added contract-shaped coverage for extension command, template, and script paths.

Co-authored-by: nicolehaugen <10600161+nicolehaugen@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 26, 2026 21:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Files not reviewed (1)
  • plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json: Generated file
Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/catalog/shared.mjs:61

  • On timeout, the captured stdout is necessarily incomplete but can still look like valid list output. The downstream parsers then mark only that prefix as installed, causing remaining installed presets/extensions/bundles to render as absent. Treat a timeout as a failed query instead of returning partial data.
        const timer = setTimeout(() => {
            try { child.kill(); } catch { /* best-effort */ }
            done(stdout || null);

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/artifact-cli.test.mjs:6

  • The replacement suite only covers the CLI mapper and fast path, while deleting composition.test.mjs also removes all tests for still-live canonical.mjs, effective-phases.mjs, phase-runtime.resolvePipelineEntry, and hook parsing behavior. Preserve those regression tests in focused test files rather than deleting them with the retired assembler tests.
import {
    buildCompositionFromCli,
} from "../composition/artifact-cli.mjs";
import { computePipelineFastPath } from "../composition/pipeline-fast-path.mjs";

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/composition-apply.mjs:307

  • This preserves the deprecated stage2Needed API even though this flow now exposes pipelineFastPath; dispatch.mjs consequently still uses the old Stage 2 terminology. The developer resolution on the prior thread explicitly requested replacing all Stage 2 references, so return pipelineFastPath only and update the dispatch condition to check it directly.
        return {
            ok: true,
            reason,
            pipelineFastPath: fastPath.canSynthesize,
            stage2Needed: !fastPath.canSynthesize,
        };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/package-lock.json:23

  • The lockfile now pins this public dependency to an Azure Artifacts mirror instead of the npm registry and downgrades integrity metadata from SHA-512 to SHA-1. Consumers will be forced through that mirror regardless of their configured registry, reducing portability and verification strength. Regenerate the lockfile against registry.npmjs.org.
  • Files reviewed: 19/20 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +358 to +360
const owns = artifact.stack.some(
(l) => l.layer === "extension" && l.presetId === extensionId,
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants