Skip to content
Closed
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
18 changes: 18 additions & 0 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
],
"scripts": {
"clean": "npm run clean --workspaces",
"build": "cd packages/tui && npm run build && cd ../telemetry && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../session-backends/sqlite-node && npm run build && cd ../../protocol && npm run build && cd ../client && npm run build && cd ../server && npm run build && cd ../coding-agent && npm run build",
"build:offline": "cd packages/tui && npm run build && cd ../telemetry && npm run build && cd ../ai && npm run build:offline && cd ../agent && npm run build && cd ../session-backends/sqlite-node && npm run build && cd ../../protocol && npm run build && cd ../client && npm run build && cd ../server && npm run build && cd ../coding-agent && npm run build",
"build": "cd packages/harness-state && npm run build && cd ../tui && npm run build && cd ../telemetry && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../session-backends/sqlite-node && npm run build && cd ../../protocol && npm run build && cd ../client && npm run build && cd ../server && npm run build && cd ../coding-agent && npm run build",
"build:offline": "cd packages/harness-state && npm run build && cd ../tui && npm run build && cd ../telemetry && npm run build && cd ../ai && npm run build:offline && cd ../agent && npm run build && cd ../session-backends/sqlite-node && npm run build && cd ../../protocol && npm run build && cd ../client && npm run build && cd ../server && npm run build && cd ../coding-agent && npm run build",
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && npm run check:shrinkwrap && npm run check:install-lock:coding-agent && tsgo --noEmit && npm run check:browser-smoke",
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
"check:pinned-deps": "node scripts/check-pinned-deps.mjs",
Expand Down
36 changes: 36 additions & 0 deletions packages/harness-state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `@diffexai/diffex-harness-state`

Private workspace package for the Diffex harness-state schema and directory-bundle persistence.

## State and bundle format

A logical state contains only the evolvable static system prompt:

```ts
{
systemPrompt: string;
}
```

The prompt must not start with U+FEFF and must contain a non-whitespace character. A portable bundle root is a direct directory, not a symbolic link, with exactly these direct regular files and exact casing:

```text
state-directory/
|-- manifest.json # SHA-256 metadata for the exact EVOLVE.md bytes
`-- EVOLVE.md # systemPrompt UTF-8 bytes, preserved verbatim
```

Both files must be fatal, BOM-free UTF-8. The manifest must contain only `contentHash`, formatted as `sha256:` followed by 64 lowercase hexadecimal characters. Loading verifies that it matches the exact `EVOLVE.md` bytes, including line endings and trailing whitespace.

## API

- `HarnessStateSchema` and `HarnessStateManifestSchema` are strict TypeBox schemas. Their inferred `HarnessState` and `HarnessStateManifest` types expose readonly fields.
- `loadHarnessState(bundlePath)` validates a bundle, verifies its content hash, and returns its flat logical state without normalizing the prompt.
- `saveHarnessState(state, destinationPath)` validates the state, derives its content hash, stages a canonical two-file bundle beside the destination, and renames it into place. The parent must already be a directory, any existing destination entry is rejected, and a failed save attempts to remove its owned staging directory.
- `loadDefaultHarnessState()` loads the packaged clean-default bundle through `loadHarnessState()`.

Invalid state, layout, encoding, manifest, or prompt data rejects with an ordinary descriptive `Error`. JSON syntax failures retain the original syntax error as their cause. Other filesystem errors propagate unchanged.

## Limitations

The state stores only the static system prompt. It has no migration or compatibility behavior and does not represent tools, models, context, sessions, lineage, provenance, or evolution metadata beyond the derived content hash. Persistence does not provide cross-process coordination, retries, or durability guarantees.
34 changes: 34 additions & 0 deletions packages/harness-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@diffexai/diffex-harness-state",
"version": "0.2.0",
"description": "Persistent harness state schema and bundle storage for Diffex",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist",
"README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json && shx rm -rf dist/default && shx cp -r src/default dist/default",
"test": "vitest --run"
},
"engines": {
"node": ">=22.19.0"
},
"dependencies": {
"typebox": "1.3.7"
},
"devDependencies": {
"shx": "0.4.0",
"vitest": "4.1.9"
}
}
5 changes: 5 additions & 0 deletions packages/harness-state/src/default/EVOLVE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
You are an expert coding assistant operating inside Diffex, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.

Guidelines:
- Be concise in your responses
- Show file paths clearly when working with files
1 change: 1 addition & 0 deletions packages/harness-state/src/default/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"contentHash":"sha256:86f539ee34b13502d336a4973223597516ac3079794ee2505565b00008ecc82f"}
132 changes: 132 additions & 0 deletions packages/harness-state/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { createHash } from "node:crypto";
import { lstat, mkdtemp, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { TextDecoder, TextEncoder } from "node:util";
import Type, { type Static } from "typebox";
import { Check } from "typebox/value";

const SystemPromptSchema = Type.Readonly(Type.String({ pattern: "^(?!\\uFEFF)(?=[\\s\\S]*\\S)[\\s\\S]*$" }));

export const HarnessStateSchema = Type.Object({ systemPrompt: SystemPromptSchema }, { additionalProperties: false });
export type HarnessState = Static<typeof HarnessStateSchema>;

export const HarnessStateManifestSchema = Type.Object(
{ contentHash: Type.Readonly(Type.String({ pattern: "^sha256:[0-9a-f]{64}$" })) },
{ additionalProperties: false },
);
export type HarnessStateManifest = Static<typeof HarnessStateManifestSchema>;

const MANIFEST_NAME = "manifest.json";
const PROMPT_NAME = "EVOLVE.md";
const BUNDLE_ENTRY_NAMES = [MANIFEST_NAME, PROMPT_NAME] as const;
const UTF8_DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
const UTF8_ENCODER = new TextEncoder();

function computeContentHash(bytes: Uint8Array): string {
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
}

function decodeBundleFile(bytes: Uint8Array, filePath: string): string {
let value: string;
try {
value = UTF8_DECODER.decode(bytes);
} catch (cause) {
throw new Error(`Invalid UTF-8 in harness state file "${filePath}"`, { cause });
}
if (value.startsWith("\uFEFF")) throw new Error(`Harness state file "${filePath}" must not start with a UTF-8 BOM`);
return value;
}

function parseManifest(value: string, manifestPath: string): HarnessStateManifest {
let parsed: unknown;
try {
parsed = JSON.parse(value);
} catch (cause) {
throw new Error(`Invalid JSON in harness state manifest "${manifestPath}"`, { cause });
}
if (!Check(HarnessStateManifestSchema, parsed)) {
throw new Error(`Invalid harness state manifest at "${manifestPath}"`);
}
return parsed;
}

async function inspectBundleDirectory(bundlePath: string): Promise<void> {
const bundleStats = await lstat(bundlePath);
if (!bundleStats.isDirectory() || bundleStats.isSymbolicLink()) {
throw new Error(`Harness state bundle "${bundlePath}" must be a direct directory`);
}

const entries = await readdir(bundlePath, { withFileTypes: true });
const expectedNames = new Set<string>(BUNDLE_ENTRY_NAMES);
if (entries.length !== BUNDLE_ENTRY_NAMES.length || entries.some((entry) => !expectedNames.has(entry.name))) {
throw new Error(`Harness state bundle "${bundlePath}" must contain exactly manifest.json and EVOLVE.md`);
}
for (const name of BUNDLE_ENTRY_NAMES) {
const entry = entries.find((candidate) => candidate.name === name);
if (!entry?.isFile() || entry.isSymbolicLink()) {
throw new Error(`Harness state bundle entry "${join(bundlePath, name)}" must be a direct regular file`);
}
}
}

export async function loadHarnessState(bundlePath: string): Promise<HarnessState> {
await inspectBundleDirectory(bundlePath);
const manifestPath = join(bundlePath, MANIFEST_NAME);
const promptPath = join(bundlePath, PROMPT_NAME);
const manifestText = decodeBundleFile(await readFile(manifestPath), manifestPath);
const manifest = parseManifest(manifestText, manifestPath);
const promptBytes = await readFile(promptPath);
const systemPrompt = decodeBundleFile(promptBytes, promptPath);
const state = { systemPrompt };
if (!Check(HarnessStateSchema, state)) throw new Error(`Invalid harness state prompt at "${promptPath}"`);
if (manifest.contentHash !== computeContentHash(promptBytes)) {
throw new Error(`Harness state content hash mismatch at "${promptPath}"`);
}
return state;
}

async function assertDestinationAbsent(destinationPath: string): Promise<void> {
try {
await lstat(destinationPath);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
throw error;
}
throw new Error(`Harness state destination "${destinationPath}" already exists`);
}

export async function saveHarnessState(state: unknown, destinationPath: string): Promise<void> {
if (!Check(HarnessStateSchema, state)) throw new Error("Invalid harness state to save");
const systemPrompt = state.systemPrompt;
const promptBytes = UTF8_ENCODER.encode(systemPrompt);
const manifest = `${JSON.stringify({ contentHash: computeContentHash(promptBytes) })}\n`;
const absoluteDestinationPath = resolve(destinationPath);
const parentPath = dirname(absoluteDestinationPath);
const parentStats = await stat(parentPath);
if (!parentStats.isDirectory()) {
throw new Error(`Harness state destination parent "${parentPath}" must be a directory`);
}
await assertDestinationAbsent(absoluteDestinationPath);

let temporaryPath: string | undefined;
try {
temporaryPath = await mkdtemp(join(parentPath, `.${basename(absoluteDestinationPath)}.harness-state.tmp-`));
await writeFile(join(temporaryPath, MANIFEST_NAME), manifest, "utf8");
await writeFile(join(temporaryPath, PROMPT_NAME), promptBytes);
await assertDestinationAbsent(absoluteDestinationPath);
await rename(temporaryPath, absoluteDestinationPath);
temporaryPath = undefined;
} catch (error) {
if (temporaryPath !== undefined) {
try {
await rm(temporaryPath, { recursive: true, force: true });
} catch {}
}
throw error;
}
}

export async function loadDefaultHarnessState(): Promise<HarnessState> {
return loadHarnessState(fileURLToPath(new URL("./default", import.meta.url)));
}
Loading
Loading