From 124d02194eff670241f65c9dc4464d942ace9201 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Thu, 23 Apr 2026 23:36:46 -0400 Subject: [PATCH 1/4] chore: rename fixture family path to conformance --- AGENTS.md | 2 +- CONTRIBUTING.md | 6 +++--- scripts/fixtures-check.sh | 4 ++-- scripts/fixtures-sync.sh | 4 ++-- .../{v2 => conformance}/step/001_set_premise_update.json | 0 .../step/002_use_item_normalization.json | 0 .../step/003_conflict_prohibit_clarify.json | 0 .../step/004_remove_policy_missing_idempotent_update.json | 0 .../step/005_exact_prefix_passthrough_leading_space.json | 0 .../step/006_near_miss_set_premise_to.json | 0 .../step/007_near_miss_change_premise_missing_to.json | 0 .../step/008_replace_missing_source_clarify_prompt.json | 0 .../step/009_pending_affirmative_normalized_token.json | 0 .../step/010_pending_negative_normalized_token.json | 0 .../step/011_pending_unmatched_reuses_prompt.json | 0 .../step/012_clear_premise_populated_update.json | 0 .../step/013_clear_premise_already_null_update.json | 0 .../step/014_reset_policies_populated_update.json | 0 .../step/015_reset_policies_already_empty_update.json | 0 .../step/016_clear_state_populated_update.json | 0 .../step/017_clear_state_already_empty_update.json | 0 .../transcript/001_user_only_replay_state.json | 0 .../transcript/002_non_string_user_content_ignored.json | 0 .../transcript/003_stops_at_first_clarify_later_yes.json | 0 .../transcript/004_stops_at_first_clarify_later_no.json | 0 tests/harness/fixtures.ts | 2 +- tests/step-fixtures.test.ts | 2 +- tests/transcript-fixtures.test.ts | 2 +- 28 files changed, 11 insertions(+), 11 deletions(-) rename tests/fixtures/{v2 => conformance}/step/001_set_premise_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/002_use_item_normalization.json (100%) rename tests/fixtures/{v2 => conformance}/step/003_conflict_prohibit_clarify.json (100%) rename tests/fixtures/{v2 => conformance}/step/004_remove_policy_missing_idempotent_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/005_exact_prefix_passthrough_leading_space.json (100%) rename tests/fixtures/{v2 => conformance}/step/006_near_miss_set_premise_to.json (100%) rename tests/fixtures/{v2 => conformance}/step/007_near_miss_change_premise_missing_to.json (100%) rename tests/fixtures/{v2 => conformance}/step/008_replace_missing_source_clarify_prompt.json (100%) rename tests/fixtures/{v2 => conformance}/step/009_pending_affirmative_normalized_token.json (100%) rename tests/fixtures/{v2 => conformance}/step/010_pending_negative_normalized_token.json (100%) rename tests/fixtures/{v2 => conformance}/step/011_pending_unmatched_reuses_prompt.json (100%) rename tests/fixtures/{v2 => conformance}/step/012_clear_premise_populated_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/013_clear_premise_already_null_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/014_reset_policies_populated_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/015_reset_policies_already_empty_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/016_clear_state_populated_update.json (100%) rename tests/fixtures/{v2 => conformance}/step/017_clear_state_already_empty_update.json (100%) rename tests/fixtures/{v2 => conformance}/transcript/001_user_only_replay_state.json (100%) rename tests/fixtures/{v2 => conformance}/transcript/002_non_string_user_content_ignored.json (100%) rename tests/fixtures/{v2 => conformance}/transcript/003_stops_at_first_clarify_later_yes.json (100%) rename tests/fixtures/{v2 => conformance}/transcript/004_stops_at_first_clarify_later_no.json (100%) diff --git a/AGENTS.md b/AGENTS.md index eca2d20..cb1ef37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ Behavior is defined by the upstream Python project: The following are authoritative: - Directive grammar specification -- Fixture corpus under `tests/fixtures/v2` +- Fixture corpus under `tests/fixtures/conformance` - Python engine behavior as exercised by those fixtures If behavior differs, the TypeScript implementation is incorrect. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b945285..ca94880 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,17 +70,17 @@ Python fixtures are the source of truth. Default source path: -- `../context-compiler/tests/fixtures/v2` +- `../context-compiler/tests/fixtures/conformance` Commands: -- `npm run fixtures:sync` to copy fixtures from Python into `tests/fixtures/v2` +- `npm run fixtures:sync` to copy fixtures from Python into `tests/fixtures/conformance` - `npm run fixtures:check` to detect drift between local fixtures and Python fixtures Optional override: - Set `FIXTURES_SOURCE` to use a different source path, for example: - - `FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/v2 npm run fixtures:check` + - `FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:check` ## Pull Requests diff --git a/scripts/fixtures-check.sh b/scripts/fixtures-check.sh index 8f3da2d..f2a1797 100755 --- a/scripts/fixtures-check.sh +++ b/scripts/fixtures-check.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -SOURCE_DIR="${FIXTURES_SOURCE:-../context-compiler/tests/fixtures/v2}" -TARGET_DIR="tests/fixtures/v2" +SOURCE_DIR="${FIXTURES_SOURCE:-../context-compiler/tests/fixtures/conformance}" +TARGET_DIR="tests/fixtures/conformance" if [[ ! -d "$SOURCE_DIR" ]]; then echo "[fixtures:check] Source fixture directory not found: $SOURCE_DIR" >&2 diff --git a/scripts/fixtures-sync.sh b/scripts/fixtures-sync.sh index b7c9cf9..3f55fb8 100755 --- a/scripts/fixtures-sync.sh +++ b/scripts/fixtures-sync.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -SOURCE_DIR="${FIXTURES_SOURCE:-../context-compiler/tests/fixtures/v2}" -TARGET_DIR="tests/fixtures/v2" +SOURCE_DIR="${FIXTURES_SOURCE:-../context-compiler/tests/fixtures/conformance}" +TARGET_DIR="tests/fixtures/conformance" if [[ ! -d "$SOURCE_DIR" ]]; then echo "[fixtures:sync] Source fixture directory not found: $SOURCE_DIR" >&2 diff --git a/tests/fixtures/v2/step/001_set_premise_update.json b/tests/fixtures/conformance/step/001_set_premise_update.json similarity index 100% rename from tests/fixtures/v2/step/001_set_premise_update.json rename to tests/fixtures/conformance/step/001_set_premise_update.json diff --git a/tests/fixtures/v2/step/002_use_item_normalization.json b/tests/fixtures/conformance/step/002_use_item_normalization.json similarity index 100% rename from tests/fixtures/v2/step/002_use_item_normalization.json rename to tests/fixtures/conformance/step/002_use_item_normalization.json diff --git a/tests/fixtures/v2/step/003_conflict_prohibit_clarify.json b/tests/fixtures/conformance/step/003_conflict_prohibit_clarify.json similarity index 100% rename from tests/fixtures/v2/step/003_conflict_prohibit_clarify.json rename to tests/fixtures/conformance/step/003_conflict_prohibit_clarify.json diff --git a/tests/fixtures/v2/step/004_remove_policy_missing_idempotent_update.json b/tests/fixtures/conformance/step/004_remove_policy_missing_idempotent_update.json similarity index 100% rename from tests/fixtures/v2/step/004_remove_policy_missing_idempotent_update.json rename to tests/fixtures/conformance/step/004_remove_policy_missing_idempotent_update.json diff --git a/tests/fixtures/v2/step/005_exact_prefix_passthrough_leading_space.json b/tests/fixtures/conformance/step/005_exact_prefix_passthrough_leading_space.json similarity index 100% rename from tests/fixtures/v2/step/005_exact_prefix_passthrough_leading_space.json rename to tests/fixtures/conformance/step/005_exact_prefix_passthrough_leading_space.json diff --git a/tests/fixtures/v2/step/006_near_miss_set_premise_to.json b/tests/fixtures/conformance/step/006_near_miss_set_premise_to.json similarity index 100% rename from tests/fixtures/v2/step/006_near_miss_set_premise_to.json rename to tests/fixtures/conformance/step/006_near_miss_set_premise_to.json diff --git a/tests/fixtures/v2/step/007_near_miss_change_premise_missing_to.json b/tests/fixtures/conformance/step/007_near_miss_change_premise_missing_to.json similarity index 100% rename from tests/fixtures/v2/step/007_near_miss_change_premise_missing_to.json rename to tests/fixtures/conformance/step/007_near_miss_change_premise_missing_to.json diff --git a/tests/fixtures/v2/step/008_replace_missing_source_clarify_prompt.json b/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json similarity index 100% rename from tests/fixtures/v2/step/008_replace_missing_source_clarify_prompt.json rename to tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json diff --git a/tests/fixtures/v2/step/009_pending_affirmative_normalized_token.json b/tests/fixtures/conformance/step/009_pending_affirmative_normalized_token.json similarity index 100% rename from tests/fixtures/v2/step/009_pending_affirmative_normalized_token.json rename to tests/fixtures/conformance/step/009_pending_affirmative_normalized_token.json diff --git a/tests/fixtures/v2/step/010_pending_negative_normalized_token.json b/tests/fixtures/conformance/step/010_pending_negative_normalized_token.json similarity index 100% rename from tests/fixtures/v2/step/010_pending_negative_normalized_token.json rename to tests/fixtures/conformance/step/010_pending_negative_normalized_token.json diff --git a/tests/fixtures/v2/step/011_pending_unmatched_reuses_prompt.json b/tests/fixtures/conformance/step/011_pending_unmatched_reuses_prompt.json similarity index 100% rename from tests/fixtures/v2/step/011_pending_unmatched_reuses_prompt.json rename to tests/fixtures/conformance/step/011_pending_unmatched_reuses_prompt.json diff --git a/tests/fixtures/v2/step/012_clear_premise_populated_update.json b/tests/fixtures/conformance/step/012_clear_premise_populated_update.json similarity index 100% rename from tests/fixtures/v2/step/012_clear_premise_populated_update.json rename to tests/fixtures/conformance/step/012_clear_premise_populated_update.json diff --git a/tests/fixtures/v2/step/013_clear_premise_already_null_update.json b/tests/fixtures/conformance/step/013_clear_premise_already_null_update.json similarity index 100% rename from tests/fixtures/v2/step/013_clear_premise_already_null_update.json rename to tests/fixtures/conformance/step/013_clear_premise_already_null_update.json diff --git a/tests/fixtures/v2/step/014_reset_policies_populated_update.json b/tests/fixtures/conformance/step/014_reset_policies_populated_update.json similarity index 100% rename from tests/fixtures/v2/step/014_reset_policies_populated_update.json rename to tests/fixtures/conformance/step/014_reset_policies_populated_update.json diff --git a/tests/fixtures/v2/step/015_reset_policies_already_empty_update.json b/tests/fixtures/conformance/step/015_reset_policies_already_empty_update.json similarity index 100% rename from tests/fixtures/v2/step/015_reset_policies_already_empty_update.json rename to tests/fixtures/conformance/step/015_reset_policies_already_empty_update.json diff --git a/tests/fixtures/v2/step/016_clear_state_populated_update.json b/tests/fixtures/conformance/step/016_clear_state_populated_update.json similarity index 100% rename from tests/fixtures/v2/step/016_clear_state_populated_update.json rename to tests/fixtures/conformance/step/016_clear_state_populated_update.json diff --git a/tests/fixtures/v2/step/017_clear_state_already_empty_update.json b/tests/fixtures/conformance/step/017_clear_state_already_empty_update.json similarity index 100% rename from tests/fixtures/v2/step/017_clear_state_already_empty_update.json rename to tests/fixtures/conformance/step/017_clear_state_already_empty_update.json diff --git a/tests/fixtures/v2/transcript/001_user_only_replay_state.json b/tests/fixtures/conformance/transcript/001_user_only_replay_state.json similarity index 100% rename from tests/fixtures/v2/transcript/001_user_only_replay_state.json rename to tests/fixtures/conformance/transcript/001_user_only_replay_state.json diff --git a/tests/fixtures/v2/transcript/002_non_string_user_content_ignored.json b/tests/fixtures/conformance/transcript/002_non_string_user_content_ignored.json similarity index 100% rename from tests/fixtures/v2/transcript/002_non_string_user_content_ignored.json rename to tests/fixtures/conformance/transcript/002_non_string_user_content_ignored.json diff --git a/tests/fixtures/v2/transcript/003_stops_at_first_clarify_later_yes.json b/tests/fixtures/conformance/transcript/003_stops_at_first_clarify_later_yes.json similarity index 100% rename from tests/fixtures/v2/transcript/003_stops_at_first_clarify_later_yes.json rename to tests/fixtures/conformance/transcript/003_stops_at_first_clarify_later_yes.json diff --git a/tests/fixtures/v2/transcript/004_stops_at_first_clarify_later_no.json b/tests/fixtures/conformance/transcript/004_stops_at_first_clarify_later_no.json similarity index 100% rename from tests/fixtures/v2/transcript/004_stops_at_first_clarify_later_no.json rename to tests/fixtures/conformance/transcript/004_stops_at_first_clarify_later_no.json diff --git a/tests/harness/fixtures.ts b/tests/harness/fixtures.ts index 6d58368..94e73dc 100644 --- a/tests/harness/fixtures.ts +++ b/tests/harness/fixtures.ts @@ -40,7 +40,7 @@ export interface NamedFixture { payload: T; } -const FIXTURE_ROOT = resolve(process.cwd(), 'tests', 'fixtures', 'v2'); +const FIXTURE_ROOT = resolve(process.cwd(), 'tests', 'fixtures', 'conformance'); async function listJsonFilesRecursive(dir: string): Promise { const entries = await readdir(dir, { withFileTypes: true }); diff --git a/tests/step-fixtures.test.ts b/tests/step-fixtures.test.ts index 0d01128..3024a96 100644 --- a/tests/step-fixtures.test.ts +++ b/tests/step-fixtures.test.ts @@ -4,7 +4,7 @@ import { loadStepFixtures } from './harness/fixtures.js'; const fixtures = await loadStepFixtures(); -describe('step fixtures (v2)', () => { +describe('step fixtures (conformance)', () => { for (const fixture of fixtures) { it(fixture.name, () => { expect(fixture.payload.kind).toBe('step'); diff --git a/tests/transcript-fixtures.test.ts b/tests/transcript-fixtures.test.ts index 7f9b64d..2ab476e 100644 --- a/tests/transcript-fixtures.test.ts +++ b/tests/transcript-fixtures.test.ts @@ -4,7 +4,7 @@ import { loadTranscriptFixtures } from './harness/fixtures.js'; const fixtures = await loadTranscriptFixtures(); -describe('transcript fixtures (v2)', () => { +describe('transcript fixtures (conformance)', () => { for (const fixture of fixtures) { it(fixture.name, () => { expect(fixture.payload.kind).toBe('transcript'); From 5ceaafac2d24aa920f41ba216268f57f0a9ffa57 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Thu, 23 Apr 2026 23:49:45 -0400 Subject: [PATCH 2/4] chore: require explicit fixture source for sync and check --- .github/workflows/ci.yml | 15 +++++++++++++-- CONTRIBUTING.md | 13 +++---------- scripts/fixtures-check.sh | 12 ++++++++++-- scripts/fixtures-sync.sh | 12 ++++++++++-- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c49f858..fd18070 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,9 @@ jobs: build-and-test: runs-on: ubuntu-latest timeout-minutes: 15 + env: + PY_FIXTURE_REF: v0.6.0 + PY_FIXTURE_CHECKOUT: ${{ runner.temp }}/context-compiler-source steps: - name: Checkout @@ -29,10 +32,18 @@ jobs: - name: Checkout Python fixture source # TS 0.5.2 fixtures include cases added after Python v0.5.2; pin to v0.6.0 # to keep fixture drift checks aligned with the current TS fixture corpus. - run: git clone --depth 1 --branch v0.6.0 https://github.com/rlippmann/context-compiler ../context-compiler + run: | + echo "Python fixture ref: ${PY_FIXTURE_REF}" + echo "Python checkout path: ${PY_FIXTURE_CHECKOUT}" + git clone --depth 1 --branch "${PY_FIXTURE_REF}" https://github.com/rlippmann/context-compiler "${PY_FIXTURE_CHECKOUT}" + echo "Resolved fixture source: ${PY_FIXTURE_CHECKOUT}/tests/fixtures/conformance" - name: Check fixture drift - run: npm run fixtures:check + env: + FIXTURES_SOURCE: ${{ runner.temp }}/context-compiler-source/tests/fixtures/conformance + run: | + echo "Using FIXTURES_SOURCE=${FIXTURES_SOURCE}" + npm run fixtures:check - name: Build run: npm run build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca94880..33f7985 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,19 +68,12 @@ All tests must pass before submitting changes. Python fixtures are the source of truth. -Default source path: - -- `../context-compiler/tests/fixtures/conformance` - Commands: -- `npm run fixtures:sync` to copy fixtures from Python into `tests/fixtures/conformance` -- `npm run fixtures:check` to detect drift between local fixtures and Python fixtures - -Optional override: +- `FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:sync` to copy fixtures from Python into `tests/fixtures/conformance` +- `FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:check` to detect drift between local fixtures and Python fixtures -- Set `FIXTURES_SOURCE` to use a different source path, for example: - - `FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:check` +`FIXTURES_SOURCE` is required for both commands. ## Pull Requests diff --git a/scripts/fixtures-check.sh b/scripts/fixtures-check.sh index f2a1797..b845883 100755 --- a/scripts/fixtures-check.sh +++ b/scripts/fixtures-check.sh @@ -1,12 +1,20 @@ #!/usr/bin/env bash set -euo pipefail -SOURCE_DIR="${FIXTURES_SOURCE:-../context-compiler/tests/fixtures/conformance}" +if [[ -z "${FIXTURES_SOURCE:-}" ]]; then + echo "[fixtures:check] FIXTURES_SOURCE is required." >&2 + echo "[fixtures:check] Example: FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:check" >&2 + exit 1 +fi + +SOURCE_DIR="$FIXTURES_SOURCE" TARGET_DIR="tests/fixtures/conformance" +echo "[fixtures:check] Using source fixture directory: $SOURCE_DIR" + if [[ ! -d "$SOURCE_DIR" ]]; then echo "[fixtures:check] Source fixture directory not found: $SOURCE_DIR" >&2 - echo "[fixtures:check] Set FIXTURES_SOURCE to override the default source path." >&2 + echo "[fixtures:check] Set FIXTURES_SOURCE to a valid Python fixture source path." >&2 exit 1 fi diff --git a/scripts/fixtures-sync.sh b/scripts/fixtures-sync.sh index 3f55fb8..80e242f 100755 --- a/scripts/fixtures-sync.sh +++ b/scripts/fixtures-sync.sh @@ -1,12 +1,20 @@ #!/usr/bin/env bash set -euo pipefail -SOURCE_DIR="${FIXTURES_SOURCE:-../context-compiler/tests/fixtures/conformance}" +if [[ -z "${FIXTURES_SOURCE:-}" ]]; then + echo "[fixtures:sync] FIXTURES_SOURCE is required." >&2 + echo "[fixtures:sync] Example: FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:sync" >&2 + exit 1 +fi + +SOURCE_DIR="$FIXTURES_SOURCE" TARGET_DIR="tests/fixtures/conformance" +echo "[fixtures:sync] Using source fixture directory: $SOURCE_DIR" + if [[ ! -d "$SOURCE_DIR" ]]; then echo "[fixtures:sync] Source fixture directory not found: $SOURCE_DIR" >&2 - echo "[fixtures:sync] Set FIXTURES_SOURCE to override the default source path." >&2 + echo "[fixtures:sync] Set FIXTURES_SOURCE to a valid Python fixture source path." >&2 exit 1 fi From 179519964dfe8fabbbe10e6fe9131b783b6e2745 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Apr 2026 00:01:18 -0400 Subject: [PATCH 3/4] fix: align replacement clarify prompt with python conformance --- .github/workflows/ci.yml | 10 ++++++---- CONTRIBUTING.md | 6 ++++++ src/engine.ts | 13 +------------ tests/engine_state_immutability.test.ts | 2 +- .../008_replace_missing_source_clarify_prompt.json | 2 +- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd18070..7f4d271 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 env: - PY_FIXTURE_REF: v0.6.0 + PY_FIXTURE_REF: dba101b662410c0ae0c85842cd78d8adb55f184b PY_FIXTURE_CHECKOUT: ${{ runner.temp }}/context-compiler-source steps: @@ -30,12 +30,14 @@ jobs: run: npm ci - name: Checkout Python fixture source - # TS 0.5.2 fixtures include cases added after Python v0.5.2; pin to v0.6.0 - # to keep fixture drift checks aligned with the current TS fixture corpus. + # Pin to an explicit Python commit SHA for deterministic fixture drift checks. run: | echo "Python fixture ref: ${PY_FIXTURE_REF}" echo "Python checkout path: ${PY_FIXTURE_CHECKOUT}" - git clone --depth 1 --branch "${PY_FIXTURE_REF}" https://github.com/rlippmann/context-compiler "${PY_FIXTURE_CHECKOUT}" + git init "${PY_FIXTURE_CHECKOUT}" + git -C "${PY_FIXTURE_CHECKOUT}" remote add origin https://github.com/rlippmann/context-compiler + git -C "${PY_FIXTURE_CHECKOUT}" fetch --depth 1 origin "${PY_FIXTURE_REF}" + git -C "${PY_FIXTURE_CHECKOUT}" checkout --detach FETCH_HEAD echo "Resolved fixture source: ${PY_FIXTURE_CHECKOUT}/tests/fixtures/conformance" - name: Check fixture drift diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33f7985..b08df59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,12 @@ All tests must pass before submitting changes. Python fixtures are the source of truth. +Conformance fixture policy: + +- Do not hand-edit fixture JSON files under `tests/fixtures/conformance`. +- Update conformance fixtures only via `npm run fixtures:sync` with explicit `FIXTURES_SOURCE` pointing to the Python source of truth. +- If fixture updates introduce test failures, update TypeScript implementation to conform to fixtures. + Commands: - `FIXTURES_SOURCE=/path/to/context-compiler/tests/fixtures/conformance npm run fixtures:sync` to copy fixtures from Python into `tests/fixtures/conformance` diff --git a/src/engine.ts b/src/engine.ts index 3356934..4507981 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -235,18 +235,7 @@ class EngineImpl implements Engine { const oldState = this._state.policies[oldKey]; const newState = this._state.policies[newKey]; if (!Object.prototype.hasOwnProperty.call(this._state.policies, oldKey)) { - const promptLines = [ - `No exact policy found for "${action.old_item}".`, - 'Replacement requires an exact policy match.' - ]; - const diagnosticHints = diagnosticPolicyContainsHints(this._state.policies, action.old_item); - if (diagnosticHints !== '') { - promptLines.push(`Existing policies containing that text: ${diagnosticHints}.`); - promptLines.push(`Confirm to use "${action.new_item}" and keep ${diagnosticHints}?`); - } else { - promptLines.push(`Confirm to use "${action.new_item}" and keep existing policies?`); - } - const prompt = promptLines.join('\n'); + const prompt = `Did you mean to use "${action.new_item}" instead?`; this._pendingReplacement = { kind: 'use_only', new_item: action.new_item }; this._pendingPrompt = prompt; return clarify(prompt); diff --git a/tests/engine_state_immutability.test.ts b/tests/engine_state_immutability.test.ts index 84042b3..04d2a74 100644 --- a/tests/engine_state_immutability.test.ts +++ b/tests/engine_state_immutability.test.ts @@ -105,7 +105,7 @@ describe('engine error-path regressions', () => { expectClarifyNoMutation( missingSource, 'use kubectl instead of docker', - 'No exact policy found for "docker".\nReplacement requires an exact policy match.\nConfirm to use "kubectl" and keep existing policies?' + 'Did you mean to use "kubectl" instead?' ); const oldIsProhibit = createEngine({ state: { premise: null, policies: { docker: 'prohibit' }, version: 2 } }); diff --git a/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json b/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json index 4b87a0b..d5f476d 100644 --- a/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json +++ b/tests/fixtures/conformance/step/008_replace_missing_source_clarify_prompt.json @@ -10,7 +10,7 @@ "expected": { "decision": { "kind": "clarify", - "prompt_to_user": "No exact policy found for \"docker\".\nReplacement requires an exact policy match.\nConfirm to use \"kubectl\" and keep existing policies?", + "prompt_to_user": "Did you mean to use \"kubectl\" instead?", "state": null }, "state": { From 11fa36c439d221dd0131fb044482d123c5e67412 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 24 Apr 2026 00:05:49 -0400 Subject: [PATCH 4/4] docs: consolidate conformance fixture policy --- AGENTS.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index cb1ef37..4f32f52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,10 +78,46 @@ A change is correct only if all fixtures pass. Do not modify fixtures to make tests pass. -Fixture updates are allowed only when syncing from the authoritative Python source for the targeted compatibility line. +Fixture updates must follow the process defined in 'Conformance fixtures' below. If synced fixtures introduce failures, fix TypeScript behavior rather than editing fixture expectations. +## Conformance fixtures + +Conformance fixtures are sourced from the Python repository and define the canonical cross-port contract. + +### Rules + +- Do not hand-edit fixture JSON files. +- Do not modify fixture JSON to make TS tests pass. +- TS implementation must conform to the fixtures, not the reverse. +- Fixture JSON files are read-only artifacts in this repository. + +### Updating fixtures + +When fixtures need to be updated: + +1. Obtain the Python source of truth (pinned commit or tag). +2. Run the sync script with an explicit source: + + FIXTURES_SOURCE=/tests/fixtures/conformance npm run fixtures:sync + +3. Commit only the resulting changes. + +### Verification + +After syncing: + +npm test +FIXTURES_SOURCE= npm run fixtures:check + +### Constraints + +- FIXTURES_SOURCE must be explicitly provided; no implicit local fallback is allowed. +- Do not copy/paste or manually edit fixture contents, even for single-file fixes. +- If a fixture appears incorrect, fix it in the Python repo first, then re-sync. +- Manual fixture JSON edits should be rejected in review. + ## Test Coverage Expectations Before opening a PR, consider: