Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ Context Compiler solves a common state-management problem: storing user rules is
easy, but deciding when those rules are allowed to change is not.

It gives your app deterministic rules for explicit state changes such as
setting a premise, replacing a policy, blocking a conflicting update, or
asking for clarification before anything changes.
setting a premise, replacing a policy, or blocking a conflicting update.

A dict stores state. Context Compiler makes state changes verifiable.

Expand All @@ -27,24 +26,23 @@ availability, or other host behavior, but your app still needs rules for when
that state is allowed to change:

- when a replacement is valid
- when a conflicting update should stop and ask for confirmation
- when a change should be rejected instead of silently overwriting state
- how to restore both saved state and an in-progress clarification flow
- how to save and restore authoritative state

## How it solves it

Context Compiler lets a host application:

- prevent silent overwrites when a new update conflicts with what is already saved
- require clarification before conflicting or confirmation-only changes are accepted
- let the host preview a change before applying it and keep live state unchanged until it is accepted
- restore both saved state and an in-progress clarification flow safely between requests
- preserve state until an explicit directive is accepted
- let the host inspect advisory repairs without applying them automatically
- save and restore state through the JSON persistence API

Each user input produces a decision for the host:

- `update` -> stored premise/policy rules changed
- `passthrough` -> input does not affect saved state
- `clarify` -> do not mutate state; ask the user to confirm or clarify
- `update` -> the directive was accepted; `changed` reports whether state changed
- `no_directive` -> input did not produce a canonical directive
- `error` -> the directive was rejected; the result identifies the semantic failure and advisory repairs

Directive examples:

Expand Down Expand Up @@ -78,8 +76,23 @@ changing them.
## Public API

The package root exposes the Python 0.9 decision model, policy constants, and
the `Engine` surface. Checkpoint persistence and the former controller/helper
aliases are not part of the 0.9 package API.
the `Engine` surface. The supported engine persistence methods are
`export_json()` and `import_json()`.

The public grammar API is available from the `@rlippmann/context-compiler/grammar`
namespace.

```ts
import {
CanonicalDirective,
DirectiveKind,
decompose_directive
} from '@rlippmann/context-compiler/grammar';
```

The grammar namespace contains the public directive constructors, metadata,
syntax classifications, and parsing helpers. Internal parsing helpers are not
part of the supported API.

## Directive Drafting

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rlippmann/context-compiler",
"version": "0.9.0-dev.0",
"version": "0.9.0-dev.1",
"description": "Store AI rules and corrections separately from chat history so they stay consistent across turns.",
"keywords": [
"llm",
Expand Down Expand Up @@ -29,6 +29,10 @@
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./grammar": {
"types": "./dist/src/grammar.d.ts",
"import": "./dist/src/grammar.js"
}
},
"files": [
Expand Down
38 changes: 38 additions & 0 deletions tests/api_parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolve } from 'node:path';
import * as ts from 'typescript';
import { describe, expect, it } from 'vitest';
import * as cc from '../src/index.js';
import * as grammar from '../src/grammar.js';
import { CanonicalDirective } from '../src/grammar.js';
import type { Decision } from '../src/index.js';

Expand Down Expand Up @@ -71,6 +72,7 @@ type ApiContractFixture = {
members: Record<string, EngineMemberSpec>;
};
};
namespaces?: Record<string, { contract: string }>;
};

function getGeneratedDeclarationExportNames(): Map<string, string[]> {
Expand Down Expand Up @@ -106,6 +108,13 @@ function loadApiContractFixture(): ApiContractFixture {
return JSON.parse(raw) as ApiContractFixture;
}

function loadGrammarContract(): { exports: { names: string[] } } {
const path = resolve(process.cwd(), 'tests', 'fixtures', 'conformance', 'api', 'public-grammar-v1.json');
return JSON.parse(readFileSync(path, 'utf8')) as { exports: { names: string[] } };
}

const contract = loadApiContractFixture();

function getCanonicalRuntimeExportNames(fixture: ApiContractFixture): string[] {
return fixture.exports.names.filter((name) => {
const member = fixture.exports.members[name];
Expand Down Expand Up @@ -249,6 +258,35 @@ function expectShape(value: unknown, shape: ReturnShape, label: string): void {
}

describe('public API parity contract (conformance fixture)', () => {
it('consumes declared public namespace contracts', () => {
const namespaces = contract.namespaces ?? {};
const grammarNamespace = namespaces['context_compiler.grammar'];
expect(grammarNamespace, 'Missing grammar namespace contract').toEqual({ contract: 'public-grammar-v1' });

if (grammarNamespace?.contract === 'public-grammar-v1') {
const grammarContract = loadGrammarContract();
expect(Object.keys(grammar).sort(), 'Grammar namespace exports').toEqual([...grammarContract.exports.names].sort());
}
});

it('exposes declared namespaces through the package exports map', async () => {
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as {
exports?: Record<string, { types?: string; import?: string }>;
};
const grammarNamespace = contract.namespaces?.['context_compiler.grammar'];
expect(grammarNamespace, 'Missing grammar namespace contract').toEqual({ contract: 'public-grammar-v1' });
expect(packageJson.exports?.['./grammar'], 'Missing package ./grammar export').toEqual({
types: './dist/src/grammar.d.ts',
import: './dist/src/grammar.js'
});

const packageGrammar = await import('@rlippmann/context-compiler/grammar');
const grammarContract = loadGrammarContract();
expect(Object.keys(packageGrammar).sort(), 'Packaged grammar namespace exports').toEqual(
[...grammarContract.exports.names].sort()
);
});

it('syncs the stricter canonical Python fixture schema', () => {
const fixture = loadApiContractFixture();
expect(fixture.exports.mode).toBe('exact');
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/.source-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e1e04bd6464aa46f5e3693d634faaa264d890f66
ee49df7fdcd8965392da31d4ead37c7514d53d29
25 changes: 5 additions & 20 deletions tests/fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ These fixtures are synchronized from `rlippmann/context-compiler` at the commit

* [`conformance/`](conformance/) — core engine cross-language conformance contract.
Includes a small public API presence contract under `conformance/api/`.
* [`engine-regression/structured/`](engine-regression/structured/) — deterministic per-turn engine regression fixtures (including checkpoint snapshots).
* [`engine-regression/structured/`](engine-regression/structured/) — deterministic per-turn engine regression fixtures.

`conformance/` and `engine-regression/structured/` both cover engine behavior at different layers.
Both synchronized fixture families must come from the same Python checkout revision recorded in `tests/fixtures/.source-commit`.

## API contract fixture

[`conformance/api/public-api-v1.json`](conformance/api/public-api-v1.json) defines a small portable core API presence contract for the current Python 0.9 surface that ports must expose.
[`conformance/api/public-api-v2.json`](conformance/api/public-api-v2.json) defines the portable package-root API contract for the current Python 0.9 surface. The `context_compiler.grammar` namespace references [`public-grammar-v1.json`](conformance/api/public-grammar-v1.json) for the separate grammar API.

Ports may sync this artifact with conformance fixtures.

Ports should check equivalent public exports and methods using language-appropriate names where casing differs, while preserving the current alias-compatible API contract.
Ports should check equivalent public exports and methods using language-appropriate names where casing differs.

Behavioral semantics remain covered by conformance and structured fixtures.

Expand All @@ -37,10 +37,6 @@ Then asserts:
* returned `Decision`
* final `engine.state`

### Prelude

`prelude` simulates prior user inputs to reach states that are not representable via `initial_state` (for example, pending clarification).

## State JSON fixtures

For [`conformance/state-json/`](conformance/state-json/):
Expand All @@ -49,14 +45,6 @@ Portable serialization contract coverage for `engine.export_json()` and
`engine.import_json(...)`, including canonical export payload shape and
deterministic validation/error boundaries.

## Checkpoint fixtures

For [`conformance/checkpoint/`](conformance/checkpoint/):

Portable checkpoint import contract coverage for
`engine.import_checkpoint(...)`, including deterministic validation/error
boundaries, atomic failure behavior, and pending-clarification clearing semantics.

## Controller fixtures

For [`conformance/controller/`](conformance/controller/):
Expand All @@ -83,15 +71,13 @@ Files under synchronized fixture directories must not be edited manually; update

[`engine-regression/structured/`](engine-regression/structured/)

These fixtures capture deterministic per-turn engine behavior, including checkpoint snapshots, and are exercised by the TypeScript structured regression test in [`structured-regression-fixtures.test.ts`](../structured-regression-fixtures.test.ts).
These fixtures capture deterministic per-turn engine behavior and are exercised by the TypeScript structured regression test in [`structured-regression-fixtures.test.ts`](../structured-regression-fixtures.test.ts).

They validate:

* per-turn input handling
* `Decision.kind` outcomes
* clarification prompt behavior
* checkpoint export parity against expected snapshots
* continuation state restoration from checkpoints
* decision outcomes and state snapshots

Directive-drafter conformance is maintained in the separate
`context-compiler-directive-drafter` repositories and is not part of the core
Expand All @@ -103,6 +89,5 @@ See the TypeScript fixture runners in this repository for execution details:

* [`step-fixtures.test.ts`](../step-fixtures.test.ts)
* [`state-json-fixtures.test.ts`](../state-json-fixtures.test.ts)
* [`checkpoint-fixtures.test.ts`](../checkpoint-fixtures.test.ts)
* [`controller-fixtures.test.ts`](../controller-fixtures.test.ts)
* [`structured-regression-fixtures.test.ts`](../structured-regression-fixtures.test.ts)
Loading
Loading