From 1aacf11ad986f090a85652e28071e6afb5f6a93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Tue, 2 Jun 2026 21:30:59 +0800 Subject: [PATCH] refactor: remove uwf cas subcommand, use ocas CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .workflows/e2e-walkthrough.yaml | 8 +- .workflows/solve-issue.yaml | 8 +- docs/architecture.md | 18 +- examples/solve-issue.yaml | 10 +- packages/cli-workflow/README.md | 19 +- .../src/__tests__/cas-exit-code.test.ts | 171 ------------------ .../cli-workflow/src/__tests__/cas.test.ts | 74 -------- packages/cli-workflow/src/cli.ts | 118 ------------ packages/cli-workflow/src/commands/cas.ts | 136 -------------- packages/workflow-util/src/actor-reference.ts | 25 +-- .../workflow-util/src/author-reference.ts | 2 +- packages/workflow-util/src/cli-reference.ts | 22 +-- packages/workflow-util/src/user-reference.ts | 19 +- scripts/e2e-walkthrough.sh | 11 +- workflows/solve-issue.yaml | 6 +- 15 files changed, 77 insertions(+), 570 deletions(-) delete mode 100644 packages/cli-workflow/src/__tests__/cas-exit-code.test.ts delete mode 100644 packages/cli-workflow/src/__tests__/cas.test.ts delete mode 100644 packages/cli-workflow/src/commands/cas.ts diff --git a/.workflows/e2e-walkthrough.yaml b/.workflows/e2e-walkthrough.yaml index 043fb17..b25751b 100644 --- a/.workflows/e2e-walkthrough.yaml +++ b/.workflows/e2e-walkthrough.yaml @@ -177,10 +177,10 @@ roles: 4. `uwf thread read ` — verify non-empty output CAS operations: - 5. `uwf cas get ` — verify returns a type field - 6. `uwf cas has ` — verify exits 0 - 7. `uwf cas refs ` — list refs (may be empty) - 8. `uwf cas walk ` — verify returns non-empty array + 5. `ocas get ` — verify returns a type field + 6. `ocas has ` — verify exits 0 + 7. `ocas refs ` — list refs (may be empty) + 8. `ocas walk ` — verify returns non-empty array Report results. Pass threadId, lastStepHash, workflowName, containerName forward. output: "Report test results. Set $status to pass (with threadId, lastStepHash, workflowName, containerName) or fail." diff --git a/.workflows/solve-issue.yaml b/.workflows/solve-issue.yaml index 3c23cec..3a1ac3c 100644 --- a/.workflows/solve-issue.yaml +++ b/.workflows/solve-issue.yaml @@ -20,8 +20,8 @@ roles: 2. Revise the test spec accordingly After producing the test spec: - 1. Store it via `uwf cas put-text ""` and capture the returned hash - 2. Put the hash in frontmatter.plan (required when $status=ready) + 1. The test spec is stored in CAS automatically by the uwf pipeline (agents do not need to call `ocas put` directly) + 2. Put the plan hash in frontmatter.plan (required when $status=ready) 3. Set repoPath to the absolute path of the repository root IMPORTANT: Extract the repo remote (owner/repo) from git: @@ -63,7 +63,7 @@ roles: 5. ALL subsequent work must happen inside the worktree directory. Then implement TDD: - 6. Read the test spec from CAS: `uwf cas get ` (find the hash from the planner's output in your task prompt) + 6. Read the test spec from CAS: `ocas get ` (find the hash from the planner's output in your task prompt) 7. If bounced back from reviewer or tester: read the previous role's feedback in your task prompt 8. Write tests first based on the spec 9. Implement the code to make tests pass @@ -151,7 +151,7 @@ roles: The worktree path is provided in your task prompt. cd into it first. 1. Run `bun test` for automated test verification - 2. Read the test spec from CAS: `uwf cas get ` (find the hash from the planner step in the thread history) + 2. Read the test spec from CAS: `ocas get ` (find the hash from the planner step in the thread history) 3. Verify each scenario in the spec is covered and passing 4. Determine outcome: - passed: all scenarios verified, tests pass diff --git a/docs/architecture.md b/docs/architecture.md index 6745b60..8767dd4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -447,16 +447,18 @@ Binary: `uwf` ### CAS commands +Use the `ocas` CLI for direct CAS operations (`~/.ocas/` store, shared with `uwf`): + | Command | Description | |---------|-------------| -| `uwf cas get ` | Read a CAS node. | -| `uwf cas put ` | Store a node, print its hash. | -| `uwf cas has ` | Check if a hash exists. | -| `uwf cas refs ` | List direct CAS references. | -| `uwf cas walk ` | Recursive traversal from a node. | -| `uwf cas reindex` | Rebuild type index from all nodes. | -| `uwf cas schema list` | List registered schemas. | -| `uwf cas schema get ` | Show a schema by type hash. | +| `ocas get ` | Read a CAS node. | +| `ocas put ` | Store a node, print its hash. | +| `ocas has ` | Check if a hash exists. | +| `ocas refs ` | List direct CAS references. | +| `ocas walk ` | Recursive traversal from a node. | +| `ocas reindex` | Rebuild type index from all nodes. | +| `ocas schema list` | List registered schemas. | +| `ocas schema get ` | Show a schema by type hash. | ### Setup diff --git a/examples/solve-issue.yaml b/examples/solve-issue.yaml index f3ba8eb..a6af54b 100644 --- a/examples/solve-issue.yaml +++ b/examples/solve-issue.yaml @@ -22,7 +22,7 @@ roles: 3. Assess whether the issue has enough information to produce a test spec 4. If insufficient info: comment on the issue via `echo "..." | tea comment -r ` (skip if you already commented), then output $status=insufficient_info 5. If sufficient: produce a detailed TDD test spec in markdown covering all scenarios - 6. Store it via `uwf cas put-text ""` and capture the returned hash + 6. The test spec is stored in CAS automatically by the uwf pipeline (agents do not need to call `ocas put` directly) 7. Output **$status=ready** with plan hash and repoPath **Mode B — Continue on existing PR (prompt mentions PR, branch, or review feedback):** @@ -32,14 +32,14 @@ roles: 3. Read the existing issue for full context: `tea issues -r ` 4. Look for project conventions files (CLAUDE.md, CONTRIBUTING.md, .cursor/rules/) in the repo 5. Produce a TDD test spec that ONLY covers the changes requested in the review — do NOT re-spec already-implemented features - 6. Store it via `uwf cas put-text ""` and capture the returned hash + 6. The test spec is stored in CAS automatically by the uwf pipeline (agents do not need to call `ocas put` directly) 7. Find the existing worktree: `git worktree list` and locate the branch 8. Output **$status=continue** with plan hash, repoPath, branch name, and worktree path **Mode C — Bounced back by tester (fix_spec):** 1. Read the tester's output from the previous step to understand what's wrong with the spec 2. Revise the test spec accordingly - 3. Store it via `uwf cas put-text ""` and capture the returned hash + 3. The test spec is stored in CAS automatically by the uwf pipeline (agents do not need to call `ocas put` directly) 4. Output **$status=ready** with plan hash and repoPath IMPORTANT: Extract the repo remote (owner/repo) from git: @@ -91,7 +91,7 @@ roles: 6. ALL subsequent work must happen inside the worktree directory. Then implement TDD: - 6. Read the test spec from CAS: `uwf cas get ` (find the hash from the planner's output in your task prompt) + 6. Read the test spec from CAS: `ocas get ` (find the hash from the planner's output in your task prompt) 7. If bounced back from reviewer or tester: read the previous role's feedback in your task prompt 8. Write tests first based on the spec 9. Implement the code to make tests pass @@ -160,7 +160,7 @@ roles: The worktree path is provided in your task prompt. cd into it first. 1. Run `bun test` for automated test verification - 2. Read the test spec from CAS: `uwf cas get ` (find the hash from the planner step in the thread history) + 2. Read the test spec from CAS: `ocas get ` (find the hash from the planner step in the thread history) 3. Verify each scenario in the spec is covered and passing 4. Determine outcome: - passed: all scenarios verified, tests pass diff --git a/packages/cli-workflow/README.md b/packages/cli-workflow/README.md index 98c9a15..ec765cf 100644 --- a/packages/cli-workflow/README.md +++ b/packages/cli-workflow/README.md @@ -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 [--timestamp]` | Read a CAS node | -| `uwf cas put ` | Store a node, print hash | -| `uwf cas put-text ` | Store plain text, print hash | -| `uwf cas has ` | Check existence | -| `uwf cas refs ` | List direct references | -| `uwf cas walk ` | Recursive traversal | -| `uwf cas reindex` | Rebuild type index | -| `uwf cas schema list` | List registered schemas | -| `uwf cas schema get ` | Show a schema | +| `ocas get [--timestamp]` | Read a CAS node | +| `ocas put ` | Store a node, print hash | +| `ocas has ` | Check existence | +| `ocas refs ` | List direct references | +| `ocas walk ` | Recursive traversal | +| `ocas reindex` | Rebuild type index | +| `ocas schema list` | List registered schemas | +| `ocas schema get ` | Show a schema | ### Setup diff --git a/packages/cli-workflow/src/__tests__/cas-exit-code.test.ts b/packages/cli-workflow/src/__tests__/cas-exit-code.test.ts deleted file mode 100644 index 3a65a4c..0000000 --- a/packages/cli-workflow/src/__tests__/cas-exit-code.test.ts +++ /dev/null @@ -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 - 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 - 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 - 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 - }); -}); diff --git a/packages/cli-workflow/src/__tests__/cas.test.ts b/packages/cli-workflow/src/__tests__/cas.test.ts deleted file mode 100644 index 4afff5a..0000000 --- a/packages/cli-workflow/src/__tests__/cas.test.ts +++ /dev/null @@ -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 }); - }); -}); diff --git a/packages/cli-workflow/src/cli.ts b/packages/cli-workflow/src/cli.ts index de39e96..e072feb 100755 --- a/packages/cli-workflow/src/cli.ts +++ b/packages/cli-workflow/src/cli.ts @@ -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("", "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 (schema) hash") - .argument("", "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 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("", "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("", "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("", "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("", "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 diff --git a/packages/cli-workflow/src/commands/cas.ts b/packages/cli-workflow/src/commands/cas.ts deleted file mode 100644 index 689e64a..0000000 --- a/packages/cli-workflow/src/commands/cas.ts +++ /dev/null @@ -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 { - 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; - 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 { - 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 { - 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 }; -} diff --git a/packages/workflow-util/src/actor-reference.ts b/packages/workflow-util/src/actor-reference.ts index 6c80094..b8cf417 100644 --- a/packages/workflow-util/src/actor-reference.ts +++ b/packages/workflow-util/src/actor-reference.ts @@ -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 # store plain text, print hash -uwf cas put # store typed JSON data, print hash -uwf cas get # read a CAS node (type + payload) -uwf cas has # check if a hash exists -uwf cas refs # list direct references from a node -uwf cas walk # recursive traversal from a node -uwf cas schema list # list registered schemas -uwf cas schema get # show a schema definition +ocas put # store typed JSON data, print hash +ocas get # read a CAS node (type + payload) +ocas has # check if a hash exists +ocas refs # list direct references from a node +ocas walk # recursive traversal from a node +ocas schema list # list registered schemas +ocas schema get # 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 '"section 1 content"') +HASH2=$(ocas put '"section 2 content"') # Reference hashes in your frontmatter or in a parent node \`\`\` diff --git a/packages/workflow-util/src/author-reference.ts b/packages/workflow-util/src/author-reference.ts index 897dd5e..e63d081 100644 --- a/packages/workflow-util/src/author-reference.ts +++ b/packages/workflow-util/src/author-reference.ts @@ -161,7 +161,7 @@ uwf step list uwf step show # Check the CAS data -uwf cas get +ocas get \`\`\` ### Validation Checklist diff --git a/packages/workflow-util/src/cli-reference.ts b/packages/workflow-util/src/cli-reference.ts index 96274d8..6b14337 100644 --- a/packages/workflow-util/src/cli-reference.ts +++ b/packages/workflow-util/src/cli-reference.ts @@ -49,19 +49,19 @@ uwf step fork # 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 # read a CAS node (type + payload) +ocas get # read a CAS node (type + payload) [--timestamp] # include timestamp in output -uwf cas put # store a node, print its hash - # : JSON file path or inline JSON string -uwf cas put-text # store a plain text string, print its hash - # shortcut for put with the built-in text schema -uwf cas has # check if a hash exists -uwf cas refs # list direct CAS references from a node -uwf cas walk # 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 # show a schema by its type hash +ocas put # store a node, print its hash + # : JSON file path or inline JSON string +ocas has # check if a hash exists +ocas refs # list direct CAS references from a node +ocas walk # recursive traversal from a node +ocas reindex # rebuild type index from all CAS nodes +ocas schema list # list all registered schemas +ocas schema get # show a schema by its type hash \`\`\` ## Log Commands diff --git a/packages/workflow-util/src/user-reference.ts b/packages/workflow-util/src/user-reference.ts index fc8dd65..a4f01fa 100644 --- a/packages/workflow-util/src/user-reference.ts +++ b/packages/workflow-util/src/user-reference.ts @@ -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 # read a node (type + payload) +ocas get # read a node (type + payload) [--timestamp] # include timestamp -uwf cas put # store typed JSON, print hash -uwf cas put-text # store plain text, print hash -uwf cas has # check existence -uwf cas refs # list direct references -uwf cas walk # recursive traversal -uwf cas reindex # rebuild type index -uwf cas schema list # list schemas -uwf cas schema get # show schema definition +ocas put # store typed JSON, print hash +ocas has # check existence +ocas refs # list direct references +ocas walk # recursive traversal +ocas reindex # rebuild type index +ocas schema list # list schemas +ocas schema get # show schema definition \`\`\` ## Log Commands diff --git a/scripts/e2e-walkthrough.sh b/scripts/e2e-walkthrough.sh index 25ebc90..ba3dafb 100755 --- a/scripts/e2e-walkthrough.sh +++ b/scripts/e2e-walkthrough.sh @@ -84,8 +84,9 @@ REAL_HOME="${6:-$HOME}" export HOME="$REAL_HOME" export PATH="$REAL_HOME/.bun/bin:$REAL_HOME/.hermes/hermes-agent/venv/bin:$REAL_HOME/.local/share/npm/bin:$PATH" -# Resolve uwf +# Resolve uwf and ocas UWF="bun $REPO_DIR/packages/cli-workflow/src/cli.ts" +OCAS="ocas" PASS=0 FAIL=0 @@ -267,14 +268,14 @@ run_test "thread read produces output" bash -c "[ -n '$OUT' ]" # CAS operations if [ -n "$LAST_STEP" ]; then - OUT=$(run_test "uwf cas get" bash -c "$UWF cas get $LAST_STEP") + OUT=$(run_test "ocas get" bash -c "$OCAS get $LAST_STEP") run_test "cas get returns type" bash -c "echo '$OUT' | jq -e '.type'" - OUT=$(run_test "uwf cas has" bash -c "$UWF cas has $LAST_STEP") + OUT=$(run_test "ocas has" bash -c "$OCAS has $LAST_STEP") - OUT=$(run_test "uwf cas refs" bash -c "$UWF cas refs $LAST_STEP") + OUT=$(run_test "ocas refs" bash -c "$OCAS refs $LAST_STEP") - OUT=$(run_test "uwf cas walk" bash -c "$UWF cas walk $LAST_STEP") + OUT=$(run_test "ocas walk" bash -c "$OCAS walk $LAST_STEP") run_test "cas walk returns nodes" bash -c "echo '$OUT' | jq -e 'length > 0'" fi diff --git a/workflows/solve-issue.yaml b/workflows/solve-issue.yaml index 08bb637..9ad9b0a 100644 --- a/workflows/solve-issue.yaml +++ b/workflows/solve-issue.yaml @@ -29,7 +29,7 @@ roles: After producing the test spec: - 1. Store it via `uwf cas put-text ""` and capture the returned hash + 1. The test spec is stored in CAS automatically by the uwf pipeline (agents do not need to call `ocas put` directly) 2. Put the hash in frontmatter.plan (required when $status=ready) @@ -79,7 +79,7 @@ roles: \ set up an isolated worktree:\n1. cd into the repo path provided in your task prompt\n2. `git fetch origin` to get latest refs\n3. First time (no existing branch):\n - `git worktree add .worktrees/fix/-\ \ -b fix/- origin/main`\n - `cd .worktrees/fix/- && bun install`\n4. If bounced back from reviewer or tester (branch already exists):\n - cd\ \ into the existing worktree under `.worktrees/fix/-`\n - `git fetch origin && git rebase origin/main`\n5. ALL subsequent work must happen inside the worktree directory.\n\ - \nThen implement TDD:\n6. Read the test spec from CAS: `uwf cas get ` (find the hash from the planner's output in your task prompt)\n7. If bounced back from reviewer or tester: read the\ + \nThen implement TDD:\n6. Read the test spec from CAS: `ocas get ` (find the hash from the planner's output in your task prompt)\n7. If bounced back from reviewer or tester: read the\ \ previous role's feedback in your task prompt\n8. Write tests first based on the spec (use vitest)\n9. Implement the code to make tests pass\n10. Ensure `bun run build` passes with no errors\n11.\ \ Run `bun test` to verify all tests pass\n\nIf you cannot complete the implementation (e.g. the issue is too complex, blocked by external factors,\nor repeated attempts fail), set $status=failed\ \ with a reason.\n" @@ -192,7 +192,7 @@ roles: goal: You are a tester agent. You verify that the implementation correctly satisfies every scenario in the test spec. capabilities: - testing - procedure: "The worktree path is provided in your task prompt. cd into it first.\n\n1. Run `bun test` for automated test verification\n2. Read the test spec from CAS: `uwf cas get ` (find\ + procedure: "The worktree path is provided in your task prompt. cd into it first.\n\n1. Run `bun test` for automated test verification\n2. Read the test spec from CAS: `ocas get ` (find\ \ the hash from the planner step in the thread history)\n3. Verify each scenario in the spec is covered and passing\n4. Determine outcome:\n - passed: all scenarios verified, tests pass\n - fix_code:\ \ tests fail or implementation doesn't match spec → send back to developer\n - fix_spec: the spec itself is wrong or incomplete → send back to planner\n" output: Report test results per scenario. Set $status to passed (with branch/worktree), fix_code (with report), or fix_spec (with report).