refactor: remove uwf cas subcommand, use ocas CLI #15
@@ -177,10 +177,10 @@ roles:
|
|||||||
4. `uwf thread read <threadId>` — verify non-empty output
|
4. `uwf thread read <threadId>` — verify non-empty output
|
||||||
|
|
||||||
CAS operations:
|
CAS operations:
|
||||||
5. `uwf cas get <lastStepHash>` — verify returns a type field
|
5. `ocas get <lastStepHash>` — verify returns a type field
|
||||||
6. `uwf cas has <lastStepHash>` — verify exits 0
|
6. `ocas has <lastStepHash>` — verify exits 0
|
||||||
7. `uwf cas refs <lastStepHash>` — list refs (may be empty)
|
7. `ocas refs <lastStepHash>` — list refs (may be empty)
|
||||||
8. `uwf cas walk <lastStepHash>` — verify returns non-empty array
|
8. `ocas walk <lastStepHash>` — verify returns non-empty array
|
||||||
|
|
||||||
Report results. Pass threadId, lastStepHash, workflowName, containerName forward.
|
Report results. Pass threadId, lastStepHash, workflowName, containerName forward.
|
||||||
output: "Report test results. Set $status to pass (with threadId, lastStepHash, workflowName, containerName) or fail."
|
output: "Report test results. Set $status to pass (with threadId, lastStepHash, workflowName, containerName) or fail."
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ roles:
|
|||||||
2. Revise the test spec accordingly
|
2. Revise the test spec accordingly
|
||||||
|
|
||||||
After producing the test spec:
|
After producing the test spec:
|
||||||
1. Store it via `uwf cas put-text "<markdown content>"` 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)
|
2. Put the plan hash in frontmatter.plan (required when $status=ready)
|
||||||
3. Set repoPath to the absolute path of the repository root
|
3. Set repoPath to the absolute path of the repository root
|
||||||
|
|
||||||
IMPORTANT: Extract the repo remote (owner/repo) from git:
|
IMPORTANT: Extract the repo remote (owner/repo) from git:
|
||||||
@@ -63,7 +63,7 @@ roles:
|
|||||||
5. ALL subsequent work must happen inside the worktree directory.
|
5. ALL subsequent work must happen inside the worktree directory.
|
||||||
|
|
||||||
Then implement TDD:
|
Then implement TDD:
|
||||||
6. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the planner's output in your task prompt)
|
6. Read the test spec from CAS: `ocas get <plan hash>` (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
|
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
|
8. Write tests first based on the spec
|
||||||
9. Implement the code to make tests pass
|
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.
|
The worktree path is provided in your task prompt. cd into it first.
|
||||||
|
|
||||||
1. Run `bun test` for automated test verification
|
1. Run `bun test` for automated test verification
|
||||||
2. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the planner step in the thread history)
|
2. Read the test spec from CAS: `ocas get <plan hash>` (find the hash from the planner step in the thread history)
|
||||||
3. Verify each scenario in the spec is covered and passing
|
3. Verify each scenario in the spec is covered and passing
|
||||||
4. Determine outcome:
|
4. Determine outcome:
|
||||||
- passed: all scenarios verified, tests pass
|
- passed: all scenarios verified, tests pass
|
||||||
|
|||||||
+10
-8
@@ -447,16 +447,18 @@ Binary: `uwf`
|
|||||||
|
|
||||||
### CAS commands
|
### CAS commands
|
||||||
|
|
||||||
|
Use the `ocas` CLI for direct CAS operations (`~/.ocas/` store, shared with `uwf`):
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `uwf cas get <hash>` | Read a CAS node. |
|
| `ocas get <hash>` | Read a CAS node. |
|
||||||
| `uwf cas put <type-hash> <data>` | Store a node, print its hash. |
|
| `ocas put <type-hash> <data>` | Store a node, print its hash. |
|
||||||
| `uwf cas has <hash>` | Check if a hash exists. |
|
| `ocas has <hash>` | Check if a hash exists. |
|
||||||
| `uwf cas refs <hash>` | List direct CAS references. |
|
| `ocas refs <hash>` | List direct CAS references. |
|
||||||
| `uwf cas walk <hash>` | Recursive traversal from a node. |
|
| `ocas walk <hash>` | Recursive traversal from a node. |
|
||||||
| `uwf cas reindex` | Rebuild type index from all nodes. |
|
| `ocas reindex` | Rebuild type index from all nodes. |
|
||||||
| `uwf cas schema list` | List registered schemas. |
|
| `ocas schema list` | List registered schemas. |
|
||||||
| `uwf cas schema get <hash>` | Show a schema by type hash. |
|
| `ocas schema get <hash>` | Show a schema by type hash. |
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ roles:
|
|||||||
3. Assess whether the issue has enough information to produce a test spec
|
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 <number> -r <owner/repo>` (skip if you already commented), then output $status=insufficient_info
|
4. If insufficient info: comment on the issue via `echo "..." | tea comment <number> -r <owner/repo>` (skip if you already commented), then output $status=insufficient_info
|
||||||
5. If sufficient: produce a detailed TDD test spec in markdown covering all scenarios
|
5. If sufficient: produce a detailed TDD test spec in markdown covering all scenarios
|
||||||
6. Store it via `uwf cas put-text "<markdown content>"` 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
|
7. Output **$status=ready** with plan hash and repoPath
|
||||||
|
|
||||||
**Mode B — Continue on existing PR (prompt mentions PR, branch, or review feedback):**
|
**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 <number> -r <owner/repo>`
|
3. Read the existing issue for full context: `tea issues <number> -r <owner/repo>`
|
||||||
4. Look for project conventions files (CLAUDE.md, CONTRIBUTING.md, .cursor/rules/) in the repo
|
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
|
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 "<markdown content>"` 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
|
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
|
8. Output **$status=continue** with plan hash, repoPath, branch name, and worktree path
|
||||||
|
|
||||||
**Mode C — Bounced back by tester (fix_spec):**
|
**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
|
1. Read the tester's output from the previous step to understand what's wrong with the spec
|
||||||
2. Revise the test spec accordingly
|
2. Revise the test spec accordingly
|
||||||
3. Store it via `uwf cas put-text "<markdown content>"` 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
|
4. Output **$status=ready** with plan hash and repoPath
|
||||||
|
|
||||||
IMPORTANT: Extract the repo remote (owner/repo) from git:
|
IMPORTANT: Extract the repo remote (owner/repo) from git:
|
||||||
@@ -91,7 +91,7 @@ roles:
|
|||||||
6. ALL subsequent work must happen inside the worktree directory.
|
6. ALL subsequent work must happen inside the worktree directory.
|
||||||
|
|
||||||
Then implement TDD:
|
Then implement TDD:
|
||||||
6. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the planner's output in your task prompt)
|
6. Read the test spec from CAS: `ocas get <plan hash>` (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
|
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
|
8. Write tests first based on the spec
|
||||||
9. Implement the code to make tests pass
|
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.
|
The worktree path is provided in your task prompt. cd into it first.
|
||||||
|
|
||||||
1. Run `bun test` for automated test verification
|
1. Run `bun test` for automated test verification
|
||||||
2. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the planner step in the thread history)
|
2. Read the test spec from CAS: `ocas get <plan hash>` (find the hash from the planner step in the thread history)
|
||||||
3. Verify each scenario in the spec is covered and passing
|
3. Verify each scenario in the spec is covered and passing
|
||||||
4. Determine outcome:
|
4. Determine outcome:
|
||||||
- passed: all scenarios verified, tests pass
|
- passed: all scenarios verified, tests pass
|
||||||
|
|||||||
@@ -99,17 +99,18 @@ uwf step fork 32GCDE899RRQ3
|
|||||||
|
|
||||||
### CAS
|
### CAS
|
||||||
|
|
||||||
|
Use the [`ocas`](https://www.npmjs.com/package/@ocas/cli) CLI for direct CAS operations (`~/.ocas/` store, shared with `uwf`):
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `uwf cas get <hash> [--timestamp]` | Read a CAS node |
|
| `ocas get <hash> [--timestamp]` | Read a CAS node |
|
||||||
| `uwf cas put <type-hash> <data>` | Store a node, print hash |
|
| `ocas put <type-hash> <data>` | Store a node, print hash |
|
||||||
| `uwf cas put-text <text>` | Store plain text, print hash |
|
| `ocas has <hash>` | Check existence |
|
||||||
| `uwf cas has <hash>` | Check existence |
|
| `ocas refs <hash>` | List direct references |
|
||||||
| `uwf cas refs <hash>` | List direct references |
|
| `ocas walk <hash>` | Recursive traversal |
|
||||||
| `uwf cas walk <hash>` | Recursive traversal |
|
| `ocas reindex` | Rebuild type index |
|
||||||
| `uwf cas reindex` | Rebuild type index |
|
| `ocas schema list` | List registered schemas |
|
||||||
| `uwf cas schema list` | List registered schemas |
|
| `ocas schema get <hash>` | Show a schema |
|
||||||
| `uwf cas schema get <hash>` | Show a schema |
|
|
||||||
|
|
||||||
### Setup
|
### 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 type { CasRef, ThreadId, ThreadStatus } from "@united-workforce/protocol";
|
||||||
import { Command } from "commander";
|
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 { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js";
|
||||||
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
|
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
|
||||||
import {
|
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");
|
const log = program.command("log").description("Process-level debug logs");
|
||||||
|
|
||||||
log
|
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)
|
## 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
|
### Commands
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
uwf cas put-text <text> # store plain text, print hash
|
ocas put <type-hash> <json> # store typed JSON data, print hash
|
||||||
uwf cas put <type-hash> <json> # store typed JSON data, print hash
|
ocas get <hash> # read a CAS node (type + payload)
|
||||||
uwf cas get <hash> # read a CAS node (type + payload)
|
ocas has <hash> # check if a hash exists
|
||||||
uwf cas has <hash> # check if a hash exists
|
ocas refs <hash> # list direct references from a node
|
||||||
uwf cas refs <hash> # list direct references from a node
|
ocas walk <hash> # recursive traversal from a node
|
||||||
uwf cas walk <hash> # recursive traversal from a node
|
ocas schema list # list registered schemas
|
||||||
uwf cas schema list # list registered schemas
|
ocas schema get <hash> # show a schema definition
|
||||||
uwf cas 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
|
### Merkle DAG Pattern
|
||||||
|
|
||||||
For large outputs, store parts individually and reference their hashes:
|
For large outputs, store parts individually and reference their hashes:
|
||||||
|
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
# Store individual sections
|
# Store individual sections (use ocas put with the appropriate type hash)
|
||||||
HASH1=$(uwf cas put-text "section 1 content")
|
HASH1=$(ocas put <type-hash> '"section 1 content"')
|
||||||
HASH2=$(uwf cas put-text "section 2 content")
|
HASH2=$(ocas put <type-hash> '"section 2 content"')
|
||||||
|
|
||||||
# Reference hashes in your frontmatter or in a parent node
|
# Reference hashes in your frontmatter or in a parent node
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ uwf step list <thread-id>
|
|||||||
uwf step show <step-hash>
|
uwf step show <step-hash>
|
||||||
|
|
||||||
# Check the CAS data
|
# Check the CAS data
|
||||||
uwf cas get <output-hash>
|
ocas get <output-hash>
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
### Validation Checklist
|
### Validation Checklist
|
||||||
|
|||||||
@@ -49,19 +49,19 @@ uwf step fork <step-hash> # fork a thread from a specific step
|
|||||||
|
|
||||||
## CAS Commands
|
## 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
|
[--timestamp] # include timestamp in output
|
||||||
uwf cas put <type-hash> <data> # store a node, print its hash
|
ocas put <type-hash> <data> # store a node, print its hash
|
||||||
# <data>: JSON file path or inline JSON string
|
# <data>: JSON file path or inline JSON string
|
||||||
uwf cas put-text <text> # store a plain text string, print its hash
|
ocas has <hash> # check if a hash exists
|
||||||
# shortcut for put with the built-in text schema
|
ocas refs <hash> # list direct CAS references from a node
|
||||||
uwf cas has <hash> # check if a hash exists
|
ocas walk <hash> # recursive traversal from a node
|
||||||
uwf cas refs <hash> # list direct CAS references from a node
|
ocas reindex # rebuild type index from all CAS nodes
|
||||||
uwf cas walk <hash> # recursive traversal from a node
|
ocas schema list # list all registered schemas
|
||||||
uwf cas reindex # rebuild type index from all CAS nodes
|
ocas schema get <hash> # show a schema by its type hash
|
||||||
uwf cas schema list # list all registered schemas
|
|
||||||
uwf cas schema get <hash> # show a schema by its type hash
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Log Commands
|
## Log Commands
|
||||||
|
|||||||
@@ -91,17 +91,18 @@ Forking creates a new thread that shares history up to the fork point — useful
|
|||||||
|
|
||||||
## CAS Commands
|
## 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
|
[--timestamp] # include timestamp
|
||||||
uwf cas put <type-hash> <data> # store typed JSON, print hash
|
ocas put <type-hash> <data> # store typed JSON, print hash
|
||||||
uwf cas put-text <text> # store plain text, print hash
|
ocas has <hash> # check existence
|
||||||
uwf cas has <hash> # check existence
|
ocas refs <hash> # list direct references
|
||||||
uwf cas refs <hash> # list direct references
|
ocas walk <hash> # recursive traversal
|
||||||
uwf cas walk <hash> # recursive traversal
|
ocas reindex # rebuild type index
|
||||||
uwf cas reindex # rebuild type index
|
ocas schema list # list schemas
|
||||||
uwf cas schema list # list schemas
|
ocas schema get <hash> # show schema definition
|
||||||
uwf cas schema get <hash> # show schema definition
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Log Commands
|
## Log Commands
|
||||||
|
|||||||
@@ -84,8 +84,9 @@ REAL_HOME="${6:-$HOME}"
|
|||||||
export HOME="$REAL_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"
|
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"
|
UWF="bun $REPO_DIR/packages/cli-workflow/src/cli.ts"
|
||||||
|
OCAS="ocas"
|
||||||
|
|
||||||
PASS=0
|
PASS=0
|
||||||
FAIL=0
|
FAIL=0
|
||||||
@@ -267,14 +268,14 @@ run_test "thread read produces output" bash -c "[ -n '$OUT' ]"
|
|||||||
|
|
||||||
# CAS operations
|
# CAS operations
|
||||||
if [ -n "$LAST_STEP" ]; then
|
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'"
|
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'"
|
run_test "cas walk returns nodes" bash -c "echo '$OUT' | jq -e 'length > 0'"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ roles:
|
|||||||
|
|
||||||
After producing the test spec:
|
After producing the test spec:
|
||||||
|
|
||||||
1. Store it via `uwf cas put-text "<markdown content>"` 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)
|
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/<issue-number>-<short-slug>\
|
\ 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/<issue-number>-<short-slug>\
|
||||||
\ -b fix/<issue-number>-<short-slug> origin/main`\n - `cd .worktrees/fix/<issue-number>-<short-slug> && bun install`\n4. If bounced back from reviewer or tester (branch already exists):\n - cd\
|
\ -b fix/<issue-number>-<short-slug> origin/main`\n - `cd .worktrees/fix/<issue-number>-<short-slug> && bun install`\n4. If bounced back from reviewer or tester (branch already exists):\n - cd\
|
||||||
\ into the existing worktree under `.worktrees/fix/<issue-number>-<short-slug>`\n - `git fetch origin && git rebase origin/main`\n5. ALL subsequent work must happen inside the worktree directory.\n\
|
\ into the existing worktree under `.worktrees/fix/<issue-number>-<short-slug>`\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 <plan hash>` (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 <plan hash>` (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.\
|
\ 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\
|
\ 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"
|
\ 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.
|
goal: You are a tester agent. You verify that the implementation correctly satisfies every scenario in the test spec.
|
||||||
capabilities:
|
capabilities:
|
||||||
- testing
|
- 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 <plan hash>` (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 <plan hash>` (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:\
|
\ 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"
|
\ 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).
|
output: Report test results per scenario. Set $status to passed (with branch/worktree), fix_code (with report), or fix_spec (with report).
|
||||||
|
|||||||
Reference in New Issue
Block a user