Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
Foundation Plans with their users, then submit a valid Plan for the current bounded compilation and private GitHub
publication journey.

The current `0.1.x` line includes local Plan initialization, application-key and UUID generation, conditional
The current `0.2.x` source line includes local Plan initialization, application-key and UUID generation, conditional
whole-document push, whole-graph analysis polling, compile-and-publish orchestration, and read-only retained
Compilation download. Remote commands require a compatible First Draft service and are intended for coordinated
trials; publishing this CLI does not make the wider service generally available.
Compilation download. Analysis status exposes the complete reviewed GapSet before Compile. Remote commands require
a compatible First Draft service and are intended for coordinated trials; publishing this CLI does not make the
wider service generally available.

## Install

Expand Down
18 changes: 10 additions & 8 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ binds its Foundation Plan format to the implemented CLI constant.

The `script/release_compatibility_check` evaluator in `firstdraft/firstdraft` reads this declaration with the matching
declarations from exact, clean checkouts of `firstdraft/firstdraft` and `firstdraft/skills`. It implements SemVer 2.0
precedence. This CLI requires the service's `0.2.0` API contract because the always-present GitHub Publication
progress projection is incompatible with the strict response shape accepted by the published
`@firstdraft.com/cli@0.1.0-alpha.2`. Comparator arrays form one conjunction, while `foundation_plan_formats` lists
alternatives. A prerelease satisfies a comparator set only when a comparator explicitly names a prerelease with the
same major, minor, and patch numbers. Skills names the candidate CLI version explicitly, so a stale comparator makes
the three-repository candidate ineligible. `firstdraft.release-compatibility/1` is intentionally closed. The
evaluator in `firstdraft/firstdraft` rejects an unrecognized format and unknown keys, so adding a key requires a
coordinated compatibility-format bump rather than silently changing version 1.
precedence. CLI `0.2.x` requires the service's `0.3.x` API contract because Analysis now returns the complete reviewed
GapSet and digest. The released CLI `0.1.0` accepts only API `0.2.x`, uses a generic 2 MiB response bound for Analysis,
and cannot safely consume every schema-valid API `0.3.x` result. CLI `0.2.x` retains that generic bound but gives
Analysis and Compilation artifacts dedicated 128 MiB bounds. Comparator arrays form one conjunction, while
`foundation_plan_formats` lists alternatives. A prerelease satisfies a comparator set only when a comparator
explicitly names a prerelease with the same major, minor, and patch numbers. Skills names the candidate CLI version
explicitly, so a stale comparator makes the three-repository candidate ineligible.
`firstdraft.release-compatibility/1` is intentionally closed. The evaluator in `firstdraft/firstdraft` rejects an
unrecognized format and unknown keys, so adding a key requires a coordinated compatibility-format bump rather than
silently changing version 1.

A compatible result establishes candidate eligibility, not authorization or runtime proof. Exact Git SHAs identify
the three-repository candidate. A merge to `main` is integration only: report the merged SHA and ask the user whether
Expand Down
33 changes: 21 additions & 12 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
This page owns the detailed public semantics of the current command surface. Run `firstdraft --help` or a command
group's `--help` for concise executable syntax. See [Errors and recovery](errors.md) before retrying a failed mutation.

The current `0.1.x` line contains the auditable command shell, local Foundation Plan initialization, local
The current `0.2.x` source line contains the auditable command shell, local Foundation Plan initialization, local
application-key and UUID generation, conditional whole-document push, whole-graph analysis status polling,
compile-and-publish orchestration, and read-only retained-Compilation download. The current `0.1.0` CLI requires the
service's `0.2.x` API contract. See the [release policy](../RELEASING.md) for versioning and channel semantics and
compile-and-publish orchestration, and read-only retained-Compilation download. CLI `0.2.x` requires the service's
`0.3.x` API contract. See the [release policy](../RELEASING.md) for versioning and channel semantics and
[release history](release-history.md) for the transition from prereleases.

## Command map
Expand Down Expand Up @@ -116,11 +116,19 @@ or `superseded`. Every validated analysis status is a successful read with exit
`analysis.status` value and inspect `analysis.diagnostics` rather than treating a completed analysis with issues as
a transport failure.

The projection includes the exact Head digest, Analyzer and Compiler releases, selected target, and
`analysis.gap_set` plus `analysis.gap_set_sha256`. A `valid` run always returns the complete parsed canonical
`firstdraft.foundation-gaps/2` object, including every ordered gap record and an empty `gaps` array when nothing is
missing. Both GapSet fields are `null` for every other status. The CLI validates the GapSet's Head, Project,
generation, releases, target, canonical digest, and complete record shapes, then prints the records without
truncating or rewriting them.

Status reads require the API origin pinned by a successful push. They never select an origin from the current
environment, expose the private ETag, follow redirects, or modify local state. Each request has a bounded timeout,
every response is byte-bounded and fully validated, and polling will not silently switch to a replacement analysis.
The wait repeats only validated `processing` responses and stops on its first failed read. A network failure is safe
to retry a bounded number of times because the command sends only `GET` requests. See
ordinary response reads retain a 2 MiB bound, while this potentially gap-heavy response has a dedicated 128 MiB
bound. Every response is fully validated, and polling will not silently switch to a replacement analysis. The wait
repeats only validated `processing` responses and stops on its first failed read. A network failure is safe to retry
a bounded number of times because the command sends only `GET` requests. See
[read-only failures](errors.md#read-only-status-failures) if the problem persists.

## Compile and publish the current Plan
Expand All @@ -133,10 +141,10 @@ firstdraft plan compile

`plan compile` is the single terminal action. It first pushes the exact current bytes in
`.firstdraft/foundation-plan.json`, even when those bytes are unchanged, and saves the accepted ETag using the same
contract as `plan push`. It then waits up to two minutes for an analysis whose graph version exactly matches that
accepted push, polling past a terminal result retained for an older Head. Invalid JSON, schema diagnostics, semantic
diagnostics, a failed analysis, a superseded analysis, or a recurring diagnostic stop the command with structured
output; no Compilation or Publication is requested.
contract as `plan push`. It then waits up to two minutes for an analysis whose graph version and
`head_source_sha256` exactly match that accepted push, polling past a terminal result retained for an older Head.
Invalid JSON, schema diagnostics, semantic diagnostics, a failed analysis, a superseded analysis, or a recurring
diagnostic stop the command with structured output; no Compilation or Publication is requested.

Only a `valid` analysis proceeds to the internal GitHub Publication lifecycle. Invoking `plan compile` is the
authorization to request that lifecycle. Immediately before its conditional mutation, the CLI re-reads the local
Expand All @@ -152,7 +160,7 @@ URLs, raw server projections, local paths, or environment values. Success writes
GitHub repository URL plus a newline to stdout. If the command fails after progress has begun, its structured JSON
error envelope is the final stderr document after the progress lines.

The closed API `0.2.x` progress-reason allowlist is `github.configuration_missing`, `github.oauth_unavailable`,
The closed API `0.3.x` progress-reason allowlist is `github.configuration_missing`, `github.oauth_unavailable`,
`github.api_unavailable`, `github.reauthorization_required`, `github.account_mismatch`,
`github.installation_unavailable`, `github.installation_not_ready`, `github.preflight_unavailable`, the legacy-only
`github.preflight_unclassified`, and these stage-specific fallbacks: `github.preflight_unavailable.configuration`,
Expand Down Expand Up @@ -208,4 +216,5 @@ ETag, exact-byte SHA-256, canonical UTF-8 JSON envelope, provenance, metadata-on
strict Base64 contents, file digests, modes, owners, and source-subject UUIDs. It writes only into a uniquely created
sibling directory, verifies the complete tree, and atomically renames it into the still-absent destination. On
POSIX, directories use mode `0755` and files use artifact-declared `0644` or `0755`; Windows verifies structure,
contents, and digests without claiming POSIX mode bits.
contents, and digests without claiming POSIX mode bits. The declared and streamed artifact envelope is bounded at
128 MiB.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@firstdraft.com/cli",
"version": "0.1.0",
"version": "0.2.0",
"description": "Command-line interface for First Draft",
"license": "MIT",
"type": "module",
Expand Down
4 changes: 2 additions & 2 deletions release/compatibility.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"format": "firstdraft.release-compatibility/1",
"component": "cli",
"version": "0.1.0",
"version": "0.2.0",
"requires": {
"api_contract": [">= 0.2.0", "< 0.3.0"],
"api_contract": [">= 0.3.0", "< 0.4.0"],
"foundation_plan_formats": ["firstdraft.foundation-plan.sketch/0.19"]
}
}
19 changes: 18 additions & 1 deletion scripts/smoke-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ async function exercisePackedCompilation(projectDirectory) {
);
const statusPath = `/v1/projects/${projectId}/compilations/${compilationId}`;
const artifactPath = `${statusPath}/artifact`;
const analyzerRelease = "foundation-plan-rails/application-2026-08";
const compilerRelease = "foundation-plan-rails/compiler-scalar-2026-08";
const target = { id: "rails", profile: "rails-sketch/2026-08" };
const contents = Buffer.from("class Movie < ApplicationRecord\nend\n");
Expand Down Expand Up @@ -406,14 +407,30 @@ async function exercisePackedCompilation(projectDirectory) {
completed_at: "2026-07-30T12:00:02.000000Z",
},
};
const gapSet = {
format: "firstdraft.foundation-gaps/2",
source: { sha256: headSha256 },
project: { id: projectId, graph_version: 1 },
analysis: { release: analyzerRelease },
compiler_release: compilerRelease,
target,
gaps: [],
};
const analysis = {
project: { id: projectId, graph_version: 1 },
analysis: {
id: analysisId,
graph_version: 1,
analyzer_release: "foundation-plan-analyzer/2026-08",
head_source_sha256: headSha256,
analyzer_release: analyzerRelease,
compiler_release: compilerRelease,
target,
status: "valid",
diagnostics: [],
gap_set: gapSet,
gap_set_sha256: sha256(
Buffer.from(`${JSON.stringify(gapSet, null, 2)}\n`),
),
started_at: "2026-07-30T12:00:00.000Z",
completed_at: "2026-07-30T12:00:01.000Z",
},
Expand Down
6 changes: 3 additions & 3 deletions src/api-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export async function sendRequest(fetchFunction, endpoint, request) {
}
}

/** @param {Response} response */
export async function readResponseBody(response) {
const bytes = await readResponseBytes(response);
/** @param {Response} response @param {number} [maximumBytes] */
export async function readResponseBody(response, maximumBytes) {
const bytes = await readResponseBytes(response, maximumBytes);

let text;
try {
Expand Down
6 changes: 5 additions & 1 deletion src/commands/plan-compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export async function compilePlan({
const acceptedGraphVersion = /** @type {{graph_version: number}} */ (
pushed.body.project
).graph_version;
const acceptedHeadSourceSha256 = /** @type {{source_sha256: string}} */ (
pushed.body.foundation_plan
).source_sha256;

let status;
onProgress({ phase: "analysis", status: "waiting" });
Expand Down Expand Up @@ -133,7 +136,8 @@ export async function compilePlan({
}
if (
status.body.project.graph_version !== acceptedGraphVersion ||
status.body.analysis.graph_version !== acceptedGraphVersion
status.body.analysis.graph_version !== acceptedGraphVersion ||
status.body.analysis.head_source_sha256 !== acceptedHeadSourceSha256
) {
throw new PlanStatusChangedError(status.body);
}
Expand Down
Loading