- Remove entire 'uwf cas' command group from CLI - Delete commands/cas.ts (only used by CLI + tests) - Delete cas.test.ts and cas-exit-code.test.ts - Update workflow YAMLs: uwf cas get/has/refs/walk → ocas - Update e2e-walkthrough script to use ocas - Update docs and reference files - Keep store-global-cas.test.ts (internal CAS store tests) CAS operations now go through 'ocas' CLI exclusively. Agent text storage handled internally by uwf pipeline. Closes #10
This commit is contained in:
@@ -99,17 +99,18 @@ uwf step fork 32GCDE899RRQ3
|
||||
|
||||
### CAS
|
||||
|
||||
Use the [`ocas`](https://www.npmjs.com/package/@ocas/cli) CLI for direct CAS operations (`~/.ocas/` store, shared with `uwf`):
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `uwf cas get <hash> [--timestamp]` | Read a CAS node |
|
||||
| `uwf cas put <type-hash> <data>` | Store a node, print hash |
|
||||
| `uwf cas put-text <text>` | Store plain text, print hash |
|
||||
| `uwf cas has <hash>` | Check existence |
|
||||
| `uwf cas refs <hash>` | List direct references |
|
||||
| `uwf cas walk <hash>` | Recursive traversal |
|
||||
| `uwf cas reindex` | Rebuild type index |
|
||||
| `uwf cas schema list` | List registered schemas |
|
||||
| `uwf cas schema get <hash>` | Show a schema |
|
||||
| `ocas get <hash> [--timestamp]` | Read a CAS node |
|
||||
| `ocas put <type-hash> <data>` | Store a node, print hash |
|
||||
| `ocas has <hash>` | Check existence |
|
||||
| `ocas refs <hash>` | List direct references |
|
||||
| `ocas walk <hash>` | Recursive traversal |
|
||||
| `ocas reindex` | Rebuild type index |
|
||||
| `ocas schema list` | List registered schemas |
|
||||
| `ocas schema get <hash>` | Show a schema |
|
||||
|
||||
### Setup
|
||||
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import { execSync } from "node:child_process";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { cmdCasPutText } from "../commands/cas.js";
|
||||
|
||||
let storageRoot: string;
|
||||
let casDir: string;
|
||||
let uwfPath: string;
|
||||
let originalEnv: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
storageRoot = join(
|
||||
tmpdir(),
|
||||
`uwf-cas-exit-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
);
|
||||
casDir = join(storageRoot, "cas");
|
||||
await mkdir(storageRoot, { recursive: true });
|
||||
await mkdir(casDir, { recursive: true });
|
||||
|
||||
// Set UNCAGED_CAS_DIR for this test
|
||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||
process.env.UNCAGED_CAS_DIR = casDir;
|
||||
|
||||
// Find the uwf CLI path
|
||||
uwfPath = join(__dirname, "../../src/cli.ts");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(storageRoot, { recursive: true, force: true });
|
||||
|
||||
// Restore original environment
|
||||
if (originalEnv === undefined) {
|
||||
delete process.env.UNCAGED_CAS_DIR;
|
||||
} else {
|
||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||
}
|
||||
});
|
||||
|
||||
type ExecResult = {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
exitCode: number;
|
||||
};
|
||||
|
||||
function execUwf(args: string[]): ExecResult {
|
||||
try {
|
||||
const stdout = execSync(`bun ${uwfPath} ${args.join(" ")}`, {
|
||||
env: {
|
||||
...process.env,
|
||||
WORKFLOW_STORAGE_ROOT: storageRoot,
|
||||
UNCAGED_CAS_DIR: casDir,
|
||||
},
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return { stdout, stderr: "", exitCode: 0 };
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"stdout" in error &&
|
||||
"stderr" in error &&
|
||||
"status" in error
|
||||
) {
|
||||
return {
|
||||
stdout: (error.stdout as Buffer | string).toString(),
|
||||
stderr: (error.stderr as Buffer | string).toString(),
|
||||
exitCode: error.status as number,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
describe("uwf cas has CLI exit codes", () => {
|
||||
test("exits 0 when hash exists", async () => {
|
||||
// Setup: Create a temp storage root, put a text node, capture hash
|
||||
const putResult = await cmdCasPutText(storageRoot, "test content");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: uwf cas has <hash>
|
||||
const result = execUwf(["cas", "has", hash]);
|
||||
|
||||
// Assert: stdout contains {"exists":true}, exit code === 0
|
||||
expect(result.stdout).toContain('"exists":true');
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("exits 1 when hash does not exist", () => {
|
||||
// Setup: Create a temp storage root (empty CAS store)
|
||||
// Execute: uwf cas has NOSUCHHASH123
|
||||
const result = execUwf(["cas", "has", "NOSUCHHASH123"]);
|
||||
|
||||
// Assert: stdout contains {"exists":false}, exit code === 1
|
||||
expect(result.stdout).toContain('"exists":false');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test("JSON output format unchanged for exists=true", async () => {
|
||||
// Setup: Create store, put node
|
||||
const putResult = await cmdCasPutText(storageRoot, "test");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: uwf cas has <hash>
|
||||
const result = execUwf(["cas", "has", hash]);
|
||||
|
||||
// Assert: stdout JSON parses correctly to {exists: true}
|
||||
const parsed = JSON.parse(result.stdout.trim());
|
||||
expect(parsed).toEqual({ exists: true });
|
||||
});
|
||||
|
||||
test("JSON output format unchanged for exists=false", () => {
|
||||
// Setup: Create empty store
|
||||
// Execute: uwf cas has INVALID
|
||||
const result = execUwf(["cas", "has", "INVALID"]);
|
||||
|
||||
// Assert: stdout JSON parses correctly to {exists: false}
|
||||
const parsed = JSON.parse(result.stdout.trim());
|
||||
expect(parsed).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("YAML output format preserves exit code behavior for exists=true", async () => {
|
||||
// Setup: Create store with node
|
||||
const putResult = await cmdCasPutText(storageRoot, "test");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: uwf --format yaml cas has <hash>
|
||||
const result = execUwf(["--format", "yaml", "cas", "has", hash]);
|
||||
|
||||
// Assert: exit code === 0, output is YAML format
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("exists:");
|
||||
expect(result.stdout).toContain("true");
|
||||
});
|
||||
|
||||
test("YAML output format preserves exit code behavior for exists=false", () => {
|
||||
// Setup: Create empty store
|
||||
// Execute: uwf --format yaml cas has INVALID
|
||||
const result = execUwf(["--format", "yaml", "cas", "has", "INVALID"]);
|
||||
|
||||
// Assert: exit code === 1, output is YAML format
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stdout).toContain("exists:");
|
||||
expect(result.stdout).toContain("false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("regression: other cas commands unaffected", () => {
|
||||
test("uwf cas get still exits 1 on not-found with error message", () => {
|
||||
// Execute: uwf cas get NOSUCHHASH
|
||||
const result = execUwf(["cas", "get", "NOSUCHHASH"]);
|
||||
|
||||
// Assert: exit code === 1, stderr contains "Node not found"
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stderr).toContain("Node not found");
|
||||
});
|
||||
|
||||
test("uwf cas put-text behavior unchanged", () => {
|
||||
// Execute: uwf cas put-text "hello"
|
||||
const result = execUwf(["cas", "put-text", "hello"]);
|
||||
|
||||
// Assert: exit code === 0, returns hash
|
||||
expect(result.exitCode).toBe(0);
|
||||
const parsed = JSON.parse(result.stdout.trim());
|
||||
expect(parsed).toHaveProperty("hash");
|
||||
expect(typeof parsed.hash).toBe("string");
|
||||
expect(parsed.hash.length).toBe(13); // Crockford Base32 XXH64 hash length
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { cmdCasHas, cmdCasPutText } from "../commands/cas.js";
|
||||
|
||||
let storageRoot: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storageRoot = join(tmpdir(), `uwf-cas-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
await mkdir(storageRoot, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(storageRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("cmdCasHas", () => {
|
||||
test("returns {exists: true} for existing hash", async () => {
|
||||
// Setup: Create a test store, put a node, get its hash
|
||||
const putResult = await cmdCasPutText(storageRoot, "test content");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: Call cmdCasHas with the valid hash
|
||||
const result = await cmdCasHas(storageRoot, hash);
|
||||
|
||||
// Assert: Result equals {exists: true}
|
||||
expect(result).toEqual({ exists: true });
|
||||
});
|
||||
|
||||
test("returns {exists: false} for non-existent hash", async () => {
|
||||
// Setup: Create an empty test store
|
||||
// (storageRoot already created in beforeEach)
|
||||
|
||||
// Execute: Call cmdCasHas with an invalid hash
|
||||
const result = await cmdCasHas(storageRoot, "INVALIDHASH12");
|
||||
|
||||
// Assert: Result equals {exists: false}
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("does not throw for non-existent hash", async () => {
|
||||
// Setup: Create an empty test store
|
||||
// Execute & Assert: Does not throw, returns {exists: false}
|
||||
await expect(cmdCasHas(storageRoot, "NOSUCHHASH123")).resolves.toEqual({
|
||||
exists: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("handles malformed hash gracefully", async () => {
|
||||
// Setup: Create a test store
|
||||
// Execute: Call cmdCasHas with a too-short hash
|
||||
const result = await cmdCasHas(storageRoot, "xyz");
|
||||
|
||||
// Assert: Returns {exists: false} (store.has() returns false)
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("handles empty hash string", async () => {
|
||||
// Execute: Call cmdCasHas with an empty string
|
||||
const result = await cmdCasHas(storageRoot, "");
|
||||
|
||||
// Assert: Returns {exists: false}
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("handles hash with special characters", async () => {
|
||||
// Execute: Call cmdCasHas with special characters
|
||||
const result = await cmdCasHas(storageRoot, "HASH!@#");
|
||||
|
||||
// Assert: Returns {exists: false}
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
});
|
||||
@@ -2,17 +2,6 @@
|
||||
|
||||
import type { CasRef, ThreadId, ThreadStatus } from "@united-workforce/protocol";
|
||||
import { Command } from "commander";
|
||||
import {
|
||||
cmdCasGet,
|
||||
cmdCasHas,
|
||||
cmdCasPut,
|
||||
cmdCasPutText,
|
||||
cmdCasRefs,
|
||||
cmdCasReindex,
|
||||
cmdCasSchemaGet,
|
||||
cmdCasSchemaList,
|
||||
cmdCasWalk,
|
||||
} from "./commands/cas.js";
|
||||
import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js";
|
||||
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
|
||||
import {
|
||||
@@ -616,113 +605,6 @@ program
|
||||
},
|
||||
);
|
||||
|
||||
const cas = program.command("cas").description("Content-addressable storage operations");
|
||||
|
||||
cas
|
||||
.command("get")
|
||||
.description("Read a CAS node (type + payload; use --timestamp to include timestamp)")
|
||||
.argument("<hash>", "CAS hash (13 char)")
|
||||
.option("--timestamp", "Include timestamp in output")
|
||||
.action((hash: string, opts: { timestamp?: boolean }) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasGet(storageRoot, hash, opts));
|
||||
});
|
||||
});
|
||||
|
||||
cas
|
||||
.command("put")
|
||||
.description("Store a node, print its hash")
|
||||
.argument("<type-hash>", "Type (schema) hash")
|
||||
.argument("<data>", "JSON file path or inline JSON string")
|
||||
.action((typeHash: string, data: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasPut(storageRoot, typeHash, data));
|
||||
});
|
||||
});
|
||||
|
||||
cas
|
||||
.command("put-text")
|
||||
.description("Store a plain text string, print its hash")
|
||||
.argument("<text>", "Text content to store")
|
||||
.action((text: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasPutText(storageRoot, text));
|
||||
});
|
||||
});
|
||||
|
||||
cas
|
||||
.command("has")
|
||||
.description("Check if a hash exists")
|
||||
.argument("<hash>", "CAS hash (13 char)")
|
||||
.action((hash: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
const result = await cmdCasHas(storageRoot, hash);
|
||||
writeOutput(result);
|
||||
if (!result.exists) {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
cas
|
||||
.command("refs")
|
||||
.description("List direct CAS references from a node")
|
||||
.argument("<hash>", "CAS hash (13 char)")
|
||||
.action((hash: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasRefs(storageRoot, hash));
|
||||
});
|
||||
});
|
||||
|
||||
cas
|
||||
.command("walk")
|
||||
.description("Recursive traversal from a node")
|
||||
.argument("<hash>", "CAS hash (13 char)")
|
||||
.action((hash: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasWalk(storageRoot, hash));
|
||||
});
|
||||
});
|
||||
|
||||
cas
|
||||
.command("reindex")
|
||||
.description("Rebuild type index from all CAS nodes")
|
||||
.action(() => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasReindex(storageRoot));
|
||||
});
|
||||
});
|
||||
|
||||
const casSchema = cas.command("schema").description("CAS schema operations");
|
||||
|
||||
casSchema
|
||||
.command("list")
|
||||
.description("List all registered schemas")
|
||||
.action(() => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasSchemaList(storageRoot));
|
||||
});
|
||||
});
|
||||
|
||||
casSchema
|
||||
.command("get")
|
||||
.description("Show a schema by its type hash")
|
||||
.argument("<hash>", "Schema type hash")
|
||||
.action((hash: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasSchemaGet(storageRoot, hash));
|
||||
});
|
||||
});
|
||||
|
||||
const log = program.command("log").description("Process-level debug logs");
|
||||
|
||||
log
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import type { JSONSchema, Store } from "@ocas/core";
|
||||
import { bootstrap, getSchema, putSchema, refs, walk } from "@ocas/core";
|
||||
import { createFsStore } from "@ocas/fs";
|
||||
|
||||
import { TEXT_SCHEMA } from "../schemas.js";
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
function openStore(storageRoot: string): Store {
|
||||
return createFsStore(join(storageRoot, "cas"));
|
||||
}
|
||||
|
||||
function readJsonArg(fileOrInline: string): unknown {
|
||||
try {
|
||||
return JSON.parse(fileOrInline);
|
||||
} catch {
|
||||
try {
|
||||
return JSON.parse(readFileSync(fileOrInline, "utf-8"));
|
||||
} catch (e) {
|
||||
throw new Error(`Cannot parse JSON from "${fileOrInline}": ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Commands (all return JSON-serializable data) ----
|
||||
|
||||
export async function cmdCasGet(
|
||||
storageRoot: string,
|
||||
hash: string,
|
||||
opts: { timestamp?: boolean },
|
||||
): Promise<unknown> {
|
||||
const store = openStore(storageRoot);
|
||||
const node = store.get(hash);
|
||||
if (node === null) {
|
||||
throw new Error(`Node not found: ${hash}`);
|
||||
}
|
||||
if (opts.timestamp) {
|
||||
return node;
|
||||
}
|
||||
const { timestamp: _, ...rest } = node as Record<string, unknown>;
|
||||
return rest;
|
||||
}
|
||||
|
||||
export async function cmdCasPut(
|
||||
storageRoot: string,
|
||||
typeHash: string,
|
||||
data: string,
|
||||
): Promise<{ hash: string }> {
|
||||
const store = openStore(storageRoot);
|
||||
const payload = readJsonArg(data);
|
||||
const hash = await store.put(typeHash, payload);
|
||||
return { hash };
|
||||
}
|
||||
|
||||
export async function cmdCasHas(storageRoot: string, hash: string): Promise<{ exists: boolean }> {
|
||||
const store = openStore(storageRoot);
|
||||
return { exists: store.has(hash) };
|
||||
}
|
||||
|
||||
export async function cmdCasRefs(storageRoot: string, hash: string): Promise<{ refs: string[] }> {
|
||||
const store = openStore(storageRoot);
|
||||
const node = store.get(hash);
|
||||
if (node === null) {
|
||||
throw new Error(`Node not found: ${hash}`);
|
||||
}
|
||||
return { refs: refs(store, node) };
|
||||
}
|
||||
|
||||
export async function cmdCasWalk(storageRoot: string, hash: string): Promise<{ hashes: string[] }> {
|
||||
const store = openStore(storageRoot);
|
||||
const result: string[] = [];
|
||||
walk(store, hash, (h) => {
|
||||
result.push(h);
|
||||
});
|
||||
return { hashes: result };
|
||||
}
|
||||
|
||||
export type SchemaListEntry = {
|
||||
hash: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export async function cmdCasSchemaList(storageRoot: string): Promise<SchemaListEntry[]> {
|
||||
const store = openStore(storageRoot);
|
||||
const aliases = await bootstrap(store);
|
||||
const metaHash = aliases["@ocas/schema"];
|
||||
if (metaHash === undefined) {
|
||||
throw new Error("Meta-schema not found in bootstrap result");
|
||||
}
|
||||
const entries: SchemaListEntry[] = [];
|
||||
|
||||
// Include meta-schema itself
|
||||
entries.push({ hash: metaHash, title: "(meta-schema)" });
|
||||
|
||||
for (const { hash } of store.listByType(metaHash)) {
|
||||
if (hash === metaHash) continue;
|
||||
const node = store.get(hash);
|
||||
if (node !== null) {
|
||||
const schema = node.payload as JSONSchema;
|
||||
const title =
|
||||
(schema.title as string | undefined) ??
|
||||
(schema.description as string | undefined) ??
|
||||
"(unnamed)";
|
||||
entries.push({ hash, title });
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
export async function cmdCasReindex(storageRoot: string): Promise<{ status: string }> {
|
||||
const indexDir = join(storageRoot, "cas", "_index");
|
||||
const { rmSync } = await import("node:fs");
|
||||
rmSync(indexDir, { recursive: true, force: true });
|
||||
// Re-open store to trigger migration rebuild
|
||||
openStore(storageRoot);
|
||||
return { status: "reindexed" };
|
||||
}
|
||||
|
||||
export async function cmdCasSchemaGet(storageRoot: string, hash: string): Promise<unknown> {
|
||||
const store = openStore(storageRoot);
|
||||
const schema = getSchema(store, hash);
|
||||
if (schema === null) {
|
||||
throw new Error(`Schema not found: ${hash}`);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
export async function cmdCasPutText(storageRoot: string, text: string): Promise<{ hash: string }> {
|
||||
const store = openStore(storageRoot);
|
||||
const typeHash = await putSchema(store, TEXT_SCHEMA);
|
||||
const hash = await store.put(typeHash, text);
|
||||
return { hash };
|
||||
}
|
||||
@@ -36,29 +36,30 @@ If the engine cannot parse your frontmatter, it will ask you to retry (up to 2 t
|
||||
|
||||
## 2. CAS (Content-Addressable Store)
|
||||
|
||||
Your frontmatter output is automatically stored in CAS. You can also **use CAS directly** to store intermediate artifacts, build merkle DAGs for large outputs, or reference data from previous steps.
|
||||
Your frontmatter output is automatically stored in CAS. You can also **use CAS directly** via the \`ocas\` CLI to store intermediate artifacts, build merkle DAGs for large outputs, or reference data from previous steps.
|
||||
|
||||
### Commands
|
||||
|
||||
\`\`\`
|
||||
uwf cas put-text <text> # store plain text, print hash
|
||||
uwf cas put <type-hash> <json> # store typed JSON data, print hash
|
||||
uwf cas get <hash> # read a CAS node (type + payload)
|
||||
uwf cas has <hash> # check if a hash exists
|
||||
uwf cas refs <hash> # list direct references from a node
|
||||
uwf cas walk <hash> # recursive traversal from a node
|
||||
uwf cas schema list # list registered schemas
|
||||
uwf cas schema get <hash> # show a schema definition
|
||||
ocas put <type-hash> <json> # store typed JSON data, print hash
|
||||
ocas get <hash> # read a CAS node (type + payload)
|
||||
ocas has <hash> # check if a hash exists
|
||||
ocas refs <hash> # list direct references from a node
|
||||
ocas walk <hash> # recursive traversal from a node
|
||||
ocas schema list # list registered schemas
|
||||
ocas schema get <hash> # show a schema definition
|
||||
\`\`\`
|
||||
|
||||
Plain-text storage for agent output is handled internally by the uwf pipeline — agents do not need to call \`ocas put\` for their deliverables.
|
||||
|
||||
### Merkle DAG Pattern
|
||||
|
||||
For large outputs, store parts individually and reference their hashes:
|
||||
|
||||
\`\`\`bash
|
||||
# Store individual sections
|
||||
HASH1=$(uwf cas put-text "section 1 content")
|
||||
HASH2=$(uwf cas put-text "section 2 content")
|
||||
# Store individual sections (use ocas put with the appropriate type hash)
|
||||
HASH1=$(ocas put <type-hash> '"section 1 content"')
|
||||
HASH2=$(ocas put <type-hash> '"section 2 content"')
|
||||
|
||||
# Reference hashes in your frontmatter or in a parent node
|
||||
\`\`\`
|
||||
|
||||
@@ -161,7 +161,7 @@ uwf step list <thread-id>
|
||||
uwf step show <step-hash>
|
||||
|
||||
# Check the CAS data
|
||||
uwf cas get <output-hash>
|
||||
ocas get <output-hash>
|
||||
\`\`\`
|
||||
|
||||
### Validation Checklist
|
||||
|
||||
@@ -49,19 +49,19 @@ uwf step fork <step-hash> # fork a thread from a specific step
|
||||
|
||||
## CAS Commands
|
||||
|
||||
Use the \`ocas\` CLI for direct CAS operations (\`~/.ocas/\` store, shared with \`uwf\`):
|
||||
|
||||
\`\`\`
|
||||
uwf cas get <hash> # read a CAS node (type + payload)
|
||||
ocas get <hash> # read a CAS node (type + payload)
|
||||
[--timestamp] # include timestamp in output
|
||||
uwf cas put <type-hash> <data> # store a node, print its hash
|
||||
# <data>: JSON file path or inline JSON string
|
||||
uwf cas put-text <text> # store a plain text string, print its hash
|
||||
# shortcut for put with the built-in text schema
|
||||
uwf cas has <hash> # check if a hash exists
|
||||
uwf cas refs <hash> # list direct CAS references from a node
|
||||
uwf cas walk <hash> # recursive traversal from a node
|
||||
uwf cas reindex # rebuild type index from all CAS nodes
|
||||
uwf cas schema list # list all registered schemas
|
||||
uwf cas schema get <hash> # show a schema by its type hash
|
||||
ocas put <type-hash> <data> # store a node, print its hash
|
||||
# <data>: JSON file path or inline JSON string
|
||||
ocas has <hash> # check if a hash exists
|
||||
ocas refs <hash> # list direct CAS references from a node
|
||||
ocas walk <hash> # recursive traversal from a node
|
||||
ocas reindex # rebuild type index from all CAS nodes
|
||||
ocas schema list # list all registered schemas
|
||||
ocas schema get <hash> # show a schema by its type hash
|
||||
\`\`\`
|
||||
|
||||
## Log Commands
|
||||
|
||||
@@ -91,17 +91,18 @@ Forking creates a new thread that shares history up to the fork point — useful
|
||||
|
||||
## CAS Commands
|
||||
|
||||
Use the \`ocas\` CLI for direct CAS operations (\`~/.ocas/\` store, shared with \`uwf\`):
|
||||
|
||||
\`\`\`
|
||||
uwf cas get <hash> # read a node (type + payload)
|
||||
ocas get <hash> # read a node (type + payload)
|
||||
[--timestamp] # include timestamp
|
||||
uwf cas put <type-hash> <data> # store typed JSON, print hash
|
||||
uwf cas put-text <text> # store plain text, print hash
|
||||
uwf cas has <hash> # check existence
|
||||
uwf cas refs <hash> # list direct references
|
||||
uwf cas walk <hash> # recursive traversal
|
||||
uwf cas reindex # rebuild type index
|
||||
uwf cas schema list # list schemas
|
||||
uwf cas schema get <hash> # show schema definition
|
||||
ocas put <type-hash> <data> # store typed JSON, print hash
|
||||
ocas has <hash> # check existence
|
||||
ocas refs <hash> # list direct references
|
||||
ocas walk <hash> # recursive traversal
|
||||
ocas reindex # rebuild type index
|
||||
ocas schema list # list schemas
|
||||
ocas schema get <hash> # show schema definition
|
||||
\`\`\`
|
||||
|
||||
## Log Commands
|
||||
|
||||
Reference in New Issue
Block a user