diff --git a/README.md b/README.md index f264c19..b880884 100644 --- a/README.md +++ b/README.md @@ -74,17 +74,17 @@ npm install @rlippmann/context-compiler ## Quick Start ```ts -import { createEngine, get_clarify_prompt, is_clarify, is_passthrough, is_update } from '@rlippmann/context-compiler'; +import { createEngine, getClarifyPrompt, isClarify, isPassthrough, isUpdate } from '@rlippmann/context-compiler'; const engine = createEngine(); const decision = engine.step('set premise concise replies'); -if (is_update(decision)) { +if (isUpdate(decision)) { // Use the updated stored rules from the engine. console.log(engine.state); -} else if (is_clarify(decision)) { - console.log(get_clarify_prompt(decision)); -} else if (is_passthrough(decision)) { +} else if (isClarify(decision)) { + console.log(getClarifyPrompt(decision)); +} else if (isPassthrough(decision)) { // passthrough } ``` @@ -94,16 +94,16 @@ if (is_update(decision)) { - `createEngine(init?)` -> create an engine instance. - `engine.step(input)` -> apply one user input and return a `Decision`. - `engine.state` -> current stored premise/policy rules snapshot. -- `engine.has_pending_clarification()` -> check whether confirmation-only input is currently required. +- `engine.hasPendingClarification()` -> check whether confirmation-only input is currently required. - `engine.exportJson()` / `engine.importJson(payload)` -> state serialization utilities. - `engine.exportCheckpoint()` / `engine.importCheckpoint(payload)` -> checkpoint persistence (`authoritative_state` + pending confirmation state) that safely resumes pending confirmations. - `engine.exportCheckpointJson()` / `engine.importCheckpointJson(payload)` -> JSON checkpoint wrapper persistence helpers. -- `compile_transcript(messages)` -> replay user messages and return `state` or `confirm`. -- `engine.apply_transcript(messages)` -> replay user messages onto an existing engine instance. +- `compileTranscript(messages)` -> replay user messages and return `state` or `confirm`. +- `engine.applyTranscript(messages)` -> replay user messages onto an existing engine instance. - `getPremiseValue(state)` / `getPolicyItems(state, value?)` -> read helpers for state. - `step(engine, input)` -> controller step envelope (`output_version`, `mode`, `decision`, `state`). - `preview(engine, input)` -> dry-run step envelope with `state_before`, `state_after`, `diff`, and `would_mutate` (live engine state is restored). -- `state_diff(before, after)` -> structural state diff used by preview. +- `stateDiff(before, after)` -> structural state diff used by preview. - `DECISION_PASSTHROUGH` / `DECISION_UPDATE` / `DECISION_CLARIFY` -> decision kind constants. ## Experimental Preprocessor @@ -120,11 +120,7 @@ Preprocessor output should always be parsed/validated before passing it to the e Experimental preprocessor APIs are available via package subpath: ```ts -import { - preprocess_heuristic, - parse_preprocessor_output, - validate_preprocessor_output -} from '@rlippmann/context-compiler/experimental/preprocessor'; +import { preprocessHeuristic, parsePreprocessorOutput, validatePreprocessorOutput } from '@rlippmann/context-compiler/experimental/preprocessor'; ``` This module is intentionally experimental and separate from the deterministic core engine API. diff --git a/demos/06_llm_context_compaction.ts b/demos/06_llm_context_compaction.ts index f8a2b2e..5c410dd 100644 --- a/demos/06_llm_context_compaction.ts +++ b/demos/06_llm_context_compaction.ts @@ -1,4 +1,4 @@ -import { compile_transcript, getPremiseValue } from '../src/index.js'; +import { compileTranscript, getPremiseValue } from '../src/index.js'; import { compactUserTurns, isVerbose, printInfoReport } from './common.js'; const DEMO_NAME = '06_context_compaction — superseded directives eliminated'; @@ -41,7 +41,7 @@ function buildTurns(turnCount: number): string[] { function compilePremise(turns: string[]): string { const messages: TranscriptMessage[] = turns.map((turn) => ({ role: 'user', content: turn })); - const result = compile_transcript(messages); + const result = compileTranscript(messages); if (result.kind !== 'state') { throw new Error('Unexpected clarification while compiling transcript'); } diff --git a/demos/common.ts b/demos/common.ts index 425c457..e65e2fe 100644 --- a/demos/common.ts +++ b/demos/common.ts @@ -4,9 +4,9 @@ import { createEngine, getPolicyItems, getPremiseValue, - get_clarify_prompt, - is_clarify, - is_update + getClarifyPrompt, + isClarify, + isUpdate } from '../src/index.js'; import type { Decision, EngineState } from '../src/index.js'; import type { Message } from './llm_client.js'; @@ -82,12 +82,12 @@ export function printDecision(title: string, decision: Decision, state: EngineSt return; } console.log(`Compiler decision (${title}):`); - if (is_update(decision)) { + if (isUpdate(decision)) { console.log('result: updated'); printStateSummary(state); - } else if (is_clarify(decision)) { + } else if (isClarify(decision)) { console.log('result: clarify'); - const clarifyPrompt = get_clarify_prompt(decision); + const clarifyPrompt = getClarifyPrompt(decision); if (clarifyPrompt) { printMultilinePrompt('clarify prompt', clarifyPrompt); } @@ -237,12 +237,12 @@ export function compactUserTurns(userTurns: string[]): { for (const turn of userTurns) { const decision = engine.step(turn); - if (is_update(decision)) { + if (isUpdate(decision)) { continue; } compactedTurns.push(turn); - if (is_clarify(decision)) { - promptToUser = get_clarify_prompt(decision); + if (isClarify(decision)) { + promptToUser = getClarifyPrompt(decision); break; } } diff --git a/examples/03_ambiguity_with_clarification.ts b/examples/03_ambiguity_with_clarification.ts index 3f8252b..2086208 100644 --- a/examples/03_ambiguity_with_clarification.ts +++ b/examples/03_ambiguity_with_clarification.ts @@ -1,4 +1,4 @@ -import { createEngine, get_clarify_prompt, is_clarify } from '../src/index.js'; +import { createEngine, getClarifyPrompt, isClarify } from '../src/index.js'; declare const process: { argv: string[] }; @@ -14,7 +14,7 @@ export function runExample03(): { const contradictionDecision = engine.step('use peanuts'); let llmCalled = false; - if (!is_clarify(contradictionDecision)) { + if (!isClarify(contradictionDecision)) { llmCalled = true; } @@ -22,7 +22,7 @@ export function runExample03(): { return { clarifyKind: contradictionDecision.kind, - clarifyPrompt: get_clarify_prompt(contradictionDecision), + clarifyPrompt: getClarifyPrompt(contradictionDecision), llmCalled, resetKind: resetDecision.kind }; diff --git a/examples/05_llm_integration_pattern.ts b/examples/05_llm_integration_pattern.ts index 7293e97..6b47e72 100644 --- a/examples/05_llm_integration_pattern.ts +++ b/examples/05_llm_integration_pattern.ts @@ -1,4 +1,4 @@ -import { createEngine, is_passthrough, is_update } from '../src/index.js'; +import { createEngine, isPassthrough, isUpdate } from '../src/index.js'; declare const process: { argv: string[] }; @@ -6,10 +6,10 @@ type HostAction = 'call_llm_without_state' | 'call_llm_with_state' | 'show_clari function handleTurn(engine: ReturnType, input: string): HostAction { const decision = engine.step(input); - if (is_passthrough(decision)) { + if (isPassthrough(decision)) { return 'call_llm_without_state'; } - if (is_update(decision)) { + if (isUpdate(decision)) { return 'call_llm_with_state'; } return 'show_clarify_prompt'; diff --git a/examples/06_transcript_replay.ts b/examples/06_transcript_replay.ts index f847dbe..7b30f73 100644 --- a/examples/06_transcript_replay.ts +++ b/examples/06_transcript_replay.ts @@ -1,4 +1,4 @@ -import { compile_transcript, createEngine, getPolicyItems, type TranscriptResult } from '../src/index.js'; +import { compileTranscript, createEngine, getPolicyItems, type TranscriptResult } from '../src/index.js'; declare const process: { argv: string[] }; @@ -21,11 +21,11 @@ export function runExample06(): { { role: 'user', content: 'change premise to vegan curry' } ]; - const freshReplay = compile_transcript(transcript); + const freshReplay = compileTranscript(transcript); const engine = createEngine(); engine.step('prohibit shellfish'); - const currentReplay: TranscriptResult = engine.apply_transcript(transcript); + const currentReplay: TranscriptResult = engine.applyTranscript(transcript); const freshPolicies = freshReplay.kind === 'state' ? getPolicyItems(freshReplay.state) : []; diff --git a/examples/08_controller_preview_diff.ts b/examples/08_controller_preview_diff.ts index f2066fe..d94ab99 100644 --- a/examples/08_controller_preview_diff.ts +++ b/examples/08_controller_preview_diff.ts @@ -6,13 +6,13 @@ import { POLICY_USE, createEngine, getPolicyItems, - get_clarify_prompt, + getClarifyPrompt, getPremiseValue, - get_decision_state, - is_clarify, - is_update, + getDecisionState, + isClarify, + isUpdate, preview, - state_diff, + stateDiff, step, type Decision, type EngineState @@ -37,8 +37,8 @@ function summarizeDecision(decision: Decision): { promptToUser: string | null; decisionState: ReturnType | null; } { - if (is_update(decision)) { - const decisionState = get_decision_state(decision); + if (isUpdate(decision)) { + const decisionState = getDecisionState(decision); return { kind: DECISION_UPDATE, promptToUser: null, @@ -46,10 +46,10 @@ function summarizeDecision(decision: Decision): { }; } - if (is_clarify(decision)) { + if (isClarify(decision)) { return { kind: DECISION_CLARIFY, - promptToUser: get_clarify_prompt(decision), + promptToUser: getClarifyPrompt(decision), decisionState: null }; } @@ -80,7 +80,7 @@ export function runExample08(): { const previewResult = preview(engine, 'prohibit peanuts'); const stateAfterPreview = engine.state; - const diffAfterPreview = state_diff(stateBeforePreview, stateAfterPreview); + const diffAfterPreview = stateDiff(stateBeforePreview, stateAfterPreview); const stepResult = step(engine, 'prohibit peanuts'); diff --git a/examples/README.md b/examples/README.md index 213c221..4b2d4ae 100644 --- a/examples/README.md +++ b/examples/README.md @@ -30,7 +30,7 @@ Includes single-item correction with `remove policy `. ## 06_transcript_replay.ts -Shows transcript replay with `compile_transcript(messages)` and replay onto current engine state with `engine.apply_transcript(...)`. +Shows transcript replay with `compileTranscript(messages)` and replay onto current engine state with `engine.applyTranscript(...)`. ## 07_single_policy_correction.ts @@ -39,7 +39,7 @@ Demonstrates explicit single-policy correction without `reset policies`: ## 08_controller_preview_diff.ts -Shows controller-layer auditability with `preview(engine, input)` and `state_diff(before, after)`. +Shows controller-layer auditability with `preview(engine, input)` and `stateDiff(before, after)`. Shows that preview does not mutate live engine state, then applies the same input with `step(engine, input)`. ## Integrations diff --git a/examples/integrations/nextjs-basic/app/api/chat/route.ts b/examples/integrations/nextjs-basic/app/api/chat/route.ts index cfa5536..6a7614f 100644 --- a/examples/integrations/nextjs-basic/app/api/chat/route.ts +++ b/examples/integrations/nextjs-basic/app/api/chat/route.ts @@ -4,7 +4,7 @@ import { createEngine, getPolicyItems, getPremiseValue, - is_clarify, + isClarify, type EngineState } from '@rlippmann/context-compiler'; import { loadSessionState, saveSessionState } from '../../../lib/context-sessions'; @@ -68,7 +68,7 @@ export async function POST(req: Request): Promise { const replayMessages = history.filter( (m): m is { role: 'user'; content: string } => m.role === 'user' && typeof m.content === 'string' ); - const replay = engine.apply_transcript(replayMessages); + const replay = engine.applyTranscript(replayMessages); if (replay.kind === 'confirm') { saveSessionState(sessionId, engine.exportCheckpointJson()); const payload: ChatResponse = { @@ -83,7 +83,7 @@ export async function POST(req: Request): Promise { const decision = engine.step(input); - if (is_clarify(decision)) { + if (isClarify(decision)) { saveSessionState(sessionId, engine.exportCheckpointJson()); const payload: ChatResponse = { kind: DECISION_CLARIFY, diff --git a/examples/integrations/node-basic/README.md b/examples/integrations/node-basic/README.md index c0a289b..b27bcbe 100644 --- a/examples/integrations/node-basic/README.md +++ b/examples/integrations/node-basic/README.md @@ -19,5 +19,5 @@ This example uses `exportCheckpointJson()` / `importCheckpointJson()` for per-se That preserves both saved compiler state and pending clarify/confirm state. It also demonstrates a minimal experimental preprocessor pass before `engine.step(...)` -using `preprocess_heuristic(...)` plus `parse_preprocessor_output(...)` from +using `preprocessHeuristic(...)` plus `parsePreprocessorOutput(...)` from `@rlippmann/context-compiler/experimental/preprocessor`. diff --git a/examples/integrations/node-basic/server.ts b/examples/integrations/node-basic/server.ts index 7d1b067..a619113 100644 --- a/examples/integrations/node-basic/server.ts +++ b/examples/integrations/node-basic/server.ts @@ -5,12 +5,12 @@ import { createEngine, getPolicyItems, getPremiseValue, - is_clarify, + isClarify, type EngineState } from '@rlippmann/context-compiler'; import { - parse_preprocessor_output, - preprocess_heuristic + parsePreprocessorOutput, + preprocessHeuristic } from '@rlippmann/context-compiler/experimental/preprocessor'; type ChatBody = { @@ -64,9 +64,9 @@ function minimalRecentContext(history: ChatBody['history']) { } function normalizeInputWithPreprocessor(input: string): string { - const heuristic = preprocess_heuristic(input); + const heuristic = preprocessHeuristic(input); if (heuristic.classification === 'directive' && heuristic.output !== null) { - const parsed = parse_preprocessor_output(heuristic.output, { source_input: input }); + const parsed = parsePreprocessorOutput(heuristic.output, { source_input: input }); if (parsed !== null) { return parsed; } @@ -108,7 +108,7 @@ const server = http.createServer(async (req, res) => { const replayMessages = history.filter( (m): m is { role: 'user'; content: string } => m.role === 'user' && typeof m.content === 'string' ); - const replay = engine.apply_transcript(replayMessages); + const replay = engine.applyTranscript(replayMessages); if (replay.kind === 'confirm') { saveCheckpoint(sessionId, engine.exportCheckpointJson()); const payload: ChatResponse = { kind: DECISION_CLARIFY, prompt_to_user: replay.prompt_to_user }; @@ -120,7 +120,7 @@ const server = http.createServer(async (req, res) => { const preprocessedInput = normalizeInputWithPreprocessor(input); const decision = engine.step(preprocessedInput); - if (is_clarify(decision)) { + if (isClarify(decision)) { saveCheckpoint(sessionId, engine.exportCheckpointJson()); const payload: ChatResponse = { kind: DECISION_CLARIFY, prompt_to_user: decision.prompt_to_user }; sendJson(res, 200, payload); diff --git a/src/controller.ts b/src/controller.ts index 27d6ddb..a698feb 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -88,6 +88,8 @@ export function state_diff(before: EngineState, after: EngineState): StructuralD }; } +export const stateDiff = state_diff; + export function step(engine: Engine, user_input: string): StepResult { const decision = engine.step(user_input); return { diff --git a/src/engine.ts b/src/engine.ts index 7d29799..5024fea 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -11,7 +11,9 @@ export interface Engine { step(input: string): Decision; readonly state: EngineState; has_pending_clarification(): boolean; + hasPendingClarification(): boolean; apply_transcript(messages: unknown[]): TranscriptResult; + applyTranscript(messages: unknown[]): TranscriptResult; exportJson(): string; importJson(payload: string): void; exportCheckpoint(): EngineCheckpoint; @@ -68,6 +70,10 @@ class EngineImpl implements Engine { return this._pendingReplacement !== null; } + hasPendingClarification(): boolean { + return this.has_pending_clarification(); + } + exportJson(): string { return stringifyCanonicalJson(sortKeysDeep(this._state)); } @@ -191,6 +197,10 @@ class EngineImpl implements Engine { }; } + applyTranscript(messages: unknown[]): TranscriptResult { + return this.apply_transcript(messages); + } + private _replaceState(state: EngineState): void { this._state = state; this._pendingReplacement = null; @@ -349,6 +359,8 @@ export function compile_transcript(messages: unknown[]): TranscriptResult { return engine.apply_transcript(messages); } +export const compileTranscript = compile_transcript; + export function getPremiseValue(state: EngineState): string | null { return state.premise; } diff --git a/src/experimental/preprocessor/index.ts b/src/experimental/preprocessor/index.ts index 6916141..cd1f956 100644 --- a/src/experimental/preprocessor/index.ts +++ b/src/experimental/preprocessor/index.ts @@ -320,11 +320,15 @@ export function validate_preprocessor_output( return validated; } +export const validatePreprocessorOutput = validate_preprocessor_output; + export function parse_preprocessor_output(rawOutput: unknown, opts?: { source_input?: string }): string | null { const validated = validate_preprocessor_output(rawOutput, opts); return validated.classification === PREPROCESS_OUTCOME_DIRECTIVE ? validated.output : null; } +export const parsePreprocessorOutput = parse_preprocessor_output; + export function preprocess_heuristic(message: string): PreprocessResultType { if (LIST_MARKER_PATTERN.test(message)) { return unknown(); @@ -379,6 +383,8 @@ export function preprocess_heuristic(message: string): PreprocessResultType { return noDirective(); } +export const preprocessHeuristic = preprocess_heuristic; + function stripLeadingHeaders(promptTemplate: string): string { const lines = promptTemplate.split(/\r?\n/); let start = 0; @@ -409,6 +415,8 @@ export function render_prompt(promptTemplate: string, state: EngineState): strin return stripped.replaceAll(PROMPT_TOKEN_NULL_OR_VALUE, premiseValue).replaceAll(PROMPT_TOKEN_POLICY_SET, policiesValue); } +export const renderPrompt = render_prompt; + function normalizeItem(input: string): string { let out = input.toLowerCase(); out = out.normalize('NFKC'); diff --git a/src/index.ts b/src/index.ts index 71615d6..34fd219 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,11 @@ -export { createEngine, compile_transcript, getPremiseValue, getPolicyItems } from './engine.js'; -export { OUTPUT_VERSION, preview, state_diff, step } from './controller.js'; +export { + createEngine, + compile_transcript, + compileTranscript, + getPremiseValue, + getPolicyItems +} from './engine.js'; +export { OUTPUT_VERSION, preview, state_diff, stateDiff, step } from './controller.js'; export type { Engine, EngineInit } from './engine.js'; export type { PreviewResult, StepResult, StructuralDiff } from './controller.js'; import type { Decision, EngineState } from './types.js'; @@ -13,22 +19,32 @@ export function is_update(decision: Decision): boolean { return decision.kind === DECISION_UPDATE; } +export const isUpdate = is_update; + export function is_clarify(decision: Decision): boolean { return decision.kind === DECISION_CLARIFY; } +export const isClarify = is_clarify; + export function is_passthrough(decision: Decision): boolean { return decision.kind === DECISION_PASSTHROUGH; } +export const isPassthrough = is_passthrough; + export function get_clarify_prompt(decision: Decision): string | null { return is_clarify(decision) ? decision.prompt_to_user : null; } +export const getClarifyPrompt = get_clarify_prompt; + export function get_decision_state(decision: Decision): EngineState | null { return decision.state; } +export const getDecisionState = get_decision_state; + export type { ApplyResult, CheckpointPendingReplacement, diff --git a/tests/api_aliases.test.ts b/tests/api_aliases.test.ts new file mode 100644 index 0000000..14a30a6 --- /dev/null +++ b/tests/api_aliases.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import * as cc from '../src/index.js'; +import * as pre from '../src/experimental/preprocessor/index.js'; + +describe('root API aliases', () => { + it('keeps snake_case exports and adds camelCase aliases', () => { + expect(cc.compileTranscript).toBe(cc.compile_transcript); + expect(cc.isUpdate).toBe(cc.is_update); + expect(cc.isClarify).toBe(cc.is_clarify); + expect(cc.isPassthrough).toBe(cc.is_passthrough); + expect(cc.getClarifyPrompt).toBe(cc.get_clarify_prompt); + expect(cc.getDecisionState).toBe(cc.get_decision_state); + expect(cc.stateDiff).toBe(cc.state_diff); + }); + + it('exposes camelCase engine method aliases without changing behavior', () => { + const engine = cc.createEngine(); + expect(engine.hasPendingClarification()).toBe(engine.has_pending_clarification()); + expect(engine.applyTranscript([])).toEqual(engine.apply_transcript([])); + }); +}); + +describe('preprocessor API aliases', () => { + it('adds camelCase aliases for snake_case preprocessor exports', () => { + expect(pre.validatePreprocessorOutput).toBe(pre.validate_preprocessor_output); + expect(pre.parsePreprocessorOutput).toBe(pre.parse_preprocessor_output); + expect(pre.preprocessHeuristic).toBe(pre.preprocess_heuristic); + expect(pre.renderPrompt).toBe(pre.render_prompt); + }); +});