Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f2350cd
Add live-CLI integration + fixture-drift tests for artifact adapter
nicolehaugen Aug 25, 2026
0a62ac8
Fix blank flash on wizard first-open (boot overlay race)
nicolehaugen Aug 25, 2026
bb5edf2
Parallelize catalog hydration to cut boot time
nicolehaugen Aug 25, 2026
34fb942
Revert "Parallelize catalog hydration to cut boot time"
nicolehaugen Aug 25, 2026
9fdbcc8
Fix catalog-loading hang: async parallel CLI fan-out
nicolehaugen Aug 25, 2026
b9b95d1
Harden catalog boot: timeouts + group-level parallelism
nicolehaugen Aug 25, 2026
237d05d
Move composition to single-call CLI shape (uptake PR #4305)
nicolehaugen Aug 25, 2026
013f877
Regen live-cli-list.json against post-#4305 CLI
nicolehaugen Aug 25, 2026
187bb33
Split 'Loading catalogs' into catalog + composition tracker steps
nicolehaugen Aug 25, 2026
7a898ff
Scrub PR #4305 references and past-tense narration from comments
nicolehaugen Aug 25, 2026
60d5305
Remove live-CLI integration test — belongs in spec-kit repo
nicolehaugen Aug 25, 2026
2a811e6
Address review comments: scrub phantom AGENTS.md quotes and dev title
nicolehaugen Aug 26, 2026
ce6d39e
Potential fix for pull request finding
nicolehaugen Aug 26, 2026
659333b
Potential fix for pull request finding
nicolehaugen Aug 26, 2026
19560a7
Recover from composition boot failures
Copilot Aug 26, 2026
65490c6
Potential fix for pull request finding
nicolehaugen Aug 26, 2026
633e908
Potential fix for pull request finding
nicolehaugen Aug 26, 2026
3ba4d51
Preserve CLI artifact source paths
Copilot Aug 26, 2026
e1e8c41
Merge branch 'main' of https://github.com/github/spec-kit-copilot int…
nicolehaugen Sep 8, 2026
a2a9dc0
Align wizard provider identity with artifact contract
nicolehaugen Sep 9, 2026
6b6332a
Use public registry URL in wizard lockfile
nicolehaugen Sep 9, 2026
d26e5c7
Restrict hook suppression to active extension
nicolehaugen Sep 9, 2026
9e14423
Restore focused wizard pipeline coverage
nicolehaugen Sep 9, 2026
087b03a
Use source IDs for extension providers
nicolehaugen Sep 9, 2026
f2132f6
Correct composition refresh documentation
nicolehaugen Sep 9, 2026
c485eab
Test extension hook enrichment
nicolehaugen Sep 9, 2026
e41293c
Revert unintended js-yaml upgrade
nicolehaugen Sep 9, 2026
f8e5025
Use extension manifest display metadata
nicolehaugen Sep 9, 2026
73f0c43
Preserve authoritative hook targets
nicolehaugen Sep 9, 2026
3fd5128
Preserve CLI provider precedence
nicolehaugen Sep 9, 2026
084596a
Document project layer scope
nicolehaugen Sep 9, 2026
4075c10
Remove artifact stack version badges
nicolehaugen Sep 9, 2026
1f308e8
Read nested extension metadata
nicolehaugen Sep 9, 2026
37f796d
Address scoped composition regressions
nicolehaugen Sep 9, 2026
a95b4df
Use authoritative extension manifest paths
nicolehaugen Sep 9, 2026
4993503
Retain existing provider ordering
nicolehaugen Sep 9, 2026
4c24927
Document provider ordering scope
nicolehaugen Sep 9, 2026
f5b772e
Document artifact inventory bound
nicolehaugen Sep 9, 2026
f8ef930
Clarify provider summary scope
nicolehaugen Sep 9, 2026
7ec1d4f
Preserve kind-qualified artifacts
nicolehaugen Sep 9, 2026
76fc884
Document temporary hook compatibility scope
nicolehaugen Sep 10, 2026
6d99a97
Ensure wizard prefers managed CLI paths
nicolehaugen Sep 10, 2026
dbaa728
Document project override source fallback
nicolehaugen Sep 10, 2026
96b7689
Clarify unsupported project overrides
nicolehaugen Sep 10, 2026
f3090fa
Preserve existing PATH precedence
nicolehaugen Sep 10, 2026
812d918
Treat task-to-issues as an optional phase
nicolehaugen Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Scrub PR #4305 references and past-tense narration from comments
Comments now describe current behavior only.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2d44fd06-6323-4654-8a80-b756561ef669
  • Loading branch information
nicolehaugen and Copilot committed Aug 25, 2026
commit 7a898ffecf5bf54e5454126c71c2119408543a09
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,6 @@ export async function specifyArtifactList(root, { runner = defaultAsyncRunner }
return JSON.parse(String(stdout));
}

// specifyArtifactInfo removed intentionally. Prior to spec-kit PR #4305 the
// wizard called `specify artifact info <id>` once per artifact to obtain the
// composition stack, which cost ~1s (Python cold-start) × N artifacts on
// every boot. PR #4305 makes `list --json` return the same rich shape per
// row, so the wizard no longer needs an info fetch. If a future feature
// needs single-artifact detail beyond what `list` returns, add it back — but
// do NOT reintroduce a per-artifact fan-out on the boot critical path.

// ---------------------------------------------------------------------------
// Shape mapping helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -384,19 +376,13 @@ function applyHookAttributions(artifacts, extensionHookInfo, hooksMap) {
*
* ## Upstream contract
*
* This function assumes `specify artifact list --json` now returns one row
* per artifact carrying the FULL composition stack — i.e. list rows have
* the same shape as `info --json` output (with `stack: [...]`). That
* change ships in spec-kit PR #4305. Prior to that PR the CLI required a
* separate `info` call per artifact to obtain the stack, which forced the
* wizard to fan out N shell-outs on boot (~60s regression on stacks of
* 60+ artifacts). See AGENTS.md: "CLI is the source of truth for
* composition."
* `specify artifact list --json` returns one row per artifact carrying the
* FULL composition stack (i.e. list rows include `stack: [...]`). See
* AGENTS.md: "CLI is the source of truth for composition."
*
* If a rollback ever ships a CLI where `list --json` omits `stack`, this
* function still returns a well-formed payload — artifacts get empty
* stacks and the composition summary folds to `[]`. Not desirable, but not
* a crash.
* If a CLI ships where `list --json` omits `stack`, this function still
* returns a well-formed payload — artifacts get empty stacks and the
* composition summary folds to `[]`. Not desirable, but not a crash.
*
* @param {object} opts
* @param {string} opts.workspaceRoot Absolute path to the workspace root.
Expand All @@ -411,13 +397,10 @@ export async function buildCompositionFromCli({
extensionItems,
runner = defaultAsyncRunner,
} = {}) {
// 1. Single list call — each row now carries `stack` (PR #4305).
// 1. Single list call — each row carries `stack`.
const list = await specifyArtifactList(workspaceRoot, { runner });

// 2. Shape each row directly. shapeArtifact already reads `stack` off
// its input, so no code change needed there — the swap from
// per-artifact `info` payloads to per-row list payloads is
// transparent as long as the fields match.
// 2. Shape each row directly. shapeArtifact reads `stack` off its input.
const artifactsRaw = [];
for (const row of list) {
const shaped = shapeArtifact(row);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,10 @@ import { readMarkdownArtifact, extractMarker } from "./project-scanner/markdown.

export { readMarkdownArtifact };

// Composition inventory used to be scanned from `.specify/presets.json` and
// `.specify/extensions.json`, which no released `specify` CLI ever writes.
// PR #4305 makes the CLI the authoritative source of composition data, so
// the wizard now reads composition exclusively from
// `specify artifact list --json` (see composition/artifact-cli.mjs) and
// overlays it via `overlayCachedComposition`. There is intentionally no
// direct fs read here for presets or extensions — see AGENTS.md governing
// principles: "CLI is the source of truth for composition. No direct fs
// reads. Ever."
// Composition is read exclusively from `specify artifact list --json` (see
// composition/artifact-cli.mjs) and overlaid via `overlayCachedComposition`.
// No direct fs read here for presets or extensions — see AGENTS.md:
// "CLI is the source of truth for composition. No direct fs reads. Ever."

// deps shape:
// readFile(path, enc) → Promise<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ function hasSpecifyCli() {
}

// Detect whether the installed `specify` CLI emits per-row `stack` on
// `artifact list --json` (PR github/spec-kit#4305). Pre-#4305 CLIs return
// bare rows and the live test would fail the "exactly one active layer"
// invariant. Skip in that case — the fixture-drift test above covers the
// same shape once the fixture is regenerated post-#4305.
// `artifact list --json`. Without `stack` the live test would fail the
// "exactly one active layer" invariant — skip in that case.
function specifyListEmitsStack() {
try {
const out = execFileSync("specify", ["artifact", "list", "--json"], {
Expand Down Expand Up @@ -98,32 +96,28 @@ describe("artifact-cli — live CLI", { skip: skipLive }, () => {
// Regenerate the snapshot with:
// specify artifact list --json > test/fixtures/live-cli-list.json
//
// Post PR #4305, `list --json` returns rows with the full composition stack
// embedded, so a separate `info` fixture is no longer needed. If you're on
// a pre-#4305 CLI the snapshot will lack `stack` and the test will fail —
// that's the intended drift signal.
// `list --json` returns rows with the full composition stack embedded, so
// no separate `info` fixture is needed.
describe("artifact-cli — fixture drift", () => {
const listPath = join(FIXTURES_DIR, "live-cli-list.json");
const snapshotAvailable = existsSync(listPath);

// Detect pre-#4305 snapshots: list rows without `stack`. In that case
// skip the drift assertion (rather than fail CI) — the test's purpose
// is to catch NEW drift once we're on the target contract, not to
// block on a stale capture. Regenerate the fixture once the CLI
// upgrade is in and the test will start guarding again.
let snapshotIsPre4305 = false;
// If a snapshot lacks `stack`, skip the drift assertion rather than
// fail CI — the test's purpose is to catch NEW drift against the
// current contract, not to block on a stale capture.
let snapshotMissingStack = false;
if (snapshotAvailable) {
try {
const parsed = JSON.parse(readFileSync(listPath, "utf8"));
if (Array.isArray(parsed) && parsed.length > 0 && !Object.hasOwn(parsed[0], "stack")) {
snapshotIsPre4305 = true;
snapshotMissingStack = true;
}
} catch {
// fall through — snapshotAvailable stays true; the JSON.parse
// in the test will surface the real error
}
}
const skipDrift = !snapshotAvailable || snapshotIsPre4305;
const skipDrift = !snapshotAvailable || snapshotMissingStack;

test("captured list rows carry the fields the wizard reads (id/name/kind/description/stack[])", { skip: skipDrift }, () => {
const rows = JSON.parse(readFileSync(listPath, "utf8"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { join } from "node:path";
// ---------------------------------------------------------------------------
// Fake runner — mimics the specify CLI for `artifact list --json`.
//
// Post PR #4305 the list payload IS the composition data: each row carries
// its own `stack`. Fixtures are a flat array of rows.
// The list payload IS the composition data: each row carries its own
// `stack`. Fixtures are a flat array of rows.
// ---------------------------------------------------------------------------

function fakeRunner(rows) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ shape regressions if the CLI's JSON contract changes.
It is **not** consumed by the fixture-based unit tests in
`test/artifact-cli.test.mjs` — those tests use synthetic fixtures inline.

Post spec-kit PR #4305, `list --json` returns the full composition stack per
row, so a separate `info` snapshot is no longer needed.
`list --json` returns the full composition stack per row, so no separate
`info` snapshot is needed.

### Regenerating

Expand Down