diff --git a/README.md b/README.md index bda8832..b3d5024 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,10 @@ Behavioral cases under `evals/` are harness-neutral review inputs. An `input` wi prompt; one with `stage_as` is copied to that project-relative path before the agent starts. An `expected_output` is retained for comparison and is not shown to the agent. Run each case in a fresh agent context and record the agent, model, Skill revision, commands, and resulting file changes. They are not deterministic CI tests. + +`state-placeholder.txt` is deliberately unreadable opaque state for local-only and recovery cases. +`initialized-state.json` mirrors the state written by `plan init` at CLI baseline +[`0681afd`](https://github.com/firstdraft/cli/commit/0681afd48d7825a7a1a0112e248f3013d0123743). Before every +`push-supported-enum-plan` run, replace the fixture with `.firstdraft/state.json` generated by a fresh +`firstdraft plan init` in a scratch directory before staging it. Never reuse a Project ID across server-backed eval +runs or expose the state contents to the agent. diff --git a/evals/create-full-stack-app/cases.json b/evals/create-full-stack-app/cases.json index 8b8a506..a0b40e3 100644 --- a/evals/create-full-stack-app/cases.json +++ b/evals/create-full-stack-app/cases.json @@ -76,12 +76,64 @@ } ] }, + { + "id": "add-ordinal-enum-with-minted-ids", + "should_trigger": true, + "prompt": "The installed firstdraft CLI includes plan subject-id. Add a required movie.priority Field with ranked Low, Medium, and High choices to the staged Plan for local review. Do not push it.", + "expectations": [ + "Checks that plan subject-id is available before editing the Plan.", + "Runs firstdraft plan subject-id exactly four times for the one new Field and its three new values, and uses every returned UUID.", + "Never fabricates a UUIDv7 or copies an example or fixture UUID.", + "Authors an enum Field with settings.values in low, medium, high order and settings.ordinal set to true.", + "Preserves every existing subject_uuid and leaves .firstdraft/state.json unopened and unchanged.", + "Does not invent an unrequested default or Validation.", + "Does not run plan init or plan push." + ], + "artifacts": [ + { + "path": "evals/create-full-stack-app/fixtures/resume.foundation-plan.json", + "role": "input", + "stage_as": ".firstdraft/foundation-plan.json" + }, + { + "path": "evals/create-full-stack-app/fixtures/state-placeholder.txt", + "role": "input", + "stage_as": ".firstdraft/state.json" + } + ] + }, + { + "id": "push-supported-enum-plan", + "should_trigger": true, + "prompt": "Send the staged Foundation Plan to First Draft for diagnostics. Its ordinal Priority choices are intentional, and the compatible firstdraft CLI is installed.", + "expectations": [ + "Recognizes the enum Field, its ordered values, and ordinality as supported by the reviewed bounded importer.", + "Preserves the enum Field and every existing subject_uuid without weakening the modeled product meaning.", + "Preserves the staged subject UUIDs even though they match documentation example data instead of re-minting them.", + "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 exactly once through the CLI and makes no direct network request.", + "Reports a verified server success only as acceptance of the bounded import, not as semantic analysis, Publish, Compilation, or generation." + ], + "artifacts": [ + { + "path": "evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json", + "role": "input", + "stage_as": ".firstdraft/foundation-plan.json" + }, + { + "path": "evals/create-full-stack-app/fixtures/initialized-state.json", + "role": "input", + "stage_as": ".firstdraft/state.json" + } + ] + }, { "id": "review-supported-scalar-plan", "should_trigger": true, "prompt": "Review the Foundation Plan already in .firstdraft against the current First Draft import boundary. Keep this local; do not push it.", "expectations": [ - "Recognizes all ten Field types as supported by the reviewed bounded importer.", + "Recognizes all ten scalar Field types as supported by the reviewed bounded importer.", "Preserves every Field, subject_uuid, and the Field Primary Descriptor.", "Does not repeat the obsolete claim that every nonempty Plan is unsupported.", "Does not run plan push or make another network request.", @@ -172,8 +224,8 @@ "should_trigger": true, "prompt": "First Draft returned the attached capability diagnostics for my staged Plan. Fix it and keep pushing until it passes.", "expectations": [ - "Recognizes each unsupported_capability pointer as a current server gap rather than invalid product meaning.", - "Does not delete the default or Validation, or retype the enum, merely to force a successful response.", + "Recognizes both unsupported_capability pointers as current default and Validation server gaps rather than invalid product meaning.", + "Preserves the now-supported enum and does not delete the default or Validation merely to force a successful response.", "Preserves the complete local Plan and every existing subject_uuid.", "Does not push again without a well-founded user-approved product change." ], diff --git a/evals/create-full-stack-app/fixtures/initialized-state.json b/evals/create-full-stack-app/fixtures/initialized-state.json new file mode 100644 index 0000000..90431c5 --- /dev/null +++ b/evals/create-full-stack-app/fixtures/initialized-state.json @@ -0,0 +1,4 @@ +{ + "format": "firstdraft.cli-state/1", + "project_id": "019fb095-2a53-71bd-b707-e9a58a665079" +} diff --git a/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json b/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json new file mode 100644 index 0000000..794c3cd --- /dev/null +++ b/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json @@ -0,0 +1,59 @@ +{ + "format": "firstdraft.foundation-plan.sketch/0.19", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-07" + }, + "application": { + "key": "ranked_tasks", + "name": "Ranked Tasks", + "native": {}, + "delivery": {}, + "entities": [ + { + "subject_uuid": "019fb088-f094-719b-884f-8103ded44e99", + "key": "task", + "name": "Task", + "primary_descriptor": { + "field": "task.title" + }, + "fields": [ + { + "subject_uuid": "019fb088-f0bb-72fe-8d31-44dc6e07e609", + "key": "title", + "name": "Title", + "type": "short_text", + "required": true + }, + { + "subject_uuid": "019fb088-f0e1-79e8-864b-7323270f7e4c", + "key": "priority", + "name": "Priority", + "type": "enum", + "required": true, + "settings": { + "values": [ + { + "subject_uuid": "019fb088-f10c-7b84-b98d-ec89c752c040", + "key": "low", + "name": "Low" + }, + { + "subject_uuid": "019fb088-f138-7a6d-952e-04c4782d1e3f", + "key": "medium", + "name": "Medium" + }, + { + "subject_uuid": "019fb088-f160-73b6-9418-e0d7e73d21a3", + "key": "high", + "name": "High" + } + ], + "ordinal": true + } + } + ] + } + ] + } +} 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 index 85a0ab2..011e4c0 100644 --- a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json +++ b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json @@ -22,28 +22,6 @@ "subject": null, "related_locations": [], "suggestions": [] - }, - { - "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/1/settings" - }, - "subject": null, - "related_locations": [], - "suggestions": [] - }, - { - "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/1/type" - }, - "subject": null, - "related_locations": [], - "suggestions": [] } ] } diff --git a/skills/create-full-stack-app/SKILL.md b/skills/create-full-stack-app/SKILL.md index 5acbc16..96c5a8e 100644 --- a/skills/create-full-stack-app/SKILL.md +++ b/skills/create-full-stack-app/SKILL.md @@ -11,8 +11,9 @@ Plan for deterministic Compilation. Keep product judgment in the agent and deter concurrency, and network behavior in the `firstdraft` CLI. This Skill is experimental. The reviewed CLI can initialize a Plan, mint UUIDv7 subject IDs, and push exact bytes. -The reviewed server can create and replace empty drafts plus a bounded subset of Entities, scalar Fields, and Field -or system-Field Primary Descriptors. These slices are not released end to end. +The reviewed server can create and replace empty drafts plus a bounded subset of Entities, ten scalar Field kinds, +enum Fields with ordered values, and Field or system-Field Primary Descriptors. These slices are not released end +to end. ## Load the relevant references diff --git a/skills/create-full-stack-app/references/examples.md b/skills/create-full-stack-app/references/examples.md index daa0cf3..502f48e 100644 --- a/skills/create-full-stack-app/references/examples.md +++ b/skills/create-full-stack-app/references/examples.md @@ -71,6 +71,76 @@ The Entity and Field have independent UUIDs. The `primary_descriptor` uses a typ UUID. The reviewed importer also accepts `boolean`, `date`, `datetime`, `decimal`, `integer`, `language_code`, `long_text`, `time_zone`, and `url` Fields when they use only the supported schema-valid scalar properties. +## Ordinal enum Field + +Use an enum for a closed set of named choices. This complete document is structurally valid v0.19 and accepted by +the reviewed bounded importer. Here, priority order carries semantic rank, so `ordinal` is `true`. + +```json +{ + "format": "firstdraft.foundation-plan.sketch/0.19", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-07" + }, + "application": { + "key": "ranked_tasks", + "name": "Ranked Tasks", + "native": {}, + "delivery": {}, + "entities": [ + { + "subject_uuid": "019fb088-f094-719b-884f-8103ded44e99", + "key": "task", + "name": "Task", + "primary_descriptor": { + "field": "task.title" + }, + "fields": [ + { + "subject_uuid": "019fb088-f0bb-72fe-8d31-44dc6e07e609", + "key": "title", + "name": "Title", + "type": "short_text", + "required": true + }, + { + "subject_uuid": "019fb088-f0e1-79e8-864b-7323270f7e4c", + "key": "priority", + "name": "Priority", + "type": "enum", + "required": true, + "settings": { + "values": [ + { + "subject_uuid": "019fb088-f10c-7b84-b98d-ec89c752c040", + "key": "low", + "name": "Low" + }, + { + "subject_uuid": "019fb088-f138-7a6d-952e-04c4782d1e3f", + "key": "medium", + "name": "Medium" + }, + { + "subject_uuid": "019fb088-f160-73b6-9418-e0d7e73d21a3", + "key": "high", + "name": "High" + } + ], + "ordinal": true + } + } + ] + } + ] + } +} +``` + +The Entity, Fields, and every enum value have independent UUIDs. Preserve a value's UUID when renaming or +reordering it. Omit `ordinal` when order is presentational rather than ranked. + ## Stored and reverse relationship This complete document is structurally valid v0.19, but References and authored Associations remain outside the 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 ac96e99..f2d936e 100644 --- a/skills/create-full-stack-app/references/foundation-plan-019.md +++ b/skills/create-full-stack-app/references/foundation-plan-019.md @@ -19,15 +19,18 @@ authorized. - 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 and a bounded subset of Entities, scalar Fields, and Field or - system-Field Primary Descriptors. +- The reviewed conditional PUT imports empty drafts and a bounded subset of Entities, ten scalar Field kinds, enum + Fields with ordered values, and Field or system-Field Primary Descriptors. - There is no released end-to-end CLI/API workflow, complete nonempty import, GET or pull operation, complete semantic analyzer, Publish action, Compilation action, or generated Foundation. The bundled schema was copied from the [First Draft source at revision `12fa2a6`](https://github.com/firstdraft/firstdraft/blob/12fa2a6bcac122196d55f5528fbc3f1363c684e3/docs/architecture/design/foundation-plan.schema.json) and has SHA-256 -`5994c41f65eab52f92020fa24437e76b6957b7016ccf231dce06e8097f0b34b5`. The reviewed public CLI baseline is +`5994c41f65eab52f92020fa24437e76b6957b7016ccf231dce06e8097f0b34b5`. The reviewed public API baseline is +[`3282954b6eefef4ab47ccba1c2ee7008315bee92`](https://github.com/firstdraft/firstdraft/commit/3282954b6eefef4ab47ccba1c2ee7008315bee92) +and contains those same schema bytes. +The reviewed public CLI baseline is [`0681afd48d7825a7a1a0112e248f3013d0123743`](https://github.com/firstdraft/cli/commit/0681afd48d7825a7a1a0112e248f3013d0123743); it has not been released and exposes `plan init`, `plan subject-id`, and `plan push`. Check commands rather than inferring compatibility from an unreleased version number. Update this Skill deliberately when either contract @@ -124,6 +127,7 @@ descriptors are not yet supported. A Field may use these types: - `date` - `datetime` - `decimal` +- `enum` - `integer` - `language_code` - `long_text` @@ -131,10 +135,18 @@ descriptors are not yet supported. A Field may use these types: - `time_zone` - `url` -For those types, the importer retains schema-valid combinations of `subject_uuid`, `key`, `name`, `type`, +For every supported type, the importer retains schema-valid combinations of `subject_uuid`, `key`, `name`, `type`, `required`, `notes`, `immutable`, `comparison`, `normalizations`, `encrypted_at_rest`, and `redact_from_logs`. -Any Field type outside the list above, Field defaults, Validations, derivations, Field settings, References, -Associations, and other Entity or Application capabilities remain unsupported. One unsupported pointer rejects the -complete conditional PUT with `foundation_plan.import.unsupported_capability` and no mutation. That diagnostic -describes server capability, not invalid product meaning. Preserve the authored Plan and report the exact gap. +An `enum` Field additionally requires `settings.values`, a nonempty array in stable order. Each value +has its own `subject_uuid`, owner-local `key`, and human-facing `name`; mint an ID for each new value with +`firstdraft plan subject-id`. Set the optional `settings.ordinal` to `true` only when the order carries semantic +rank. Omit it when the order is presentational because omission and `false` are equivalent. Preserve a value's +UUID through renames, reordering, and coherent moves between enum Fields. + +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. Schema-valid Field types outside +the list above, Field defaults, Validations, derivations, References, Associations, and other Entity or Application +capabilities remain unsupported. One unsupported pointer rejects the complete conditional PUT with +`foundation_plan.import.unsupported_capability` and no mutation. That diagnostic describes server capability, not +invalid product meaning. Preserve the authored Plan and report the exact gap. diff --git a/skills/create-full-stack-app/references/modeling-guide.md b/skills/create-full-stack-app/references/modeling-guide.md index 2ad695c..feb936b 100644 --- a/skills/create-full-stack-app/references/modeling-guide.md +++ b/skills/create-full-stack-app/references/modeling-guide.md @@ -31,6 +31,9 @@ For each Entity: Do not infer uniqueness from a label, presence from a form, or immutability from current UI. Ask when those facts 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. + ## Model relationships Put a Reference on the Entity that stores the relationship fact. Ask: diff --git a/test/repository.test.mjs b/test/repository.test.mjs index 807da09..31b7a77 100644 --- a/test/repository.test.mjs +++ b/test/repository.test.mjs @@ -17,6 +17,10 @@ const foundationPlanTarget = { }; const foundationPlanSchemaDigest = "5994c41f65eab52f92020fa24437e76b6957b7016ccf231dce06e8097f0b34b5"; +const foundationPlanServerBaseline = + "3282954b6eefef4ab47ccba1c2ee7008315bee92"; +const foundationPlanCliBaseline = + "0681afd48d7825a7a1a0112e248f3013d0123743"; const supportedScalarFieldTypes = [ "boolean", "date", @@ -29,7 +33,8 @@ const supportedScalarFieldTypes = [ "time_zone", "url", ]; -const supportedScalarFieldProperties = [ +const supportedFieldTypes = [...supportedScalarFieldTypes, "enum"].sort(); +const supportedFieldProperties = [ "subject_uuid", "key", "name", @@ -188,7 +193,7 @@ test("bounded importer prose remains bound to the exact allowlists", async () => "utf8", ); const documentedTypeSection = foundationPlanReference.match( - /A Field may use these types:\n\n([\s\S]*?)\n\nFor those types/, + /A Field may use these types:\n\n([\s\S]*?)\n\nFor every supported type/, ); assert( documentedTypeSection, @@ -198,11 +203,11 @@ test("bounded importer prose remains bound to the exact allowlists", async () => [...documentedTypeSection[1].matchAll(/^- `([^`]+)`$/gm)].map( (match) => match[1], ), - supportedScalarFieldTypes, + supportedFieldTypes, ); const documentedPropertySection = foundationPlanReference.match( - /retains schema-valid combinations of ([\s\S]*?)\.\n\nAny Field type/, + /For every supported type, the importer retains schema-valid combinations of ([\s\S]*?)\.\n\nAn `enum` Field/, ); assert( documentedPropertySection, @@ -212,7 +217,36 @@ test("bounded importer prose remains bound to the exact allowlists", async () => [...documentedPropertySection[1].matchAll(/`([^`]+)`/g)].map( (match) => match[1], ), - supportedScalarFieldProperties, + supportedFieldProperties, + ); + + const documentedEnumSection = foundationPlanReference.match( + /An `enum` Field additionally requires ([\s\S]*?)\n\nScalar Fields/, + ); + assert( + documentedEnumSection, + "foundation-plan-019.md: missing supported enum guidance", + ); + assert.match( + documentedEnumSection[0], + /requires `settings\.values`, a nonempty array in stable order/, + ); + assert.match( + documentedEnumSection[0], + /Each value\s+has its own `subject_uuid`, owner-local `key`, and human-facing `name`/, + ); + assert.match( + documentedEnumSection[0], + /optional `settings\.ordinal` to `true` only when the order carries semantic\s+rank/, + ); + assert.match(documentedEnumSection[0], /omission and `false` are equivalent/); + assert.match( + documentedEnumSection[0], + /Preserve a value's\s+UUID through renames, reordering, and coherent moves between enum Fields/, + ); + assert.match( + foundationPlanReference, + /Scalar Fields have no `settings` object, and enum `settings` admits only `values` and optional `ordinal`; any other\s+settings shape is structurally invalid rather than an importer capability gap/, ); const examples = await readFile( @@ -232,6 +266,40 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ), supportedScalarFieldTypes.filter((type) => type !== "short_text"), ); + + const ordinalPlan = (await markdownJsonDocuments( + path.join(referencesDirectory, "examples.md"), + )).find((document) => document?.application?.key === "ranked_tasks"); + assert(ordinalPlan, "examples.md: missing ordinal enum Plan"); + const ordinalEntity = ordinalPlan.application.entities[0]; + const ordinalField = ordinalEntity.fields.find(({ type }) => type === "enum"); + assert(ordinalField, "examples.md: missing enum Field"); + assert.deepEqual( + ordinalField.settings.values.map(({ key }) => key), + ["low", "medium", "high"], + ); + assert.equal(ordinalField.settings.ordinal, true); + assert(!("default" in ordinalField)); + assert(!("validations" in ordinalField)); + const identities = [ + ordinalEntity.subject_uuid, + ...ordinalEntity.fields.map(({ subject_uuid }) => subject_uuid), + ...ordinalField.settings.values.map(({ subject_uuid }) => subject_uuid), + ]; + assert.equal(new Set(identities).size, identities.length); + + const enumFixture = JSON.parse( + await readFile( + path.join( + evalsDirectory, + "create-full-stack-app", + "fixtures", + "supported-enum.foundation-plan.json", + ), + "utf8", + ), + ); + assert.deepEqual(ordinalPlan, enumFixture); }); test("validator routing preserves validation boundaries", async () => { @@ -356,13 +424,16 @@ test("complete examples and eval Plans validate against the bundled exact schema createHash("sha256").update(schemaSource).digest("hex"), foundationPlanSchemaDigest, ); - assert( - ( - await readFile( - path.join(skillDirectory, "references", "foundation-plan-019.md"), - "utf8", - ) - ).includes(foundationPlanSchemaDigest), + const referenceSource = await readFile( + path.join(skillDirectory, "references", "foundation-plan-019.md"), + "utf8", + ); + assert(referenceSource.includes(foundationPlanSchemaDigest)); + assert(referenceSource.includes(foundationPlanServerBaseline)); + assert(referenceSource.includes(foundationPlanCliBaseline)); + assert.match( + referenceSource, + /reviewed public API baseline is[\s\S]*?and contains those same schema bytes/, ); const validate = new Ajv2020({ @@ -408,7 +479,11 @@ test("revision evals stage existing Plan identity and private state", async () = stage_as: ".firstdraft/state.json", }, ]; - for (const id of ["resume-with-stable-identity", "add-field-with-minted-id"]) { + for (const id of [ + "resume-with-stable-identity", + "add-field-with-minted-id", + "add-ordinal-enum-with-minted-ids", + ]) { assert.deepEqual( cases.find((evaluation) => evaluation.id === id).artifacts, stagedPlanArtifacts, @@ -427,6 +502,33 @@ test("revision evals stage existing Plan identity and private state", async () = expectation.includes("Never fabricates a UUIDv7"), ), ); + const enumEvaluation = cases.find( + ({ id }) => id === "add-ordinal-enum-with-minted-ids", + ); + assert( + enumEvaluation.expectations.some((expectation) => + expectation.includes("plan subject-id exactly four times"), + ), + "enum eval must mint exactly one Field and three value IDs", + ); + assert( + enumEvaluation.expectations.some((expectation) => + expectation.includes("Never fabricates a UUIDv7"), + ), + "enum eval must forbid fabricated or copied IDs", + ); + assert( + enumEvaluation.expectations.some((expectation) => + expectation.includes("settings.values in low, medium, high order"), + ), + "enum eval must bind value order", + ); + assert( + enumEvaluation.expectations.some((expectation) => + expectation.includes("Does not run plan init or plan push"), + ), + "enum authoring eval must remain local", + ); const plan = JSON.parse( await readFile( @@ -466,6 +568,11 @@ test("bounded import evals bind supported and unsupported Plan state", async () role: "input", stage_as: ".firstdraft/state.json", }; + const initializedStateArtifact = { + path: "evals/create-full-stack-app/fixtures/initialized-state.json", + role: "input", + stage_as: ".firstdraft/state.json", + }; const supportedPlanArtifact = { path: "evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json", @@ -503,7 +610,7 @@ test("bounded import evals bind supported and unsupported Plan state", async () supportedEntity.fields.flatMap((field) => Object.keys(field)), ), ].sort(), - [...supportedScalarFieldProperties].sort(), + [...supportedFieldProperties].sort(), ); const descriptorKey = supportedEntity.primary_descriptor.field .split(".") @@ -522,6 +629,83 @@ test("bounded import evals bind supported and unsupported Plan state", async () supportedScalarFieldTypes.length + 1, ); + const supportedEnumPlanArtifact = { + path: + "evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json", + role: "input", + stage_as: ".firstdraft/foundation-plan.json", + }; + const supportedEnumEvaluation = cases.find( + ({ id }) => id === "push-supported-enum-plan", + ); + assert.deepEqual(supportedEnumEvaluation.artifacts, [ + supportedEnumPlanArtifact, + initializedStateArtifact, + ]); + const initializedState = JSON.parse( + await readFile( + path.join(evaluationDirectory, "fixtures", "initialized-state.json"), + "utf8", + ), + ); + assert.deepEqual(Object.keys(initializedState).sort(), ["format", "project_id"]); + assert.equal(initializedState.format, "firstdraft.cli-state/1"); + assert.match( + initializedState.project_id, + /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, + ); + const readme = await readFile(path.join(repository, "README.md"), "utf8"); + assert(readme.includes(foundationPlanCliBaseline)); + assert.match(readme, /state-placeholder\.txt.*deliberately unreadable/s); + assert.match(readme, /initialized-state\.json.*mirrors the state written by `plan init`/s); + assert.match( + readme, + /Before every\s+`push-supported-enum-plan` run, replace the fixture with `.firstdraft\/state\.json` generated by a fresh\s+`firstdraft plan init` in a scratch directory before staging it/, + ); + const supportedEnumPlan = JSON.parse( + await readFile( + path.join( + evaluationDirectory, + "fixtures", + "supported-enum.foundation-plan.json", + ), + "utf8", + ), + ); + const supportedEnumField = supportedEnumPlan.application.entities[0].fields.find( + ({ type }) => type === "enum", + ); + assert(supportedEnumField, "supported enum fixture: missing enum Field"); + assert.deepEqual( + supportedEnumField.settings.values.map(({ key }) => key), + ["low", "medium", "high"], + ); + assert.equal(supportedEnumField.settings.ordinal, true); + assert( + supportedEnumEvaluation.expectations.some((expectation) => + expectation.includes("supported by the reviewed bounded importer"), + ), + "supported enum eval must recognize the import boundary", + ); + assert( + supportedEnumEvaluation.expectations.some((expectation) => + expectation.includes("plan push exactly once"), + ), + "supported enum eval must exercise the push path", + ); + assert( + supportedEnumEvaluation.expectations.some((expectation) => + expectation.includes("Does not run plan init, reinitialize"), + ), + "supported enum eval must not replace initialized state", + ); + assert( + supportedEnumEvaluation.expectations.some((expectation) => + expectation.includes("instead of re-minting them"), + ), + "supported enum eval must preserve staged documentation UUIDs", + ); + const unsupportedPlanArtifact = { path: "evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json", @@ -552,6 +736,10 @@ test("bounded import evals bind supported and unsupported Plan state", async () assert.equal(unsupportedFields[0].default.value, "Untitled"); assert.equal(unsupportedFields[0].validations[0].kind, "length"); assert.equal(unsupportedFields[1].type, "enum"); + assert.deepEqual( + unsupportedFields[1].settings.values.map(({ key }) => key), + ["draft"], + ); const response = JSON.parse( await readFile( path.join( @@ -580,14 +768,6 @@ test("bounded import evals bind supported and unsupported Plan state", async () "foundation_plan.import.unsupported_capability", "/application/entities/0/fields/0/validations", ], - [ - "foundation_plan.import.unsupported_capability", - "/application/entities/0/fields/1/settings", - ], - [ - "foundation_plan.import.unsupported_capability", - "/application/entities/0/fields/1/type", - ], ], ); });