Skip to content

Fix Spec Kit wizard artifact and phase handling - #24

Merged
nicolehaugen merged 69 commits into
mainfrom
nicolehaugen-fix-wizard-artifacts-phases
Sep 8, 2026
Merged

Fix Spec Kit wizard artifact and phase handling#24
nicolehaugen merged 69 commits into
mainfrom
nicolehaugen-fix-wizard-artifacts-phases

Conversation

@nicolehaugen

@nicolehaugen nicolehaugen commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several Spec Kit Wizard canvas regressions around artifact detection, phase tracking, command lists, inline clarification reruns, and phase run feedback.

Bug fixes

  • Tasks artifact not viewable: narrows placeholder detection so valid task markers like [P], [ID], and [US1] are treated as task content, not unresolved template placeholders.
  • Checklist artifact links resolving to folders: resolves checklist artifacts dynamically by honoring formValues.checklistFile, then choosing the most recently modified generated checklist file when the phase has run, with the checklist folder as fallback.
  • Converge rejected by tracking/runtime: adds converge to the wizard phase registry and fallback phase order so setPhaseStatus and execution reporting accept it consistently.
  • Core add-list omitted customized canonical commands: keeps the Core command list populated from the full canonical surface when commands are not already represented by active preset/customized entries.
  • Extension phases stuck running / missing artifacts: maps non-canonical speckit.* commands to commands/<full-command-id> for status and artifact lookup.
  • Non-canonical single-segment commands got tracked as phases: gates prompt-side phase classification against the registered wizard phase registry so commands like speckit.review remain untracked.
  • Absolute extension artifact targets opened as 403s: converts absolute in-workspace extension writesTo targets back to workspace-relative artifact paths before exposing them to the viewer.
  • Failed reruns hid existing artifacts: keeps View artifact available whenever the scanner has a concrete artifact path, even if the latest rerun records error.
  • Artifact Clarify pills failed at runtime: imports the phase-runtime helpers used by the artifact viewer so inline clarification controls work from any markdown artifact.
  • Apply and Rerun no-op for queued clarifications: dispatches queued answers through /api/phase/submit, marks the phase running, updates last-submitted args only on success, and keeps queued answers visible on failed submit.
  • Run feedback depended on fragile lifecycle tracking: changes Running… to a short local dispatch acknowledgement. The chat window is the source of truth for execution progress, and scanner-confirmed files are the source of truth for artifact buttons.
  • Stale canonical callbacks could overwrite newer status: keeps lightweight runId tokens so older terminal callbacks are rejected after a newer same-phase dispatch, including tokenless terminal callbacks while a tracked run is active, without adding server-side lifecycle locks.
  • Previous execution reports could appear after failed/skipped runs: ignores prior witness reports unless the current phase status is done.
  • Dead lifecycle code remained after simplification: removes the unused session-activity tracker once run progress stopped depending on turn correlation.

Run-feedback scope

The wizard is a launcher plus artifact dashboard: users monitor the chat for live execution, while the wizard briefly acknowledges dispatch and then relies on scanner-confirmed workspace files for artifact buttons. Canonical phases keep only a lightweight status-token guard to reject stale callbacks. Broader session lifecycle tracking, duplicate-run locking, turn correlation, extension run locking, and active-run snapshot state are intentionally not part of this model.

Tests

  • Added focused scanner coverage for task marker placeholder handling, dynamic checklist artifact resolution, absolute in-workspace extension artifact path normalization, and non-canonical single-segment command classification.
  • Added phase-runtime coverage for local running acknowledgement, scanner-driven artifact visibility, extension status lookup, customized canonical Core-list rendering, and artifact visibility after failed reruns.
  • Replaced brittle source-inspection clarify coverage with behavior-level tests for queued clarification reruns and failed-submit retention.
  • Simplified run-token coverage to the useful contract: newer canonical dispatches supersede older tokens, stale canonical callbacks cannot persist terminal status, and tokenless terminal callbacks cannot bypass an active token.
  • Added focused execution-report coverage so failed terminal status does not reuse a previous successful witness report.

Validation: npm test from plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas, plus a local Playwright walkthrough of the reloaded wizard runtime.

Fix wizard phase and artifact handling across task, checklist, converge, extension, and inline clarification flows.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc
@nicolehaugen
nicolehaugen requested a review from mnriem as a code owner September 3, 2026 02:33
Copilot AI balanced review requested due to automatic review settings September 3, 2026 02:33

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.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 4 Medium severity

New issues introduced by this change (4)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​wizard-phases.mjs — The new Converge phase advertises specs/&lt;slug&gt;/tasks.md, but scanner hydration only replaces the…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — This length cutoff still misclassifies valid multi-digit task markers such as [US10] and…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — The persisted artifactPath is checked before the newly selected formValues.checklistFile. After…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.jsmarkPhaseRunning installs a five-minute run lock before submission is confirmed, but the failure…
What changed in this PR

Fixes wizard regressions in artifact detection, phase tracking, command rendering, and clarification reruns.

Changes:

  • Adds complete converge phase support.
  • Improves checklist/task artifact handling and extension run-lock tracking.
  • Adds behavior-focused regression tests.
File Description
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​state.js Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-runtime.js Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.js Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​test/​state-and-scanner.test.mjs Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​test/​server-integration.test.mjs Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​test/​composition.test.mjs Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts/​pipeline.mjs Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs Updated as part of this pull request.
plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​wizard-phases.mjs Updated as part of this pull request.
Suppressed comments (1)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-runtime.js:765

  • Making coreCandidates unconditional leaves customizedIds (lines 749–760) completely unused, while the preceding explanation still says it prevents customized canonicals from appearing in both sections. Remove that dead computation and stale comment so this new intentional duplication has one clear source of truth.
    // CORE group: the full canonical Spec Kit surface, including commands
    // customized by presets. The Core list is an explicit escape hatch for
    // adding the canonical command back to the pipeline even when the active
    // preset also contributes a customized version.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 3, 2026 15:01
Co-authored-by: Copilot <223556219+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.

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review tier: Balanced
Findings: 2 Medium severity

Pre-existing issues (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.jsmarkPhaseRunning installs a five-minute run lock before submission is confirmed, but the failure… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — This length cutoff still misclassifies valid multi-digit task markers such as [US10] and… View comment
Issues resolved since last review (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — The persisted artifactPath is checked before the newly selected formValues.checklistFile. After… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​wizard-phases.mjs — The new Converge phase advertises specs/&lt;slug&gt;/tasks.md, but scanner hydration only replaces the… View resolved comment
Suppressed comments (3)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/fs-helpers.mjs:41

  • The length cutoff still classifies valid multi-digit user-story markers such as [US10] and [US11] as placeholders. A task list containing tasks for two such stories therefore has two distinct matches and is downgraded to empty; explicitly exclude task markers (P, ID, and US\d+) rather than relying only on token length.
export const PLACEHOLDER_TOKEN_RE = /\[[A-Z][A-Z0-9_]{3,}\]/g;

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:106

  • artifactPath is checked before formValues.checklistFile, so a previously persisted checklist folder/file wins and the explicitly configured checklist is ignored. This contradicts the intended resolution order and can open the newest unrelated checklist instead of the user's selected file when both fields are present; prioritize checklistFile before falling back to artifactPath.
        const configuredSources = [
            phases.checklist?.formValues?.checklistFile,
            phases.checklist?.artifactPath,
        ];

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js:448

  • The run lock is acquired before submission, but the failure path returns without releasing it. Because postJson converts HTTP/network errors to undefined, every failed clarification submit leaves the phase marked running (and its regular controls disabled) until the five-minute safety timer expires; import clearPhaseRunning and call it in the catch path while preserving the queued answers.
        markPhaseRunning(p.commandName);
        const result = await __postJson("/api/phase/submit", { commandName: p.commandName, args });
        if (!result) throw new Error("phase submit did not return a queued response");

Copilot AI review requested due to automatic review settings September 3, 2026 15:06

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.

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review tier: Balanced
Findings: 2 Medium severity

Pre-existing issues (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.jsmarkPhaseRunning installs a five-minute run lock before submission is confirmed, but the failure… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — This length cutoff still misclassifies valid multi-digit task markers such as [US10] and… View comment
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/project-scanner/spec-phases.mjs:87

  • A persisted checklist path is accepted even when it belongs to a different spec slug. Because the scanner selects the newest slug but merges global state first, switching from old to new can leave specs/old/checklists/security.md as the first existing candidate and the viewer continues opening the old feature’s checklist. Reject candidates outside the active checklistsDir so resolution falls back to the newest file for the selected slug.
    plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts/pipeline.mjs:40
  • Converge appends remediation tasks to the existing tasks.md, whose provenance remains speckit:tasks; routing it through this generic branch instructs the agent to replace the first line with speckit:converge. That conflicts with the command’s append-only contract and mislabels the shared Tasks artifact. Give Converge a dedicated artifact instruction that preserves the existing first line.
    plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js:514
  • The artifact viewer is also called from ui/composition-artifacts.js:272-276 with only artifactPath, shortLabel, and id, so p.commandName is undefined for those markdown files. Command sources can contain literal [NEEDS CLARIFICATION: …] examples, and after these imports fix the prior runtime error they now render Clarify pills whose answers are queued under undefined; flushClarifications immediately returns false and Apply and Rerun can never work. Only enable clarification parsing for viewers with a rerunnable command, or pass an unambiguous command name from every caller.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/fs-helpers.mjs:41

  • The length cutoff still treats valid multi-digit user-story markers such as [US10] and [US11] as placeholders. Because looksLikeUnfilledTemplate declares a file unfilled after two distinct matches, a normal task list with those markers is downgraded to empty. Exclude the US<digits> marker grammar explicitly rather than relying only on token length.
export const PLACEHOLDER_TOKEN_RE = /\[[A-Z][A-Z0-9_]{3,}\]/g;

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js:448

  • When submission fails, markPhaseRunning has already installed a five-minute run lock, but this catch path preserves it. The clarification button is re-enabled, yet the phase card remains falsely running/disabled until its safety timer expires. Mark the phase running only after the server confirms that the rerun was queued (or explicitly clear the lock in the failure path).
        markPhaseRunning(p.commandName);
        const result = await __postJson("/api/phase/submit", { commandName: p.commandName, args });
        if (!result) throw new Error("phase submit did not return a queued response");

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 3, 2026 16:28
Co-authored-by: Copilot <223556219+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.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — When the phase is done but no Markdown candidate exists (for example, a configured file was not…
Issues resolved since last review (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.jsmarkPhaseRunning installs a five-minute run lock before submission is confirmed, but the failure… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — This length cutoff still misclassifies valid multi-digit task markers such as [US10] and… View resolved comment
Suppressed comments (1)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js:448

  • markPhaseRunning starts a five-minute run lock before the fallible submit, but the failure path never clears it. Since postJson converts network/HTTP errors to undefined, the new error branch leaves the phase rendered as running and disables its normal Run/Rerun controls until the safety timeout; the failure test currently masks this by clearing the lock in finally. Start the lock only after validating the queued response (or explicitly clear it in catch).
    try {
        markPhaseRunning(p.commandName);
        const result = await __postJson("/api/phase/submit", { commandName: p.commandName, args });

Copilot AI review requested due to automatic review settings September 3, 2026 16:33

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.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.jsflushClarifications has no in-flight guard even though it can be invoked both automatically after…
Pre-existing issues (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — When the phase is done but no Markdown candidate exists (for example, a configured file was not… View comment
Suppressed comments (2)

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

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts/pipeline.mjs:40

  • Routing Converge through the generic artifact branch tells the agent that the first line of the existing tasks.md must become <!-- speckit:converge v1 -->. Converge is defined to append remediation work to the task list, so this conflicts with the existing Tasks provenance header and can cause the shared artifact to be rewritten incorrectly. Special-case this prompt to preserve the existing document/header while appending remediation tasks.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:140

  • When Checklist is marked done but the configured/persisted file is missing and the directory contains no Markdown files, checklistArtifactPath returns null and this block leaves the stale path (often specs/<slug>/checklists/) untouched. That contradicts the promised folder fallback and still produces an unresolved or missing artifact target. Persist the concrete checklist directory whenever no file can be resolved.
        const artifactPath = await checklistArtifactPath(checklistsDir);
        if (artifactPath) {
            phases.checklist = {
                ...phases.checklist,
                artifactPath,
            };

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 3, 2026 16:47
Co-authored-by: Copilot <223556219+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.

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review tier: Balanced
Findings: None

Issues resolved since last review (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.jsflushClarifications has no in-flight guard even though it can be invoked both automatically after… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — When the phase is done but no Markdown candidate exists (for example, a configured file was not… View resolved comment
Suppressed comments (5)

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

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/fs-helpers.mjs:40

  • The {3,} cutoff suppresses every 1–3 character placeholder, not only task markers. For example, a template containing distinct unresolved [API] and [URL] tokens is now considered filled by looksLikeUnfilledTemplate. Preserve the original token-length grammar and explicitly exclude the valid task markers instead.
    plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts/pipeline.mjs:40
  • Putting Converge in this shared branch instructs it to make the first line of the existing task list <!-- speckit:converge v1 -->. Converge is defined as appending remediation to tasks.md, whose existing provenance header is <!-- speckit:tasks v1 -->; replacing that header contradicts the append-only behavior and can misidentify the task artifact. Give Converge an artifact instruction that preserves the existing header.
    plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js:451
  • The pending queue remains mutable while this request is in flight, but the artifact viewer does not lock all clarification controls. The automatic all-answered path leaves its Apply button enabled, allowing the same arguments to be submitted twice; the manual path leaves unanswered pills active, and answers added after args was built are then discarded by clearClarifications() on success. Add a per-command in-flight guard or disable/re-enable all clarification controls, and clear only the submitted snapshot.
    plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-runtime.js:123
  • Resolving this extension slice still does not provide a valid completion signal for reruns. Extension artifact hydration derives status from file existence and never advances lastRunAt, so a command whose artifact already exists starts at done; on the next snapshot, observePhaseProgress() sees that pre-existing terminal status and immediately clears the newly mapped lock before the rerun finishes. The test only covers empty → done with a fabricated timestamp. Track a baseline transition/file mtime or emit an explicit extension completion update before clearing the lock.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:106

  • Including the persisted artifactPath here can bypass the required newest-file fallback. If a completed phase points to requirements.md and a newer checklist was generated—or the scanner has selected a newer feature slug—the old existing path is returned before newestChecklistFile(checklistsDir) runs. Treat only checklistFile as an explicit selection, then choose the newest file from the active checklist directory.
        const configuredSources = [
            phases.checklist?.formValues?.checklistFile,
            phases.checklist?.artifactPath,
        ];

Copilot AI review requested due to automatic review settings September 3, 2026 16:53

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.

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate issues affect rerun locking, command identity, artifact viewing, and queued-answer retention.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 1 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-runtime.js — Mapping extension commands to their phase slice makes reruns unlock too early when that slice is…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​modals.js — Answers can still be queued or edited while this request is awaiting the server, but a successful…
Suppressed comments (3)

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

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-runtime.js:777

  • These synthesized Core cards use the same canonical id and commandName as the preset-customized cards. The add endpoint stores only that bare id, and resolvePipelineEntry() resolves it through the active preset winner, so clicking the advertised Core escape hatch adds another customized phase rather than the canonical implementation. Preserve a core-layer identity through the card/pipeline/dispatch path, or avoid presenting this as a Core bypass.
        .map((id) => __synthesizeCanonicalPhase(id))
        .sort((a, b) => collator.compare(a.shortLabel || a.id, b.shortLabel || b.id))
        .map((p) => renderCard(p, "core"))

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:122

  • Returning the directory as artifactPath still produces a broken View action for a completed checklist with no Markdown file. renderGraphPhaseCard() treats every nonempty done-phase artifact path as a file and openArtifactViewer() sends it to /api/artifact, whose readFile() call returns 404 for directories; the existing folder-browser path is not invoked. Represent this fallback as a folder target and open the folder browser, or suppress the file View action until a file exists.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-runtime.js:773

  • Making the Core candidates unconditional leaves the preceding customizedIds computation completely unused, and its comment still claims customized commands are routed away from Core to prevent duplicates—the opposite of the new behavior. Remove that obsolete block and update the surrounding explanation so this render path has one consistent policy.
    const coreCandidates = [
        ...canonicalSpine(),
        ...CANONICAL_UNSEEDED,
    ];

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/modals.js Outdated
Copilot AI review requested due to automatic review settings September 3, 2026 18:24
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc

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.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 Medium severity · 1 Low severity

New issues introduced by this change (3)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — For skipped or error, the new prompt intentionally sends no reportExecution, but this…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the…
Low severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — The generated prompt tells the agent that the Run button remains locked until this callback or a…
Issues resolved since last review (2)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​run-tracker.mjs — The duplicate guard has no recovery path if a queued turn is cancelled, crashes, or otherwise omits… View resolved comment
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​extension-artifacts.mjs — For an absolute writesTo located inside the workspace, the new containment branch accepts the… View resolved comment
Suppressed comments (4)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:94

  • A terminal callback with no runId bypasses this stale-run check, then clearRun(..., null) deletes whichever token is currently active. Because runId remains optional in the action schema, an omitted-ID callback from an older overlapping run can still persist its status and clear the newer token. Require a matching ID whenever an active token exists while preserving any intentionally supported untracked status-update path.
                if (["done", "skipped", "error"].includes(status)
                    && runId
                    && !activeRunMatches(inst.instanceId, `speckit.${phase}`, runId)) {
                    return { ok: false, error: "stale phase run" };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/run-tracker.mjs:29

  • This unconditionally replaces an existing same-command token, and the new test explicitly expects overlapping runs. That conflicts with the PR description's claim that a second canonical run is rejected until the terminal callback clears the guard. Since the code and README document launcher semantics, update the PR description or restore the stated duplicate-run rejection contract.
    };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/extension-artifacts.mjs:145

  • An absolute writesTo that is still inside the workspace passes this check, but line 149 exposes it unchanged as artifactPath; the artifact and reveal endpoints reject absolute UI paths. Reject absolute cache targets here (as below), or convert them to workspace-relative paths before storing them.
            const abs = isAbsolute(resolvedPath) ? resolvedPath : join(cwd, resolvedPath);
            const lexicallyInsideWorkspace = isPathContained(canonicalizePath(cwd), canonicalizePath(abs));
            if (!lexicallyInsideWorkspace) {
                phases[key] = next;

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs:127

  • This lifecycle description contradicts the new three-second local acknowledgement: phase-runtime.js no longer waits for snapshots or a terminal callback to unlock the button. Update the API documentation to describe status persistence/token cleanup rather than a callback-driven UI lock.
 * `setPhaseStatus` with a terminal status before returning. The wizard keeps
 * the Run button locked until that callback lands, with only a timeout as a
 * last-resort fallback. Kept as a short, plain-English preamble so it doesn't

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs Outdated

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.

Copilot review overview

🟡 Changes recommended

Moderate issues remain in run-token validation, command classification, artifact visibility, and duplicate-run feedback.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 5 Medium severity · 1 Low severity

New issues introduced by this change (3)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can…
Pre-existing issues (3)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — For skipped or error, the new prompt intentionally sends no reportExecution, but this… View comment
Low severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — The generated prompt tells the agent that the Run button remains locked until this callback or a… View comment
Suppressed comments (3)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:94

  • A terminal callback without runId bypasses this stale-run check, then clearRun(..., undefined) acts as a wildcard and deletes the currently active token. A delayed or malformed callback can therefore persist stale completion and unlock a newer run. When a tracked run exists, require a supplied matching ID before persisting; keep tokenless updates only for phases with no active run.
                if (["done", "skipped", "error"].includes(status)
                    && runId
                    && !activeRunMatches(inst.instanceId, `speckit.${phase}`, runId)) {
                    return { ok: false, error: "stale phase run" };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs:159

  • This instruction is inaccurate after the local acknowledgement change: the Run button unlocks after three seconds regardless of the callback; only the server-side duplicate guard remains active. Describe the token guard instead so the generated prompt matches runtime behavior.
        `Do not leave the phase in progress, and do not omit this terminal callback because the wizard's Run button stays locked until it receives one or the safety timeout expires.`,

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/server/handlers-phase.mjs:78

  • These comments still describe a witness window, but this path now only adds a terminal-status preamble and asks the agent to self-report execution on success. Keeping the removed lifecycle mechanism in the handler documentation makes the new dispatch contract misleading.
    // `track: true` prepends the wizard tracking preamble so the agent
    // reports a terminal phase status, and opens a witness window so the
    // extension records which artifacts actually fired on successful runs.

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-card.js Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc

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.

Copilot review overview

🟡 Changes recommended

Stale or tokenless callbacks can still corrupt current phase status and execution evidence.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 6 Medium severity · 1 Low severity

New issues introduced by this change (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — A stale run whose setPhaseStatus(done) call is rejected can still call reportExecution, which…
Low severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — This docstring still says the Run button remains locked until the terminal callback, but the new…
Pre-existing issues (5)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — For skipped or error, the new prompt intentionally sends no reportExecution, but this… View comment
Issues resolved since last review (1)
Severity Finding
Low severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — The generated prompt tells the agent that the Run button remains locked until this callback or a… View resolved comment
Suppressed comments (2)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:94

  • Terminal callbacks without runId bypass this stale-run check. Since runId remains optional, a delayed or legacy callback can persist its terminal status and then clearRun(..., null) removes whichever newer token is active, defeating the guard this PR adds. When an active token exists, require a matching runId; allow tokenless terminal updates only when no run is tracked.
                if (["done", "skipped", "error"].includes(status)
                    && runId
                    && !activeRunMatches(inst.instanceId, `speckit.${phase}`, runId)) {
                    return { ok: false, error: "stale phase run" };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/app.js:249

  • observePhaseProgress() is now an empty function, so invoking it on every snapshot refresh leaves dead lifecycle plumbing after the simplification. Remove this call, its import, and the no-op export rather than retaining an API that suggests snapshots still drive running feedback.
        observePhaseProgress();

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs Outdated
Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs Outdated
nicolehaugen and others added 2 commits September 4, 2026 18:21
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+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.

Copilot review overview

🟡 Changes recommended

Two critical stale-token defects and two moderate issues remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 High severity · 4 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — Terminal callbacks can omit runId, bypass the stale-token check, persist their status, and then…
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The stale-token check is not atomic with the status write. persistAndBroadcast yields for disk…
Pre-existing issues (4)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Issues resolved since last review (3)
Severity Finding
Low severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — This docstring still says the Run button remains locked until the terminal callback, but the new… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — A stale run whose setPhaseStatus(done) call is rejected can still call reportExecution, which… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — For skipped or error, the new prompt intentionally sends no reportExecution, but this… View resolved comment
Suppressed comments (2)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:145

  • The new folder fallback is dropped before the phase card can use it. buildStateSnapshot omits folderPath from core phase slices (canvas-runtime/snapshot-builder.mjs:43-56) and buildCommands also omits it (snapshot-builder.mjs:196-209), while renderPhaseCard prefers that command entry. Thus this completed-checklist case still renders no browse-folder link; forward folderPath through both projections and add a scanner-to-snapshot test.
            phases.checklist = {
                ...phases.checklist,
                artifactPath: target.artifactPath,
                ...(target.folderPath ? { folderPath: target.folderPath } : {}),
            };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs:128

  • This comment describes a callback-driven lock and timeout that no longer exist. markPhaseRunning now clears a local acknowledgement after three seconds regardless of setPhaseStatus, so keeping this text will mislead future lifecycle changes.
 * then call `setPhaseStatus` with a terminal status before returning. The local
 * Run button state is only a short acknowledgement animation: chat owns live
 * progress, `setPhaseStatus` persists terminal phase state, and the scanner
 * confirms files before artifact buttons become available. Kept as a short,

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.

Copilot review overview

🟡 Changes recommended

Run-token handling, non-phase command tracking, and clarification failure-retention coverage remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 High severity · 5 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​test/​modals.test.mjs — The new tests only exercise successful submissions, so the advertised failure-retention contract is…
Pre-existing issues (6)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The stale-token check is not atomic with the status write. persistAndBroadcast yields for disk… View comment
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — Terminal callbacks can omit runId, bypass the stale-token check, persist their status, and then… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Suppressed comments (2)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:109

  • A terminal callback without runId reaches this branch and calls clearRun(..., null), which deletes the currently active token regardless of which run produced the callback. Thus a legacy or token-omitting older turn can still overwrite status and clear a newer run, bypassing the stale-callback protection. Preserve untagged compatibility only when no active token exists; reject an untagged terminal callback while a run is active.
                if (["done", "skipped", "error"].includes(status)) {
                    if (runId) {
                        finishRun(inst.instanceId, `speckit.${phase}`, runId, { allowReport: status === "done" });
                    } else {
                        clearRun(inst.instanceId, `speckit.${phase}`, runId);

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/dispatch.mjs:155

  • phaseIdForCommandName() classifies any one-segment speckit.* name as a phase, but presets can add non-canonical commands such as speckit.review (test/composition.test.mjs:1101-1123). This line therefore creates a token and an invalid setPhaseStatus({ phase: "review" }) preamble for that command instead of treating it as untracked; the callback cannot pass the phase enum. Gate the candidate against PHASE_BY_ID before calling beginRun.
    const run = phaseId ? beginRun(inst?.instanceId, commandName) : null;

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc

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.

Copilot review overview

🟡 Changes recommended

Run-token races can persist stale status or reports, and setup/preset remain incorrectly classified as canonical commands.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 High severity · 5 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjsconsumeReportableRun removes the only run-id guard before the asynchronous report merge is…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — This registry check still classifies the non-command setup and preset pseudo-phases as…
Pre-existing issues (5)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The stale-token check is not atomic with the status write. persistAndBroadcast yields for disk… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Issues resolved since last review (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​test/​modals.test.mjs — The new tests only exercise successful submissions, so the advertised failure-retention contract is… View resolved comment
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — Terminal callbacks can omit runId, bypass the stale-token check, persist their status, and then… View resolved comment
Suppressed comments (1)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:105

  • The run-token check is separated from the terminal status write by await persistAndBroadcast(...). Because a newer same-phase dispatch is allowed, beginRun can replace the token while this write is awaiting disk I/O; the old callback then persists its terminal status, finishRun fails, but the handler still returns { ok: true }. This breaks the stated stale-callback guarantee. Serialize beginRun with terminal commits per run key, or atomically reserve the matching token before the awaited write and prevent replacement until the commit finishes.
                await persistAndBroadcast(inst, {
                    phases: {
                        [phase]: {
                            status,
                            artifactPath: artifactPath ?? undefined,

Comment thread plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/prompts.mjs Outdated
Gate phase tracking on the canonical command inventory so setup and preset pseudo-phases are not treated as runnable skills while keeping canonical commands like converge tracked.

Co-authored-by: Copilot <223556219+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.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 5 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjsrunPhase still accepts the setup and preset meta-phase IDs because both are in…
Pre-existing issues (5)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The stale-token check is not atomic with the status write. persistAndBroadcast yields for disk… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Issues resolved since last review (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​prompts.mjs — This registry check still classifies the non-command setup and preset pseudo-phases as… View resolved comment
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjsconsumeReportableRun removes the only run-id guard before the asynchronous report merge is… View resolved comment
Suppressed comments (2)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:95

  • The stale-token check is not atomic with the status write. While this handler awaits persistAndBroadcast, a newer same-phase dispatch can replace the active token; the older callback then persists its terminal status before finishRun notices the mismatch. That violates the stated supersession guarantee and can show the old run as done/error while the newer run is active. Serialize status persistence with token replacement, or revalidate the token within the same guarded critical section that commits the patch.
                    if (runId) {
                        if (!activeRunMatches(inst.instanceId, commandName, runId)) {
                            return { ok: false, error: "stale phase run" };

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/fs-helpers.mjs:43

  • securePathWithin returns the realpath rather than the validated workspace spelling. If the workspace itself was opened through a symlink (for example /work/link -> /data/project), checklist callers later compute relative(cwd, securedPath) as ../data/project/...; that path is then rejected by /api/artifact as outside the workspace. Preserve a workspace-relative/lexical path for UI serialization while still performing the realpath checks for containment.
    return realCandidate;

nicolehaugen and others added 2 commits September 4, 2026 21:02
Exclude setup and preset meta screens from phase action schemas and runtime validation so agent-triggered phase runs cannot dispatch non-phase slash commands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc

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.

Copilot review overview

🟡 Changes recommended

A critical stale-token race can persist outdated terminal status, and dispatch latency plus missing canonical checklist folder projection remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 High severity · 4 Medium severity

New issues introduced by this change (1)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The run token is checked before persistAndBroadcast(), which performs multiple awaited…
Pre-existing issues (5)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The stale-token check is not atomic with the status write. persistAndBroadcast yields for disk… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View comment
Issues resolved since last review (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjsrunPhase still accepts the setup and preset meta-phase IDs because both are in… View resolved comment
Suppressed comments (2)

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

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/dispatch.mjs:166

  • Awaiting this promise delays the HTTP 202/action response until the Copilot turn finishes, because the SDK's session.send() promise resolves with the final assistant message rather than when the prompt is queued. As a result the UI's three-second acknowledgement expires while postJson is still pending, and clarification flushes keep the viewer locked for the whole run. Return after scheduling/accepting the send and handle the eventual send rejection asynchronously (including token cleanup) instead.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:145

  • This folder-only fallback is dropped before reaching the browser. buildStateSnapshot copies only artifactPath for canonical phases (canvas-runtime/snapshot-builder.mjs:43-56), unlike its extension projection, so a completed checklist with no markdown file loses folderPath and renders neither a viewer nor a browse-folder action. Carry folderPath through the canonical snapshot projection and cover the scanner-to-snapshot path.
            phases.checklist = {
                ...phases.checklist,
                artifactPath: target.artifactPath,
                ...(target.folderPath ? { folderPath: target.folderPath } : {}),
            };

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.

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review tier: Balanced
Findings: 2 Medium severity

Pre-existing issues (2)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Issues resolved since last review (4)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The run token is checked before persistAndBroadcast(), which performs multiple awaited… View resolved comment
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​actions/​phase.mjs — The stale-token check is not atomic with the status write. persistAndBroadcast yields for disk… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — The local acknowledgement re-enables this button after three seconds while the server token can… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​fs-helpers.mjs — Returning realCandidate breaks callers that later compute a workspace-relative UI path. If the… View resolved comment
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/canvas-runtime/run-tracker.mjs:10

  • These module-level maps are never cleared when a canvas instance closes; onClose removes the instance record but leaves any active/reportable tokens keyed by that instance ID. Reopening instances will steadily retain stale token entries (and a reused instance ID can inherit stale run state). Add an instance-scoped cleanup function and invoke it from the canvas teardown path.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:104

  • The freshness check is separated from the status write by an await. A newer same-phase dispatch can call beginRun while this callback is inside persistAndBroadcast; the older callback then persists its terminal status, and the later finishRun merely returns false (which is ignored). This allows exactly the stale overwrite these tokens are intended to prevent. Make token validation and status persistence serialized/atomic per phase, and add a test that pauses persistence while starting the newer run.
                    if (runId) {
                        if (!activeRunMatches(inst.instanceId, commandName, runId)) {
                            return { ok: false, error: "stale phase run" };
                        }
                    } else if (hasActiveRun(inst.instanceId, commandName)) {
                        return { ok: false, error: "stale phase run" };
                    }
                }

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:145

  • The folder-only fallback is lost before it reaches the UI. hydrateSpecPhases sets phases.checklist.folderPath, but buildStateSnapshot projects core phases without folderPath (and buildCommands also omits it), so a completed checklist with no matching file still renders neither a folder link nor a usable artifact action. Please carry this field through the core phase/command snapshot projections and cover the scanner → snapshot path in the test.
            phases.checklist = {
                ...phases.checklist,
                artifactPath: target.artifactPath,
                ...(target.folderPath ? { folderPath: target.folderPath } : {}),
            };

nicolehaugen and others added 6 commits September 4, 2026 21:19
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc
Co-authored-by: nicolehaugen <10600161+nicolehaugen@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc

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.

Copilot review overview

🟡 Changes recommended

The critical dispatch regression and unresolved token, artifact, and command-rendering issues must be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 3 Medium severity

New issues introduced by this change (3)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjssession.send() completes with the agent turn, not merely when the prompt is accepted. Since both…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — A failed checklist rerun sets the persisted status to error, so this gate stops resolving the…
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.jsartifactPath is also populated with expected/template paths for phases whose files do not exist…
Pre-existing issues (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Issues resolved since last review (1)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.js — A failed rerun now persists status: &quot;error&quot;, but the scanner still supplies the previously… View resolved comment
Suppressed comments (3)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:108

  • The freshness check occurs before await persistAndBroadcast(), leaving a time-of-check/time-of-use race. If a newer same-phase dispatch calls beginRun() while this persistence is awaiting disk I/O/rescan, the older callback still writes its terminal status and only afterward fails finishRun(), so stale status can overwrite the newer run despite the token guard. Token acceptance and terminal persistence need per-phase serialization or another atomic ordering mechanism.
                    if (runId) {
                        if (!activeRunMatches(inst.instanceId, commandName, runId)) {
                            return { ok: false, error: "stale phase run" };
                        }
                    } else if (hasActiveRun(inst.instanceId, commandName)) {
                        return { ok: false, error: "stale phase run" };
                    }
                }
                await persistAndBroadcast(inst, {

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:145

  • The new folder fallback is lost before rendering: this function stores folderPath on the scanned core checklist slice, but buildStateSnapshot() only forwards folderPath for commands/* extension slices, not for core phases. Consequently a completed checklist with no matching markdown file never gets the advertised Browse folder action. Forward this field for core phase snapshots as well.
            // The checklist directory is fixed for a feature's lifetime, so
            // keeping an existing folder fallback alongside a later file
            // target is safe and still points at the same artifact area.
            phases.checklist = {
                ...phases.checklist,

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-runtime.js:667

  • This fallback now derives overrides only from composition.artifacts. When that cached/generated composition payload is absent but the on-disk preset graph still emits a canonical command with source: "preset:…", the command is grouped under the preset yet is not added to overriddenCanonicals, so the same canonical is also offered as a Core card. Retain the command-list source fallback for snapshots where winner metadata is unavailable.
    const overriddenCanonicals = new Set();
    for (const id of [...canonicalSpine(), ...CANONICAL_UNSEEDED]) {
        const winner = winnerByCmdId.get(id) || winnerByCmdId.get(`speckit.${id}`);
        if (winner && winner.layer !== "core") overriddenCanonicals.add(id);
    }

Co-authored-by: Copilot <223556219+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.

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review tier: Balanced
Findings: 3 Medium severity

Pre-existing issues (3)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.jsartifactPath is also populated with expected/template paths for phases whose files do not exist… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — A failed checklist rerun sets the persisted status to error, so this gate stops resolving the… View comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View comment
Issues resolved since last review (1)
Severity Finding
High severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjssession.send() completes with the agent turn, not merely when the prompt is accepted. Since both… View resolved comment
Suppressed comments (4)

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

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-contributors.js:579

  • A done status does not establish that this report belongs to the latest run. beginRun only clears the in-memory reportable token, while the persisted execution report has no run ID; if a successful rerun omits or fails reportExecution, this lookup keeps showing the previous run's verdict instead of the documented fallback. Persist/compare the report's run ID, or clear the prior report when a new run starts.

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:104

  • The stale-token check happens before persistAndBroadcast, which awaits a disk read, write, and rescan. A newer same-phase dispatch can call beginRun during any of those awaits, after this callback has passed the check; the older callback then still persists its terminal status and finishRun merely fails afterward. Make token validation and the status commit atomic for the phase (or serialize both beginRun and terminal commits on the same per-phase guard) so an older callback cannot overwrite a newer run.
                    if (runId) {
                        if (!activeRunMatches(inst.instanceId, commandName, runId)) {
                            return { ok: false, error: "stale phase run" };
                        }
                    } else if (hasActiveRun(inst.instanceId, commandName)) {

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:138

  • A failed checklist rerun has status error, so this gate skips dynamic resolution even when a prior checklist file still exists. Because the error callback omits artifactPath, normalization restores the default specs/<slug>/checklists/ directory path; the UI then attempts to open that directory as a file instead of retaining the prior checklist. Treat error as an attempted checklist run for artifact resolution while preserving the error status.
    const hasChecklistRun = phases.checklist?.status === "done";
    const hasConfiguredChecklist = typeof phases.checklist?.formValues?.checklistFile === "string"
        && !!phases.checklist.formValues.checklistFile.trim();
    if ((hasChecklistRun || hasConfiguredChecklist) && await deps.pathExists(checklistsDir)) {

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-card.js:492

  • artifactPath is not necessarily scanner-confirmed: canonical phase slices seed it from the declared output template, and extension scanning assigns it before existence/realpath validation. Gating only on truthiness therefore exposes “View artifact” for unrun or missing outputs (for example, an untouched checklist points at specs/<slug>/checklists/ and the file endpoint returns an error). More seriously, an extension path rejected because it resolves through a symlink outside the workspace is still exposed here, while /api/artifact performs only a lexical containment check and follows that symlink. Keep the expected writes-to path separate from a scanner-verified artifact path (or add an explicit verified flag), and enable this action only for the verified value.
    const canViewArtifact = !!p.artifactPath;

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.

Copilot review overview

🔵 Needs a closer look

Three moderate artifact-visibility, checklist-resolution, and stale-status race issues remain unresolved.

Review tier: Balanced
Findings: None

Issues resolved since last review (3)
Severity Finding
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​ui/​phase-card.jsartifactPath is also populated with expected/template paths for phases whose files do not exist… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​project-scanner/​spec-phases.mjs — A failed checklist rerun sets the persisted status to error, so this gate stops resolving the… View resolved comment
Medium severity plugins/​spec-kit-copilot-wizard/​extensions/​speckit-wizard-canvas/​canvas-runtime/​dispatch.mjsphaseIdForCommandName only checks the command's shape, so valid non-canonical single-segment… View resolved comment
Suppressed comments (3)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/canvas-runtime/actions/phase.mjs:104

  • The token check is separated from persistence by several awaits. An old callback can pass this check, then a newer same-phase dispatch can replace the active token (and even persist its terminal result) while the old persistAndBroadcast is pending; the old callback can subsequently write stale status and still return { ok: true }. The write queue only serializes already-computed payloads, so validation and commit need a shared per-phase serialized/conditional operation that rechecks the active token at commit time.
                    if (runId) {
                        if (!activeRunMatches(inst.instanceId, commandName, runId)) {
                            return { ok: false, error: "stale phase run" };
                        }
                    } else if (hasActiveRun(inst.instanceId, commandName)) {

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/project-scanner/spec-phases.mjs:137

  • The new error terminal status is excluded from checklist artifact resolution. After a checklist rerun fails, setPhaseStatus omits an artifact path and normalization restores the phase's folder template; without formValues.checklistFile (the normal graph form does not persist it), this branch is skipped, so an existing checklist file is no longer selected and the failed-rerun View action targets the folder instead. Treat an errored checklist as having run for artifact lookup while preserving its error status.
    const hasChecklistRun = phases.checklist?.status === "done";
    const hasConfiguredChecklist = typeof phases.checklist?.formValues?.checklistFile === "string"
        && !!phases.checklist.formValues.checklistFile.trim();

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-card.js:492

  • artifactPath is not an existence signal. Core slices seed it with the expected template before a run (wizard-phases.mjs:198-207), and the extension scanner intentionally retains a missing writesTo path (extension-artifacts.mjs:149-157; state-and-scanner.test.mjs:1206-1210). Consequently, every unrun phase with an expected path now gets a View button that opens a missing or <slug> path and fails. Track scanner-confirmed existence separately (or separate expected and resolved paths) and use that signal here while still allowing confirmed artifacts when status is error.
    const canViewArtifact = !!p.artifactPath;

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 23ff38e2-a233-493e-a8b7-c885652f57bc

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.

Copilot review overview

🔵 Needs a closer look

Artifact actions must be gated on scanner-confirmed file existence rather than an expected artifact path.

Review tier: Balanced
Findings: None

Suppressed comments (1)

plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/phase-card.js:493

  • artifactPath does not mean that the scanner found a file. Every empty canonical phase is initialized with its expected template path (for example, Plan gets specs/<slug>/plan.md from emptyPhaseSlice), and extension inference also retains missing targets. Consequently an untouched phase now renders View artifact and sends a placeholder/nonexistent path to /api/artifact. Please separate the expected writes-to path from scanner-confirmed file existence (or expose an explicit existence flag) and gate this action on the latter.
    const canViewArtifact = !!p.artifactPath;

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.

4 participants