From 0e38f77c5fa8d0b8a39934d3897f102445338947 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Sat, 29 Aug 2026 05:35:45 -0500 Subject: [PATCH 1/3] Prepare CLI 0.2.2 candidate Advance the backward-compatible 0.2 line for integrated current-directory root output without reusing the immutable 0.2.1 registry or tag identities. Preserve candidate publication under next and leave latest unchanged. --- docs/release-history.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- release/compatibility.json | 2 +- test/package.test.js | 4 ++++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/release-history.md b/docs/release-history.md index fce0477..acdadff 100644 --- a/docs/release-history.md +++ b/docs/release-history.md @@ -38,3 +38,10 @@ source or documentation does not mutate either dist-tag. promoted stable release. - Package version `0.2.0` and protected tag `v0.2.0` are consumed and immutable. A backward-compatible addition to the `0.2.x` line therefore requires a higher patch version rather than reusing those identities. + +## 0.2.1 candidate publication + +- On August 28, 2026, protected tag `v0.2.1` published ordinary version `0.2.1` under `next`; `latest` remained + `0.1.0`. The patch hardened direct Compilation recovery while preserving the `0.2.x` compatibility line. +- Package version `0.2.1` and protected tag `v0.2.1` are consumed and immutable. The current-directory root-output + behavior integrated on August 29 is not part of those registry bytes and requires the next patch candidate. diff --git a/package-lock.json b/package-lock.json index 6ba64af..addb262 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@firstdraft.com/cli", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@firstdraft.com/cli", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "bin": { "firstdraft": "bin/firstdraft.js" diff --git a/package.json b/package.json index 1dab0ec..4ced463 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@firstdraft.com/cli", - "version": "0.2.1", + "version": "0.2.2", "description": "Command-line interface for First Draft", "license": "MIT", "type": "module", diff --git a/release/compatibility.json b/release/compatibility.json index b979685..2e4e193 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -1,7 +1,7 @@ { "format": "firstdraft.release-compatibility/1", "component": "cli", - "version": "0.2.1", + "version": "0.2.2", "requires": { "api_contract": [">= 0.3.0", "< 0.4.0"], "foundation_plan_formats": ["firstdraft.foundation-plan.sketch/0.19"] diff --git a/test/package.test.js b/test/package.test.js index 39c6ecd..f816eb1 100644 --- a/test/package.test.js +++ b/test/package.test.js @@ -130,6 +130,10 @@ test("stable release completion requires qualified latest promotion", () => { releaseHistory, /Protected tag `v0\.1\.0` and package version `0\.1\.0` were consumed and immutable/, ); + assert.match( + releaseHistory, + /Package version `0\.2\.1` and protected tag `v0\.2\.1` are consumed and immutable[\s\S]*?current-directory root-output[\s\S]*?requires the next patch candidate/, + ); assert.match( releasingGuide, /If either identity is already[\s\S]*?consumed, prepare the next version required by the pre-1\.0 policy rather than moving or reusing it/, From bf398664332c0929226975d4f0978407511dd711 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Sat, 29 Aug 2026 05:39:47 -0500 Subject: [PATCH 2/3] Qualify packed root output Exercise the installed package's current-directory materialization on POSIX and its explicit unsupported contract on Windows so the supported-platform CI matrix proves the shipped behavior. --- scripts/smoke-package.js | 79 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/scripts/smoke-package.js b/scripts/smoke-package.js index ad364de..c106266 100644 --- a/scripts/smoke-package.js +++ b/scripts/smoke-package.js @@ -6,6 +6,7 @@ import { lstatSync, mkdtempSync, mkdirSync, + realpathSync, readFileSync, rmSync, writeFileSync, @@ -652,11 +653,85 @@ First Draft: Application compiled. 0o644, ); } + + const rootOutput = await spawnPackedCliAsync( + ["plan", "compile", "--output", "."], + projectDirectory, + ); + const expectedCompilationStarts = process.platform === "win32" ? 1 : 2; + + if (process.platform === "win32") { + assertHandledFailure(rootOutput, 2, { + error: "invalid_output_path", + detail: + "The compilation output must be an absent path beneath an existing real directory or the eligible current directory.", + reason: "root_platform_unsupported", + }); + } else { + const realProjectDirectory = realpathSync(projectDirectory); + assert.equal(rootOutput.status, 0); + assert.equal( + rootOutput.stderr, + `First Draft: Analyzing Foundation Plan... +First Draft: Foundation Plan analysis valid. +First Draft: Compiling application... +First Draft: Application compiled. +`, + ); + assert.deepEqual(JSON.parse(rootOutput.stdout).output, { + path: realProjectDirectory, + file_count: 1, + manifest_sha256: manifestSha256, + root_adoption: { + design_path: path.join(realProjectDirectory, "design"), + moved_entry_count: 3, + git_repository_preserved: false, + git_index_replaced: false, + }, + }); + assert.equal( + readFileSync( + path.join(realProjectDirectory, "app/models/movie.rb"), + "utf8", + ), + contents.toString("utf8"), + ); + assert.deepEqual( + readFileSync( + path.join( + realProjectDirectory, + "design/.firstdraft/foundation-plan.json", + ), + ), + plan, + ); + assert.equal( + readFileSync( + path.join( + realProjectDirectory, + "design/application/app/models/movie.rb", + ), + "utf8", + ), + contents.toString("utf8"), + ); + assert.equal( + readFileSync( + path.join( + realProjectDirectory, + "design/generated/app/models/movie.rb", + ), + "utf8", + ), + contents.toString("utf8"), + ); + } + assert.deepEqual(seen, { plan: true, analysis: true, publication: 1, - compilationStarts: 1, + compilationStarts: expectedCompilationStarts, status: true, artifact: true, }); @@ -692,7 +767,7 @@ function sha256(value) { } /** - * @param {ReturnType} execution + * @param {{status: number | null, stdout: string, stderr: string}} execution * @param {number} status * @param {Record} error */ From aa7e55b26a764fb83584703e1a0d241f3ad49221 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Sat, 29 Aug 2026 05:45:04 -0500 Subject: [PATCH 3/3] Make candidate history self-invalidating Bind the root-output integration observation to its exact main tree and reject any candidate version already recorded as consumed. This keeps the release note durable after publication instead of teaching a transient next-version instruction. --- docs/release-history.md | 5 +++-- test/package.test.js | 12 +++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/release-history.md b/docs/release-history.md index acdadff..1910077 100644 --- a/docs/release-history.md +++ b/docs/release-history.md @@ -43,5 +43,6 @@ source or documentation does not mutate either dist-tag. - On August 28, 2026, protected tag `v0.2.1` published ordinary version `0.2.1` under `next`; `latest` remained `0.1.0`. The patch hardened direct Compilation recovery while preserving the `0.2.x` compatibility line. -- Package version `0.2.1` and protected tag `v0.2.1` are consumed and immutable. The current-directory root-output - behavior integrated on August 29 is not part of those registry bytes and requires the next patch candidate. +- Package version `0.2.1` and protected tag `v0.2.1` are consumed and immutable. As observed on August 29, 2026, + current-directory root-output had integrated to `main` at `4352f64baf673ad93457e8bc84273e9d1d9a9501` (tree + `b43ba6de98e27328e548cc3410ba9f39dfa9fcee`) but was not part of those registry bytes. diff --git a/test/package.test.js b/test/package.test.js index f816eb1..44ae034 100644 --- a/test/package.test.js +++ b/test/package.test.js @@ -132,7 +132,17 @@ test("stable release completion requires qualified latest promotion", () => { ); assert.match( releaseHistory, - /Package version `0\.2\.1` and protected tag `v0\.2\.1` are consumed and immutable[\s\S]*?current-directory root-output[\s\S]*?requires the next patch candidate/, + /Package version `0\.2\.1` and protected tag `v0\.2\.1` are consumed and immutable[\s\S]*?As observed on August 29, 2026,[\s\S]*?current-directory root-output[\s\S]*?`4352f64baf673ad93457e8bc84273e9d1d9a9501`[\s\S]*?`b43ba6de98e27328e548cc3410ba9f39dfa9fcee`[\s\S]*?was not part of those registry bytes/, + ); + const consumedVersions = [ + ...releaseHistory.matchAll( + /(?:Package|package) version `([^`]+)`(?=[\s\S]{0,120}?consumed and immutable)/g, + ), + ].map((match) => match[1]); + assert.equal( + consumedVersions.includes(metadata.version), + false, + `package version ${metadata.version} is already recorded as consumed`, ); assert.match( releasingGuide,