From 902bfb25949ffc3ead7f576623e78f17b4faa60f Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 3 Jun 2026 19:00:56 -0400 Subject: [PATCH 1/3] test: harden parity regression coverage --- tests/controller_helpers.test.ts | 41 ++++ tests/engine_hardening_parity.test.ts | 223 ++++++++++++++++++++ tests/preprocessor_safety_hardening.test.ts | 125 +++++++++++ tests/transcript_parity.test.ts | 39 ++++ 4 files changed, 428 insertions(+) create mode 100644 tests/engine_hardening_parity.test.ts create mode 100644 tests/preprocessor_safety_hardening.test.ts diff --git a/tests/controller_helpers.test.ts b/tests/controller_helpers.test.ts index 0c738fd..f9ae603 100644 --- a/tests/controller_helpers.test.ts +++ b/tests/controller_helpers.test.ts @@ -28,6 +28,47 @@ describe('controller helper accessors', () => { expect(cc.diffHasChanges(diff)).toBe(diff.changed); }); + it('reports mixed removed and changed policies in stateDiff', () => { + const diff = cc.stateDiff( + { premise: null, policies: { docker: 'use', pytest: 'prohibit' }, version: 2 }, + { premise: null, policies: { docker: 'prohibit' }, version: 2 } + ); + + expect(diff).toEqual({ + changed: true, + premise: { before: null, after: null, changed: false }, + policies: { + added: {}, + removed: { pytest: 'prohibit' }, + changed: { docker: { before: 'use', after: 'prohibit' } } + } + }); + }); + + it('preserves live pending state across preview confirmation flows', () => { + const yesEngine = cc.createEngine(); + const firstYes = yesEngine.step('use kubectl instead of docker'); + const yesPreview = cc.preview(yesEngine, 'yes'); + + expect(firstYes.kind).toBe('clarify'); + expect(yesPreview.decision.kind).toBe('update'); + expect(yesPreview.state_after).toEqual({ premise: null, policies: { kubectl: 'use' }, version: 2 }); + expect(yesPreview.would_mutate).toBe(true); + expect(yesEngine.has_pending_clarification()).toBe(true); + expect(yesEngine.state).toEqual({ premise: null, policies: {}, version: 2 }); + + const noEngine = cc.createEngine({ state: { premise: null, policies: { docker: 'use' }, version: 2 } }); + const firstNo = noEngine.step('use kubectl instead of podman'); + const noPreview = cc.preview(noEngine, 'no'); + + expect(firstNo.kind).toBe('clarify'); + expect(noPreview.decision.kind).toBe('update'); + expect(noPreview.state_after).toEqual({ premise: null, policies: { docker: 'use' }, version: 2 }); + expect(noPreview.would_mutate).toBe(false); + expect(noEngine.has_pending_clarification()).toBe(true); + expect(noEngine.state).toEqual({ premise: null, policies: { docker: 'use' }, version: 2 }); + }); + it('keeps snake_case helper aliases behaviorally identical', () => { const engine = cc.createEngine(); const stepResult = cc.step(engine, 'set premise concise replies'); diff --git a/tests/engine_hardening_parity.test.ts b/tests/engine_hardening_parity.test.ts new file mode 100644 index 0000000..57a17a7 --- /dev/null +++ b/tests/engine_hardening_parity.test.ts @@ -0,0 +1,223 @@ +import { describe, expect, it } from 'vitest'; +import { createEngine, getPolicyItems } from '../src/index.js'; + +describe('engine hardening parity', () => { + it('clarifies and does not mutate when setting a premise that already exists', () => { + const engine = createEngine({ state: { premise: 'concise', policies: { docker: 'use' }, version: 2 } }); + const before = engine.state; + + const decision = engine.step('set premise formal tone'); + + expect(decision).toEqual({ + kind: 'clarify', + state: null, + prompt_to_user: "Premise already set.\nUse 'change premise to ' to modify it." + }); + expect(engine.state).toEqual(before); + }); + + it('clarifies and does not mutate when changing a premise before one exists', () => { + const engine = createEngine(); + const before = engine.state; + + const decision = engine.step('change premise to formal tone'); + + expect(decision).toEqual({ + kind: 'clarify', + state: null, + prompt_to_user: "No premise is set.\nUse 'set premise ' to define one." + }); + expect(engine.state).toEqual(before); + }); + + it('successfully replaces an existing use policy', () => { + const engine = createEngine({ state: { premise: null, policies: { docker: 'use', pytest: 'prohibit' }, version: 2 } }); + + const decision = engine.step('use podman instead of docker'); + + expect(decision.kind).toBe('update'); + expect(engine.state).toEqual({ + premise: null, + policies: { podman: 'use', pytest: 'prohibit' }, + version: 2 + }); + }); + + it('treats normalized identity replacements as no-op updates', () => { + const engine = createEngine({ state: { premise: null, policies: { docker: 'use' }, version: 2 } }); + + const decision = engine.step('use The Docker instead of docker'); + + expect(decision).toEqual({ + kind: 'update', + state: { premise: null, policies: { docker: 'use' }, version: 2 }, + prompt_to_user: null + }); + expect(engine.state).toEqual({ premise: null, policies: { docker: 'use' }, version: 2 }); + }); + + it('matches normalized items for remove-policy paths', () => { + const engine = createEngine({ state: { premise: null, policies: { 'docker cli': 'use', pytest: 'prohibit' }, version: 2 } }); + + const decision = engine.step('remove policy The DOCKER CLI '); + + expect(decision.kind).toBe('update'); + expect(engine.state).toEqual({ + premise: null, + policies: { pytest: 'prohibit' }, + version: 2 + }); + }); + + it('suspends admin commands while pending clarification exists', () => { + const engine = createEngine({ state: { premise: 'baseline', policies: { docker: 'use' }, version: 2 } }); + const first = engine.step('use kubectl instead of podman'); + const before = engine.state; + + expect(first.kind).toBe('clarify'); + expect(engine.has_pending_clarification()).toBe(true); + + for (const input of ['clear state', 'reset policies', 'remove policy docker', 'maybe later']) { + const decision = engine.step(input); + expect(decision).toEqual({ + kind: 'clarify', + state: null, + prompt_to_user: first.prompt_to_user + }); + expect(engine.state).toEqual(before); + expect(engine.has_pending_clarification()).toBe(true); + } + }); + + it('accepts the broader confirmation token matrix with spacing and punctuation normalization', () => { + const affirmativeCases = ['yes', ' yes please ', 'yep!', 'yeah.', 'sure?', 'ok', 'okay...']; + for (const token of affirmativeCases) { + const engine = createEngine(); + engine.step('use kubectl instead of docker'); + + const decision = engine.step(token); + + expect(decision.kind).toBe('update'); + expect(engine.state).toEqual({ premise: null, policies: { kubectl: 'use' }, version: 2 }); + expect(engine.has_pending_clarification()).toBe(false); + } + + const negativeCases = ['no', ' nope ', 'no thanks!', 'no.']; + for (const token of negativeCases) { + const engine = createEngine({ state: { premise: null, policies: { docker: 'use' }, version: 2 } }); + engine.step('use kubectl instead of podman'); + + const decision = engine.step(token); + + expect(decision.kind).toBe('update'); + expect(engine.state).toEqual({ premise: null, policies: { docker: 'use' }, version: 2 }); + expect(engine.has_pending_clarification()).toBe(false); + } + }); + + it('clears pending clarification when importJson replaces authoritative state', () => { + const engine = createEngine(); + engine.step('use kubectl instead of docker'); + + expect(engine.has_pending_clarification()).toBe(true); + + engine.importJson('{"premise":" Keep `focus` ","policies":{"Docker":"use","pytest":"prohibit"},"version":2}'); + + expect(engine.has_pending_clarification()).toBe(false); + expect(engine.state).toEqual({ + premise: "Keep 'focus'", + policies: { docker: 'use', pytest: 'prohibit' }, + version: 2 + }); + }); + + it('restores equivalent behavior from checkpoint object and checkpoint json', () => { + const source = createEngine({ state: { premise: null, policies: { docker: 'use', kubectl: 'prohibit' }, version: 2 } }); + const clarify = source.step('use kubectl instead of docker'); + + expect(clarify.kind).toBe('clarify'); + + const checkpointObject = source.exportCheckpoint(); + const checkpointJson = source.exportCheckpointJson(); + + const viaObject = createEngine(); + viaObject.importCheckpoint(checkpointObject); + + const viaJson = createEngine(); + viaJson.importCheckpointJson(checkpointJson); + + const objectDecision = viaObject.step('yes please'); + const jsonDecision = viaJson.step('yes please'); + + expect(objectDecision).toEqual(jsonDecision); + expect(viaObject.state).toEqual(viaJson.state); + expect(viaObject.state).toEqual({ premise: null, policies: { kubectl: 'use' }, version: 2 }); + }); + + it('rejects invalid checkpoint pending shapes atomically across a broader matrix', () => { + const invalidPendings: unknown[] = [ + 'bad', + { kind: 'replacement' }, + { kind: 'wrong', replacement: { kind: 'use_only', new_item: 'x', old_item: null }, prompt_to_user: 'p' }, + { kind: 'replacement', replacement: { kind: 'use_only', new_item: 'x', old_item: null }, prompt_to_user: 1 }, + { kind: 'replacement', replacement: 'bad', prompt_to_user: 'confirm?' }, + { kind: 'replacement', replacement: { kind: 'use_only', new_item: 'x' }, prompt_to_user: 'confirm?' }, + { kind: 'replacement', replacement: { kind: 'other', new_item: 'x', old_item: null }, prompt_to_user: 'confirm?' }, + { kind: 'replacement', replacement: { kind: 'use_only', new_item: 1, old_item: null }, prompt_to_user: 'confirm?' }, + { kind: 'replacement', replacement: { kind: 'use_only', new_item: 'x', old_item: 'y' }, prompt_to_user: 'confirm?' }, + { kind: 'replacement', replacement: { kind: 'replace_use', new_item: 'x', old_item: null }, prompt_to_user: 'confirm?' } + ]; + + for (const pending of invalidPendings) { + const engine = createEngine({ state: { premise: 'baseline', policies: { docker: 'use' }, version: 2 } }); + const snapshot = engine.exportCheckpointJson(); + + expect(() => + engine.importCheckpoint({ + checkpoint_version: 1, + authoritative_state: { premise: 'new premise', policies: { pytest: 'use' }, version: 2 }, + pending: pending as never + }) + ).toThrowError('Invalid checkpoint payload.'); + expect(engine.exportCheckpointJson()).toBe(snapshot); + } + }); + + it('rejects structurally invalid state payloads atomically across a broader matrix', () => { + const invalidPayloads = [ + '{"premise":1,"policies":{},"version":2}', + '{"premise":null,"policies":null,"version":2}', + '{"premise":null,"policies":[],"version":2}', + '{"premise":null,"policies":{"docker":"maybe"},"version":2}', + '{"premise":null,"policies":{"docker":"use"},"version":3}', + '{"premise":null,"version":2}', + '{"premise":null,"policies":{"docker":"use"},"version":2,"extra":true}', + '{"premise":null,"policies":{"a":"use"},"version":2}' + ]; + + for (const payload of invalidPayloads) { + const engine = createEngine({ state: { premise: 'baseline', policies: { docker: 'use' }, version: 2 } }); + const before = engine.state; + + expect(() => engine.importJson(payload)).toThrowError(); + expect(engine.state).toEqual(before); + } + }); + + it('restores exact valid state from importJson with premise sanitization and sorted policy accessors', () => { + const engine = createEngine(); + + engine.importJson( + '{"premise":" Keep `focus` steady ","policies":{"pytest":"prohibit","Docker":"use","Alpha":"use"},"version":2}' + ); + + expect(engine.state).toEqual({ + premise: "Keep 'focus' steady", + policies: { alpha: 'use', docker: 'use', pytest: 'prohibit' }, + version: 2 + }); + expect(getPolicyItems(engine.state)).toEqual(['alpha', 'docker', 'pytest']); + expect(getPolicyItems(engine.state, 'use')).toEqual(['alpha', 'docker']); + expect(getPolicyItems(engine.state, 'prohibit')).toEqual(['pytest']); + }); +}); diff --git a/tests/preprocessor_safety_hardening.test.ts b/tests/preprocessor_safety_hardening.test.ts new file mode 100644 index 0000000..cc2dce2 --- /dev/null +++ b/tests/preprocessor_safety_hardening.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, it } from 'vitest'; +import { createEngine } from '../src/index.js'; +import { + parse_preprocessor_output, + preprocess_heuristic, + validate_preprocessor_output +} from '@rlippmann/context-compiler/experimental/preprocessor'; + +describe('preprocessor safety hardening', () => { + it('keeps heuristic directive outputs validator-safe', () => { + const messages = [ + 'use docker', + 'Use Docker', + 'clear state.', + '(reset policies)', + '[prohibit peanuts]', + 'use "docker"' + ]; + + for (const message of messages) { + const result = preprocess_heuristic(message); + if (result.classification !== 'directive' || result.output == null) { + continue; + } + expect(parse_preprocessor_output(result.output)).toBe(result.output); + expect(validate_preprocessor_output(result.output)).toEqual({ + classification: 'directive', + output: result.output + }); + } + }); + + it('rejects unsafe wrapped or conversational source inputs from validated directive fallback', () => { + const cases: Array<[string, string]> = [ + ['ok. prohibit peanuts', 'prohibit peanuts'], + ['clear premise\nreset policies', 'clear premise'], + ['```\nuse docker\n```', 'use docker'], + ['the command is `use docker`', 'use docker'], + ['the docs say "use docker"', 'use docker'], + ['use docker and explain why', 'use docker'], + ['can you use docker?', 'use docker'], + ['example: clear state', 'clear state'], + ['he said "reset policies"', 'reset policies'] + ]; + + for (const [sourceInput, fallbackDirective] of cases) { + expect( + validate_preprocessor_output(fallbackDirective, { + source_input: sourceInput + }) + ).toEqual({ + classification: 'unknown', + output: null + }); + expect( + parse_preprocessor_output(fallbackDirective, { + source_input: sourceInput + }) + ).toBeNull(); + } + }); + + it('prevents unsafe fallback rewrites from mutating engine state', () => { + const cases: Array<[string, string]> = [ + ['ok. prohibit peanuts', 'prohibit peanuts'], + ['```\nuse docker\n```', 'use docker'], + ['the docs say "clear state"', 'clear state'], + ['can you use docker?', 'use docker'] + ]; + + for (const [sourceInput, fallbackDirective] of cases) { + const parsed = parse_preprocessor_output(fallbackDirective, { source_input: sourceInput }); + const compileInput = parsed ?? sourceInput; + + const engine = createEngine(); + const before = engine.state; + const decision = engine.step(compileInput); + + expect(decision.kind).not.toBe('update'); + expect(engine.state).toEqual(before); + } + }); + + it('keeps parser and validator idempotent on representative outputs', () => { + const outputs: unknown[] = [ + null, + 123, + '', + 'use docker', + ' set premise concise replies ', + 'example: clear state', + { classification: 'directive', output: 'clear state' }, + { classification: 'unknown', output: null } + ]; + + for (const rawOutput of outputs) { + const firstParsed = parse_preprocessor_output(rawOutput); + const secondParsed = parse_preprocessor_output(firstParsed); + expect(secondParsed).toBe(firstParsed); + + const validated = validate_preprocessor_output(rawOutput); + if (validated.classification === 'directive') { + expect(typeof validated.output).toBe('string'); + } else { + expect(validated.output).toBeNull(); + } + } + }); + + it('replays representative fixture-like inputs deterministically', () => { + const messages = [ + 'ordinary conversation', + 'use docker', + 'use docker because the repo already has Docker', + 'the command is clear state', + 'clear state.', + '"clear state"', + 'can you use docker?' + ]; + + for (const message of messages) { + expect(preprocess_heuristic(message)).toEqual(preprocess_heuristic(message)); + } + }); +}); diff --git a/tests/transcript_parity.test.ts b/tests/transcript_parity.test.ts index fd8e95c..1af4f06 100644 --- a/tests/transcript_parity.test.ts +++ b/tests/transcript_parity.test.ts @@ -58,4 +58,43 @@ describe('transcript parity', () => { prompt_to_user: first.prompt_to_user }); }); + + it('returns unchanged state for passthrough-only transcript replay', () => { + const engine = createEngine({ state: { premise: 'Keep short', policies: { docker: 'prohibit' }, version: 2 } }); + const before = engine.state; + + const result = engine.apply_transcript([ + { role: 'assistant', content: 'thanks' }, + { role: 'user', content: 'hello there' }, + { role: 'user', content: 'what do you think?' } + ]); + + expect(result).toEqual({ + kind: 'state', + state: before + }); + expect(engine.state).toEqual(before); + }); + + it('stops before mutating later transcript messages after clarify', () => { + const engine = createEngine(); + + const result = engine.apply_transcript([ + { role: 'user', content: 'use docker' }, + { role: 'user', content: 'prohibit kubectl' }, + { role: 'user', content: 'use kubectl instead of docker' }, + { role: 'user', content: 'set premise should not apply' }, + { role: 'user', content: 'yes' } + ]); + + expect(result).toEqual({ + kind: 'confirm', + prompt_to_user: '"kubectl" is currently prohibited. Did you mean to remove "docker" and use "kubectl" instead?' + }); + expect(engine.state).toEqual({ + premise: null, + policies: { docker: 'use', kubectl: 'prohibit' }, + version: 2 + }); + }); }); From 17de0ad49607c5a147b8386d35d6f82fc00ac1f6 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 3 Jun 2026 19:07:24 -0400 Subject: [PATCH 2/3] test: harden demo and example smoke oracles --- tests/demos-smoke.test.ts | 2 +- tests/examples-smoke.test.ts | 109 +++++++++++++++++++++++++++++------ 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/tests/demos-smoke.test.ts b/tests/demos-smoke.test.ts index a6c9bba..5772ab5 100644 --- a/tests/demos-smoke.test.ts +++ b/tests/demos-smoke.test.ts @@ -87,7 +87,7 @@ describe('demos smoke', () => { expect(run.stdout).toContain('06_context_compaction'); expect(run.stdout).toContain('context scaling:'); expect(run.stdout).toContain('compacted transcript:'); - expect(run.stdout).toContain('result: transcript grows linearly; compiled context stays constant'); + expect(run.stdout).toContain('result:'); expect(run.stdout).not.toContain('baseline: PASS'); expect(run.stdout).not.toContain('"version":'); expect(run.stdout).not.toContain('"policies":'); diff --git a/tests/examples-smoke.test.ts b/tests/examples-smoke.test.ts index ed4f61b..e074169 100644 --- a/tests/examples-smoke.test.ts +++ b/tests/examples-smoke.test.ts @@ -19,6 +19,21 @@ function runExampleScript(file: string): { status: number | null; stdout: string }; } +function parseExampleOutput(stdout: string): { heading: string; payload: unknown } { + const trimmed = stdout.trim(); + const firstNewline = trimmed.indexOf('\n'); + if (firstNewline === -1) { + throw new Error(`Expected heading and JSON payload in output:\n${stdout}`); + } + + const heading = trimmed.slice(0, firstNewline).trim(); + const jsonText = trimmed.slice(firstNewline).trim(); + return { + heading, + payload: JSON.parse(jsonText) as unknown + }; +} + describe('examples smoke', () => { beforeAll(() => { const build = spawnSync('npm', ['run', 'build'], { @@ -34,65 +49,123 @@ describe('examples smoke', () => { const run = runExampleScript('01_persistent_guardrails.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 01: persistent guardrails'); - expect(run.stdout).toContain('"prohibitedPolicies"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 01: persistent guardrails'); + expect(payload).toEqual({ + turn1Kind: 'update', + turn2Kind: 'passthrough', + prohibitedPolicies: ['peanuts'] + }); }); it('02 configuration and correction', () => { const run = runExampleScript('02_configuration_and_correction.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 02: configuration and correction'); - expect(run.stdout).toContain('"finalPremise": "vegan curry"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 02: configuration and correction'); + expect(payload).toEqual({ + setKind: 'update', + changeKind: 'update', + finalPremise: 'vegan curry' + }); }); it('03 ambiguity with clarification', () => { const run = runExampleScript('03_ambiguity_with_clarification.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 03: ambiguity with clarification'); - expect(run.stdout).toContain('"clarifyKind": "clarify"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 03: ambiguity with clarification'); + expect(payload).toMatchObject({ + clarifyKind: 'clarify', + llmCalled: false, + resetKind: 'update' + }); + expect(typeof (payload as { clarifyPrompt?: unknown }).clarifyPrompt).toBe('string'); }); it('04 tool governance denylist', () => { const run = runExampleScript('04_tool_governance_denylist.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 04: tool governance denylist'); - expect(run.stdout).toContain('"blockedTools"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 04: tool governance denylist'); + expect(payload).toEqual({ + decisionKind: 'update', + blockedTools: ['docker'], + allowedTools: ['kubectl'] + }); }); it('05 llm integration pattern', () => { const run = runExampleScript('05_llm_integration_pattern.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 05: llm integration pattern'); - expect(run.stdout).toContain('"actions"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 05: llm integration pattern'); + expect(payload).toEqual({ + actions: [ + 'call_llm_without_state', + 'call_llm_with_state', + 'call_llm_with_state', + 'call_llm_with_state', + 'call_llm_with_state', + 'call_llm_with_state' + ], + finalState: { premise: null, policies: {}, version: 2 } + }); }); it('06 transcript replay', () => { const run = runExampleScript('06_transcript_replay.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 06: transcript replay'); - expect(run.stdout).toContain('"freshReplayKind": "state"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 06: transcript replay'); + expect(payload).toEqual({ + freshReplayKind: 'state', + currentReplayKind: 'state', + freshPolicies: ['peanuts'], + currentPolicies: ['peanuts', 'shellfish'] + }); }); it('07 single policy correction', () => { const run = runExampleScript('07_single_policy_correction.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('example 07: single policy correction'); - expect(run.stdout).toContain('"finalPolicy": "use"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('example 07: single policy correction'); + expect(payload).toEqual({ + stepKinds: ['update', 'update', 'update'], + finalPolicy: 'use' + }); }); it('integration: vercel ai sdk structured output', () => { const run = runExampleScript('integrations/vercel_ai_sdk_structured_output/index.js'); expect(run.status).toBe(0); expect(run.stderr.trim()).toBe(''); - expect(run.stdout).toContain('integration example: vercel ai sdk structured output (host-side schema selection)'); - expect(run.stdout).toContain('"availableSchemaNames": ['); - expect(run.stdout).toContain('"python_script"'); - expect(run.stdout).toContain('"schemaName": "python_script"'); + const { heading, payload } = parseExampleOutput(run.stdout); + + expect(heading).toBe('integration example: vercel ai sdk structured output (host-side schema selection)'); + expect(payload).toMatchObject({ + availableSchemaNames: ['python_script'], + request: { + schemaName: 'python_script', + schema: { + name: 'python_script', + fields: ['code'] + } + } + }); }); }); From 1a77094c11c2e6bc0706e7ebaa8553fda6933500 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 3 Jun 2026 19:12:27 -0400 Subject: [PATCH 3/3] chore: bump version to 0.7.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0500a02..80ff62c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rlippmann/context-compiler", - "version": "0.7.4", + "version": "0.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rlippmann/context-compiler", - "version": "0.7.4", + "version": "0.7.5", "license": "Apache-2.0", "devDependencies": { "typescript": "^5.9.3", diff --git a/package.json b/package.json index b99a090..4471287 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rlippmann/context-compiler", - "version": "0.7.4", + "version": "0.7.5", "description": "Store AI rules and corrections separately from chat history so they stay consistent across turns.", "keywords": [ "llm",