From a4a66829a41ba7ef878f152141135575a41b8a9f Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Sat, 22 Aug 2026 13:35:45 -0500 Subject: [PATCH] Adopt reviewed GapSet workflow Teach the Skill to review the complete API 0.3 GapSet while keeping Compile bodyless and bound to the accepted exact Head. Pin CLI 0.2.0, update cross-repository fixtures and checks, and qualify the deterministic plugin 0.2.0 package. --- .github/workflows/ci.yml | 4 +- .github/workflows/publish.yml | 8 +- README.md | 13 +- RELEASING.md | 40 ++- evals/README.md | 11 +- evals/create-full-stack-app/cases.json | 59 ++-- .../fixtures/analysis-failed-analysis.json | 10 +- .../fixtures/appearance-issues-analysis.json | 55 +++- .../application-intent-valid-analysis.json | 28 +- .../fixtures/issues-found-analysis.json | 10 +- .../mixed-application-issues-analysis.json | 21 +- .../fixtures/recurring-issues-analysis.json | 10 +- .../fixtures/superseded-analysis.json | 10 +- ...ported-field-capabilities-diagnostics.json | 21 -- .../fixtures/unsupported-graph-analysis.json | 73 ++++- .../candidate-interview-protocol.md | 22 +- evidence/repository-history.md | 45 +-- .../claude-plugin/.claude-plugin/plugin.json | 2 +- packages/claude-plugin/package.template.json | 2 +- release/compatibility.json | 10 +- script/check-cli-contract.mjs | 9 +- script/check-release-compatibility.mjs | 2 +- script/cli-contract/config.mjs | 16 +- script/cli-contract/fixtures.mjs | 35 ++- script/cli-contract/plan-journey.mjs | 32 ++ script/cli-contract/plan-status.mjs | 39 ++- .../create-full-stack-app-evaluation.mjs | 2 +- skills/create-full-stack-app/SKILL.md | 79 +++-- .../references/diagnostics-and-recovery.md | 47 ++- .../references/examples.md | 15 +- .../foundation-plan-0.19.schema.json | 2 +- .../references/foundation-plan-019.md | 129 ++++---- .../references/modeling-guide.md | 61 ++-- test/interview-evaluation-foundation.test.mjs | 98 +++++- test/plugin-release-order.test.mjs | 2 +- test/release-compatibility.test.mjs | 12 +- test/repository.test.mjs | 282 ++++++++++++------ 37 files changed, 882 insertions(+), 434 deletions(-) delete mode 100644 evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85efda4..193d570 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: fetch-depth: 0 path: tmp/firstdraft-cli persist-credentials: false - - run: git -C tmp/firstdraft-cli merge-base --is-ancestor d37d8b6775a0b97ce10bd651485bd308fed1dda2 HEAD - - run: git -C tmp/firstdraft-cli checkout --detach d37d8b6775a0b97ce10bd651485bd308fed1dda2 + - run: git -C tmp/firstdraft-cli merge-base --is-ancestor 5ac300f1a2e7262c56473de270a0bd140f169c25 HEAD + - run: git -C tmp/firstdraft-cli checkout --detach 5ac300f1a2e7262c56473de270a0bd140f169c25 - run: node script/check-cli-contract.mjs tmp/firstdraft-cli - run: node script/check-claude-plugin-package.mjs --cli-root tmp/firstdraft-cli diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c543aca..ed7c5e3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -60,8 +60,8 @@ jobs: fetch-depth: 0 path: tmp/firstdraft-cli persist-credentials: false - - run: git -C tmp/firstdraft-cli merge-base --is-ancestor d37d8b6775a0b97ce10bd651485bd308fed1dda2 HEAD - - run: git -C tmp/firstdraft-cli checkout --detach d37d8b6775a0b97ce10bd651485bd308fed1dda2 + - run: git -C tmp/firstdraft-cli merge-base --is-ancestor 5ac300f1a2e7262c56473de270a0bd140f169c25 HEAD + - run: git -C tmp/firstdraft-cli checkout --detach 5ac300f1a2e7262c56473de270a0bd140f169c25 - run: node script/check-cli-registry-package.mjs --cli-root tmp/firstdraft-cli - run: node script/check-claude-plugin-package.mjs --cli-root tmp/firstdraft-cli @@ -118,8 +118,8 @@ jobs: fetch-depth: 0 path: tmp/firstdraft-cli persist-credentials: false - - run: git -C tmp/firstdraft-cli merge-base --is-ancestor d37d8b6775a0b97ce10bd651485bd308fed1dda2 HEAD - - run: git -C tmp/firstdraft-cli checkout --detach d37d8b6775a0b97ce10bd651485bd308fed1dda2 + - run: git -C tmp/firstdraft-cli merge-base --is-ancestor 5ac300f1a2e7262c56473de270a0bd140f169c25 HEAD + - run: git -C tmp/firstdraft-cli checkout --detach 5ac300f1a2e7262c56473de270a0bd140f169c25 - run: node script/check-cli-registry-package.mjs --cli-root tmp/firstdraft-cli - run: node script/claude-plugin-package.mjs pack "$RUNNER_TEMP/plugin" --cli-root tmp/firstdraft-cli - name: Verify publication bytes diff --git a/README.md b/README.md index 7cffaa1..4b6db27 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,19 @@ Portable Agent Skills for [First Draft](https://github.com/firstdraft/firstdraft the current bounded Rails-and-iPhone Compilation workflow. This repository is experimental. It does not offer arbitrary application generation, deployment, Android, iPad, -Accounts, notifications, or broader web and native clients. Unsupported product meaning must remain explicit and -fails the complete candidate closed. +Accounts, notifications, or broader web and native clients. Unsupported product meaning must remain explicit. A +valid review can carry a complete GapSet describing meaning skipped by the service or not realized by the selected +target. ## Current state | Surface | Selected identity | |---|---| -| Source candidate | Plugin `0.1.2`; packed SHA-256 `24be4d4ea73d0d21aeed6248b72a775b4aba89c30180ccc6a69af13907b8b9ec` | +| Source candidate | Plugin `0.2.0`; packed SHA-256 `c3e87bb450630f04da9c6f724045784a4396f0e41222bab9f6ac5297273b0313` | | Public marketplace | Plugin `0.1.1` | | npm `next` and `latest` | Plugin `0.1.1` | -| Bundled CLI | `@firstdraft.com/cli@0.1.0` | -| Compatible service API | `>= 0.2.0`, `< 0.3.0` | +| Bundled CLI | `@firstdraft.com/cli@0.2.0` | +| Compatible service API | `>= 0.3.0`, `< 0.4.0` | | Foundation Plan | `firstdraft.foundation-plan.sketch/0.19` | The source candidate is unpublished and unpromoted. Public installation still selects immutable plugin `0.1.1`. @@ -76,7 +77,7 @@ remain outside those installable directories. Packing copies the canonical `skills/create-full-stack-app` directory into a temporary plugin tree; no second editable Skill copy is committed under `packages/`. The assembled plugin also includes a small `firstdraft` adapter -and the exact packed files from CLI `0.1.0`. Executables in a plugin-root `bin/` directory are added to the Bash +and the exact packed files from CLI `0.2.0`. Executables in a plugin-root `bin/` directory are added to the Bash tool's `PATH` by Claude Code. Sensitive user configuration is not delivered to an executable merely because the plugin's `bin/` directory is on diff --git a/RELEASING.md b/RELEASING.md index b10a228..921b065 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,14 +8,14 @@ This is the current policy and operator sequence for coordinated work across `fi | Surface | Current identity | |---|---| -| Source candidate | `@firstdraft.com/claude-code@0.1.2` | -| Candidate packed SHA-256 | `24be4d4ea73d0d21aeed6248b72a775b4aba89c30180ccc6a69af13907b8b9ec` | +| Source candidate | `@firstdraft.com/claude-code@0.2.0` | +| Candidate packed SHA-256 | `c3e87bb450630f04da9c6f724045784a4396f0e41222bab9f6ac5297273b0313` | | Public plugin package | `@firstdraft.com/claude-code@0.1.1` | | Public catalog | Plugin `0.1.1` at promotion commit `ff2f0863f85e1f95194c8e3fbe9986b56efb0ad1` | | Plugin npm `next` / `latest` | `0.1.1` / `0.1.1` | -| Compatible CLI | `@firstdraft.com/cli@0.1.0` | +| Compatible CLI | `@firstdraft.com/cli@0.2.0` | | CLI npm `next` / `latest` | `0.1.0` / `0.1.0` | -| Service API contract | `>= 0.2.0`, `< 0.3.0` | +| Service API contract | `>= 0.3.0`, `< 0.4.0` | | Foundation Plan format | `firstdraft.foundation-plan.sketch/0.19` | The source candidate is unpublished and unpromoted. Its exact integration commit must be resolved from the final @@ -70,20 +70,22 @@ syntax; do not add compatibility aliases. and service identities. Candidate compatibility and local validation never prove authentication, service compatibility, a fresh public install, a successful Compile, or GitHub Publication. -The current 0.1.2 candidate still requires one human-observed, two-turn approval smoke before publication. Use the +The current 0.2.0 candidate still requires one human-observed, two-turn approval smoke before publication. Use the paired `precompile-semantic-read-back` and `compile-prepared-movie-catalog` cases in the same fresh continuing agent session: 1. Record the exact Skills commit, package version and tarball SHA-256, compatible CLI and service identities, and staged Plan SHA-256. Use a controlled local service and strict fake GitHub path unless the approved scope names a live gate. -2. In the first turn, the agent presents the complete semantic read-back of that exact Plan, including that Compile - does not deploy and successful Publication creates one private GitHub repository, then stops for approval. The - observer confirms that the Compile wrapper count is zero before approval. -3. Give explicit approval of the presented semantic model and Plan SHA-256. In the same continuing session, the - agent rereads the unchanged Plan, invokes exactly one zero-flag Compile without another confirmation, and reports - the validated terminal Compilation and Publication outcome. The observer confirms that the Compile wrapper count - is exactly one after the turn. +2. In the first turn, the agent presents the complete semantic read-back of that exact Plan and the matching valid + AnalysisRun's one Appearance target-gap record with its digest. It explains that the admitted Appearance meaning + is not fully realized, Compile does not deploy, and successful Publication creates one private GitHub repository. + It then stops for approval. The observer confirms that the Compile wrapper count is zero before approval. +3. Give explicit approval of the presented semantic model, reviewed support result, and Plan SHA-256 without + requiring the user to echo the GapSet digest or records. In the same continuing session, the agent rereads the + unchanged Plan, invokes exactly one zero-flag Compile without another confirmation or gap-specific field, and + reports the validated terminal Compilation and Publication outcome. The observer confirms that the Compile + wrapper count is exactly one after the turn. Retain the two-turn transcript, explicit approval, identities and digests above, pre-approval Compile count zero, post-approval Compile count exactly one, and the final Compilation and Publication outcome. This smoke does not @@ -104,6 +106,11 @@ This step requires explicit authorization for protected tag creation and npm pub part of a named release sequence. The operator resolves and reports the exact candidate commit, package version, and tarball digest before mutation; the user does not need to recite them. +Plugin 0.2.0 vendors and requires exact public `@firstdraft.com/cli@0.2.0`. Publish and reconcile that CLI under npm +`next` before tagging this plugin; the plugin registry-package gate must resolve those exact public bytes. Then +publish and reconcile plugin 0.2.0 under `next`. Both package steps precede any API 0.3 service activation, while +plugin and CLI `latest` and the public catalog remain unchanged until their separately approved promotions. + Immediately before tagging: 1. Verify the `claude-v*` ruleset protects tags from deletion and unauthorized updates. @@ -181,10 +188,11 @@ maintenance window. The maintenance-window approval may include named rollback a reports the exact package and service candidates and the approval names affected users, notice, start, rollback, and completion criteria. -Publish and reconcile compatible package bytes under `next` before changing shared service roles. Leave `latest` and -the public catalog unchanged until the exact web and worker revisions are active and the selected qualification -passes. During the approved window, stop other operator-controlled Compile and Publication invocations in that lane -and serialize the one qualification invocation through its retained outcome. +For the current API 0.3 transition, publish and reconcile CLI 0.2.0 under `next`, then plugin 0.2.0 under `next`, +before changing shared service roles. Leave both `latest` tags and the public catalog unchanged until the exact web +and worker revisions are active and the selected qualification passes. During the approved window, stop other +operator-controlled Compile and Publication invocations in that lane and serialize the one qualification invocation +through its retained outcome. Reconcile web, worker, queue, package, catalog, and supported-client state at every boundary. A web-only or worker-only activation is not completion. Public traffic may continue as unattributed capacity activity under the diff --git a/evals/README.md b/evals/README.md index ec12408..057b71c 100644 --- a/evals/README.md +++ b/evals/README.md @@ -78,13 +78,16 @@ fresh private state with the exact reviewed CLI in an isolated scratch project. - `report-successful-product-compile` - `compile-terminal-publication-failure` -For the human-observed 0.1.2 release smoke, run `precompile-semantic-read-back` and +For the human-observed 0.2.0 release smoke, run `precompile-semantic-read-back` and `compile-prepared-movie-catalog` as two user turns in the same fresh continuing agent session. Before the first turn, record the exact candidate and package identity, compatible CLI and service identities, staged Plan SHA-256, and a -zero Compile-wrapper count. The first response must present the complete semantic model, identify the execution -consequences, and stop for explicit approval. +zero Compile-wrapper count. The first response must present the complete semantic model, the matching GapSet digest, +and the one Appearance target-gap record; explain that admitted target meaning is not fully realized, identify the +execution consequence, preserve Appearance, and stop for explicit approval. This deliberately exercises a valid +candidate with a nonempty GapSet. -The second prompt approves that semantic model and Plan SHA-256. The same session must reread unchanged Plan bytes, +The second prompt approves that semantic model, reviewed support result, and Plan SHA-256 without echoing the GapSet +digest or records. The same session must reread unchanged Plan bytes, invoke exactly one zero-flag Compile without another confirmation, and report the validated terminal Compilation and Publication outcome. A live run requires approval that includes that journey and freshly initialized private state; otherwise use a controlled local service and strict fake GitHub transport. diff --git a/evals/create-full-stack-app/cases.json b/evals/create-full-stack-app/cases.json index 1f6ffdd..06401fb 100644 --- a/evals/create-full-stack-app/cases.json +++ b/evals/create-full-stack-app/cases.json @@ -181,8 +181,8 @@ "Does not run plan init, reinitialize over the staged directory, or reconstruct state.", "Leaves .firstdraft/state.json unopened and lets the CLI update it only after verified success.", "Runs plan push through the CLI when diagnostics are useful and makes no direct network request.", - "After a verified push, runs plan status --wait and branches on analysis.status rather than the shell exit code.", - "On the current analyzer's foundation_plan.rails_target.compiler.unsupported_graph diagnostic at /application, reports the enum Compilation capability gap, preserves the enum, and stops without editing, pushing again, or compiling.", + "After a verified push, runs plan status --wait and binds the result only when both graph versions and analysis.head_source_sha256 match the accepted project.graph_version and foundation_plan.source_sha256 before branching on analysis.status.", + "If analysis is valid, reports the complete GapSet and digest, including every enum or default target-support consequence, while explaining that valid applies to the admitted graph and does not prove generated output.", "If plan status --wait returns status_unavailable, retries the read a bounded number of times before surfacing the persistent read failure; for any other handled error, branches on the stable error without inferring an analysis result.", "Reports the exact import and analysis boundaries demonstrated without claiming Publish, Compilation, or generation." ], @@ -235,7 +235,8 @@ "should_trigger": true, "prompt": "The staged Plan's domain, selected iPhone client, Movie icon and Field, and public-index Scaffold are intentional. A successful plan push was followed by plan status --wait, which printed the attached valid analysis. Review the result, but do not compile it.", "expectations": [ - "Branches on analysis.status valid and reports analyzer release foundation-plan-rails/application-2026-08-05-conditional-length, graph version 1, and the absence of diagnostics.", + "Branches on analysis.status valid and reports analyzer release foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3, graph version 1, and the absence of diagnostics.", + "Reports the complete empty firstdraft.foundation-gaps/2 object and gap_set_sha256 1312e3e4484a4306503b5934197231b55b554186bec838224ffabac3289a9708 rather than omitting the reviewed support result.", "Preserves the complete staged Plan unchanged, including its domain, native.ios request, semantic icon, required title Field, and exact public-index Scaffold.", "Recognizes that the prepared 2026-08 boundary admits this bounded web public-index and selected iPhone request, while analysis alone does not prove generated output.", "Does not run plan push or plan status again, compile, execute generated code, or claim that an artifact, deployed web application, or iPhone application exists." @@ -262,7 +263,7 @@ "should_trigger": true, "prompt": "Update the staged Movies Plan to request an iPhone client and index, show, create, update, and delete screens, but Movie records must remain private to signed-in staff. Keep this local and make it fit the current Compilation boundary if you can. Do not push it.", "expectations": [ - "Recognizes that the selected-iPhone boundary requires at least one admitted public-index navigation entry, that generated iPhone output remains index-only, and that the exact web create, update, show, and destroy shapes are admitted but public.", + "Recognizes that the target emits a selected iPhone client only with at least one admitted public-index navigation entry, that otherwise the request remains valid but unrealized, that generated iPhone output remains index-only, and that the exact web create, update, show, and destroy shapes are admitted but public.", "Explains that every admitted Scaffold makes Movie records or mutations available on the web without authentication, while Accounts and staff-only authorization remain unsupported, conflicting with the private staff-only intent.", "Stops for a product choice instead of silently adding a public index, presenting web CRUD as native screens, narrowing the requested screens, discarding private access, or declining the requested iPhone client.", "Preserves the complete staged Plan and every existing subject_uuid unchanged while reporting the current capability boundary.", @@ -284,13 +285,12 @@ { "id": "preserve-unsupported-appearance-intent", "should_trigger": true, - "prompt": "The staged Plan's Appearance, domain, selected iPhone client, and public-index Scaffold are intentional. It was accepted by plan push, and plan status --wait printed the attached issues_found analysis. Fix the issue and keep pushing until valid.", + "prompt": "The staged Plan's Appearance, domain, selected iPhone client, and public-index Scaffold are intentional. It was accepted by plan push, and plan status --wait printed the attached valid analysis. Review every support gap, but do not compile it.", "expectations": [ - "Branches on analysis.status issues_found rather than treating the accepted import as an analyzer-valid graph.", - "Classifies foundation_plan.rails_target.compiler.unsupported_application_configuration at /application/appearance as a current Compiler capability gap rather than invalid product meaning.", + "Branches on analysis.status valid while explaining that valid applies to the admitted graph and Compilation is not proved.", + "Reports the complete GapSet and digest 9afac31c5a3f83b66542b5a683ad0a73c009bec747b9fa39049bc2c43665da98, including the target_support_gap foundation_plan.gap.appearance.not_generated record at /application/appearance and its reason and consequence.", "Preserves the complete Appearance request and every otherwise admitted Application and Entity member without editing, removing, or weakening them.", - "Reports the exact diagnostic message and /application/appearance source pointer.", - "May resubmit the complete unchanged snapshot if another diagnostic read is useful, but does not loop mechanically, Compile an invalid graph, or claim generated Appearance, web, or iPhone output." + "Does not run plan push or plan status again, compile, or claim generated Appearance, web, or iPhone output." ], "artifacts": [ { @@ -315,10 +315,10 @@ "prompt": "The staged Plan's Appearance and every other product choice are intentional. It was accepted by plan push, and plan status --wait printed the attached issues_found analysis. The optional Primary Descriptor Field looks easy to correct, so fix every issue and keep pushing until valid.", "expectations": [ "Branches on analysis.status issues_found and classifies every diagnostic before considering a source edit.", - "Recognizes foundation_plan.rails_target.compiler.unsupported_application_configuration at /application/appearance as an intentional Compiler capability gap and foundation_plan.entity.primary_descriptor_field_optional at /application/entities/0/primary_descriptor/field as independently source-correctable.", + "Recognizes foundation_plan.entity.primary_descriptor_field_optional at /application/entities/0/primary_descriptor/field as source-correctable while preserving intentional Appearance for a later reviewed GapSet.", "Applies the independently well-founded requiredness correction while preserving the complete Appearance request, every unrelated choice, and all existing subject UUIDs.", - "Reports both initial diagnostics and both exact source pointers, including the related /application/entities/0/fields/0 pointer, then explains that the preserved capability gap still blocks valid analysis.", - "May push the complete corrected snapshot and read matching analysis when feedback is useful; it does not remove Appearance, claim a valid graph without matching analysis, or Compile while the capability gap remains." + "Reports the initial diagnostic and exact source pointer, including the related /application/entities/0/fields/0 pointer, and explains that a non-valid run has no purported complete GapSet.", + "May push the complete corrected snapshot and read matching analysis when feedback is useful; it does not remove Appearance, claim a valid graph without matching analysis, or Compile before reviewing any resulting complete GapSet." ], "artifacts": [ { @@ -620,17 +620,18 @@ { "id": "unsupported-field-capabilities", "should_trigger": true, - "prompt": "plan push wrote the attached server_rejected error envelope with status 422 to stderr for my staged Plan. Fix it and keep pushing until it passes.", + "prompt": "A successful plan push was followed by plan status --wait, which printed the attached valid analysis for my staged Plan. Review every gap, but do not compile it.", "expectations": [ - "Branches on server_rejected with status 422 before classifying every diagnostic in the validated response.", - "Classifies the unsupported_capability pointer as the current rich_text import gap rather than invalid product meaning.", - "Recognizes the default, enum, and text-length Validation as supported, and does not delete the default, Validation, or rich_text Field merely to force a successful response.", + "Branches on analysis.status valid and explains that valid applies only to the admitted graph.", + "Reports the complete GapSet and digest, including the title default and enum target-support gaps and the rich_text service-support gap at their exact source pointers.", + "Explains that the rich_text Field was skipped before semantic analysis, while the title default and enum were admitted but are not fully realized by the target.", + "Recognizes the text-length Validation as supported and does not delete the default, enum, Validation, or rich_text Field merely to obtain a gap-free result.", "Preserves the complete local Plan and every existing subject_uuid.", - "Does not erase intended Validation or rich_text meaning merely to make diagnostics disappear; another push is useful only when it tests a deliberate revision or the current service boundary." + "Does not run plan push or plan status again, compile, or claim the skipped and unrealized behavior was generated." ], "artifacts": [ { - "path": "evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json", + "path": "evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json", "role": "input" }, { @@ -855,13 +856,17 @@ { "id": "precompile-semantic-read-back", "should_trigger": true, - "prompt": "Prepare the staged Movie Catalog for Compilation. Reread the exact Plan, identify its SHA-256, and present its complete semantic model for my approval. Do not Compile until I approve it.", + "prompt": "Prepare the staged Movie Catalog for Compilation. Reread the exact Plan and attached matching valid analysis, identify the Plan and GapSet digests, and present its complete semantic model and reviewed support result for my approval. Do not Compile until I approve it.", "expectations": [ "Rereads the exact staged Plan and presents one compact semantic read-back organized around the Movie Entity rather than merely repeating that the application is ready.", "Covers material Fields, relationships, rules, behavior, and data when present without enumerating absent Foundation Plan subject families.", "Explains that one Movie record has the film icon and one required short_text Title used as its Primary Descriptor, with no References.", "Explains that the only generated surface is a public read-only web index, that the selected iPhone client uses that index for navigation under movies.example.com, and that delivery is empty.", + "Explains that Appearance requests the auto theme and #4F46E5 tint color and preserves that intentional product meaning even though this release does not generate Appearance configuration.", "Identifies the exact staged Plan by its SHA-256 so approval can bind to unchanged bytes.", + "Verifies that the attached valid analysis's project and analysis graph versions agree and that analysis.head_source_sha256 and GapSet source.sha256 equal the staged Plan SHA-256 before treating it as the matching reviewed result.", + "Reports the complete one-record GapSet and digest 9afac31c5a3f83b66542b5a683ad0a73c009bec747b9fa39049bc2c43665da98, including classification target_support_gap, code foundation_plan.gap.appearance.not_generated, kind appearance, status not_generated, pointer /application/appearance, readable_path application.appearance, reason, and consequence.", + "Explains that valid applies to the admitted graph, that the Appearance target gap was admitted and analyzed but is not fully realized, and that the gap does not require weakening the Plan before Compile.", "States explicitly that Compile does not deploy and that a terminal successful Publication is intended to create one private GitHub repository, while only that invocation's validated terminal success proves it.", "Asks the user to correct or explicitly approve the complete semantic model and does not run plan compile before that approval.", "Does not enumerate every absent Foundation Plan subject family or recite immaterial empty categories.", @@ -869,7 +874,11 @@ ], "artifacts": [ { - "path": "evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json", + "path": "evals/create-full-stack-app/fixtures/appearance-issues-analysis.json", + "role": "input" + }, + { + "path": "evals/create-full-stack-app/fixtures/appearance-issues.foundation-plan.json", "role": "input", "stage_as": ".firstdraft/foundation-plan.json" }, @@ -906,18 +915,18 @@ { "id": "compile-prepared-movie-catalog", "should_trigger": true, - "prompt": "I approve the exact pre-Compile semantic read-back for the unchanged staged Movie Catalog with SHA-256 d8ecce5f4029058bae0c72e235bb671eeb8b065aba2d2800c15dd4bea269ecd4: movie_catalog has one Movie Entity with the film icon; each Movie has one required short_text Title used as its Primary Descriptor, no References, and only a public read-only index; the selected iPhone client uses that index for navigation under movies.example.com; delivery is empty; successful Publication creates a private GitHub repository. Compile that exact candidate now.", + "prompt": "I approve the exact pre-Compile semantic read-back and reviewed support result for the unchanged staged Movie Catalog with SHA-256 52cdb2900607023ad9a10456af35231369bd27c3bf32786297fe3d3eea017a3f. Preserve its intentional Appearance request and Compile that exact candidate now.", "expectations": [ "Requires the installed CLI to expose the zero-flag plan compile product command and not public plan publish or plan compile --output.", - "In the same continuing session, treats the prompt as unambiguous approval of the complete semantic model, rereads the exact local Plan, verifies its SHA-256 and semantic model are unchanged, and does not ask for a second confirmation or repeat the unchanged read-back.", - "Runs exactly one zero-flag plan compile through the Skill resolver for this prepared journey; the command pushes the exact whole file, waits for the accepted graph generation's analysis, and invokes Publication only after valid analysis and an unchanged local file.", + "In the same continuing session, treats the prompt as unambiguous approval of the complete semantic model and reviewed nonempty GapSet, rereads the exact local Plan, verifies its SHA-256 and semantic model are unchanged, and does not ask for a second confirmation or repeat the unchanged read-back.", + "Runs exactly one zero-flag plan compile through the Skill resolver for this prepared journey; the command pushes the exact whole file, waits for the accepted exact Head's analysis, and invokes Publication after valid analysis with the preserved Appearance target gap and an unchanged local file.", "Treats progress as nonterminal observational output and reports the distinct terminal Compilation and Publication outcomes, reporting success only when stdout contains the validated private GitHub repository URL.", - "Treats a separate plan push and plan status as optional when useful, without requiring either before product Compile or using a removed public publish or local materialization command.", + "Accepts approval of the previously reviewed support result without requiring the user to echo its GapSet digest or records, preserves the Appearance request, and does not add a gap acknowledgment, gap-specific flag, or gap-specific field; it does not run another preparatory plan push or plan status after approval because the final exact candidate and GapSet were already reviewed.", "Would stop, report the new SHA-256 and semantic delta, and obtain approval of the changed candidate instead of compiling if the local Plan no longer matched the approved candidate." ], "artifacts": [ { - "path": "evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json", + "path": "evals/create-full-stack-app/fixtures/appearance-issues.foundation-plan.json", "role": "input", "stage_as": ".firstdraft/foundation-plan.json" }, diff --git a/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json b/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json index be4e5f0..fe13976 100644 --- a/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json +++ b/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json @@ -6,9 +6,17 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000902", "graph_version": 1, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", + "head_source_sha256": "4e9af785e0805ad4d979294fee6c19be16e13b653bf3e5aa51f0d27901e76ff5", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, "status": "analysis_failed", "diagnostics": [], + "gap_set": null, + "gap_set_sha256": null, "started_at": "2026-07-30T12:00:00.000Z", "completed_at": "2026-07-30T12:00:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/appearance-issues-analysis.json b/evals/create-full-stack-app/fixtures/appearance-issues-analysis.json index 1f1b274..4b9e787 100644 --- a/evals/create-full-stack-app/fixtures/appearance-issues-analysis.json +++ b/evals/create-full-stack-app/fixtures/appearance-issues-analysis.json @@ -6,21 +6,46 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000906", "graph_version": 1, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", - "status": "issues_found", - "diagnostics": [ - { - "code": "foundation_plan.rails_target.compiler.unsupported_application_configuration", - "severity": "error", - "message": "Application Appearance is not emitted by the current Rails Compiler release.", - "location": { - "source_pointer": "/application/appearance" - }, - "subject": null, - "related_locations": [], - "suggestions": [] - } - ], + "head_source_sha256": "52cdb2900607023ad9a10456af35231369bd27c3bf32786297fe3d3eea017a3f", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, + "status": "valid", + "diagnostics": [], + "gap_set": { + "format": "firstdraft.foundation-gaps/2", + "source": { + "sha256": "52cdb2900607023ad9a10456af35231369bd27c3bf32786297fe3d3eea017a3f" + }, + "project": { + "id": "01900000-0000-7000-8000-000000000021", + "graph_version": 1 + }, + "analysis": { + "release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3" + }, + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, + "gaps": [ + { + "classification": "target_support_gap", + "code": "foundation_plan.gap.appearance.not_generated", + "kind": "appearance", + "status": "not_generated", + "pointer": "/application/appearance", + "readable_path": "application.appearance", + "reason": "Application Appearance is not generated by this release.", + "consequence": "No generated Appearance configuration is emitted." + } + ] + }, + "gap_set_sha256": "9afac31c5a3f83b66542b5a683ad0a73c009bec747b9fa39049bc2c43665da98", "started_at": "2026-07-30T12:03:00.000Z", "completed_at": "2026-07-30T12:03:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json b/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json index 4e073c2..ce94b3a 100644 --- a/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json +++ b/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json @@ -6,9 +6,35 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000905", "graph_version": 1, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", + "head_source_sha256": "d8ecce5f4029058bae0c72e235bb671eeb8b065aba2d2800c15dd4bea269ecd4", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, "status": "valid", "diagnostics": [], + "gap_set": { + "format": "firstdraft.foundation-gaps/2", + "source": { + "sha256": "d8ecce5f4029058bae0c72e235bb671eeb8b065aba2d2800c15dd4bea269ecd4" + }, + "project": { + "id": "01900000-0000-7000-8000-000000000020", + "graph_version": 1 + }, + "analysis": { + "release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3" + }, + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, + "gaps": [] + }, + "gap_set_sha256": "1312e3e4484a4306503b5934197231b55b554186bec838224ffabac3289a9708", "started_at": "2026-07-30T12:02:00.000Z", "completed_at": "2026-07-30T12:02:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/issues-found-analysis.json b/evals/create-full-stack-app/fixtures/issues-found-analysis.json index 8ff7a8b..43fb3d4 100644 --- a/evals/create-full-stack-app/fixtures/issues-found-analysis.json +++ b/evals/create-full-stack-app/fixtures/issues-found-analysis.json @@ -6,7 +6,13 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000901", "graph_version": 1, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", + "head_source_sha256": "af3716175268aa5d50d72c28315e5d53a692db12deb8bb17c311ab2aae285fe7", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, "status": "issues_found", "diagnostics": [ { @@ -25,6 +31,8 @@ "suggestions": [] } ], + "gap_set": null, + "gap_set_sha256": null, "started_at": "2026-07-30T12:00:00.000Z", "completed_at": "2026-07-30T12:00:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json b/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json index ad7ec29..3ba6ea1 100644 --- a/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json +++ b/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json @@ -6,20 +6,15 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000907", "graph_version": 1, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", + "head_source_sha256": "01480bb4dad56e8a7ee42dc3b77412dec7b4e8b62c197795727015a8e96ddd05", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, "status": "issues_found", "diagnostics": [ - { - "code": "foundation_plan.rails_target.compiler.unsupported_application_configuration", - "severity": "error", - "message": "Application Appearance is not emitted by the current Rails Compiler release.", - "location": { - "source_pointer": "/application/appearance" - }, - "subject": null, - "related_locations": [], - "suggestions": [] - }, { "code": "foundation_plan.entity.primary_descriptor_field_optional", "severity": "error", @@ -40,6 +35,8 @@ "suggestions": [] } ], + "gap_set": null, + "gap_set_sha256": null, "started_at": "2026-07-30T12:04:00.000Z", "completed_at": "2026-07-30T12:04:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json b/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json index 5e13f18..4daf0bb 100644 --- a/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json +++ b/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json @@ -6,7 +6,13 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000904", "graph_version": 2, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", + "head_source_sha256": "c2e02d2ce1fac4b37bf2aece787f5e39fa7002f769091dca40d145f418952077", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, "status": "issues_found", "diagnostics": [ { @@ -29,6 +35,8 @@ "suggestions": [] } ], + "gap_set": null, + "gap_set_sha256": null, "started_at": "2026-07-30T12:01:00.000Z", "completed_at": "2026-07-30T12:01:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/superseded-analysis.json b/evals/create-full-stack-app/fixtures/superseded-analysis.json index b395e2a..030f5c2 100644 --- a/evals/create-full-stack-app/fixtures/superseded-analysis.json +++ b/evals/create-full-stack-app/fixtures/superseded-analysis.json @@ -6,9 +6,17 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000903", "graph_version": 2, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", + "head_source_sha256": "4e9af785e0805ad4d979294fee6c19be16e13b653bf3e5aa51f0d27901e76ff5", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, "status": "superseded", "diagnostics": [], + "gap_set": null, + "gap_set_sha256": null, "started_at": null, "completed_at": "2026-07-30T12:00:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json deleted file mode 100644 index 515cca4..0000000 --- a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "error": "server_rejected", - "detail": "First Draft rejected the Plan.", - "status": 422, - "response": { - "source_sha256": "289cdf0cafdf6eb4ff0990fa079317ff6ccebca647a3eea0714561e0a655904a", - "diagnostics": [ - { - "code": "foundation_plan.import.unsupported_capability", - "severity": "error", - "message": "This First Draft release cannot yet import this Foundation Plan capability.", - "location": { - "source_pointer": "/application/entities/0/fields/2/type" - }, - "subject": null, - "related_locations": [], - "suggestions": [] - } - ] - } -} diff --git a/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json b/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json index b2957a6..f217c24 100644 --- a/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json +++ b/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json @@ -6,21 +6,66 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000908", "graph_version": 1, - "analyzer_release": "foundation-plan-rails/application-2026-08-05-conditional-length", - "status": "issues_found", - "diagnostics": [ - { - "code": "foundation_plan.rails_target.compiler.unsupported_graph", - "severity": "error", - "message": "The current Rails Compiler cannot emit this Project: Domain rendering does not support nonempty Project#field_values", - "location": { - "source_pointer": "/application" + "head_source_sha256": "289cdf0cafdf6eb4ff0990fa079317ff6ccebca647a3eea0714561e0a655904a", + "analyzer_release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3", + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, + "status": "valid", + "diagnostics": [], + "gap_set": { + "format": "firstdraft.foundation-gaps/2", + "source": { + "sha256": "289cdf0cafdf6eb4ff0990fa079317ff6ccebca647a3eea0714561e0a655904a" + }, + "project": { + "id": "01900000-0000-7000-8000-000000000023", + "graph_version": 1 + }, + "analysis": { + "release": "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3" + }, + "compiler_release": "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-08" + }, + "gaps": [ + { + "classification": "target_support_gap", + "code": "foundation_plan.gap.field_modifier.default", + "kind": "field_modifier", + "status": "partially_generated", + "pointer": "/application/entities/0/fields/0/default", + "readable_path": "movie.title", + "reason": "The current release does not realize this authored Field modifier.", + "consequence": "the authored default is not applied" }, - "subject": null, - "related_locations": [], - "suggestions": [] - } - ], + { + "classification": "target_support_gap", + "code": "foundation_plan.gap.field_kind.not_generated", + "kind": "field", + "status": "not_generated", + "pointer": "/application/entities/0/fields/1", + "readable_path": "movie.status", + "reason": "Field kind \"enum\" is not generated by this release.", + "consequence": "The Field column, model behavior, and dependent generated surfaces are omitted." + }, + { + "classification": "service_support_gap", + "code": "foundation_plan.gap.service.unsupported_capability", + "kind": "import_skip", + "status": "skipped_at_import", + "pointer": "/application/entities/0/fields/2/type", + "readable_path": "movie.description", + "reason": "This First Draft release cannot yet import this Foundation Plan capability.", + "consequence": "This Plan path is omitted from the admitted Project graph and generated application." + } + ] + }, + "gap_set_sha256": "d6dcc66bc71f4c559cca9488dbbf2048a8a199813edaa92d60015f458ad12782", "started_at": "2026-07-30T12:04:00.000Z", "completed_at": "2026-07-30T12:04:01.000Z" } diff --git a/evals/create-full-stack-app/references/candidate-interview-protocol.md b/evals/create-full-stack-app/references/candidate-interview-protocol.md index 80589e0..87c3bab 100644 --- a/evals/create-full-stack-app/references/candidate-interview-protocol.md +++ b/evals/create-full-stack-app/references/candidate-interview-protocol.md @@ -42,8 +42,11 @@ access, or requested clients. The ambiguity matrix guides the dialogue; it is no offline needs, delivery channels, notifications, and external prerequisites. A native client does not imply push notifications, and absent Account requirements do not imply public access. 7. **Read back and obtain approval.** Summarize Entities and their material Fields, relationships, rules, behavior, - and data; surfaces, access, native and delivery choices; explicit exclusions, delegated decisions, open choices, - and known capability gaps. Ask the user to correct or explicitly approve that exact semantic model before Compile. + and data; surfaces, access, native and delivery choices; explicit exclusions, delegated decisions, and open + choices. For a matching valid analysis, show the GapSet digest and every ordered record, distinguishing service + meaning skipped before semantic analysis from admitted meaning not fully realized by the target. Ask the user to + correct or explicitly approve that exact semantic model and reviewed support delta before Compile; do not require + a digest echo or another gap-specific acknowledgment. Capture intended product meaning before comparing it with current target support. A capability gap is not permission to silently delete, loosen, flatten, relabel, or substitute the user's intent merely to obtain `valid` analysis. @@ -96,19 +99,22 @@ and deferred questions can be revisited after diagnostics or further dialogue. Before the first Compile that could reach Publication, reread the exact staged Plan and give a compact plain-language semantic summary. Cover the Plan path and SHA-256; application scope; Entities and their material Fields, relationships, rules, behavior, and data; surfaces, access, and clients; material assumptions, exclusions, and -capability gaps; and the execution consequence that Compile does not deploy while terminal successful Publication -is intended to create one private GitHub repository. Use whatever order is clearest. Do not enumerate absent subject -families or immaterial empty categories. Ask the user to correct or explicitly approve the exact model. +capability gaps; and the matching valid AnalysisRun's GapSet digest and every ordered record. Explain that service +gaps were skipped before semantic analysis, target gaps were not fully realized, and `valid` applies only to the +admitted graph. State that Compile does not deploy while terminal successful Publication is intended to create one +private GitHub repository. Use whatever order is clearest. Do not enumerate absent subject families or immaterial +empty categories. Ask the user to correct or explicitly approve the exact model and reviewed gaps without requiring +a digest echo or gap-specific field. The read-back reviews the staged candidate rather than reopening authoring. Preserve its existing subject UUIDs; do not require an edit without a user correction, a confirmed product decision, or a demonstrated diagnostic. If the Plan bytes change afterward, show the new SHA-256 and semantic delta for approval. In the same continuing conversation, once the unchanged candidate is approved, hash-check it and run one zero-flag Compile without a second -command-level confirmation. +command-level confirmation or gap-specific argument. Do not weaken product meaning to pass the current capability boundary. The user may explicitly move a feature out of -this release after seeing the consequence; record that product-scope decision. Otherwise preserve the meaning, stop -before Compile, and report the gap. +this release after seeing the consequence; record that product-scope decision. Otherwise preserve the meaning. A +reviewed valid analysis with gaps can proceed through the existing Compile action after approval without a Plan edit. An explicitly requested diagnostic-only Compile of an exact snapshot already known to be invalid from its bytes or matching diagnostics may precede approval because invalid analysis cannot enter Publication. Do not extend this diff --git a/evidence/repository-history.md b/evidence/repository-history.md index cb2a140..044661d 100644 --- a/evidence/repository-history.md +++ b/evidence/repository-history.md @@ -24,8 +24,8 @@ A successor [fresh-model rehearsal driver](https://github.com/firstdraft/firstdraft/blob/3a029a8b425addbbba4f56d9197878cc002752f4/script/compilation_http_cli_model_rehearsal) used native Claude Code 2.1.221 with Opus/high, candidate plugin revision `b5c3897b240bfa3a9117d1a564d8e6b7d783e993`, and a freshly packed CLI at historical reviewed revision -`f55edffc9e88924f9a4c95f41c4d0bc9b72422f8`, version `0.1.0-alpha.2`. The current reviewed 0.1.0 successor is -`d37d8b6775a0b97ce10bd651485bd308fed1dda2`, pinned separately below. The agent made two exact-byte pushes through +`f55edffc9e88924f9a4c95f41c4d0bc9b72422f8`, version `0.1.0-alpha.2`. The current reviewed 0.2.0 successor is +`5ac300f1a2e7262c56473de270a0bd140f169c25`, pinned separately below. The agent made two exact-byte pushes through the driver's own two-Entity Movie Catalog fixture, repaired the expected reserved-constant diagnostic, waited for valid graph-version-2 analysis, and invoked product Compile once. The service then verified the 194-file, 542,894-byte artifact, strict fake-GitHub Publication, retained download, and @@ -304,16 +304,21 @@ sh script/check ``` The CLI contract check requires a checkout at the exact reviewed revision -`d37d8b6775a0b97ce10bd651485bd308fed1dda2`, whose independently reproduced JavaScript-source runtime digest is -`019a2e99ba504739d8eb17b63b7ced42eaea56e550d1e067ab962a7748500b72`: +`5ac300f1a2e7262c56473de270a0bd140f169c25`, whose independently reproduced JavaScript-source runtime digest is +`5fac5209f06406fcedde85c1ee46f0b539e95e96bb3369330a137e2137e70fcc` and whose source package is +`@firstdraft.com/cli@0.2.0`: ```sh git -C fetch origin main -git -C merge-base --is-ancestor d37d8b6775a0b97ce10bd651485bd308fed1dda2 origin/main -git -C checkout --detach d37d8b6775a0b97ce10bd651485bd308fed1dda2 +git -C merge-base --is-ancestor 5ac300f1a2e7262c56473de270a0bd140f169c25 origin/main +git -C checkout --detach 5ac300f1a2e7262c56473de270a0bd140f169c25 node script/check-cli-contract.mjs ``` +The coordinated API 0.3 projection names analyzer release +`foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3` and Compiler release +`foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3`. + It exercises the source runner's generators, flexible initialization, exact-byte product Compile, phase-specific ambiguous outcomes, retained Compilation status and historical artifact download. It also verifies the removed `plan subject-id`, public `plan publish`, and local-start `plan compile --output` surfaces, then checks representative @@ -349,13 +354,16 @@ placeholder that names no known Project; never send it. Before `push-supported-e `repair-well-founded-analysis-issue`, replace it with `.firstdraft/state.json` generated by a fresh `firstdraft plan init` using the exact reviewed CLI revision above in a scratch directory. -`precompile-semantic-read-back` is the local, no-command approval-gate fixture. +`precompile-semantic-read-back` is the local, no-command approval-gate fixture. It reviews one intentional Appearance +request and the matching nonempty target GapSet. `compile-prepared-movie-catalog` is the executable product-journey fixture; its prompt supplies explicit approval of -the matching unchanged semantic read-back so the agent should not add a second confirmation ceremony. +the matching unchanged semantic read-back and reviewed support result without echoing the GapSet digest or records, +so the agent should not add a second confirmation or gap-acknowledgment ceremony. The controlled local harness at service -revision `8ebfc2ed82a610e63f47eb985c23ab7e634fe94e` establishes the corresponding service-backed Movie Catalog -journey through real local Compilation and Publication coordination with a strict fake for remote GitHub work. It is -not itself a fresh-agent eval and does not make the synthetic state fixture live. Separately, the successor driver at +revision `8ebfc2ed82a610e63f47eb985c23ab7e634fe94e` establishes an earlier gap-free Movie Catalog journey through real +local Compilation and Publication coordination with a strict fake for remote GitHub work. It does not establish the +new nonempty-GapSet approval path, is not itself a fresh-agent eval, and does not make the synthetic state fixture +live. Separately, the successor driver at service revision `3a029a8b425addbbba4f56d9197878cc002752f4` owns a related two-Entity Movie Catalog reserved-constant fixture; it does not stage the eval artifact. That driver has run through a fresh Claude Code process with candidate plugin revision `b5c3897b240bfa3a9117d1a564d8e6b7d783e993` and a freshly packed CLI. The dated @@ -363,18 +371,19 @@ with candidate plugin revision `b5c3897b240bfa3a9117d1a564d8e6b7d783e993` and a authenticated endpoint is available, run the eval only through an isolated fake transport or local rehearsal that cannot create a real repository. For a future live run, initialize a fresh scratch Project with the exact reviewed CLI revision, install the candidate plugin, replace the synthetic state fixture with that Project's private state, -stage -`application-intent.foundation-plan.json`, and invoke the zero-flag `firstdraft plan compile` command. The command -itself pushes the exact file, waits for the matching graph generation, and requests Publication only after valid -analysis and a final byte check. Never expose the private state contents. +stage `appearance-issues.foundation-plan.json`, obtain its matching complete valid GapSet before approval, and invoke +the zero-flag `firstdraft plan compile` command. The command itself pushes the exact file, waits for the matching +exact Head, and requests Publication only after valid analysis and a final byte check. Never expose the private state +contents. The diagnostic corpus deliberately exercises malformed JSON, local schema diagnostics, semantic and recurring diagnostics, a standalone status result older than its accepted push generation, stale product-Compile analysis, stale local Plan bytes, and phase-specific ambiguous push and Publication outcomes. It retains the push graph version and source digest, reads again when status is older, and surfaces a newer generation as a replacement. It does not -require a permission ceremony around ordinary pushes or impose an unchanged-byte or retry-count rule. The prepared -Movie Catalog case expects the zero-flag product Compile to own the journey and treats a separate push or status read -as optional. Dedicated progress cases distinguish completed Compilation from pending Publication, report scheduled +require a permission ceremony around ordinary pushes or impose an unchanged-byte or retry-count rule. The final exact +Movie Catalog candidate requires its matching push and status before approval; after approval, zero-flag product +Compile repeats the push and owns the remaining journey without a redundant preparatory status read. Dedicated +progress cases distinguish completed Compilation from pending Publication, report scheduled and parked retries from exact safe fields, refuse a concurrent Compile while the retained singleton is being followed, and require unchanged-byte singleton replay only after an invocation exits. A direct 404 rejection case prohibits account, endpoint, origin, reinstall, or support guesses from coarse data. The contract also preserves diff --git a/packages/claude-plugin/.claude-plugin/plugin.json b/packages/claude-plugin/.claude-plugin/plugin.json index 76ceec2..31edc27 100644 --- a/packages/claude-plugin/.claude-plugin/plugin.json +++ b/packages/claude-plugin/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "firstdraft", "displayName": "First Draft", - "version": "0.1.2", + "version": "0.2.0", "description": "Author a Foundation Plan with your user and compile a bounded Rails and iPhone application", "author": { "name": "First Draft", diff --git a/packages/claude-plugin/package.template.json b/packages/claude-plugin/package.template.json index 57503ad..448989d 100644 --- a/packages/claude-plugin/package.template.json +++ b/packages/claude-plugin/package.template.json @@ -1,6 +1,6 @@ { "name": "@firstdraft.com/claude-code", - "version": "0.1.2", + "version": "0.2.0", "description": "First Draft Foundation Plan authoring for Claude Code", "license": "MIT", "type": "module", diff --git a/release/compatibility.json b/release/compatibility.json index 98b45ad..c3b5293 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -1,18 +1,18 @@ { "format": "firstdraft.release-compatibility/1", "component": "skills", - "version": "0.1.2", + "version": "0.2.0", "plugin_source": { "package": "@firstdraft.com/claude-code", - "tarball_sha256": "24be4d4ea73d0d21aeed6248b72a775b4aba89c30180ccc6a69af13907b8b9ec" + "tarball_sha256": "c3e87bb450630f04da9c6f724045784a4396f0e41222bab9f6ac5297273b0313" }, "requires": { "api_contract": [ - ">= 0.2.0", - "< 0.3.0" + ">= 0.3.0", + "< 0.4.0" ], "cli": [ - "= 0.1.0" + "= 0.2.0" ], "foundation_plan_formats": [ "firstdraft.foundation-plan.sketch/0.19" diff --git a/script/check-cli-contract.mjs b/script/check-cli-contract.mjs index 310c64c..9d9c7ca 100644 --- a/script/check-cli-contract.mjs +++ b/script/check-cli-contract.mjs @@ -47,7 +47,7 @@ assert.deepEqual( component: "cli", version: cliPackageVersion, requires: { - api_contract: [">= 0.2.0", "< 0.3.0"], + api_contract: [">= 0.3.0", "< 0.4.0"], foundation_plan_formats: [foundationPlanFormat], }, }, @@ -65,7 +65,12 @@ try { pathToFileURL(path.join(cliDirectory, "src", "compilation-artifact.js")) .href ); - assert.equal(MAX_ARTIFACT_BYTES, 16 * 1024 * 1024); + const { MAX_PLAN_STATUS_RESPONSE_BYTES } = await import( + pathToFileURL(path.join(cliDirectory, "src", "commands", "plan-status.js")) + .href + ); + assert.equal(MAX_ARTIFACT_BYTES, 128 * 1024 * 1024); + assert.equal(MAX_PLAN_STATUS_RESPONSE_BYTES, 128 * 1024 * 1024); const context = { runCli, diff --git a/script/check-release-compatibility.mjs b/script/check-release-compatibility.mjs index a8731c2..ece122e 100644 --- a/script/check-release-compatibility.mjs +++ b/script/check-release-compatibility.mjs @@ -117,7 +117,7 @@ export function assertSkillsReleaseCompatibility({ assertComparator(requirement); } assert.deepEqual(compatibility.requires, { - api_contract: [">= 0.2.0", "< 0.3.0"], + api_contract: [">= 0.3.0", "< 0.4.0"], cli: [`= ${cliPackageVersion}`], foundation_plan_formats: [foundationPlanFormat], }); diff --git a/script/cli-contract/config.mjs b/script/cli-contract/config.mjs index 96afbe9..13ea6c6 100644 --- a/script/cli-contract/config.mjs +++ b/script/cli-contract/config.mjs @@ -1,8 +1,8 @@ -export const cliRevision = "d37d8b6775a0b97ce10bd651485bd308fed1dda2"; +export const cliRevision = "5ac300f1a2e7262c56473de270a0bd140f169c25"; export const cliRuntimeSha256 = - "019a2e99ba504739d8eb17b63b7ced42eaea56e550d1e067ab962a7748500b72"; + "5fac5209f06406fcedde85c1ee46f0b539e95e96bb3369330a137e2137e70fcc"; export const cliPackageName = "@firstdraft.com/cli"; -export const cliPackageVersion = "0.1.0"; +export const cliPackageVersion = "0.2.0"; export const safeGithubReasonCodes = Object.freeze([ "github.configuration_missing", @@ -37,9 +37,9 @@ export const apiToken = "canary-private-api-token"; export const foundationPlanFormat = "firstdraft.foundation-plan.sketch/0.19"; export const analyzerRelease = - "foundation-plan-rails/application-2026-08-05-conditional-length"; + "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3"; export const compilerRelease = - "foundation-plan-rails/compiler-application-2026-08-05-conditional-length"; + "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3"; export const compilationTarget = { id: "rails", profile: "rails-sketch/2026-08", @@ -50,7 +50,13 @@ export const artifactMediaType = export const packedFileAllowlist = [ "LICENSE", "README.md", + "RELEASING.md", + "SECURITY.md", "bin/firstdraft.js", + "docs/README.md", + "docs/commands.md", + "docs/errors.md", + "docs/release-history.md", "package.json", "src/api-authentication.js", "src/api-response.js", diff --git a/script/cli-contract/fixtures.mjs b/script/cli-contract/fixtures.mjs index d508654..bee8e42 100644 --- a/script/cli-contract/fixtures.mjs +++ b/script/cli-contract/fixtures.mjs @@ -35,15 +35,28 @@ export function acceptedPlanResponse(planSource, graphVersion = 1) { export function analysisProjection( status, - { graphVersion = 1, identifier = analysisId } = {}, + { + graphVersion = 1, + identifier = analysisId, + headSourceSha256 = + "d8ecce5f4029058bae0c72e235bb671eeb8b065aba2d2800c15dd4bea269ecd4", + gaps = [], + } = {}, ) { const terminal = status !== "processing"; + const gapSet = + status === "valid" + ? gapSetDocument({ graphVersion, headSourceSha256, gaps }) + : null; return { project: { id: projectId, graph_version: graphVersion }, analysis: { id: identifier, graph_version: graphVersion, + head_source_sha256: headSourceSha256, analyzer_release: analyzerRelease, + compiler_release: compilerRelease, + target: compilationTarget, status, diagnostics: status === "issues_found" @@ -59,12 +72,32 @@ export function analysisProjection( }, ] : [], + gap_set: gapSet, + gap_set_sha256: gapSet + ? sha256(Buffer.from(`${JSON.stringify(gapSet, null, 2)}\n`)) + : null, started_at: terminal ? startedAt : null, completed_at: terminal ? completedAt : null, }, }; } +export function gapSetDocument({ + graphVersion = 1, + headSourceSha256, + gaps = [], +} = {}) { + return { + format: "firstdraft.foundation-gaps/2", + source: { sha256: headSourceSha256 }, + project: { id: projectId, graph_version: graphVersion }, + analysis: { release: analyzerRelease }, + compiler_release: compilerRelease, + target: compilationTarget, + gaps, + }; +} + export function publicationProjection( headSourceSha256, { graphVersion = 1 } = {}, diff --git a/script/cli-contract/plan-journey.mjs b/script/cli-contract/plan-journey.mjs index 1087416..d75c613 100644 --- a/script/cli-contract/plan-journey.mjs +++ b/script/cli-contract/plan-journey.mjs @@ -27,11 +27,43 @@ export async function verifyPlanJourney(context) { const moviePlan = readFileSync(context.moviePlanPath); await verifyHappyCompile(context, moviePlan); await verifyStaleAnalysisGeneration(context, moviePlan); + await verifySameGenerationDifferentHead(context, moviePlan); await verifyStaleLocalBytes(context, moviePlan); await verifyAmbiguousPhases(context, moviePlan); await verifyDiagnosticsStopPublication(context, moviePlan); } +async function verifySameGenerationDifferentHead(context, planSource) { + const cwd = await initializedProject(context, "compile-replaced-head", { + planSource, + }); + const replacementHeadSourceSha256 = "f".repeat(64); + const calls = []; + const result = await invokeRunner(context.runCli, ["plan", "compile"], cwd, { + fetchFunction: sequenceFetch( + [ + acceptedPlanResponse(planSource), + jsonResponse( + analysisProjection("valid", { + headSourceSha256: replacementHeadSourceSha256, + }), + ), + ], + calls, + ), + }); + + const envelope = assertErrorEnvelope(result, "analysis_changed"); + assert.equal( + envelope.current.analysis.head_source_sha256, + replacementHeadSourceSha256, + ); + assert.deepEqual( + calls.map(({ init }) => init?.method), + ["PUT", "GET"], + ); +} + async function verifyHappyCompile(context, planSource) { const cwd = await initializedProject(context, "compile-happy", { planSource }); const digest = sha256(planSource); diff --git a/script/cli-contract/plan-status.mjs b/script/cli-contract/plan-status.mjs index 4c19001..00fd535 100644 --- a/script/cli-contract/plan-status.mjs +++ b/script/cli-contract/plan-status.mjs @@ -109,15 +109,42 @@ export async function verifyPlanStatusGenerations(context) { analysisProjection("valid", { graphVersion: 3, identifier: staleAnalysisId, + gaps: [ + { + classification: "service_support_gap", + code: "foundation_plan.gap.service.unsupported_capability", + kind: "import_skip", + status: "skipped_at_import", + pointer: "/application/delivery", + reason: + "This First Draft release cannot yet import this Foundation Plan capability.", + consequence: + "This Plan path is omitted from the admitted Project graph and generated application.", + }, + ], }), ), ]), }, ); assert.equal(replacement.status, 0); - assert( - JSON.parse(replacement.stdout).project.graph_version > - acceptedGraphVersion, + const replacementProjection = JSON.parse(replacement.stdout); + assert(replacementProjection.project.graph_version > acceptedGraphVersion); + assert.equal( + replacementProjection.analysis.gap_set.gaps.length, + 1, + ); + assert.equal( + replacementProjection.analysis.gap_set.gaps[0].pointer, + "/application/delivery", + ); + assert.equal( + replacementProjection.analysis.gap_set_sha256, + sha256( + Buffer.from( + `${JSON.stringify(replacementProjection.analysis.gap_set, null, 2)}\n`, + ), + ), ); } @@ -232,6 +259,12 @@ async function verifyAnalysisFixtures(context) { ); const response = structuredClone(fixture); response.project.id = projectId; + if (response.analysis.gap_set) { + response.analysis.gap_set.project.id = projectId; + response.analysis.gap_set_sha256 = sha256( + Buffer.from(`${JSON.stringify(response.analysis.gap_set, null, 2)}\n`), + ); + } const result = await invokeRunner( context.runCli, ["plan", "status", "--wait"], diff --git a/script/support/create-full-stack-app-evaluation.mjs b/script/support/create-full-stack-app-evaluation.mjs index b7884e2..0bfeff0 100644 --- a/script/support/create-full-stack-app-evaluation.mjs +++ b/script/support/create-full-stack-app-evaluation.mjs @@ -18,7 +18,7 @@ export const candidateInterviewProtocolPath = path.join( export const movieCatalogFixturePath = path.join( evaluationDirectory, "fixtures", - "application-intent.foundation-plan.json", + "appearance-issues.foundation-plan.json", ); export const loadEvaluationDocument = async () => diff --git a/skills/create-full-stack-app/SKILL.md b/skills/create-full-stack-app/SKILL.md index e3f4157..6f94c84 100644 --- a/skills/create-full-stack-app/SKILL.md +++ b/skills/create-full-stack-app/SKILL.md @@ -13,7 +13,7 @@ deploy the application. ## Current boundary -This experimental workflow targets the coordinated plugin 0.1.2, CLI 0.1.0, and service-contract 0.2 contract. +This experimental workflow targets the coordinated plugin 0.2.0, CLI 0.2.0, and service-contract 0.3 contract. These bundled bytes do not prove that exact combination is available from the public catalog; verify availability independently before advising an installation change. @@ -25,10 +25,10 @@ The current Compiler is a narrow experiment, not arbitrary application generatio index/navigation beneath `ios/`. - Richer web routes do not become native detail or mutation screens. Every admitted generated web route is public and unauthenticated. -- Accounts, notifications, deployment, Android, iPad, broader graph or Scaffold shapes, broader native screens, - and gap-aware partial Compilation are unavailable. -- Preserve intentional unsupported meaning and report the gap; never weaken it merely to obtain `valid`. - Unsupported shapes fail the complete candidate closed. +- Accounts, notifications, deployment, Android, iPad, broader graph or Scaffold shapes, and broader native screens + remain unavailable or incomplete. +- Preserve unsupported meaning and report every reviewed gap; never weaken it to obtain `valid`. A valid run may + have gaps, and its artifact retains the submitted Plan and GapSet. Read the [current evidence and target boundary](references/foundation-plan-019.md#current-evidence-boundary) before making a support claim. One dated staging observation proves one prior OAuth/App-backed private-repository @@ -90,7 +90,7 @@ firstdraft_cli --version firstdraft_cli --help ``` -Require the version probe to succeed with one exact `0.1.0` output line and no other output, and top-level help that +Require the version probe to succeed with one exact `0.2.0` output line and no other output, and top-level help that lists `generate`, `plan`, and `compilation`. Existing cross-repository contract tests own the exhaustive leaf-command matrix, including separate stdout and stderr assertions; startup should not rediscover it through a synthesized shell loop. The compatible CLI supplies these public commands: @@ -99,11 +99,9 @@ shell loop. The compatible CLI supplies these public commands: - `plan init`, `plan push`, `plan status`, and zero-flag `plan compile`; and - `compilation status` and `compilation download`. -There is no public `plan publish`, `plan subject-id`, or `plan compile --output`. Do not install, download, upgrade, -or replace the CLI automatically. If resolution, version, or help differs, report the path and output and stop CLI -and remote operations instead of using direct HTTP. Continue interviewing, reviewing, or editing the local Plan -when that work remains useful. Recommend a marketplace repair only after independently verifying that the catalog -serves this exact plugin 0.1.2 and CLI 0.1.0 pair; otherwise report that no verified public repair is known. +There is no public `plan publish`, `plan subject-id`, or `plan compile --output`. Never replace the CLI automatically. +If its path, version, or help differs, report it and stop remote work instead of using HTTP directly; local Plan work +may continue. Recommend repair only after verifying the catalog serves plugin 0.2.0 with CLI 0.2.0. Treat `.firstdraft/state.json` as private CLI-owned concurrency state. Never print, paste, commit, or treat it as Plan content. Let the user configure `FIRSTDRAFT_API_TOKEN` and any initial `FIRSTDRAFT_API_URL` outside the @@ -144,7 +142,8 @@ For an underspecified opening request, ask only about product meaning and deferr before discussing target support unless feasibility was requested. Later, state the current Scaffold boundary precisely: the smallest public index and the exact create/update, show-projection, return-destination, and destroy extensions, with every generated route public and unauthenticated. If the user explicitly requires private or -authenticated access, preserve that meaning and stop before Compile rather than substituting a public Scaffold. +authenticated access, preserve that meaning and include its exact support consequence in review rather than +silently substituting a public requirement. Edit `.firstdraft/foundation-plan.json` throughout the conversation. Keep one complete current candidate; an incomplete or malformed local snapshot is safe to submit for diagnostics. Model product meaning rather than Rails @@ -182,42 +181,45 @@ firstdraft_cli() { if [ -x ./bin/firstdraft ]; then ./bin/firstdraft "$@"; else firstdraft_cli plan status --wait ``` -Bind diagnostics to the push only when both returned graph versions equal the accepted version. If status is for a -lower graph version, repeat this read-only poll within a bounded wait. If it is higher, another accepted Head -replaced the one just pushed. Branch on `analysis.status`, not only the process exit status: +Bind status only when both graph versions and `analysis.head_source_sha256` match the accepted result's version and +`foundation_plan.source_sha256`. Poll lower versions read-only within a bounded wait; a higher version or SHA +mismatch is a replacement. Branch on `analysis.status`, not only the process exit status: -- `valid`: that exact graph passed the named analyzer; Compilation is not proved. +- `valid`: the admitted graph passed the analyzer; Compilation is not proved. Require and inspect the complete + `analysis.gap_set` and `analysis.gap_set_sha256`, including an empty `gaps` array. Service gaps were skipped before + semantic analysis, so `valid` does not validate them; target gaps were analyzed but not fully realized. - `issues_found`: use structured diagnostics to make well-founded corrections while preserving unrelated meaning. - `analysis_failed`: report analyzer failure rather than inventing a product correction. - `superseded`: report that another accepted Head displaced the observed analysis. A bounded read-only status follow-up may report the current Project state. It is report-only and must never edit, push, or Compile the replacement. -Treat messages and suggestions as advisory. If the same diagnostic recurs without new information, do not loop -mechanically. Explain the unresolved choice or capability gap, keep intentional unsupported meaning in the local -candidate, and ask only for product input that is actually needed. Unsupported subjects are not partially compiled. -A separate push is optional because `plan compile` performs its own exact-byte push and matching analysis wait. +Treat messages and suggestions as advisory. Do not loop a repeated diagnostic without new information; preserve +intent and ask only for needed product input. Before approval, push the final exact candidate and read its matching +valid status so the complete GapSet can be reviewed. `plan compile` later repeats that exact push. ## Read back and approve the candidate before Compile Before the first `plan compile` that could reach valid analysis and Publication, reread the exact current -`.firstdraft/foundation-plan.json`. Give a compact plain-language semantic summary that covers the Plan path and -SHA-256; application scope; Entities and their material Fields, relationships, rules, behavior, and data; surfaces, -access, and clients; material assumptions, exclusions, and capability gaps; and the execution consequence that -Compile does not deploy while terminal successful Publication is intended to create one private GitHub repository. -Use whatever order is clearest for this candidate. Do not enumerate absent subject families or recite immaterial -properties. Ask the user to correct or explicitly approve that exact candidate. +`.firstdraft/foundation-plan.json`. Give a compact semantic summary covering its path and SHA-256; application scope; +Entities and material Fields, relationships, rules, behavior, and data; surfaces, access, and clients; assumptions; +and exclusions. Show the matching valid run's `gap_set_sha256` and every ordered GapSet record. Explain that service +gaps were skipped before semantic analysis, target gaps were not fully realized, and `valid` applies only to the +admitted graph. State that Compile does not deploy and successful Publication is intended to create one private +GitHub repository. Do not enumerate absent subject families or immaterial properties. Ask the user to correct or +explicitly approve the candidate and reviewed gaps; require no digest echo or gap-acknowledgment field. If the Plan bytes change, show the new SHA-256 and the semantic delta, then obtain approval of the changed candidate. In the same continuing conversation, after unambiguous approval, reread the Plan, confirm its SHA-256 is unchanged, and make the initial request with exactly one zero-flag Compile invocation. Do not ask for a second command-level confirmation. Do not delete, loosen, flatten, relabel, or substitute intended product meaning to make analysis -green. The user may explicitly move a feature out of scope after seeing the consequence; otherwise preserve it and -stop before Compile. +green. The user may explicitly move a feature out of scope after seeing the consequence; otherwise preserve it. +After approval of the unchanged candidate and GapSet, use the supplied zero-flag Compile action without more +ceremony. This gate does not block an explicitly requested diagnostic-only Compile of exact bytes already known to be -invalid from those bytes or matching diagnostics. Invalid analysis cannot enter Publication. Do not treat an -unsupported assumption as proof that a candidate cannot publish. +invalid from those bytes or matching diagnostics. Invalid analysis cannot enter Publication. Valid analysis with +gaps can; do not require removal of the corresponding Plan fields. ## Request the Compile journey @@ -229,16 +231,13 @@ firstdraft_cli() { if [ -x ./bin/firstdraft ]; then ./bin/firstdraft "$@"; else firstdraft_cli plan compile ``` -This is the Compile request; do not add a second confirmation ceremony. It pushes the current exact bytes, waits -for their analysis generation, and requests Publication only after `valid`. Immediately before that mutation, the -CLI rechecks the accepted ETag and local bytes. +This is the Compile request; do not add a second confirmation ceremony or any gap-specific flag or field. It pushes +the current exact bytes, waits for their analysis generation, and requests Publication only after `valid`. +Immediately before that mutation, the CLI rechecks the accepted ETag and local bytes. -Treat Compilation and GitHub Publication as separate retained stages. `compilation.status: "succeeded"` proves the -application artifact finished compiling; it does not prove that a repository exists or that source was published. -Call the result published only after terminal successful Publication and the CLI's validated private-repository -URL. Progress fields and messages are already validated; report their exact phase, retry time, retry count, and safe -reason without inferring provider causes. The bounded ten-minute wait does not cancel retained work when it times -out. +Treat Compilation and Publication as separate retained stages. Compilation success does not prove publication. +Call it published only after terminal Publication success and a validated private-repository URL. Report validated +progress without inferring provider causes; a wait timeout does not cancel retained work. One Publication singleton exists per Project. Never Compile concurrently. After an invocation that reached it exits with a Publication-phase outcome unknown, unavailable status, wait timeout, or interruption, wait for exit, then @@ -303,7 +302,7 @@ Report: - the Plan path and the latest boundary actually demonstrated: JSON parsing, local schema validation, server import, or whole-graph analysis; - material choices, delegated decisions, exclusions, open questions, warnings, and capability gaps; -- the analyzer release and graph version actually observed; +- the observed analyzer release, graph version, and Head SHA, plus the complete valid GapSet and its digest; - distinct Compilation and Publication statuses plus validated progress fields when available; - the private repository URL only after terminal Publication success; - a downloaded path, file count, and manifest digest only after verified materialization; and diff --git a/skills/create-full-stack-app/references/diagnostics-and-recovery.md b/skills/create-full-stack-app/references/diagnostics-and-recovery.md index ec87330..ff5fe39 100644 --- a/skills/create-full-stack-app/references/diagnostics-and-recovery.md +++ b/skills/create-full-stack-app/references/diagnostics-and-recovery.md @@ -9,9 +9,9 @@ interleaved output fail closed. Branch on the object's stable `error` and struct human-readable `detail` or broad process exit status. The reviewed CLI contract in this stack is revision -`d37d8b6775a0b97ce10bd651485bd308fed1dda2`, with JavaScript-source runtime digest -`019a2e99ba504739d8eb17b63b7ced42eaea56e550d1e067ab962a7748500b72`. Its source package is -`@firstdraft.com/cli@0.1.0`. Check the command surface rather than assuming the version alone +`5ac300f1a2e7262c56473de270a0bd140f169c25`, with JavaScript-source runtime digest +`5fac5209f06406fcedde85c1ee46f0b539e95e96bb3369330a137e2137e70fcc`. Its source package is +`@firstdraft.com/cli@0.2.0`. Check the command surface rather than assuming the version alone establishes compatibility. Candidate qualification or package publication does not prove service authentication, staging compatibility, or a complete user journey. @@ -56,10 +56,10 @@ saves the returned strong ETag before printing JSON. `created` means the Project request was accepted for an existing Project; it does not by itself prove the bytes or graph changed. Retain that result's `project.graph_version` and `foundation_plan.source_sha256`. Public `plan status` reads the -current analysis and does not receive an expected version. A lower returned Project and Analysis graph version is -an older generation; poll the read-only command again within a bounded wait. A higher version means another Head -replaced the submitted snapshot. Bind diagnostics to the push only when both returned graph versions equal the -accepted one. +current analysis and does not receive an expected version. Bind it to the push only when both returned graph +versions equal the accepted version and `analysis.head_source_sha256` equals the accepted +`foundation_plan.source_sha256`. Lower versions are older; poll read-only within a bounded wait. A higher version or +source-digest mismatch means another Head replaced the submitted snapshot, even when graph version was reused. Inspect all returned diagnostics. A `422 server_rejected` binds them to `response.source_sha256`, the digest of the submitted bytes. Correct a well-founded source problem while preserving unrelated meaning and subject @@ -71,14 +71,26 @@ two minutes. Every validated domain status exits successfully: | `analysis.status` | Meaning | | --- | --- | -| `valid` | This graph passed the named analyzer release. | +| `valid` | The admitted graph passed the named analyzer release; inspect the complete reviewed GapSet before Compile. | | `issues_found` | Structured diagnostics block the graph. | | `analysis_failed` | The analyzer failed to complete; this is not a speculative source correction. | -| `superseded` | Another accepted graph replaced this analysis generation. | +| `superseded` | Another accepted Head replaced this analysis run. | -Server messages and suggestions are advisory data. Preserve intentional meaning when a diagnostic describes a -current importer, analyzer, or Compiler gap. If a diagnostic repeats without new understanding, surface the -blocker rather than looping mechanically. +For `valid`, require non-null `analysis.gap_set` and `analysis.gap_set_sha256`. The CLI validates the canonical +`firstdraft.foundation-gaps/2` format, digest, exact Head, Project graph generation, analyzer and Compiler releases, +and target/profile before printing it. Inspect and surface every ordered record; do not replace the list with only +counts or categories. A `service_support_gap` records schema-valid meaning skipped before semantic analysis, so +`valid` does not validate that skipped meaning. A `target_support_gap` records admitted and analyzed meaning that +the selected target does not fully realize. A valid analysis with an empty `gaps` array still carries the canonical +object and digest. Every non-valid status carries null values for both fields. + +Server messages and suggestions are advisory data. Preserve intentional meaning when a diagnostic or GapSet record +describes a current importer, analyzer, or Compiler gap. If a diagnostic repeats without new understanding, surface +the blocker rather than looping mechanically. + +Before approval, `plan push` the final exact candidate and obtain its matching valid `plan status --wait` result so +the user can review the complete GapSet and digest. After approval, `plan compile` repeats that exact-byte push; do +not add another preparatory push, a gap acknowledgment, or any gap-specific field. `status_unavailable` is a read-only failure. Retry that GET a bounded number of times; if it persists, inspect the private state's pinned `api_url` locally without printing the rest of the file. `invalid_server_response` instead @@ -90,8 +102,9 @@ as the submitted candidate, and do not edit, push, or Compile the replacement. ## Product Compile -`plan compile` performs a new exact-byte push, waits for the analysis generation whose graph version came from -that accepted push, and requests the internal singleton GitHub Publication only when the result is `valid`. +`plan compile` performs a new exact-byte push, waits for the analysis whose graph version and Head digest came from +that accepted push, and requests the internal singleton GitHub Publication only when the result is `valid`. It uses +the existing bodyless action and exact Head condition; do not add a gap digest, acknowledgment field, or Plan edit. The command reserves standard output for one validated private GitHub repository URL on success. While waiting, its progress output distinguishes the retained Compilation from GitHub Publication. Treat @@ -313,8 +326,10 @@ For each diagnostic: 4. make the smallest well-founded correction; and 5. keep capability gaps distinct from invalid product meaning. -An unsupported capability rejects that submitted candidate atomically. It does not mean supported siblings were -partially applied, and it is not a reason to delete intentional content merely to obtain a green response. +Schema-valid meaning outside current service support is preserved in the exact Head and recorded as a +`service_support_gap` after the admitted graph reaches valid analysis. It was not semantically analyzed. Target +meaning that is admitted but not fully generated is recorded as a `target_support_gap`. Neither class is a reason +to delete intentional content merely to obtain a gap-free response. ## Concurrent replacement diff --git a/skills/create-full-stack-app/references/examples.md b/skills/create-full-stack-app/references/examples.md index 6175117..45e5eb0 100644 --- a/skills/create-full-stack-app/references/examples.md +++ b/skills/create-full-stack-app/references/examples.md @@ -94,16 +94,17 @@ gate, not proof that output exists. } ``` -The selected native output is iPhone-only. Do not describe it as Android or iPad support. A selected iPhone client -requires at least one admitted public-index Scaffold; a domain without selected iOS is also blocked. Web-only plans -may use the same exact Scaffold with `native: {}` and no domain. The admitted Scaffold makes Movie records readable +The selected native output is iPhone-only. Do not describe it as Android or iPad support. The target emits a selected +iPhone client only with at least one admitted public-index Scaffold; without one, a valid analysis records an +unrealized-client target gap. A domain without generated iOS is likewise admitted but recorded as ungenerated. +Web-only plans may use the same exact Scaffold with `native: {}` and no domain. The admitted Scaffold makes Movie records readable on the web without authentication. Confirm that exposure with the user before adding it; do not add it merely to satisfy the iPhone navigation requirement or silently discard private or broader access intent. -Adding `appearance` to this candidate remains structurally valid and importable, but the prepared analyzer returns -`foundation_plan.rails_target.compiler.unsupported_application_configuration` at `/application/appearance`. -Preserve an intentional Appearance request and report the capability gap rather than deleting it merely to obtain -`valid`. +Adding `appearance` to this candidate remains structurally valid and importable. A matching valid AnalysisRun +records `foundation_plan.gap.appearance.not_generated` at `/application/appearance` as a target-support gap. +Preserve an intentional Appearance request and report the complete reviewed GapSet rather than deleting it merely +to obtain a gap-free result. ## Conditional text length diff --git a/skills/create-full-stack-app/references/foundation-plan-0.19.schema.json b/skills/create-full-stack-app/references/foundation-plan-0.19.schema.json index 70e42f2..fdf84b7 100644 --- a/skills/create-full-stack-app/references/foundation-plan-0.19.schema.json +++ b/skills/create-full-stack-app/references/foundation-plan-0.19.schema.json @@ -127,7 +127,7 @@ }, "domain": { "title": "Application domain", - "description": "The owner's domain. Platform identifiers, linked domains, and a default sending address derive from it. Omission yields a placeholder identifier disclosed as an external prerequisite.", + "description": "The owner's domain. Platform identifiers, linked domains, and a default sending address derive from it. When omitted, the current selected-iOS lowering uses visibly non-production placeholder identities.", "type": "string", "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$", "examples": [ diff --git a/skills/create-full-stack-app/references/foundation-plan-019.md b/skills/create-full-stack-app/references/foundation-plan-019.md index 8d499ec..76e2440 100644 --- a/skills/create-full-stack-app/references/foundation-plan-019.md +++ b/skills/create-full-stack-app/references/foundation-plan-019.md @@ -38,21 +38,25 @@ and inspect only that definition. Use server diagnostics for the exact bytes sub - The v0.19 corpus passes the First Draft JSON Schema and strict loader. - Structural validity does not prove readable-link resolution, whole-application consistency, target support, or compilability. -- The reviewed conditional PUT imports empty drafts; Application domain, appearance, and native-client selections; - and a bounded subset of Entities, ten scalar Field kinds, enum Fields with ordered values, schema-valid tagged - Field and Reference defaults, ordinary References and their forward Associations, bounded direct referenced-side - inverses and indirect Association shapes, representative owner-local Validations, Predicates with exact - Expression JSON, Field or system-Field Primary Descriptors, and the bounded public Scaffold envelope below. +- The reviewed conditional PUT preserves each schema-valid exact source as the Project Head and imports a bounded + relational projection. Meaning outside that service projection remains in the Head and appears as ordered + `service_support_gap` records after the admitted graph reaches valid analysis. - The application analyzer and Compiler admit scalar Entities, ordinary References, the admitted inverse and indirect Association shapes, the first Validation subset, exact public Scaffold shapes, optional semantic Entity - icons, and selected iPhone output. Application `domain` is admitted only with `native.ios`, and selected iOS - requires at least one admitted public-index navigation entry. -- Enum Fields, Appearance, nonempty delivery, Android, iPad, Accounts, broader Associations, Validations and - Scaffolds, and other graph breadth remain outside that Compilation boundary. Some are retained for editing and - rejected by analysis; others are rejected atomically by the importer as described below. The current release does - not partially compile a candidate containing an unsupported shape. + icons, and selected iPhone output. The target emits Application `domain` only with a generated iOS client, and + emits selected iOS only with at least one admitted public-index navigation entry; otherwise it records target gaps. +- Enum and State Machine Fields, Appearance, nonempty delivery, Android, iPad, Accounts, broader Associations, + Validations and Scaffolds, and other graph breadth remain outside or incomplete at that Compilation boundary. A valid AnalysisRun + exposes every skipped service meaning and every admitted-but-unrealized target consequence in one canonical + `firstdraft.foundation-gaps/2` object and digest before Compile. - The project-scoped server implements bounded AnalysisRun status and Compilation start, status, cancellation, and artifact transport for the reviewed CLI contract. +- Service API 0.3 returns the complete parsed GapSet and its canonical-byte SHA-256 for every valid AnalysisRun, + including an empty `gaps` array. `valid` means the admitted graph passed semantic analysis. It does not claim that + `service_support_gap` meaning skipped before import was semantically validated. `target_support_gap` meaning was + admitted and analyzed but is not fully realized by the selected target. This contract names analyzer release + `foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3` and Compiler release + `foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3`. - First Draft's [controlled product-journey smoke](https://github.com/firstdraft/firstdraft/blob/8ebfc2ed82a610e63f47eb985c23ab7e634fe94e/script/compilation_http_cli_smoke) produced its recorded runs at service revision `8ebfc2ed82a610e63f47eb985c23ab7e634fe94e`. It drove the packed reviewed @@ -86,12 +90,12 @@ and inspect only that definition. Use server diagnostics for the exact bytes sub - The field observation is not a reproducible agent evaluation, authenticated operation, representative-user evidence, a published release, physical-device or iPad proof, deployment, or production evidence. Neither it nor the controlled smoke widens the admitted graph or proves cancellation. -- There is no Plan GET or pull operation, complete semantic analyzer, arbitrary application generation, deployment - workflow, or support for the rest of the Foundation Plan. +- There is no Plan GET or pull operation, complete semantic validation of meaning skipped by the service, arbitrary + application generation, deployment workflow, or support for the rest of the Foundation Plan. -The bundled schema was copied from `docs/architecture/design/foundation-plan.schema.json` at landed server -activation revision `35ad070beb36c66dc6480f36b33767caaed160a9` and has SHA-256 -`1954e5c95d6e6621578202ad4452686b56c150256ffcd75935078d9f4247c568`. That revision is exact contract provenance, +The bundled schema was copied from `docs/architecture/design/foundation-plan.schema.json` at reviewed service +revision `0949afedd983ba4dbf56434eb235501e1ed0be58` and has SHA-256 +`be7bc69019ef7393a8d94e8ccb255cccef793d8007f571c797e1b077ae0c1f15`. That revision is exact contract provenance, not release or execution evidence. The bounded local Compilation evidence used reviewed CLI revision @@ -99,13 +103,13 @@ The bounded local Compilation evidence used reviewed CLI revision `205e664df0ed9c7e63651a1c2c01e749a04d8879fe7f62cc4c1e13b66dce738d`. Prior contract checks used reviewed successor revision `e53eb38d7e8254e6ba1e660b38c5d32d0314be17`, with JavaScript-source runtime digest `0983106d7c1054137d70dccb1091eeadd8272ffcca1f7bba1bde9c8028452fad`. The current fixtures and check use reviewed -progress-contract revision `d37d8b6775a0b97ce10bd651485bd308fed1dda2`, with JavaScript-source runtime digest -`019a2e99ba504739d8eb17b63b7ced42eaea56e550d1e067ab962a7748500b72`, as contract provenance rather than release +GapSet-contract revision `5ac300f1a2e7262c56473de270a0bd140f169c25`, with JavaScript-source runtime digest +`5fac5209f06406fcedde85c1ee46f0b539e95e96bb3369330a137e2137e70fcc`, as contract provenance rather than release or execution evidence. The current revision exposes `generate uuid`, `generate application-key`, `plan init`, `plan push`, `plan status`, zero-flag `plan compile`, `compilation status`, and `compilation download`. It deliberately has no public `plan subject-id`, `plan publish`, or local-start `plan compile --output` contract. The coordinated checkout declares the experimental -`@firstdraft.com/cli@0.1.0` package. Check commands rather than inferring compatibility from the version +`@firstdraft.com/cli@0.2.0` package. Check commands rather than inferring compatibility from the version number. Candidate qualification or package publication does not prove authentication, staging compatibility, or a complete user journey. @@ -210,18 +214,19 @@ App Schema artifact. ### Application and clients The reviewed importer accepts the required Application properties `key`, `name`, `native`, `delivery`, and -`entities`, plus optional `domain` and `appearance`. `domain`, `appearance`, and `native` are retained as editable -graph state; a nonempty `delivery` remains outside this import boundary. - -The prepared application analyzer and Compiler admit `domain` only when `native.ios` is selected. A selected iPhone -client may omit `domain`, but it requires at least one admitted Scaffold containing a public index so its navigation -is nonempty. The index supplies the native navigation entry even when that Scaffold includes the exact admitted web +`entities`, plus optional `domain` and `appearance`. `domain`, `appearance`, and supported native selections are +retained as editable graph state. Nonempty delivery remains in the exact Head and appears as a service-support gap +instead of being silently discarded. + +The prepared Compiler fully realizes `domain` only when `native.ios` is selected. A selected iPhone +client may omit `domain`, but it is emitted only with at least one admitted Scaffold containing a public index so its +navigation is nonempty. Otherwise the reviewed GapSet records the unrealized client. The index supplies the native +navigation entry even when that Scaffold includes the exact admitted web mutation, show, or destroy extensions; those extensions do not add native detail or mutation screens. That admitted Scaffold makes the Entity's records readable on the web without authentication. Confirm that exposure with the user -before authoring it; otherwise preserve the private or broader access intent and report that the selected iPhone -request cannot yet pass analysis. `appearance`, Android, a domain without selected iOS, and any other admitted but -unconsumed Application configuration return `issues_found` with source-addressed -`foundation_plan.rails_target.compiler.unsupported_application_configuration` diagnostics. +before authoring it; otherwise preserve the private or broader access intent and review its support consequence. +`appearance`, Android, a domain without selected iOS, and any other admitted but unconsumed Application +configuration appear as target-support gaps on an otherwise valid admitted graph. The prepared Compilation emits admitted web public indexes and, when selected, an owned iPhone project beneath `ios/`; this is not general generated resource UI, Android, or iPad support. @@ -232,9 +237,9 @@ The prepared Compilation emits admitted web public indexes and, when selected, a subset below can pass current target admission. The schema also permits Entity `orderings` and `implicit_order_column`, but the current conditional PUT imports -neither. Authored ordering storage and implicit-order analysis elsewhere in First Draft do not widen this endpoint -or the Compiler. Generated public indexes currently use `Model.order(:id)`; do not author either property in this -workflow or promise that an Ordering will change list order. +neither. Their source remains in the exact Head and their omission appears in the GapSet. Authored ordering storage +and implicit-order analysis elsewhere in First Draft do not widen this endpoint or the Compiler. Generated public +indexes currently use `Model.order(:id)`; do not promise that an Ordering changes list order. The smallest accepted Application remains: @@ -263,12 +268,16 @@ yet supported. A Field may use these types: - `language_code` - `long_text` - `short_text` +- `state_machine` - `time_zone` - `url` -That is the conditional import list, not the complete schema vocabulary. `attachment` and `image` are schema-valid -Field types, but this PUT rejects them at the Field's `type` pointer and they cannot reach the current Compiler. -Active Storage and image-delivery prose describes target direction, not emitted support. +That is the conditional import list, not the complete schema vocabulary. A State Machine Field retains its states, +initial state, transitions, and transition effects; the current target emits its string storage, initial-state +default, and closed-domain validation, while the GapSet records omitted events, effects, and AASM behavior. +`attachment` and `image` are schema-valid +Field types, but they are skipped from the admitted graph and recorded as service-support gaps; they cannot reach +the current Compiler. Active Storage and image-delivery prose describes target direction, not emitted support. ### Field capability matrix @@ -279,13 +288,13 @@ remaining supported cross-cutting properties. | Property | Schema | Conditional PUT | Current Compiler for the ten scalar kinds | | --- | --- | --- | --- | | `required` | Mandatory Boolean; write `true` or `false` | Retained | Both values admitted. `true` emits database `NOT NULL` plus a model validation. | -| `default` | Closed tagged Value where the Field variant permits it | Retained structurally | Any authored default blocks Compilation. | +| `default` | Closed tagged Value where the Field variant permits it | Retained structurally | Unsupported lowering appears as a target-support gap. | | `notes` | Optional nonempty string on Fields only | Retained | Admitted as review context and round-tripped in the Plan snapshot; emits no application behavior. | -| `immutable` | Optional Boolean; omission means `false` | Retained | Omitted or `false` admitted; `true` blocks Compilation. | -| `comparison` | `case_insensitive` on `short_text` only | Retained | Any authored comparison blocks Compilation. | -| `normalizations` | Ordered pipeline on text or URL Fields, with URL restrictions | Retained | Any authored pipeline blocks Compilation. | -| `encrypted_at_rest` | Optional Boolean; omission means `false` | Retained | Omitted or `false` admitted; `true` blocks Compilation. | -| `redact_from_logs` | Optional Boolean; omission means `false` | Retained | Omitted or `false` admitted; `true` blocks Compilation. | +| `immutable` | Optional Boolean; omission means `false` | Retained | An unrealized `true` appears as a target-support gap. | +| `comparison` | `case_insensitive` on `short_text` only | Retained | Unrealized comparison behavior appears as a target-support gap. | +| `normalizations` | Ordered pipeline on text or URL Fields, with URL restrictions | Retained | Unrealized pipelines appear as target-support gaps. | +| `encrypted_at_rest` | Optional Boolean; omission means `false` | Retained | Unrealized encryption appears as a target-support gap. | +| `redact_from_logs` | Optional Boolean; omission means `false` | Retained | Unrealized filtering appears as a target-support gap. | Preserve intentional values that the Compiler cannot emit. Report the exact output gap instead of deleting a default, security property, or other product meaning to obtain `valid`. @@ -302,8 +311,8 @@ UUID through renames, reordering, and coherent moves between enum Fields. An enu selected value's owner-local `key`, not its UUID. Update that literal in the same candidate when renaming the value, while preserving the value's UUID. -Enum Fields are retained for editing, but they cannot pass the current Compilation analysis gate. Preserve the enum -and report that boundary rather than weakening it to a scalar. +Enum Fields are retained for editing, but the current Compiler does not generate their full behavior. Preserve the +enum and report every target-support gap rather than weakening it to a scalar. ### Defaults @@ -351,7 +360,8 @@ derived forward Association of another admitted Reference with `one_to_one: fals be one-to-one-false. The Compiler emits distinct traversal. These are per-Association shape rules, not per-Entity or per-Plan quotas. Author each reverse or indirect traversal the product needs. Referencing-side aliases, other indirect paths, predicates, cardinality bounds, polymorphism, exclusive arcs, immutable or defaulted References, and -broader Association shapes fail closed. +broader Association shapes can produce service- or target-support gaps. Preserve the authored relationship meaning +and review the exact consequence. ### Validations @@ -368,14 +378,14 @@ Conditions are limited to total, direct same-record Field null tests, `not`, and and length rules allow nil so requiredness owns the missing-value error. Required scalar Fields separately derive ordinary Rails presence, except Boolean Fields use inclusion in `[true, false]`. Conditional comparisons, Entity-owned rules, noninteger comparison owners, equality or linked operands, non-text presence or absence, -nonordinary References, `format`, `exclusion`, `uniqueness`, and other condition shapes fail closed. +nonordinary References, `format`, `exclusion`, `uniqueness`, and other condition shapes can produce service- or +target-support gaps. They remain invalid only when the admitted meaning itself violates semantic rules. ### Predicates A Predicate retains schema-valid combinations of `subject_uuid`, `key`, `name`, and `expression`. Import preserves the Expression's exact decoded JSON meaning without claiming link resolution, type checking, or target lowering. -Importability does not imply that the current bounded whole-graph analyzer or Compiler accepts a Project containing -enum Fields or Predicates. +Importability does not imply generated Predicate behavior; the reviewed GapSet discloses each unrealized result. ### Scaffolds @@ -393,10 +403,10 @@ Reference input's target needs an admitted scalar Primary Descriptor. Therefore, an Entity can use this mutation Scaffold only when every required Field is `short_text` and every required Reference has the admitted editable shape. A required `long_text`, Boolean, date, or other scalar Field -still compiles without that Scaffold; if mutation routes are authored, it makes the complete candidate fail target -analysis rather than silently removing routes. Preserve the honest requiredness and requested access, and report -the current form gap instead of changing either merely to obtain `valid`. The destroy extension below depends on -the complete mutation-and-show shape and inherits the same restriction. +still compiles without that Scaffold; if mutation routes are authored, the reviewed GapSet must disclose the +unrealized surface rather than silently removing routes. Preserve the honest requiredness and requested access, and +report the current form gap instead of changing either merely to obtain a gap-free result. The destroy extension +below depends on the complete mutation-and-show shape and inherits the same restriction. One exact show extension inserts `show` after `index`, makes it public, and otherwise retains the complete mutation shape. Its projection is omitted for descriptor-only detail or is a nonempty ordered list of direct owner-local @@ -411,9 +421,14 @@ Scaffold emits a native detail or mutation surface. ### Unsupported shapes Scalar Fields have no `settings` object, and enum `settings` admits only `values` and optional `ordinal`; any other -settings shape is structurally invalid rather than an importer capability gap. Nonempty delivery, development -data, derivations, Accounts, and other graph slices outside the importer boundary reject the complete conditional -PUT with source-addressed `foundation_plan.import.unsupported_capability` diagnostics and no mutation. Imported -but unadmitted shapes, including enum Fields, Predicates, broader References, Associations, Validations, and -Scaffolds, instead fail the complete candidate at target analysis. Either diagnostic describes current service -capability, not invalid product meaning. Preserve the authored Plan and report the exact gap. +settings shape is structurally invalid rather than a support gap. Nonempty delivery, development data, derivations, +and other graph slices outside the importer boundary remain in the exact submitted Head and appear as +`service_support_gap` records when the admitted graph is valid. Imported but incompletely generated shapes, including +Accounts, enum and State Machine Fields, Predicates, and imported members of References, Associations, Validations, +and Scaffolds, appear as `target_support_gap` records; pruned members of those families remain service gaps. +Service-support meaning was skipped before semantic analysis; target-support meaning was admitted and analyzed but +is not fully realized. Preserve the authored Plan and report every exact gap. + +Successful Compilation retains the exact submitted Plan at `.firstdraft/submitted-foundation-plan.json` and the +canonical machine-readable GapSet at `.firstdraft/gaps.json`. There is intentionally no duplicate +`FOUNDATION_GAPS.md`; future agents should read the one JSON authority. diff --git a/skills/create-full-stack-app/references/modeling-guide.md b/skills/create-full-stack-app/references/modeling-guide.md index 88eb458..2cc4b40 100644 --- a/skills/create-full-stack-app/references/modeling-guide.md +++ b/skills/create-full-stack-app/references/modeling-guide.md @@ -66,8 +66,8 @@ Establish enough product meaning to answer these questions for the included firs The ambiguity matrix guides the dialogue; it is not a one-message checklist. The agent may edit the local Plan incrementally and may submit the current whole-file snapshot for diagnostics whenever useful. A malformed, -incomplete, or invalid snapshot may produce descriptive diagnostics. Compilation still receives one complete -candidate snapshot accepted by whole-graph analysis. +incomplete, or invalid snapshot may produce descriptive diagnostics. Compilation receives one exact candidate +snapshot, its admitted graph after whole-graph analysis, and the matching reviewed GapSet. One complete candidate is ready for read-back when it expresses a coherent, honest first-release slice; every included Entity, Field, and Reference has enough meaning to represent that slice without silent guesses; access and @@ -91,9 +91,9 @@ Do not infer uniqueness from a label, presence from a form, or immutability from matter. Use an `enum` for a closed named set. Give every value its own stable identity, and set `ordinal` only when value -order carries semantic rank rather than presentation order alone. The current importer retains enums for editing, -but they cannot pass the bounded Compilation analysis gate; preserve the product meaning and report that capability -gap rather than replacing an enum with a scalar. +order carries semantic rank rather than presentation order alone. The current Compiler does not generate enum +behavior; preserve the product meaning and review its target-support gap rather than replacing an enum with a +scalar. The current Compiler admits unconditional integer-literal range comparisons on stored integer Fields; unconditional or conditional text length; and conditional text or ordinary-Reference presence and absence. A @@ -122,8 +122,8 @@ The current Compiler admits ordinary single-target References with Boolean `one_ with the forward traversal of another admitted Reference only when both underlying References have `one_to_one: false`. These are per-Association shape rules, not a quota. Aliases, broader indirect paths, predicates, cardinality bounds, polymorphism, exclusive arcs, immutable or defaulted References, and other relationship shapes -fail closed. A one-to-one Reference may compile as storage, but current Scaffold forms cannot accept it as an input. -Preserve broader product meaning and report the capability gap. +can produce service- or target-support gaps. A one-to-one Reference may compile as storage, but current Scaffold +forms cannot accept it as an input. Preserve broader product meaning and report the reviewed gap. ## Add behavior deliberately @@ -145,38 +145,42 @@ A second extension inserts public show into that complete mutation shape, option owner-local scalar Fields, and may return successful mutations to the mutation record's show page. A final variant appends public destroy and returns only to the same-Entity index. Read the Foundation Plan reference for the exact coupled shapes. Standalone show, Association or nested projections, non-public authorization, broader inputs or -return paths, and other route combinations fail closed. Do not silently narrow a broader requested Scaffold merely -to make it compilable. - -Select `native.ios` only when the user wants the bounded owned iPhone project. It requires at least one admitted -public-index Scaffold for navigation. Application `domain` is admitted by analysis only with selected iOS; selected -iOS may omit it. An Entity's optional semantic `icon` informs shared web and iPhone navigation, with a target -fallback when omitted. The only admitted navigation Scaffold is public: adding it makes that Entity's records +return paths, and other route combinations can remain ungenerated. Do not silently narrow a broader requested +Scaffold merely to make it compilable; preserve it and review the exact GapSet consequence. + +Select `native.ios` only when the user wants the bounded owned iPhone project. The target emits it only with at least +one admitted public-index Scaffold for navigation; otherwise the valid run records an unrealized-client target gap. +The target fully realizes Application `domain` only with generated iOS; selected iOS may omit it. An Entity's +optional semantic `icon` informs shared web and iPhone navigation, with a target +fallback when omitted. The only generated navigation Scaffold is public: adding it makes that Entity's records readable on the web without authentication. Confirm that exposure with the user before authoring it; otherwise -preserve the private or broader access intent and report that the selected iPhone request cannot yet pass analysis. -Do not add a public index merely to obtain `valid`, and do not silently decline the requested iPhone client. -Appearance and Android are retained for editing but block Compilation at analysis; nonempty delivery is not -importable. Accounts, authentication behavior, notifications and push, deployment, and iPad remain outside the -public Compiler path. Preserve requested product meaning and report the capability boundary rather than removing -intentional facts to obtain `valid`. +preserve the private or broader access intent and review the resulting support gap. Do not add a public index merely +to obtain a gap-free result, and do not silently decline the requested iPhone client. Appearance, Android, nonempty +delivery, Accounts, and authentication behavior are schema-valid but outside or incomplete in the public Compiler +path; preserve them and report every corresponding GapSet record. Requirements without a v0.19 shape, including +notification definitions, deployment, and iPad, remain in the decision ledger and semantic read-back as currently +unplannable rather than being invented as Plan JSON or promised a GapSet record. ## Preserve intent during diagnostics Fix the smallest well-founded source problem. Preserve unrelated subjects, ordering, and stable identity. If a diagnostic reveals an ambiguous product decision, ask the user rather than optimizing for a green response. -In particular, do not remove or weaken modeled content solely because the importer reports -`foundation_plan.import.unsupported_capability`. Preserve the local Plan and report the exact server gap. +In particular, do not remove or weaken modeled content solely because the reviewed GapSet reports a +`service_support_gap` or `target_support_gap`. Preserve the local Plan and report the exact pointer and consequence. ## Prepare the pre-Compile semantic read-back Immediately before the first Compile that could reach Publication, reread the exact local Plan and give a compact plain-language semantic summary. Cover the project-relative Plan path and SHA-256; the application scope; Entities -and their material Fields, relationships, rules, behavior, and data; surfaces, access, and clients; material -assumptions, exclusions, and capability gaps; and the execution consequence that Compile does not deploy while -terminal successful Publication is intended to create one private GitHub repository. Use the order that best -communicates this candidate. Do not enumerate absent subject families or recite immaterial defaults and empty -categories. Ask the user to correct or explicitly approve the exact model. +and their material Fields, relationships, rules, behavior, and data; surfaces, access, and clients; and material +assumptions and exclusions. Show the matching valid AnalysisRun's GapSet digest and every ordered record, including +its classification, code, kind, status, reason, consequence, location, and cause when present. Explain that service-support gaps were +skipped before semantic analysis and target-support gaps were not fully realized. Also state that Compile does not +deploy while terminal successful Publication is intended to create one private GitHub repository. Use the order +that best communicates this candidate. Do not enumerate absent subject families or recite immaterial defaults and +empty categories. Ask the user to correct or explicitly approve the exact model and reviewed support delta without +requiring a digest echo or gap-specific field. The read-back reviews the staged candidate; it is not a last-minute authoring pass. Preserve existing subject identity and present concerns as warnings. Do not require a candidate edit without a user correction, a confirmed @@ -185,4 +189,5 @@ semantic delta, then obtain approval of that changed candidate. Do not silently delete, loosen, flatten, relabel, or substitute intended product meaning to make import or analysis green. The user may explicitly move a feature out of this release after seeing the consequence; record that as a -product-scope decision. Otherwise preserve the meaning, stop before Compile, and report the capability gap. +product-scope decision. Otherwise preserve the meaning and, after approval, use the existing supplied Compile +action without adding ceremony. diff --git a/test/interview-evaluation-foundation.test.mjs b/test/interview-evaluation-foundation.test.mjs index 3caa958..b48c3cf 100644 --- a/test/interview-evaluation-foundation.test.mjs +++ b/test/interview-evaluation-foundation.test.mjs @@ -103,17 +103,24 @@ test("candidate protocol defines interview coverage and complete-candidate readi "Entities and their material Fields, relationships, rules, behavior, and data", "surfaces, access, and clients", "material assumptions, exclusions, and capability gaps", + "matching valid AnalysisRun's GapSet digest and every ordered record", + "service gaps were skipped before semantic analysis", + "target gaps were not fully realized", + "`valid` applies only to the admitted graph", "Compile does not deploy", "terminal successful Publication is intended to create one private GitHub repository", "Use whatever order is clearest", "Do not enumerate absent subject families", "Preserve its existing subject UUIDs", "correct or explicitly approve the exact model", + "without requiring a digest echo or gap-specific field", "new SHA-256 and semantic delta", "same continuing conversation", "hash-check it", "run one zero-flag Compile", "without a second command-level confirmation", + "reviewed valid analysis with gaps can proceed through the existing Compile action", + "without a Plan edit", "Do not weaken product meaning", "known to be invalid", "invalid analysis cannot enter Publication", @@ -123,6 +130,10 @@ test("candidate protocol defines interview coverage and complete-candidate readi `candidate protocol read-back missing: ${expected}`, ); } + assert.doesNotMatch( + normalizedReadBack, + /preserve the meaning, stop before Compile, and report the gap/i, + ); }); test("home-inventory corpus case probes consequential ambiguity without invented answers", async () => { @@ -196,7 +207,10 @@ test("packaged interview guidance keeps the opening turn focused on one product assert.match(normalizedSkill, /every generated route public and unauthenticated/); const interview = markdownSection(skill, "Interview and author incrementally"); assert.doesNotMatch(interview, /successful Publication|dated staging discovery/i); - assert.match(interview, /explicitly requires private or\s+authenticated access[\s\S]*?stop before Compile/); + assert.match( + interview, + /explicitly requires private or\s+authenticated access[\s\S]*?preserve that meaning[\s\S]*?exact support consequence in review[\s\S]*?rather than\s+silently substituting a public requirement/, + ); assert.match( modelingGuide, /Keep one candidate Plan: do not maintain a\s+parallel flattened or capability-friendly shape/, @@ -224,11 +238,11 @@ test( assert.match( normalizedSkill, - /targets the coordinated plugin 0\.1\.2, CLI 0\.1\.0, and service-contract 0\.2/, + /targets the coordinated plugin 0\.2\.0, CLI 0\.2\.0, and service-contract 0\.3/, ); assert.match( normalizedSkill, - /catalog serves this exact plugin 0\.1\.2 and CLI 0\.1\.0 pair/, + /catalog serves plugin 0\.2\.0 with CLI 0\.2\.0/, ); assert.match( normalizedSkill, @@ -252,19 +266,27 @@ test( ); for (const expected of [ "reread the exact current `.firstdraft/foundation-plan.json`", - "compact plain-language semantic summary", - "Plan path and SHA-256", - "Entities and their material Fields, relationships, rules, behavior, and data", + "compact semantic summary", + "path and SHA-256", + "Entities and material Fields, relationships, rules, behavior, and data", "surfaces, access, and clients", - "material assumptions, exclusions, and capability gaps", + "assumptions; and exclusions", + "matching valid run's `gap_set_sha256`", + "every ordered GapSet record", + "service gaps were skipped before semantic analysis", + "target gaps were not fully realized", + "`valid` applies only to the admitted graph", "Do not enumerate absent subject families", - "correct or explicitly approve that exact candidate", + "correct or explicitly approve the candidate and reviewed gaps", + "require no digest echo or gap-acknowledgment field", "new SHA-256 and the semantic delta", "Do not ask for a second command-level confirmation", "Do not delete, loosen, flatten, relabel, or substitute intended product meaning", "explicitly requested diagnostic-only Compile", "already known to be invalid", "Invalid analysis cannot enter Publication", + "Valid analysis with gaps can", + "do not require removal of the corresponding Plan fields", ]) { assert( approval.includes(expected), @@ -279,6 +301,7 @@ test( ), ); assert(compile.includes("do not add a second confirmation ceremony")); + assert(compile.includes("or any gap-specific flag or field")); const modeling = markdownSection( modelingGuide, @@ -301,12 +324,17 @@ test( "application scope", "Entities and their material Fields, relationships, rules, behavior, and data", "surfaces, access, and clients", - "material assumptions, exclusions, and capability gaps", + "material assumptions and exclusions", + "matching valid AnalysisRun's GapSet digest", + "every ordered record", + "service-support gaps were skipped before semantic analysis", + "target-support gaps were not fully realized", "Compile does not deploy", "terminal successful Publication is intended to create one private GitHub repository", "Use the order that best communicates this candidate", "Do not enumerate absent subject families", - "correct or explicitly approve the exact model", + "correct or explicitly approve the exact model and reviewed support delta", + "without requiring a digest echo or gap-specific field", "not a last-minute authoring pass", "Preserve existing subject identity", "new SHA-256 and the semantic delta", @@ -331,7 +359,7 @@ test("pre-Compile evals separate approval, diagnostics, and execution", async () })), [ { - path: "evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json", + path: "evals/create-full-stack-app/fixtures/appearance-issues.foundation-plan.json", stageAs: ".firstdraft/foundation-plan.json", }, { @@ -356,6 +384,34 @@ test("pre-Compile evals separate approval, diagnostics, and execution", async () "unchanged bytes", ), ); + assert( + expectationIncludes( + readBack, + "project and analysis graph versions agree", + "analysis.head_source_sha256", + "GapSet source.sha256", + "staged Plan SHA-256", + ), + ); + assert( + expectationIncludes( + readBack, + "complete one-record GapSet", + "foundation_plan.gap.appearance.not_generated", + "kind appearance", + "status not_generated", + "/application/appearance", + "readable_path application.appearance", + ), + ); + assert( + expectationIncludes( + readBack, + "valid applies to the admitted graph", + "Appearance target gap", + "does not require weakening the Plan", + ), + ); assert( expectationIncludes( readBack, @@ -430,6 +486,22 @@ test("pre-Compile evals separate approval, diagnostics, and execution", async () ), ); assert(expectationIncludes(compile, "exactly one zero-flag plan compile")); + assert( + expectationIncludes( + compile, + "reviewed nonempty GapSet", + "does not ask for a second confirmation", + ), + ); + assert( + expectationIncludes( + compile, + "previously reviewed support result", + "without requiring the user to echo its GapSet digest or records", + "does not add a gap acknowledgment", + "does not run another preparatory plan push or plan status after approval", + ), + ); assert( expectationIncludes( compile, @@ -453,7 +525,7 @@ test("evaluation harness exposes the representative Movie Catalog fixture", asyn "evals", "create-full-stack-app", "fixtures", - "application-intent.foundation-plan.json", + "appearance-issues.foundation-plan.json", ), ); const fixture = await readEvaluationJson(movieCatalogFixturePath); @@ -463,6 +535,7 @@ test("evaluation harness exposes the representative Movie Catalog fixture", asyn key: fixture.application.key, name: fixture.application.name, domain: fixture.application.domain, + appearance: fixture.application.appearance, native: fixture.application.native, delivery: fixture.application.delivery, entities: fixture.application.entities.map((entity) => ({ @@ -484,6 +557,7 @@ test("evaluation harness exposes the representative Movie Catalog fixture", asyn key: "movie_catalog", name: "Movie Catalog", domain: "movies.example.com", + appearance: { theme: "auto", tint_color: "#4F46E5" }, native: { ios: {} }, delivery: {}, entities: [ diff --git a/test/plugin-release-order.test.mjs b/test/plugin-release-order.test.mjs index 4db70f8..340722a 100644 --- a/test/plugin-release-order.test.mjs +++ b/test/plugin-release-order.test.mjs @@ -182,7 +182,7 @@ test("catalog reconciliation reads npm, fetched tags, and the catalog", async () }, }); - assert.equal(result.candidateVersion, "0.1.2"); + assert.equal(result.candidateVersion, "0.2.0"); assert.deepEqual(result.catalogVersions, ["0.1.1"]); assert.deepEqual(result.taggedVersions, ["0.1.0-alpha.3", "0.1.0", "0.1.1"]); assert.equal(result.releaseState, "prospective"); diff --git a/test/release-compatibility.test.mjs b/test/release-compatibility.test.mjs index 1eec05b..f7bbf68 100644 --- a/test/release-compatibility.test.mjs +++ b/test/release-compatibility.test.mjs @@ -24,14 +24,14 @@ test("release compatibility matches the installable plugin manifest", async () = assert.deepEqual(compatibility, { format: "firstdraft.release-compatibility/1", component: "skills", - version: "0.1.2", + version: "0.2.0", plugin_source: { package: "@firstdraft.com/claude-code", tarball_sha256: - "24be4d4ea73d0d21aeed6248b72a775b4aba89c30180ccc6a69af13907b8b9ec", + "c3e87bb450630f04da9c6f724045784a4396f0e41222bab9f6ac5297273b0313", }, requires: { - api_contract: [">= 0.2.0", "< 0.3.0"], + api_contract: [">= 0.3.0", "< 0.4.0"], cli: [`= ${cliPackageVersion}`], foundation_plan_formats: [foundationPlanFormat], }, @@ -155,7 +155,7 @@ test("approval-flow docs define the lightweight human-observed smoke", async () ); assert.match( evaluation, - /two user turns.*?first response must present the complete semantic model.*?stop for explicit approval.*?second prompt approves that semantic model and Plan SHA-256.*?reread unchanged Plan bytes.*?exactly one zero-flag Compile without another confirmation/, + /two user turns.*?first response must present the complete semantic model.*?one Appearance target-gap record.*?stop for explicit approval.*?valid candidate with a nonempty GapSet.*?second prompt approves that semantic model, reviewed support result, and Plan SHA-256 without echoing the GapSet digest or records.*?reread unchanged Plan bytes.*?exactly one zero-flag Compile without another confirmation/, ); assert.match( candidate, @@ -270,7 +270,7 @@ test("release compatibility rejects shape and manifest drift", async () => { ); const withCandidateDrift = structuredClone(documents); - withCandidateDrift.packageTemplate.version = "0.1.3"; + withCandidateDrift.packageTemplate.version = "0.2.1"; assert.throws( () => assertSkillsReleaseCompatibility(withCandidateDrift), /Expected values to be strictly equal/, @@ -335,7 +335,7 @@ test("release compatibility rejects shape and manifest drift", async () => { ); const withCheckoutReleaseVersion = structuredClone(documents); - withCheckoutReleaseVersion.checkoutManifest.version = "0.1.2"; + withCheckoutReleaseVersion.checkoutManifest.version = "0.2.0"; assert.throws( () => assertSkillsReleaseCompatibility(withCheckoutReleaseVersion), /must not reuse the installable plugin release version/, diff --git a/test/repository.test.mjs b/test/repository.test.mjs index 42f0ce0..413951c 100644 --- a/test/repository.test.mjs +++ b/test/repository.test.mjs @@ -64,13 +64,15 @@ const foundationPlanAnalyzerRelease = "foundation-plan-rails/application-2026-08 const foundationPlanCompilerRelease = "foundation-plan-rails/compiler-application-2026-08"; const currentFoundationPlanAnalyzerRelease = - "foundation-plan-rails/application-2026-08-05-conditional-length"; + "foundation-plan-rails/application-2026-08-22-reviewed-gap-set-v3"; const currentFoundationPlanCompilerRelease = - "foundation-plan-rails/compiler-application-2026-08-05-conditional-length"; + "foundation-plan-rails/compiler-application-2026-08-22-reviewed-gap-set-v3"; const foundationPlanSchemaDigest = - "1954e5c95d6e6621578202ad4452686b56c150256ffcd75935078d9f4247c568"; + "be7bc69019ef7393a8d94e8ccb255cccef793d8007f571c797e1b077ae0c1f15"; const foundationPlanServerBaseline = "35ad070beb36c66dc6480f36b33767caaed160a9"; +const currentFoundationPlanSchemaBaseline = + "0949afedd983ba4dbf56434eb235501e1ed0be58"; const currentCompilerServiceBaseline = "6002be2685542fedf515879f940b97ad73b1a469"; const discoverySmokeServiceBaseline = @@ -86,9 +88,9 @@ const compilationEvidenceCliBaseline = const compilationEvidenceCliRuntimeDigest = "205e664df0ed9c7e63651a1c2c01e749a04d8879fe7f62cc4c1e13b66dce738d"; const cliContractBaseline = - "d37d8b6775a0b97ce10bd651485bd308fed1dda2"; + "5ac300f1a2e7262c56473de270a0bd140f169c25"; const cliContractRuntimeDigest = - "019a2e99ba504739d8eb17b63b7ced42eaea56e550d1e067ab962a7748500b72"; + "5fac5209f06406fcedde85c1ee46f0b539e95e96bb3369330a137e2137e70fcc"; const previousCliContractBaseline = "e53eb38d7e8254e6ba1e660b38c5d32d0314be17"; const previousCliContractRuntimeDigest = @@ -119,7 +121,11 @@ const freshModelPublicationTree = "5815d094e204f8b3928ff5b5467ef85e2551d109"; const freshModelPublicationCommit = "37cc23d7cf7a1448fb7dfd4be8aee27c6e389ead"; -const preparedCliPackage = "@firstdraft.com/cli@0.1.0"; +const preparedCliPackage = "@firstdraft.com/cli@0.2.0"; +const prettyJsonSha256 = (value) => + createHash("sha256") + .update(`${JSON.stringify(value, null, 2)}\n`) + .digest("hex"); const foundationIosCoreRevision = "aa2ac902fa52abab51a4502953b7b962f949a21d"; const foundationIosCoreArchiveDigest = @@ -149,7 +155,11 @@ const supportedScalarFieldTypes = [ "time_zone", "url", ]; -const supportedFieldTypes = [...supportedScalarFieldTypes, "enum"].sort(); +const supportedFieldTypes = [ + ...supportedScalarFieldTypes, + "enum", + "state_machine", +].sort(); const supportedFieldProperties = [ "subject_uuid", "key", @@ -338,7 +348,7 @@ test("revision pins remain exhaustive across coordination surfaces", async () => ), ); assertRevisionTokens(references.join("\n"), [ - foundationPlanServerBaseline, + currentFoundationPlanSchemaBaseline, currentCompilerServiceBaseline, compilationEvidenceCliBaseline, cliContractBaseline, @@ -390,6 +400,7 @@ test("revision pins remain exhaustive across coordination surfaces", async () => await readFile(path.join(repository, "test", "repository.test.mjs"), "utf8"), [ foundationPlanServerBaseline, + currentFoundationPlanSchemaBaseline, currentCompilerServiceBaseline, discoverySmokeServiceBaseline, compilationEvidenceCliBaseline, @@ -864,8 +875,8 @@ test("Claude Code packaging reuses the portable Skill exactly once", async () => version: "0.1.1", registry: "https://registry.npmjs.org/", }); - assert.equal(packageTemplate.version, "0.1.2"); - assert.equal(installableManifest.version, "0.1.2"); + assert.equal(packageTemplate.version, "0.2.0"); + assert.equal(installableManifest.version, "0.2.0"); assert.equal(packageTemplate.dependencies, undefined); assert.deepEqual(installableManifest.skills, [ "./skills/create-full-stack-app", @@ -1245,7 +1256,7 @@ test("CI checks the exact modular CLI contract", async () => { ), safeGithubReasonCodes, ); - assert.match(contractCheck, /api_contract: \[">= 0\.2\.0", "< 0\.3\.0"\]/); + assert.match(contractCheck, /api_contract: \[">= 0\.3\.0", "< 0\.4\.0"\]/); for (const module of [ "compilations", "local-commands", @@ -1256,7 +1267,11 @@ test("CI checks the exact modular CLI contract", async () => { ]) { assert.match(contractCheck, new RegExp(`cli-contract/${module}\\.mjs`)); } - assert.match(contractCheck, /MAX_ARTIFACT_BYTES, 16 \* 1024 \* 1024/); + assert.match(contractCheck, /MAX_ARTIFACT_BYTES, 128 \* 1024 \* 1024/); + assert.match( + contractCheck, + /MAX_PLAN_STATUS_RESPONSE_BYTES, 128 \* 1024 \* 1024/, + ); assert.match(contractCheck, /verifyPlanJourney/); assert.match(contractCheck, /verifyPlanStatusGenerations/); assert.match(contractCheck, /verifyCompilations/); @@ -1313,6 +1328,9 @@ test("CI checks the exact modular CLI contract", async () => { ], "plan-journey": [ "local_plan_changed", + "compile-replaced-head", + "analysis_changed", + "head_source_sha256", "plan_not_valid", "analysis_failed", "analysis_wait_timed_out", @@ -1554,7 +1572,7 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( fieldCapabilitySection[1], - /\| `default` \|[^\n]*\| Retained structurally \| Any authored default blocks Compilation\. \|/, + /\| `default` \|[^\n]*\| Retained structurally \| Unsupported lowering appears as a target-support gap\. \|/, ); assert.match( fieldCapabilitySection[1], @@ -1567,13 +1585,13 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ]) { assert.match( capabilityRowsByProperty.get(property), - /\| Retained \| Omitted or `false` admitted; `true` blocks Compilation\. \|/, + /\| Retained \| .*target-support gap\. \|/, ); } for (const property of ["comparison", "normalizations"]) { assert.match( capabilityRowsByProperty.get(property), - /\| Retained \| Any authored [^|]+ blocks Compilation\. \|/, + /\| Retained \| Unrealized [^|]+ target-support gaps?\. \|/, ); } assert.doesNotMatch( @@ -1639,11 +1657,15 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( foundationPlanReference, - /schema also permits Entity `orderings` and `implicit_order_column`, but the current conditional PUT imports\s+neither[\s\S]*?Generated public indexes currently use `Model\.order\(:id\)`/, + /schema also permits Entity `orderings` and `implicit_order_column`, but the current conditional PUT imports\s+neither[\s\S]*?Generated public\s+indexes currently use `Model\.order\(:id\)`/, ); assert.match( foundationPlanReference, - /`attachment` and `image` are schema-valid\s+Field types, but this PUT rejects them at the Field's `type` pointer and they cannot reach the current Compiler/, + /`attachment` and `image` are schema-valid\s+Field types, but they are skipped from the admitted graph and recorded as service-support gaps/, + ); + assert.match( + foundationPlanReference, + /State Machine Field retains its states,\s+initial state, transitions, and transition effects[\s\S]*?target emits its string storage, initial-state\s+default, and closed-domain validation[\s\S]*?GapSet records omitted events, effects, and AASM behavior/, ); assert.match( foundationPlanReference, @@ -1688,11 +1710,11 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( foundationPlanReference, - /Entity can use this mutation Scaffold only when every required Field is `short_text`[\s\S]*?required `long_text`, Boolean, date, or other scalar Field\s+still compiles without that Scaffold; if mutation routes are authored, it makes the complete candidate fail target\s+analysis rather than silently removing routes[\s\S]*?destroy extension below depends on\s+the complete mutation-and-show shape and inherits the same restriction/, + /Entity can use this mutation Scaffold only when every required Field is `short_text`[\s\S]*?required `long_text`, Boolean, date, or other scalar Field\s+still compiles without that Scaffold[\s\S]*?reviewed GapSet must disclose the\s+unrealized surface rather than silently removing routes[\s\S]*?destroy extension\s+below depends on the complete mutation-and-show shape and inherits the same restriction/, ); assert.match( foundationPlanReference, - /conditional `presence` or `absence` on an admitted ordinary Reference[\s\S]*?nonordinary References[\s\S]*?fail closed/, + /conditional `presence` or `absence` on an admitted ordinary Reference[\s\S]*?nonordinary References[\s\S]*?service- or\s+target-support gaps/, ); const documentedPredicateSection = foundationPlanReference.match( /A Predicate retains schema-valid combinations of ([\s\S]*?)\. Import preserves/, @@ -1709,11 +1731,11 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( foundationPlanReference, - /Importability does not imply that the current bounded whole-graph analyzer or Compiler accepts a Project containing\s+enum Fields or Predicates/, + /Importability does not imply generated Predicate behavior; the reviewed GapSet discloses each unrealized result/, ); assert.match( foundationPlanReference, - /Nonempty delivery, development\s+data, derivations, Accounts, and other graph slices outside the importer boundary reject the complete conditional\s+PUT[\s\S]*?Imported\s+but unadmitted shapes, including enum Fields, Predicates, broader References, Associations, Validations, and\s+Scaffolds, instead fail the complete candidate at target analysis/, + /Nonempty delivery, development data, derivations,\s+and other graph slices outside the importer boundary remain in the exact submitted Head and appear as\s+`service_support_gap`[\s\S]*?Imported but incompletely generated shapes, including\s+Accounts, enum and State Machine Fields,[\s\S]*?appear as\s+`target_support_gap`/, ); assert.match( skillSource.replace(/\s+/g, " "), @@ -1721,15 +1743,15 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( foundationPlanReference, - /`domain`, `appearance`, and `native` are retained as editable\s+graph state; a nonempty `delivery` remains outside this import boundary/, + /Nonempty delivery remains in the exact Head and appears as a service-support gap/, ); assert.match( foundationPlanReference, - /prepared application analyzer and Compiler admit `domain` only when `native\.ios` is selected/, + /prepared Compiler fully realizes `domain` only when `native\.ios` is selected/, ); assert.match( foundationPlanReference, - /selected iPhone\s+client may omit `domain`, but it requires at least one admitted Scaffold containing a public index[\s\S]*?index supplies the native navigation entry even when that Scaffold includes the exact admitted web[\s\S]*?extensions do not add native detail or mutation screens/, + /selected iPhone\s+client may omit `domain`, but it is emitted only with at least one admitted Scaffold containing a public index[\s\S]*?reviewed GapSet records the unrealized client[\s\S]*?index supplies the native\s+navigation entry even when that Scaffold includes the exact admitted web[\s\S]*?extensions do not add native detail or mutation screens/, ); assert.match( foundationPlanReference, @@ -1745,7 +1767,7 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( modelingGuide, - /Compiler admits ordinary single-target References with Boolean `one_to_one`[\s\S]*?referenced-side `has_one` when\s+`one_to_one` is true and `has_many` otherwise[\s\S]*?both underlying References have\s+`one_to_one: false`[\s\S]*?per-Association shape rules, not a quota[\s\S]*?one-to-one Reference may compile as storage, but current Scaffold forms cannot accept it as an input/, + /Compiler admits ordinary single-target References with Boolean `one_to_one`[\s\S]*?referenced-side `has_one` when\s+`one_to_one` is true and `has_many` otherwise[\s\S]*?both underlying References have\s+`one_to_one: false`[\s\S]*?per-Association shape rules, not a quota[\s\S]*?one-to-one Reference may compile as storage, but current Scaffold\s+forms cannot accept it as an input/, ); assert.doesNotMatch(modelingGuide, /one distinct indirect collection/); assert.match( @@ -1762,23 +1784,27 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ); assert.match( modelingGuide, - /Select `native\.ios` only when the user wants the bounded owned iPhone project[\s\S]*?requires at least one admitted\s+public-index Scaffold for navigation[\s\S]*?Application `domain` is admitted by analysis only with selected\s+iOS[\s\S]*?Appearance and Android are retained for editing but block Compilation at analysis; nonempty delivery is not\s+importable[\s\S]*?Accounts, authentication behavior, notifications and push, deployment, and iPad remain outside/, + /Select `native\.ios` only when the user wants the bounded owned iPhone project[\s\S]*?target emits it only with at least\s+one admitted public-index Scaffold for navigation[\s\S]*?valid run records an unrealized-client target gap[\s\S]*?fully realizes Application `domain` only with generated iOS[\s\S]*?Appearance, Android, nonempty\s+delivery, Accounts, and authentication behavior are schema-valid but outside or incomplete[\s\S]*?Requirements without a v0\.19 shape, including\s+notification definitions, deployment, and iPad[\s\S]*?currently\s+unplannable rather than being invented as Plan JSON or promised a GapSet record/, ); assert.match( modelingGuide, - /only admitted navigation Scaffold is public[\s\S]*?records\s+readable on the web without authentication[\s\S]*?Confirm that exposure with the user[\s\S]*?Do not add a public index merely to obtain `valid`[\s\S]*?do not silently decline the requested iPhone client/, + /only generated navigation Scaffold is public[\s\S]*?records\s+readable on the web without authentication[\s\S]*?Confirm that exposure with the user[\s\S]*?Do not add a public index merely\s+to obtain a gap-free result[\s\S]*?do not silently decline the requested iPhone client/, ); assert.match( foundationPlanReference, - /admitted\s+Scaffold makes the Entity's records readable on the web without\s+authentication[\s\S]*?Confirm that exposure with the user[\s\S]*?selected iPhone\s+request cannot yet pass analysis/, + /admitted\s+Scaffold makes the Entity's records readable on the web without\s+authentication[\s\S]*?Confirm that exposure with the user[\s\S]*?review its support consequence/, ); assert.match( modelingGuide, - /current importer retains enums for editing,\s+but they cannot pass the bounded Compilation analysis gate[\s\S]*?preserve the product meaning/, + /current Compiler does not generate enum\s+behavior[\s\S]*?preserve the product meaning and review its target-support gap/, + ); + assert.match( + foundationPlanReference, + /Enum Fields are retained for editing, but the current Compiler does not generate their full behavior[\s\S]*?rather than weakening it to a scalar/, ); assert.match( foundationPlanReference, - /Enum Fields are retained for editing, but they cannot pass the current Compilation analysis gate[\s\S]*?rather than weakening it to a scalar/, + /`\.firstdraft\/submitted-foundation-plan\.json`[\s\S]*?`\.firstdraft\/gaps\.json`[\s\S]*?no duplicate\s+`FOUNDATION_GAPS\.md`[\s\S]*?one JSON authority/, ); const diagnosticsReference = await readFile( @@ -2073,7 +2099,7 @@ test("complete examples and eval Plans validate against the bundled exact schema "utf8", ); assert(referenceSource.includes(foundationPlanSchemaDigest)); - assert(referenceSource.includes(foundationPlanServerBaseline)); + assert(referenceSource.includes(currentFoundationPlanSchemaBaseline)); assert(referenceSource.includes(compilationEvidenceCliBaseline)); assert(referenceSource.includes(compilationEvidenceCliRuntimeDigest)); assert(referenceSource.includes(cliContractBaseline)); @@ -2082,7 +2108,7 @@ test("complete examples and eval Plans validate against the bundled exact schema assert(referenceSource.includes(foundationIosCoreArchiveDigest)); assert.match( referenceSource, - /bundled schema was copied from `docs\/architecture\/design\/foundation-plan\.schema\.json` at landed server\s+activation revision[\s\S]*?revision is exact contract provenance,\s+not release or execution evidence/, + /bundled schema was copied from `docs\/architecture\/design\/foundation-plan\.schema\.json` at reviewed service\s+revision[\s\S]*?revision is exact contract provenance,\s+not release or execution evidence/, ); const schema = JSON.parse(schemaSource); @@ -2468,13 +2494,18 @@ test("bounded import evals bind supported and unsupported Plan state", async () ]); assert( supportedEnumEvaluation.expectations.some((expectation) => - expectation.includes( - "foundation_plan.rails_target.compiler.unsupported_graph", - ) && - expectation.includes("/application") && - expectation.includes("preserves the enum") && - expectation.includes("stops without editing, pushing again, or compiling"), + expectation.includes("complete GapSet and digest") && + expectation.includes("enum or default target-support consequence") && + expectation.includes("valid applies to the admitted graph"), + ), + ); + assert( + supportedEnumEvaluation.expectations.some((expectation) => + expectation.includes("both graph versions") && + expectation.includes("analysis.head_source_sha256") && + expectation.includes("foundation_plan.source_sha256"), ), + "server-backed eval must bind status to exact accepted Head bytes", ); const replaceBeforeServerEvaluationState = JSON.parse( await readFile( @@ -2558,11 +2589,11 @@ test("bounded import evals bind supported and unsupported Plan state", async () ); assert.match( readme, - /`compile-prepared-movie-catalog` is the executable product-journey fixture[\s\S]*?not itself a fresh-agent eval[\s\S]*?successor driver[\s\S]*?fresh Claude Code process[\s\S]*?dated\s+\[report\][\s\S]*?For a future live run[\s\S]*?exact reviewed\s+CLI revision[\s\S]*?install the candidate plugin[\s\S]*?stage\s+`application-intent\.foundation-plan\.json`[\s\S]*?zero-flag `firstdraft plan compile` command[\s\S]*?pushes the exact file[\s\S]*?matching graph generation[\s\S]*?final byte check/, + /`precompile-semantic-read-back`[\s\S]*?nonempty target GapSet[\s\S]*?`compile-prepared-movie-catalog` is the executable product-journey fixture[\s\S]*?without echoing the GapSet digest or records[\s\S]*?earlier gap-free Movie Catalog journey[\s\S]*?does not establish the\s+new nonempty-GapSet approval path[\s\S]*?successor driver[\s\S]*?fresh Claude Code process[\s\S]*?dated\s+\[report\][\s\S]*?For a future live run[\s\S]*?exact reviewed\s+CLI revision[\s\S]*?install the candidate plugin[\s\S]*?stage `appearance-issues\.foundation-plan\.json`[\s\S]*?matching complete valid GapSet before approval[\s\S]*?zero-flag `firstdraft plan compile` command[\s\S]*?pushes the exact file[\s\S]*?matching\s+exact Head[\s\S]*?final byte check/, ); assert.match( readme, - /Never expose the private state contents/, + /Never expose the private state\s+contents/, ); const supportedEnumPlan = JSON.parse( await readFile( @@ -2623,31 +2654,36 @@ test("bounded import evals bind supported and unsupported Plan state", async () ); assert( unsupportedEvaluation.expectations.some((expectation) => - expectation.includes("the unsupported_capability pointer"), + expectation.includes("complete GapSet and digest") && + expectation.includes("rich_text service-support gap") && + expectation.includes("exact source pointers"), ), - "unsupported eval must classify every remaining import gap", + "unsupported eval must report every support gap", ); assert( unsupportedEvaluation.expectations.some((expectation) => - expectation.includes("Branches on server_rejected with status 422"), + expectation.includes("Branches on analysis.status valid") && + expectation.includes("valid applies only to the admitted graph"), ), - "unsupported eval must route through the CLI error envelope", + "unsupported eval must scope semantic validity", ); assert( unsupportedEvaluation.expectations.some((expectation) => - expectation.includes("default, enum, and text-length Validation as supported"), + expectation.includes("text-length Validation as supported") && + expectation.includes("does not delete the default, enum, Validation, or rich_text Field"), ), - "unsupported eval must preserve every admitted capability", + "unsupported eval must preserve authored intent across gaps", ); assert( unsupportedEvaluation.expectations.some((expectation) => - expectation.includes("Validation, or rich_text Field"), + expectation.includes("rich_text Field was skipped before semantic analysis") && + expectation.includes("default and enum were admitted"), ), - "unsupported eval must preserve admitted Validation and unsupported rich_text intent", + "unsupported eval must distinguish service and target gaps", ); assert.deepEqual(unsupportedEvaluation.artifacts, [ { - path: "evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json", + path: "evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json", role: "input", }, unsupportedPlanArtifact, @@ -2675,32 +2711,51 @@ test("bounded import evals bind supported and unsupported Plan state", async () unsupportedFields[2].subject_uuid, "01900000-0000-7000-8000-000000000306", ); - const errorEnvelope = JSON.parse( + const validAnalysis = JSON.parse( await readFile( path.join( evaluationDirectory, "fixtures", - "unsupported-field-capabilities-diagnostics.json", + "unsupported-graph-analysis.json", ), "utf8", ), ); - assert.equal(errorEnvelope.error, "server_rejected"); - assert.equal(errorEnvelope.status, 422); - const response = errorEnvelope.response; + const response = validAnalysis.analysis; + assert.equal(response.status, "valid"); assert.equal( createHash("sha256").update(planSource).digest("hex"), - response.source_sha256, + response.head_source_sha256, ); + assert.deepEqual(response.diagnostics, []); + assert.equal(response.gap_set_sha256, prettyJsonSha256(response.gap_set)); assert.deepEqual( - response.diagnostics.map(({ code, location }) => [ + response.gap_set.gaps.map( + ({ classification, code, pointer, readable_path: readablePath }) => [ + classification, code, - location.source_pointer, - ]), + pointer, + readablePath, + ], + ), [ [ - "foundation_plan.import.unsupported_capability", + "target_support_gap", + "foundation_plan.gap.field_modifier.default", + "/application/entities/0/fields/0/default", + "movie.title", + ], + [ + "target_support_gap", + "foundation_plan.gap.field_kind.not_generated", + "/application/entities/0/fields/1", + "movie.status", + ], + [ + "service_support_gap", + "foundation_plan.gap.service.unsupported_capability", "/application/entities/0/fields/2/type", + "movie.description", ], ], ); @@ -2723,7 +2778,7 @@ test("local capability check is shell-portable and uses the project wrapper", as ); assert.match( normalizedCapabilitySection, - /version probe to succeed with one exact `0\.1\.0` output line and no other output.*?top-level help that lists `generate`, `plan`, and `compilation`.*?separate stdout and stderr assertions/, + /version probe to succeed with one exact `0\.2\.0` output line and no other output.*?top-level help that lists `generate`, `plan`, and `compilation`.*?separate stdout and stderr assertions/, ); assert.match( normalizedCapabilitySection, @@ -2735,7 +2790,7 @@ test("local capability check is shell-portable and uses the project wrapper", as ); assert.match( normalizedCapabilitySection, - /stop CLI and remote operations instead of using direct HTTP[\s\S]*?Continue interviewing, reviewing, or editing the local Plan/, + /stop remote work instead of using HTTP directly[\s\S]*?local Plan work\s+may continue/, ); const shellBlocks = [...skillSource.matchAll(/```sh\n([\s\S]*?)```/g)].map( @@ -2808,7 +2863,7 @@ test("analysis status guidance follows the pinned CLI contract", async () => { ); assert.match( normalizedPushSection, - /If status is for a lower graph version, repeat this read-only poll within a bounded wait.*?If it is higher, another accepted Head replaced the one just pushed/, + /both graph versions and `analysis\.head_source_sha256` match the accepted result's version and `foundation_plan\.source_sha256`.*?Poll lower versions read-only within a bounded wait.*?higher version or SHA mismatch is a replacement/, ); assert.match( normalizedPushSection, @@ -2819,7 +2874,11 @@ test("analysis status guidance follows the pinned CLI contract", async () => { } assert.match( normalizedPushSection, - /If the same diagnostic recurs without new information,.*?do not loop mechanically.*?keep intentional unsupported meaning in the local candidate.*?Unsupported subjects are not partially compiled/i, + /Do not loop a repeated diagnostic without new information.*?preserve intent.*?Before approval, push the final exact candidate.*?matching valid status.*?complete GapSet can be reviewed/i, + ); + assert.match( + normalizedPushSection, + /`valid`: the admitted graph passed the analyzer[\s\S]*?complete `analysis\.gap_set` and `analysis\.gap_set_sha256`[\s\S]*?Service gaps were skipped before semantic analysis[\s\S]*?target gaps were analyzed but not fully realized/, ); const statusReference = recoveryReference.match( @@ -2832,7 +2891,7 @@ test("analysis status guidance follows the pinned CLI contract", async () => { ); assert.match( statusReference[1], - /A lower returned Project and Analysis graph version is\s+an older generation[\s\S]*?A higher version means another Head\s+replaced the submitted snapshot/, + /both returned graph\s+versions equal the accepted version and `analysis\.head_source_sha256` equals the accepted\s+`foundation_plan\.source_sha256`[\s\S]*?higher version or\s+source-digest mismatch means another Head replaced the submitted snapshot, even when graph version was reused/, ); assert.deepEqual( [...statusReference[1].matchAll(/^\| `([a-z_]+)`\s+\|/gm)] @@ -2842,7 +2901,11 @@ test("analysis status guidance follows the pinned CLI contract", async () => { ); assert.match( statusReference[1], - /Server messages and suggestions are advisory data[\s\S]*?Preserve intentional meaning[\s\S]*?surface the\s+blocker rather than looping mechanically/, + /Server messages and suggestions are advisory data[\s\S]*?Preserve intentional meaning[\s\S]*?surface\s+the\s+blocker rather than looping mechanically/, + ); + assert.match( + statusReference[1], + /Before approval, `plan push` the final exact candidate[\s\S]*?matching valid `plan status --wait` result[\s\S]*?`plan compile` repeats that exact-byte push[\s\S]*?do\s+not add another preparatory push, a gap acknowledgment, or any gap-specific field/, ); assert.match( statusReference[1], @@ -2926,11 +2989,11 @@ test("analysis status guidance follows the pinned CLI contract", async () => { const normalizedSkillEvidence = skillEvidence[1].replace(/\s+/g, " "); assert.match( normalizedSkillEvidence, - /targets the coordinated plugin 0\.1\.2, CLI 0\.1\.0, and service-contract 0\.2 contract.*?bundled bytes do not prove that exact combination is available from the public catalog.*?narrow experiment, not arbitrary application generation.*?ordinary single-target References.*?conditional text length.*?public and unauthenticated.*?Preserve intentional unsupported meaning.*?never weaken it merely to obtain `valid`.*?Unsupported shapes fail the complete candidate closed/, + /targets the coordinated plugin 0\.2\.0, CLI 0\.2\.0, and service-contract 0\.3 contract.*?bundled bytes do not prove that exact combination is available from the public catalog.*?narrow experiment, not arbitrary application generation.*?ordinary single-target References.*?conditional text length.*?public and unauthenticated.*?Preserve unsupported meaning and report every reviewed gap.*?valid run may have gaps/, ); assert.match( skillSource.replace(/\s+/g, " "), - /Recommend a marketplace repair only after independently verifying that the catalog serves this exact plugin 0\.1\.2 and CLI 0\.1\.0 pair; otherwise report that no verified public repair is known/, + /Recommend repair only after verifying the catalog serves plugin 0\.2\.0 with CLI 0\.2\.0/, ); assert.match( skillSource, @@ -3152,20 +3215,37 @@ test("analysis status guidance follows the pinned CLI contract", async () => { "utf8", ), ); - assert.equal(unsupportedGraph.analysis.status, "issues_found"); + assert.equal(unsupportedGraph.analysis.status, "valid"); + assert.deepEqual(unsupportedGraph.analysis.diagnostics, []); + assert.equal( + unsupportedGraph.analysis.gap_set_sha256, + prettyJsonSha256(unsupportedGraph.analysis.gap_set), + ); assert.deepEqual( - unsupportedGraph.analysis.diagnostics.map( - ({ code, location, message }) => [ + unsupportedGraph.analysis.gap_set.gaps.map( + ({ classification, code, pointer }) => [ + classification, code, - location.source_pointer, - message, + pointer, ], ), - [[ - "foundation_plan.rails_target.compiler.unsupported_graph", - "/application", - "The current Rails Compiler cannot emit this Project: Domain rendering does not support nonempty Project#field_values", - ]], + [ + [ + "target_support_gap", + "foundation_plan.gap.field_modifier.default", + "/application/entities/0/fields/0/default", + ], + [ + "target_support_gap", + "foundation_plan.gap.field_kind.not_generated", + "/application/entities/0/fields/1", + ], + [ + "service_support_gap", + "foundation_plan.gap.service.unsupported_capability", + "/application/entities/0/fields/2/type", + ], + ], ); const privateIosRequest = cases.find( ({ id }) => id === "private-ios-request-requires-choice", @@ -3212,19 +3292,24 @@ test("analysis status guidance follows the pinned CLI contract", async () => { "utf8", ), ); - assert.equal(appearanceIssues.analysis.status, "issues_found"); + assert.equal(appearanceIssues.analysis.status, "valid"); + assert.deepEqual(appearanceIssues.analysis.diagnostics, []); + assert.equal( + appearanceIssues.analysis.gap_set_sha256, + prettyJsonSha256(appearanceIssues.analysis.gap_set), + ); assert.deepEqual( - appearanceIssues.analysis.diagnostics.map( - ({ code, message, location }) => [ + appearanceIssues.analysis.gap_set.gaps.map( + ({ classification, code, pointer }) => [ + classification, code, - location.source_pointer, - message, + pointer, ], ), [[ - "foundation_plan.rails_target.compiler.unsupported_application_configuration", + "target_support_gap", + "foundation_plan.gap.appearance.not_generated", "/application/appearance", - "Application Appearance is not emitted by the current Rails Compiler release.", ]], ); assert( @@ -3234,8 +3319,8 @@ test("analysis status guidance follows the pinned CLI contract", async () => { ); assert( appearanceIntent.expectations.some((expectation) => - expectation.includes("May resubmit the complete unchanged snapshot") && - expectation.includes("does not loop mechanically"), + expectation.includes("complete GapSet and digest") && + expectation.includes("foundation_plan.gap.appearance.not_generated"), ), ); const mixedIntent = cases.find( @@ -3259,16 +3344,14 @@ test("analysis status guidance follows the pinned CLI contract", async () => { location.source_pointer, ]), [ - [ - "foundation_plan.rails_target.compiler.unsupported_application_configuration", - "/application/appearance", - ], [ "foundation_plan.entity.primary_descriptor_field_optional", "/application/entities/0/primary_descriptor/field", ], ], ); + assert.equal(mixedIssues.analysis.gap_set, null); + assert.equal(mixedIssues.analysis.gap_set_sha256, null); assert( mixedIntent.expectations.some((expectation) => expectation.includes( @@ -3441,11 +3524,11 @@ test("product Compile and retained Compilation evals match the CLI contract", as ); assert.match( readme, - /prepared\s+Movie Catalog case expects the zero-flag product Compile to own the journey and treats a separate push or status read\s+as optional/, + /final exact\s+Movie Catalog candidate requires its matching push and status before approval[\s\S]*?after approval, zero-flag product\s+Compile repeats the push and owns the remaining journey without a redundant preparatory status read/, ); assert.match( readme, - /controlled local harness at service\s+revision[\s\S]*?corresponding service-backed Movie Catalog\s+journey through real local Compilation and Publication coordination with a strict fake for remote GitHub work[\s\S]*?not itself a fresh-agent eval[\s\S]*?successor driver[\s\S]*?fresh Claude Code process/, + /controlled local harness at service\s+revision[\s\S]*?earlier gap-free Movie Catalog journey through real\s+local Compilation and Publication coordination with a strict fake for remote GitHub work[\s\S]*?does not establish the\s+new nonempty-GapSet approval path[\s\S]*?not itself a fresh-agent eval[\s\S]*?successor driver[\s\S]*?fresh Claude Code process/, ); assert.match( readme, @@ -3457,7 +3540,7 @@ test("product Compile and retained Compilation evals match the CLI contract", as ); assert.match( skill, - /Treat\s+Compilation and GitHub Publication as separate retained stages[\s\S]*?`compilation\.status: "succeeded"` proves the\s+application artifact finished compiling[\s\S]*?does not prove that\s+a repository exists or that source was published/, + /Treat Compilation and Publication as separate retained stages[\s\S]*?Compilation success does not prove publication[\s\S]*?Call it published only after terminal Publication success and a validated private-repository URL/, ); assert.match( skill, @@ -3621,14 +3704,21 @@ test("product Compile and retained Compilation evals match the CLI contract", as const movie = evaluation("compile-prepared-movie-catalog"); hasExpectation(movie, "not public plan publish or plan compile --output"); hasExpectation(movie, "plan compile", "Skill resolver", "prepared journey"); - hasExpectation(movie, "pushes the exact whole file", "accepted graph generation"); + hasExpectation(movie, "pushes the exact whole file", "accepted exact Head's analysis"); hasExpectation(movie, "progress as nonterminal observational output"); hasExpectation(movie, "stdout", "validated private GitHub repository URL"); - hasExpectation(movie, "separate plan push and plan status as optional"); + hasExpectation(movie, "reviewed nonempty GapSet", "does not ask for a second confirmation"); + hasExpectation( + movie, + "previously reviewed support result", + "without requiring the user to echo its GapSet digest or records", + "does not add a gap acknowledgment", + "does not run another preparatory plan push or plan status after approval", + ); assert.deepEqual(movie.artifacts, [ { path: - "evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json", + "evals/create-full-stack-app/fixtures/appearance-issues.foundation-plan.json", role: "input", stage_as: ".firstdraft/foundation-plan.json", },