refactor: remove legacy types.ts file and update frontend to use new contracts feat: add applyActions function to manage action application and world state mutation chore: remove empty .gitkeep file from sqlite data directory refactor: update frontend App component to align with new API contracts and improve UX docs: revise project.md to reflect updated architecture and system requirements docs: update thoughts.md with current status, architecture decisions, and remaining checks
486 lines
6.9 KiB
Markdown
486 lines
6.9 KiB
Markdown
# CharacterGarden — Iterative Implementation Plan
|
|
|
|
## Copilot Operating Rules
|
|
|
|
Work in small, reviewable steps.
|
|
|
|
After every completed step:
|
|
|
|
1. Update `thoughts.md`
|
|
2. Record files changed
|
|
3. Record assumptions made
|
|
4. Record next step
|
|
5. Do not skip ahead unless the current step is complete
|
|
|
|
Do not redesign the project without updating this plan.
|
|
|
|
---
|
|
|
|
# Phase 1 — Contracts First
|
|
|
|
## Step 1.1 — Create contracts folder
|
|
|
|
Create:
|
|
|
|
```txt
|
|
app/src/contracts/
|
|
```
|
|
|
|
Add:
|
|
|
|
```txt
|
|
app/src/contracts/action.ts
|
|
app/src/contracts/turn.ts
|
|
app/src/contracts/validation.ts
|
|
app/src/contracts/world.ts
|
|
app/src/contracts/entity.ts
|
|
```
|
|
|
|
Goal: all shared types live here.
|
|
|
|
---
|
|
|
|
## Step 1.2 — Define Action contract
|
|
|
|
In `action.ts`:
|
|
|
|
```ts
|
|
export type Action = {
|
|
actorId: string;
|
|
type: string;
|
|
targetId?: string;
|
|
locationId?: string;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
```
|
|
|
|
No other action shape should be used.
|
|
|
|
---
|
|
|
|
## Step 1.3 — Define ValidationResult contract
|
|
|
|
In `validation.ts`:
|
|
|
|
```ts
|
|
export type ValidationResult = {
|
|
actionIndex: number;
|
|
success: boolean;
|
|
reason?: string;
|
|
message?: string;
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Step 1.4 — Define Turn contract
|
|
|
|
In `turn.ts`:
|
|
|
|
```ts
|
|
import type { Action } from "./action";
|
|
import type { ValidationResult } from "./validation";
|
|
|
|
export type Turn = {
|
|
id: string;
|
|
rawText: string;
|
|
actions: Action[];
|
|
validation: ValidationResult[];
|
|
createdAt: number;
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Step 1.5 — Define Entity contract
|
|
|
|
In `entity.ts`:
|
|
|
|
```ts
|
|
export type Entity = {
|
|
id: string;
|
|
name: string;
|
|
type: string;
|
|
attributes: Record<string, unknown>;
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Step 1.6 — Define WorldState contract
|
|
|
|
In `world.ts`:
|
|
|
|
```ts
|
|
import type { Entity } from "./entity";
|
|
|
|
export type WorldState = {
|
|
id: string;
|
|
entities: Record<string, Entity>;
|
|
metadata: Record<string, unknown>;
|
|
createdAt: number;
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
# Phase 2 — Enforce Layer Boundaries
|
|
|
|
## Step 2.1 — Refactor truth engine imports
|
|
|
|
Update `truthEngine.ts` so it imports:
|
|
|
|
```ts
|
|
import type { Action } from "./contracts/action";
|
|
import type { ValidationResult } from "./contracts/validation";
|
|
import type { WorldState } from "./contracts/world";
|
|
```
|
|
|
|
Truth engine must only receive structured actions.
|
|
|
|
---
|
|
|
|
## Step 2.2 — Remove text parsing from truth engine
|
|
|
|
Search `truthEngine.ts` for:
|
|
|
|
* string parsing
|
|
* natural language interpretation
|
|
* prompt logic
|
|
* LLM calls
|
|
|
|
Move any such logic out.
|
|
|
|
Truth engine should expose:
|
|
|
|
```ts
|
|
export function validateActions(
|
|
actions: Action[],
|
|
worldState: WorldState
|
|
): ValidationResult[] {
|
|
// deterministic validation only
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Step 2.3 — Create parser layer
|
|
|
|
Create:
|
|
|
|
```txt
|
|
app/src/parser/
|
|
app/src/parser/parseTextToActions.ts
|
|
```
|
|
|
|
Function:
|
|
|
|
```ts
|
|
import type { Action } from "../contracts/action";
|
|
|
|
export function parseTextToActions(text: string): Action[] {
|
|
// temporary simple parser
|
|
return [];
|
|
}
|
|
```
|
|
|
|
For now, returning `[]` is acceptable.
|
|
|
|
---
|
|
|
|
## Step 2.4 — Create world state engine
|
|
|
|
Create:
|
|
|
|
```txt
|
|
app/src/world/
|
|
app/src/world/applyActions.ts
|
|
```
|
|
|
|
Function:
|
|
|
|
```ts
|
|
import type { Action } from "../contracts/action";
|
|
import type { ValidationResult } from "../contracts/validation";
|
|
import type { WorldState } from "../contracts/world";
|
|
|
|
export function applyActions(
|
|
actions: Action[],
|
|
results: ValidationResult[],
|
|
worldState: WorldState
|
|
): WorldState {
|
|
// apply only successful actions
|
|
return worldState;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# Phase 3 — Build First Deterministic Test Domain
|
|
|
|
Use a simple door/key room before anything complex.
|
|
|
|
## Step 3.1 — Seed initial world
|
|
|
|
Create initial world state:
|
|
|
|
* actor: `player`
|
|
* room: `room_start`
|
|
* door: `door_1`
|
|
* key: `key_1`
|
|
|
|
Door starts locked.
|
|
|
|
Key starts in room.
|
|
|
|
Player starts in room.
|
|
|
|
---
|
|
|
|
## Step 3.2 — Support action types
|
|
|
|
Truth engine should recognize:
|
|
|
|
```txt
|
|
inspect
|
|
take
|
|
open
|
|
move
|
|
```
|
|
|
|
Unknown action types fail with:
|
|
|
|
```txt
|
|
reason: "unknown_action"
|
|
```
|
|
|
|
---
|
|
|
|
## Step 3.3 — Validate take action
|
|
|
|
Rules:
|
|
|
|
* Actor must exist
|
|
* Target must exist
|
|
* Target must be in same location
|
|
* Target must be takeable
|
|
|
|
Failure reasons:
|
|
|
|
* `actor_not_found`
|
|
* `target_not_found`
|
|
* `not_in_same_location`
|
|
* `not_takeable`
|
|
|
|
---
|
|
|
|
## Step 3.4 — Validate open action
|
|
|
|
Rules:
|
|
|
|
* Actor must exist
|
|
* Target must exist
|
|
* Target must be openable
|
|
* If locked, actor must have matching key
|
|
|
|
Failure reasons:
|
|
|
|
* `actor_not_found`
|
|
* `target_not_found`
|
|
* `not_openable`
|
|
* `locked_requires_key`
|
|
|
|
---
|
|
|
|
## Step 3.5 — Apply successful take
|
|
|
|
If `take` succeeds:
|
|
|
|
* move item into actor inventory
|
|
|
|
---
|
|
|
|
## Step 3.6 — Apply successful open
|
|
|
|
If `open` succeeds:
|
|
|
|
* set door attribute `open: true`
|
|
|
|
---
|
|
|
|
# Phase 4 — Wire Full Turn Processing
|
|
|
|
## Step 4.1 — Create turn processor
|
|
|
|
Create:
|
|
|
|
```txt
|
|
app/src/turns/processTurn.ts
|
|
```
|
|
|
|
Function:
|
|
|
|
```ts
|
|
export async function processTurn(rawText: string): Promise<Turn> {
|
|
// parse
|
|
// validate
|
|
// apply
|
|
// persist
|
|
// return turn
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Step 4.2 — Enforce pipeline order
|
|
|
|
The turn processor must call:
|
|
|
|
```txt
|
|
parseTextToActions
|
|
validateActions
|
|
applyActions
|
|
persistTurn
|
|
```
|
|
|
|
In that order.
|
|
|
|
No layer may skip ahead.
|
|
|
|
---
|
|
|
|
## Step 4.3 — Add debug response
|
|
|
|
API should return:
|
|
|
|
```ts
|
|
{
|
|
rawText,
|
|
actions,
|
|
validation,
|
|
worldState
|
|
}
|
|
```
|
|
|
|
This is for MVP debugging.
|
|
|
|
---
|
|
|
|
# Phase 5 — Persistence
|
|
|
|
## Step 5.1 — Add database tables
|
|
|
|
Minimum SQLite tables:
|
|
|
|
```sql
|
|
turns
|
|
actions
|
|
validation_results
|
|
entities
|
|
world_states
|
|
```
|
|
|
|
---
|
|
|
|
## Step 5.2 — Persist every turn
|
|
|
|
Each call to `processTurn` must save:
|
|
|
|
* raw text
|
|
* parsed actions
|
|
* validation results
|
|
* resulting world state snapshot
|
|
|
|
---
|
|
|
|
## Step 5.3 — Add reset endpoint
|
|
|
|
Add an endpoint to reset world state to seed state.
|
|
|
|
This is needed for testing.
|
|
|
|
---
|
|
|
|
# Phase 6 — LLM Adapter Reintroduction
|
|
|
|
Only after deterministic flow works.
|
|
|
|
## Step 6.1 — LLM parser adapter
|
|
|
|
Add optional LLM parser:
|
|
|
|
```ts
|
|
parseTextToActionsWithLLM(text: string, worldState: WorldState): Promise<Action[]>
|
|
```
|
|
|
|
It must output only valid `Action[]`.
|
|
|
|
---
|
|
|
|
## Step 6.2 — LLM narrative adapter
|
|
|
|
Add:
|
|
|
|
```ts
|
|
generateNarrative(turn: Turn, worldState: WorldState): Promise<string>
|
|
```
|
|
|
|
The narrative adapter may describe results but must not alter them.
|
|
|
|
---
|
|
|
|
# Phase 7 — Frontend Debug UI
|
|
|
|
## Step 7.1 — Show raw text input
|
|
|
|
User can submit a turn.
|
|
|
|
---
|
|
|
|
## Step 7.2 — Show parsed actions
|
|
|
|
Display action JSON.
|
|
|
|
---
|
|
|
|
## Step 7.3 — Show validation results
|
|
|
|
Display success/failure reasons.
|
|
|
|
---
|
|
|
|
## Step 7.4 — Show world state
|
|
|
|
Display current world state JSON.
|
|
|
|
---
|
|
|
|
# MVP Completion Criteria
|
|
|
|
MVP is complete when this works:
|
|
|
|
1. User enters: `take key`
|
|
2. Parser returns a `take` action
|
|
3. Truth engine validates it
|
|
4. World state moves key to inventory
|
|
5. User enters: `open door`
|
|
6. Truth engine verifies key ownership
|
|
7. Door becomes open
|
|
8. All steps are visible in debug UI
|
|
9. All turns are persisted
|
|
|
|
---
|
|
|
|
# Do Not Do Yet
|
|
|
|
Do not implement:
|
|
|
|
* autonomous agents
|
|
* complex memory retrieval
|
|
* embeddings
|
|
* relationship simulation
|
|
* long-term summaries
|
|
* branching timelines
|
|
|
|
Until the deterministic MVP is working.
|