Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69ec8c2c5e | |||
| 81aa282c92 | |||
| a620defbcf | |||
| 439891f6b6 | |||
| df244c52e8 | |||
| cb6e0d6a11 | |||
| 9d0c6df62c | |||
| 0f5bb1f191 | |||
| 00d960daba | |||
| 3a26285872 | |||
| 13c0812944 | |||
| 2e7e5f6ec4 | |||
| 88c077d439 | |||
| aaadab4445 | |||
| adf7837975 | |||
| 513846f4ab | |||
| aee123cc82 | |||
| 8ddada5879 | |||
| aa732f5466 |
@@ -1,247 +0,0 @@
|
|||||||
name: "solve-issue"
|
|
||||||
description: "TDD-driven issue resolution for small, focused changes. Loop protection relies on engine maxRounds."
|
|
||||||
roles:
|
|
||||||
planner:
|
|
||||||
description: "Analyzes issue and outputs a TDD test spec"
|
|
||||||
goal: "You are a planning agent. You analyze Gitea issues and produce a TDD test specification that downstream roles will implement and verify."
|
|
||||||
capabilities:
|
|
||||||
- issue-analysis
|
|
||||||
- planning
|
|
||||||
procedure: |
|
|
||||||
On first run (no previous steps):
|
|
||||||
1. Read the issue and all comments from Gitea using `tea issues <number> -r <owner/repo>`
|
|
||||||
2. Look for project conventions files (CLAUDE.md, CONTRIBUTING.md, .cursor/rules/) in the repo
|
|
||||||
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
|
|
||||||
5. If sufficient: produce a detailed TDD test spec in markdown covering all scenarios
|
|
||||||
|
|
||||||
On subsequent runs (bounced back by tester with 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
|
|
||||||
|
|
||||||
After producing the test spec:
|
|
||||||
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:
|
|
||||||
```bash
|
|
||||||
git remote get-url origin | sed 's|.*[:/]\([^/]*/[^.]*\).*|\1|'
|
|
||||||
```
|
|
||||||
Store the result as repoRemote in your frontmatter output so downstream roles can use it for tea/API calls.
|
|
||||||
output: "Output a brief summary of the test spec. Set $status to ready (with plan hash and repoPath) or insufficient_info."
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status: { const: "ready" }
|
|
||||||
plan: { type: string }
|
|
||||||
repoPath: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
required: [$status, plan, repoPath, repoRemote]
|
|
||||||
- properties:
|
|
||||||
$status: { const: "insufficient_info" }
|
|
||||||
reason: { type: string }
|
|
||||||
required: [$status, reason]
|
|
||||||
developer:
|
|
||||||
description: "TDD implementation per test spec"
|
|
||||||
goal: "You are a developer agent. You implement code changes following TDD — write tests first, then implementation."
|
|
||||||
capabilities:
|
|
||||||
- coding
|
|
||||||
procedure: |
|
|
||||||
IMPORTANT: Always work in a git worktree, NEVER modify the main working directory directly.
|
|
||||||
The repo path and other details are provided in your task prompt.
|
|
||||||
|
|
||||||
Before starting any work, set up an isolated worktree:
|
|
||||||
1. cd into the repo path provided in your task prompt
|
|
||||||
2. `git fetch origin` to get latest refs
|
|
||||||
3. First time (no existing branch):
|
|
||||||
- `git worktree add .worktrees/fix/<issue-number>-<short-slug> -b fix/<issue-number>-<short-slug> origin/main`
|
|
||||||
- `cd .worktrees/fix/<issue-number>-<short-slug> && bun install`
|
|
||||||
4. If bounced back from reviewer or tester (branch already exists):
|
|
||||||
- cd into the existing worktree under `.worktrees/fix/<issue-number>-<short-slug>`
|
|
||||||
- `git fetch origin && git rebase origin/main`
|
|
||||||
5. ALL subsequent work must happen inside the worktree directory.
|
|
||||||
|
|
||||||
Then implement TDD:
|
|
||||||
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
|
|
||||||
8. Write tests first based on the spec
|
|
||||||
9. Implement the code to make tests pass
|
|
||||||
10. Ensure `bun run build` passes with no errors
|
|
||||||
11. Run `bun test` to verify all tests pass
|
|
||||||
- If tests fail on first run:
|
|
||||||
* Read the test output carefully for missing imports or setup issues
|
|
||||||
* Check if you're running tests from the correct working directory (package root vs workspace root)
|
|
||||||
* Fix the immediate issue and rerun ONCE
|
|
||||||
* If tests still fail after 2 attempts: check the test spec for ambiguities
|
|
||||||
* If stuck after 3 test cycles: set $status=failed with detailed error report rather than continuing blind retries
|
|
||||||
12. MANDATORY VERIFICATION before reporting done:
|
|
||||||
- Run `git branch --show-current` and confirm branch name matches expected
|
|
||||||
- Run `git status` and verify changed files exist
|
|
||||||
- Run `ls -la <key-implementation-files>` to verify they exist on disk
|
|
||||||
- If ANY verification fails: retry the implementation, do NOT report done
|
|
||||||
|
|
||||||
If you cannot complete the implementation (e.g. the issue is too complex, blocked by external factors,
|
|
||||||
or repeated attempts fail), set $status=failed with a reason.
|
|
||||||
output: "List all files changed and provide a summary. Set $status to done (with branch/worktree), or failed (with reason)."
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status: { const: "done" }
|
|
||||||
branch: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
required: [$status, branch, worktree]
|
|
||||||
- properties:
|
|
||||||
$status: { const: "failed" }
|
|
||||||
reason: { type: string }
|
|
||||||
required: [$status, reason]
|
|
||||||
reviewer:
|
|
||||||
description: "Code standards compliance check"
|
|
||||||
goal: "You are a code reviewer. You verify code standards compliance — NOT functionality (that's the tester's job)."
|
|
||||||
capabilities:
|
|
||||||
- code-review
|
|
||||||
- static-analysis
|
|
||||||
procedure: |
|
|
||||||
The worktree path is provided in your task prompt. cd into it first.
|
|
||||||
|
|
||||||
CRITICAL: You MUST execute every verification command below. Do NOT report results without running the actual commands. Do NOT rely on prior context or assumptions.
|
|
||||||
|
|
||||||
Before reviewing, verify the worktree and branch exist:
|
|
||||||
0. Run `cd <worktree-path> && pwd` to confirm the path is accessible
|
|
||||||
- If the cd fails: the worktree truly doesn't exist, reject with that reason
|
|
||||||
- If the cd succeeds: proceed with step 1 below
|
|
||||||
1. Run `git branch --show-current` — confirm the branch name references the issue number being worked on
|
|
||||||
2. If the branch doesn't correspond to the issue, flag it in your output and reject
|
|
||||||
|
|
||||||
Then perform code review:
|
|
||||||
Hard checks (must all pass):
|
|
||||||
3. `bun run build` — no build errors
|
|
||||||
4. `bunx biome check` — no lint violations
|
|
||||||
5. TypeScript strict mode — no type errors
|
|
||||||
|
|
||||||
Soft checks (review against project conventions if CLAUDE.md / .cursor/rules exist):
|
|
||||||
- Naming conventions, module boundaries, code style
|
|
||||||
- No `console.log` in production code
|
|
||||||
- No dynamic imports in production code
|
|
||||||
|
|
||||||
Only review standards compliance. Do NOT test functionality.
|
|
||||||
If rejecting, you MUST explain the specific reason in your output.
|
|
||||||
output: "Explain your decision with specific file/line references. Set $status to approved (with branch/worktree) or rejected (with comments)."
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status: { const: "approved" }
|
|
||||||
branch: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
required: [$status, branch, worktree]
|
|
||||||
- properties:
|
|
||||||
$status: { const: "rejected" }
|
|
||||||
comments: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
required: [$status, comments, worktree]
|
|
||||||
tester:
|
|
||||||
description: "Functional correctness verification"
|
|
||||||
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.
|
|
||||||
|
|
||||||
1. Run `bun test` for automated test verification
|
|
||||||
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
|
|
||||||
4. Determine outcome:
|
|
||||||
- passed: all scenarios verified, tests pass
|
|
||||||
- fix_code: tests fail or implementation doesn't match spec → send back to developer
|
|
||||||
- fix_spec: the spec itself is wrong or incomplete → send back to planner
|
|
||||||
output: "Report test results per scenario. Set $status to passed (with branch/worktree), fix_code (with report), or fix_spec (with report)."
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status: { const: "passed" }
|
|
||||||
branch: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
required: [$status, branch, worktree]
|
|
||||||
- properties:
|
|
||||||
$status: { const: "fix_code" }
|
|
||||||
report: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
branch: { type: string }
|
|
||||||
required: [$status, report]
|
|
||||||
- properties:
|
|
||||||
$status: { const: "fix_spec" }
|
|
||||||
report: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
branch: { type: string }
|
|
||||||
required: [$status, report]
|
|
||||||
committer:
|
|
||||||
description: "Commits and creates PR"
|
|
||||||
goal: "You are a committer agent. You create a clean commit and push a PR linking the original issue."
|
|
||||||
capabilities: []
|
|
||||||
procedure: |
|
|
||||||
The worktree path, branch name, and repo remote (owner/repo) are provided in your task prompt.
|
|
||||||
cd into the worktree first.
|
|
||||||
|
|
||||||
Note: You inherit the developer's worktree and branch. Do NOT create a new branch.
|
|
||||||
1. Check `git status` — if working tree is clean and branch is ahead of origin, skip to step 3 (push).
|
|
||||||
2. If there are unstaged/uncommitted changes: `git add -A` then `git commit -m "type: description\n\nFixes #N"`
|
|
||||||
3. Push the branch: `git push -u origin <branch-name>`
|
|
||||||
4. **Verify push succeeded** — run `git ls-remote origin <branch-name>` and confirm it prints a commit hash.
|
|
||||||
- If no output or push failed: capture the error, mark hook_failed
|
|
||||||
5. Create a PR using the Gitea API (do NOT use `tea pr create` — it fails in worktrees):
|
|
||||||
```bash
|
|
||||||
GITEA_TOKEN=$(cfg get GITEA_TOKEN)
|
|
||||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \
|
|
||||||
"https://git.shazhou.work/api/v1/repos/<owner>/<repo>/pulls" \
|
|
||||||
-d '{"title":"...","body":"...","head":"<branch>","base":"main"}'
|
|
||||||
```
|
|
||||||
- The repo remote (owner/repo format, e.g. "shazhou/united-workforce") is given in your task prompt — use it directly.
|
|
||||||
- PR body must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref
|
|
||||||
6. **Verify PR was created** — parse the curl response JSON: it must contain a `"number"` field. Print the PR URL.
|
|
||||||
- If curl returns an error or no number field: capture the response, mark hook_failed
|
|
||||||
7. After PR creation, clean up the worktree:
|
|
||||||
- cd to the repo root (parent of .worktrees)
|
|
||||||
- `git worktree remove <worktree-path>`
|
|
||||||
output: "Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error)."
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status: { const: "committed" }
|
|
||||||
prUrl: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
branch: { type: string }
|
|
||||||
required: [$status, prUrl]
|
|
||||||
- properties:
|
|
||||||
$status: { const: "hook_failed" }
|
|
||||||
error: { type: string }
|
|
||||||
repoRemote: { type: string }
|
|
||||||
worktree: { type: string }
|
|
||||||
branch: { type: string }
|
|
||||||
required: [$status, error]
|
|
||||||
graph:
|
|
||||||
$START:
|
|
||||||
new: { role: "planner", prompt: "Analyze the issue and produce an implementation plan." }
|
|
||||||
resume: { role: "planner", prompt: "Review the previous run output and continue the work." }
|
|
||||||
planner:
|
|
||||||
insufficient_info: { role: "$SUSPEND", prompt: "信息不足,需要补充:{{{reason}}}" }
|
|
||||||
ready: { role: "developer", prompt: "Implement the TDD test spec (CAS hash: {{{plan}}}) in repo {{{repoPath}}}. Repo remote: {{{repoRemote}}}." }
|
|
||||||
developer:
|
|
||||||
done: { role: "reviewer", prompt: "Review branch {{{branch}}} at {{{worktree}}} for code standards compliance. Repo remote: {{{repoRemote}}}." }
|
|
||||||
failed: { role: "$END", prompt: "Developer failed: {{{reason}}}. Ending workflow." }
|
|
||||||
reviewer:
|
|
||||||
rejected: { role: "developer", prompt: "Reviewer rejected: {{{comments}}}. Fix the issues in repo {{{worktree}}}. Repo remote: {{{repoRemote}}}." }
|
|
||||||
approved: { role: "tester", prompt: "Review passed. Run tests on branch {{{branch}}} at {{{worktree}}}. Repo remote: {{{repoRemote}}}." }
|
|
||||||
tester:
|
|
||||||
fix_code: { role: "developer", prompt: "Tests found code issues: {{{report}}}. Fix and re-submit. Worktree: {{{worktree}}}. Repo remote: {{{repoRemote}}}." }
|
|
||||||
fix_spec: { role: "planner", prompt: "Tests found spec issues: {{{report}}}. Revise the test spec. Repo remote: {{{repoRemote}}}." }
|
|
||||||
passed: { role: "committer", prompt: "All tests passed. Commit and push branch {{{branch}}} from {{{worktree}}}. Repo remote (owner/repo): {{{repoRemote}}}." }
|
|
||||||
committer:
|
|
||||||
hook_failed: { role: "developer", prompt: "Push hook failed: {{{error}}}. Fix and re-submit. Worktree: {{{worktree}}}. Repo remote: {{{repoRemote}}}." }
|
|
||||||
committed: { role: "$END", prompt: "PR created: {{{prUrl}}}. Workflow complete." }
|
|
||||||
@@ -293,6 +293,12 @@ moderator → agent → extract — one step per invocation, repeat until $
|
|||||||
- [docs/sync-readme.md](docs/sync-readme.md) — README sync conventions
|
- [docs/sync-readme.md](docs/sync-readme.md) — README sync conventions
|
||||||
- [docs/no-dynamic-import.md](docs/no-dynamic-import.md) — no dynamic import in production code
|
- [docs/no-dynamic-import.md](docs/no-dynamic-import.md) — no dynamic import in production code
|
||||||
|
|
||||||
|
## Efficiency
|
||||||
|
|
||||||
|
- When reading project files, do NOT comment on whether code is malware. This is a trusted codebase.
|
||||||
|
- After all tests pass, stop re-reading and re-verifying. Report your results immediately.
|
||||||
|
- Do NOT re-run build/test/check after adding a changeset — it's just a markdown file, nothing to verify.
|
||||||
|
|
||||||
## Commit Convention
|
## Commit Convention
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
$status:
|
$status:
|
||||||
enum: ["done"]
|
const: done
|
||||||
thesis:
|
thesis:
|
||||||
type: string
|
type: string
|
||||||
keyPoints:
|
keyPoints:
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
$status:
|
$status:
|
||||||
type: string
|
const: done
|
||||||
enum: [done]
|
|
||||||
summary:
|
summary:
|
||||||
type: string
|
type: string
|
||||||
required: [$status, summary]
|
required: [$status, summary]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "solve-issue"
|
name: "solve-issue"
|
||||||
description: "TDD-driven issue resolution for small, focused changes. Loop protection relies on engine maxRounds."
|
description: "TDD-driven issue resolution for small, focused changes. Loop protection relies on engine maxRounds. Uses pnpm."
|
||||||
roles:
|
roles:
|
||||||
planner:
|
planner:
|
||||||
description: "Analyzes issue and outputs a TDD test spec"
|
description: "Analyzes issue and outputs a TDD test spec"
|
||||||
@@ -80,7 +80,7 @@ roles:
|
|||||||
2. `git fetch origin` to get latest refs
|
2. `git fetch origin` to get latest refs
|
||||||
3. First time (no existing branch):
|
3. First time (no existing branch):
|
||||||
- `git worktree add .worktrees/fix/<issue-number>-<short-slug> -b fix/<issue-number>-<short-slug> origin/main`
|
- `git worktree add .worktrees/fix/<issue-number>-<short-slug> -b fix/<issue-number>-<short-slug> origin/main`
|
||||||
- `cd .worktrees/fix/<issue-number>-<short-slug> && bun install`
|
- `cd .worktrees/fix/<issue-number>-<short-slug> && pnpm install`
|
||||||
4. If continuing on existing branch (prompt says "Continue work on existing branch" or provides a worktree path):
|
4. If continuing on existing branch (prompt says "Continue work on existing branch" or provides a worktree path):
|
||||||
- cd directly into the worktree path provided in the prompt
|
- cd directly into the worktree path provided in the prompt
|
||||||
- `git fetch origin && git rebase origin/main`
|
- `git fetch origin && git rebase origin/main`
|
||||||
@@ -95,8 +95,20 @@ roles:
|
|||||||
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
|
||||||
10. Ensure `bun run build` passes with no errors
|
10. Ensure `pnpm run build` passes with no errors
|
||||||
11. Run `bun test` to verify all tests pass
|
11. Run `pnpm test` to verify all tests pass
|
||||||
|
|
||||||
|
After implementation, before reporting done:
|
||||||
|
12. Add a changeset file (`.changeset/<short-slug>.md`) with correct bump type:
|
||||||
|
- `patch` for bug fixes, internal refactors, test-only changes
|
||||||
|
- `minor` for new features, new CLI commands, new API surfaces
|
||||||
|
- `major` for breaking changes
|
||||||
|
List every affected package in the changeset frontmatter.
|
||||||
|
13. Update documentation if the change affects user-facing behavior:
|
||||||
|
- `README.md` — usage examples, feature descriptions
|
||||||
|
- `.cards/` — architecture decision records (if applicable)
|
||||||
|
- CLI prompt subcommand output (if CLI help text changes)
|
||||||
|
- CLI `--help` text (if flags/commands are added or changed)
|
||||||
|
|
||||||
If you cannot complete the implementation (e.g. the issue is too complex, blocked by external factors,
|
If you cannot complete the implementation (e.g. the issue is too complex, blocked by external factors,
|
||||||
or repeated attempts fail), set $status=failed with a reason.
|
or repeated attempts fail), set $status=failed with a reason.
|
||||||
@@ -127,8 +139,8 @@ roles:
|
|||||||
|
|
||||||
Then perform code review:
|
Then perform code review:
|
||||||
Hard checks (must all pass):
|
Hard checks (must all pass):
|
||||||
3. `bun run build` — no build errors
|
3. `pnpm run build` — no build errors
|
||||||
4. `bunx biome check` — no lint violations
|
4. `pnpm run check` — no lint violations
|
||||||
5. TypeScript strict mode — no type errors
|
5. TypeScript strict mode — no type errors
|
||||||
|
|
||||||
Soft checks (review against project conventions if CLAUDE.md / .cursor/rules exist):
|
Soft checks (review against project conventions if CLAUDE.md / .cursor/rules exist):
|
||||||
@@ -136,6 +148,14 @@ roles:
|
|||||||
- No `console.log` in production code
|
- No `console.log` in production code
|
||||||
- No dynamic imports in production code
|
- No dynamic imports in production code
|
||||||
|
|
||||||
|
Documentation & changeset checks:
|
||||||
|
6. Changeset exists in `.changeset/` with correct bump type (`patch`/`minor`/`major`) and lists all affected packages
|
||||||
|
7. If the change is user-facing, documentation is updated:
|
||||||
|
- `README.md` reflects new/changed behavior
|
||||||
|
- `.cards/` architecture cards updated if design decisions changed
|
||||||
|
- CLI prompt subcommand output updated (if it generates skill/reference content)
|
||||||
|
- CLI `--help` text matches new flags/commands
|
||||||
|
|
||||||
Only review standards compliance. Do NOT test functionality.
|
Only review standards compliance. Do NOT test functionality.
|
||||||
If rejecting, you MUST explain the specific reason in your output.
|
If rejecting, you MUST explain the specific reason in your output.
|
||||||
output: "Explain your decision with specific file/line references. Set $status to approved (with branch/worktree) or rejected (with comments)."
|
output: "Explain your decision with specific file/line references. Set $status to approved (with branch/worktree) or rejected (with comments)."
|
||||||
@@ -159,7 +179,7 @@ roles:
|
|||||||
procedure: |
|
procedure: |
|
||||||
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 `pnpm test` for automated test verification
|
||||||
2. Read the test spec from CAS: `ocas 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:
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
"@agentclientprotocol/sdk": "^0.22.1",
|
"@agentclientprotocol/sdk": "^0.22.1",
|
||||||
"@biomejs/biome": "^2.4.14",
|
"@biomejs/biome": "^2.4.14",
|
||||||
"@changesets/cli": "^2.31.0",
|
"@changesets/cli": "^2.31.0",
|
||||||
"@shazhou/proman": "^0.5.1",
|
"@shazhou/proman": "^0.6.3",
|
||||||
"@types/node": "^25.7.0",
|
"@types/node": "^25.7.0",
|
||||||
"@types/xxhashjs": "^0.2.4",
|
"@types/xxhashjs": "^0.2.4",
|
||||||
"@united-workforce/agent-hermes": "workspace:*",
|
"@united-workforce/agent-hermes": "workspace:*",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"test:ci": "vitest run __tests__/"
|
"test:ci": "vitest run __tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"@united-workforce/util-agent": "workspace:^"
|
"@united-workforce/util-agent": "workspace:^"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.4 — 2026-06-07
|
||||||
|
|
||||||
|
- fix: decouple session resume from isFirstVisit guard
|
||||||
|
|
||||||
|
When frontmatter validation fails, the step is never written to CAS, so isFirstVisit remains true on the next run. Both adapters now always check the session cache regardless of isFirstVisit. When resuming after a frontmatter-only failure (isFirstVisit + cache hit), a minimal correction prompt is sent via buildFrontmatterRetryPrompt() instead of re-sending the full initial prompt.
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@united-workforce/agent-claude-code",
|
"name": "@united-workforce/agent-claude-code",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"test:ci": "vitest run __tests__/"
|
"test:ci": "vitest run __tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@united-workforce/protocol": "workspace:^",
|
"@united-workforce/protocol": "workspace:^",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"@united-workforce/util-agent": "workspace:^"
|
"@united-workforce/util-agent": "workspace:^"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type AgentContext,
|
type AgentContext,
|
||||||
type AgentRunResult,
|
type AgentRunResult,
|
||||||
buildContinuationPrompt,
|
buildContinuationPrompt,
|
||||||
|
buildFrontmatterRetryPrompt,
|
||||||
buildRolePrompt,
|
buildRolePrompt,
|
||||||
buildThreadProgress,
|
buildThreadProgress,
|
||||||
createAgent,
|
createAgent,
|
||||||
@@ -176,8 +177,12 @@ async function runClaudeCode(ctx: AgentContext, model: string | null): Promise<A
|
|||||||
|
|
||||||
log("K7R2M4N8", `prompt for role=${ctx.role} (length=${fullPrompt.length}):\n${fullPrompt}`);
|
log("K7R2M4N8", `prompt for role=${ctx.role} (length=${fullPrompt.length}):\n${fullPrompt}`);
|
||||||
|
|
||||||
// Try resuming a cached session for re-entry scenarios (e.g. reviewer reject → developer re-entry).
|
// Try resuming a cached session. This covers both normal re-entry
|
||||||
if (!ctx.isFirstVisit) {
|
// (e.g. reviewer reject → developer re-entry) AND the case where a
|
||||||
|
// previous run completed but frontmatter validation failed — the step
|
||||||
|
// was never written to CAS so isFirstVisit is still true, but the
|
||||||
|
// session cache holds a valid session we should resume.
|
||||||
|
{
|
||||||
const cachedSessionId = await getCachedSessionId(
|
const cachedSessionId = await getCachedSessionId(
|
||||||
"claude-code",
|
"claude-code",
|
||||||
ctx.threadId,
|
ctx.threadId,
|
||||||
@@ -185,13 +190,20 @@ async function runClaudeCode(ctx: AgentContext, model: string | null): Promise<A
|
|||||||
ctx.storageRoot,
|
ctx.storageRoot,
|
||||||
);
|
);
|
||||||
if (cachedSessionId !== null) {
|
if (cachedSessionId !== null) {
|
||||||
|
// isFirstVisit + cache hit = previous run completed but frontmatter
|
||||||
|
// validation failed. The session already has full context — send a
|
||||||
|
// minimal correction prompt instead of the full initial prompt.
|
||||||
|
const resumePrompt = ctx.isFirstVisit
|
||||||
|
? buildFrontmatterRetryPrompt(ctx.outputFormatInstruction)
|
||||||
|
: fullPrompt;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { stdout, stderr, exitCode } = await spawnClaudeResume(
|
const { stdout, stderr, exitCode } = await spawnClaudeResume(
|
||||||
cachedSessionId,
|
cachedSessionId,
|
||||||
fullPrompt,
|
resumePrompt,
|
||||||
model,
|
model,
|
||||||
);
|
);
|
||||||
const result = await processClaudeOutput(stdout, stderr, exitCode, ctx.store, fullPrompt);
|
const result = await processClaudeOutput(stdout, stderr, exitCode, ctx.store, resumePrompt);
|
||||||
if (result.sessionId !== undefined && result.sessionId !== "") {
|
if (result.sessionId !== undefined && result.sessionId !== "") {
|
||||||
await setCachedSessionId(
|
await setCachedSessionId(
|
||||||
"claude-code",
|
"claude-code",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @united-workforce/agent-hermes
|
# @united-workforce/agent-hermes
|
||||||
|
|
||||||
|
## 0.1.5 — 2026-06-07
|
||||||
|
|
||||||
|
- fix: decouple session resume from isFirstVisit guard
|
||||||
|
|
||||||
|
When frontmatter validation fails, the step is never written to CAS, so isFirstVisit remains true on the next run. Both adapters now always check the session cache regardless of isFirstVisit. When resuming after a frontmatter-only failure (isFirstVisit + cache hit), a minimal correction prompt is sent via buildFrontmatterRetryPrompt() instead of re-sending the full initial prompt.
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@united-workforce/agent-hermes",
|
"name": "@united-workforce/agent-hermes",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"test:ci": "vitest run __tests__/"
|
"test:ci": "vitest run __tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@united-workforce/protocol": "workspace:^",
|
"@united-workforce/protocol": "workspace:^",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"@united-workforce/util-agent": "workspace:^"
|
"@united-workforce/util-agent": "workspace:^"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
type AgentContext,
|
type AgentContext,
|
||||||
type AgentRunResult,
|
type AgentRunResult,
|
||||||
buildContinuationPrompt,
|
buildContinuationPrompt,
|
||||||
|
buildFrontmatterRetryPrompt,
|
||||||
buildRolePrompt,
|
buildRolePrompt,
|
||||||
buildThreadProgress,
|
buildThreadProgress,
|
||||||
createAgent,
|
createAgent,
|
||||||
@@ -102,6 +103,8 @@ async function storePromptResult(store: Store, sessionId: string): Promise<{ det
|
|||||||
type PromptAttempt = {
|
type PromptAttempt = {
|
||||||
useContinuation: boolean;
|
useContinuation: boolean;
|
||||||
resumed: boolean;
|
resumed: boolean;
|
||||||
|
/** True when resuming after a frontmatter-only failure (isFirstVisit + cache hit). */
|
||||||
|
frontmatterRetry: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function prepareSession(
|
async function prepareSession(
|
||||||
@@ -110,28 +113,36 @@ async function prepareSession(
|
|||||||
cwd: string,
|
cwd: string,
|
||||||
resumeDisabled: boolean,
|
resumeDisabled: boolean,
|
||||||
): Promise<PromptAttempt> {
|
): Promise<PromptAttempt> {
|
||||||
if (ctx.isFirstVisit || resumeDisabled) {
|
if (resumeDisabled) {
|
||||||
await client.connect(cwd);
|
await client.connect(cwd);
|
||||||
return { useContinuation: false, resumed: false };
|
return { useContinuation: false, resumed: false, frontmatterRetry: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check session cache regardless of isFirstVisit. A previous run may
|
||||||
|
// have completed and cached its session but failed frontmatter
|
||||||
|
// validation — the step never got written to CAS so isFirstVisit is
|
||||||
|
// still true, yet we should resume the existing session.
|
||||||
const cachedSessionId = await getCachedSessionId(ctx.threadId, ctx.role, ctx.storageRoot);
|
const cachedSessionId = await getCachedSessionId(ctx.threadId, ctx.role, ctx.storageRoot);
|
||||||
if (cachedSessionId === null) {
|
if (cachedSessionId === null) {
|
||||||
log("6RWK3N8Q", `no cached session for ${ctx.threadId}:${ctx.role}, starting new session`);
|
log("6RWK3N8Q", `no cached session for ${ctx.threadId}:${ctx.role}, starting new session`);
|
||||||
await client.connect(cwd);
|
await client.connect(cwd);
|
||||||
return { useContinuation: false, resumed: false };
|
return { useContinuation: false, resumed: false, frontmatterRetry: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.resume(cachedSessionId, cwd);
|
await client.resume(cachedSessionId, cwd);
|
||||||
log("9MHT4V2P", `resumed hermes session ${cachedSessionId} for ${ctx.threadId}:${ctx.role}`);
|
log("9MHT4V2P", `resumed hermes session ${cachedSessionId} for ${ctx.threadId}:${ctx.role}`);
|
||||||
return { useContinuation: true, resumed: true };
|
return {
|
||||||
|
useContinuation: !ctx.isFirstVisit,
|
||||||
|
resumed: true,
|
||||||
|
frontmatterRetry: ctx.isFirstVisit,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
log("3XPN7K4W", `session resume failed, falling back to new session: ${message}`);
|
log("3XPN7K4W", `session resume failed, falling back to new session: ${message}`);
|
||||||
await client.close();
|
await client.close();
|
||||||
await client.connect(cwd);
|
await client.connect(cwd);
|
||||||
return { useContinuation: false, resumed: false };
|
return { useContinuation: false, resumed: false, frontmatterRetry: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,9 +165,12 @@ export function createHermesAgent(resumeDisabled: boolean): () => Promise<void>
|
|||||||
ctx: AgentContext,
|
ctx: AgentContext,
|
||||||
useContinuation: boolean,
|
useContinuation: boolean,
|
||||||
beforeTurns: TurnsSnapshot,
|
beforeTurns: TurnsSnapshot,
|
||||||
|
frontmatterRetry: boolean,
|
||||||
): Promise<AgentRunResult> {
|
): Promise<AgentRunResult> {
|
||||||
const effectiveCtx = useContinuation ? ctx : { ...ctx, isFirstVisit: true };
|
// Frontmatter retry: session has full context, just re-output the format.
|
||||||
const fullPrompt = buildHermesPrompt(effectiveCtx);
|
const fullPrompt = frontmatterRetry
|
||||||
|
? buildFrontmatterRetryPrompt(ctx.outputFormatInstruction)
|
||||||
|
: buildHermesPrompt(useContinuation ? ctx : { ...ctx, isFirstVisit: true });
|
||||||
const startMs = Date.now();
|
const startMs = Date.now();
|
||||||
const { text, sessionId, usage: acpUsage } = await client.prompt(fullPrompt);
|
const { text, sessionId, usage: acpUsage } = await client.prompt(fullPrompt);
|
||||||
const durationSec = (Date.now() - startMs) / 1000;
|
const durationSec = (Date.now() - startMs) / 1000;
|
||||||
@@ -188,7 +202,7 @@ export function createHermesAgent(resumeDisabled: boolean): () => Promise<void>
|
|||||||
const beforeTurns = snapshotTurns(beforeSession);
|
const beforeTurns = snapshotTurns(beforeSession);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await runPrompt(ctx, attempt.useContinuation, beforeTurns);
|
return await runPrompt(ctx, attempt.useContinuation, beforeTurns, attempt.frontmatterRetry);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!attempt.resumed) {
|
if (!attempt.resumed) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -199,7 +213,7 @@ export function createHermesAgent(resumeDisabled: boolean): () => Promise<void>
|
|||||||
await client.close();
|
await client.close();
|
||||||
await client.connect(cwd);
|
await client.connect(cwd);
|
||||||
// Fresh session after retry — reset snapshot to zero
|
// Fresh session after retry — reset snapshot to zero
|
||||||
return runPrompt(ctx, false, ZERO_TURNS);
|
return runPrompt(ctx, false, ZERO_TURNS, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"test:ci": "vitest run __tests__/"
|
"test:ci": "vitest run __tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@united-workforce/protocol": "workspace:^",
|
"@united-workforce/protocol": "workspace:^",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"@united-workforce/util-agent": "workspace:^",
|
"@united-workforce/util-agent": "workspace:^",
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
"uwf": "./dist/cli.js"
|
"uwf": "./dist/cli.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@ocas/fs": "^0.3.0",
|
"@ocas/fs": "^0.4.0",
|
||||||
"@united-workforce/protocol": "workspace:^",
|
"@united-workforce/protocol": "workspace:^",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"@united-workforce/util-agent": "workspace:^",
|
"@united-workforce/util-agent": "workspace:^",
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ describe("solve-issue workflow: Gitea API PR creation", () => {
|
|||||||
"..",
|
"..",
|
||||||
"..",
|
"..",
|
||||||
"..",
|
"..",
|
||||||
".workflows",
|
"examples",
|
||||||
"solve-issue.yaml",
|
"solve-issue.yaml",
|
||||||
);
|
);
|
||||||
|
|
||||||
test("committer procedure should use curl API instead of tea pr create", async () => {
|
test("committer procedure should create PR via tea pr create", async () => {
|
||||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||||
const workflow = parse(yamlContent) as WorkflowPayload;
|
const workflow = parse(yamlContent) as WorkflowPayload;
|
||||||
|
|
||||||
@@ -33,25 +33,22 @@ describe("solve-issue workflow: Gitea API PR creation", () => {
|
|||||||
const committerProcedure = workflow.roles.committer?.procedure;
|
const committerProcedure = workflow.roles.committer?.procedure;
|
||||||
expect(committerProcedure).toBeDefined();
|
expect(committerProcedure).toBeDefined();
|
||||||
|
|
||||||
// Verify the procedure uses curl API, not tea pr create
|
// Verify the procedure uses tea pr create for PR creation
|
||||||
expect(committerProcedure).toContain("curl");
|
expect(committerProcedure).toContain("tea pr create");
|
||||||
expect(committerProcedure).toContain("api/v1/repos");
|
expect(committerProcedure).toContain("git push");
|
||||||
expect(committerProcedure).toContain("/pulls");
|
expect(committerProcedure).toContain("Fixes #N");
|
||||||
|
|
||||||
// Verify it explicitly warns against tea pr create
|
|
||||||
expect(committerProcedure).toMatch(/do NOT use.*tea pr create/i);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("committer procedure should reference repoRemote from task prompt", async () => {
|
test("committer procedure should extract owner/repo from git remote", async () => {
|
||||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||||
const workflow = parse(yamlContent) as WorkflowPayload;
|
const workflow = parse(yamlContent) as WorkflowPayload;
|
||||||
|
|
||||||
const committerProcedure = workflow.roles.committer?.procedure;
|
const committerProcedure = workflow.roles.committer?.procedure;
|
||||||
expect(committerProcedure).toBeDefined();
|
expect(committerProcedure).toBeDefined();
|
||||||
|
|
||||||
// Verify the procedure mentions repoRemote is provided in task prompt
|
// Verify the procedure extracts owner/repo from remote
|
||||||
expect(committerProcedure).toMatch(/repo remote.*provided.*task prompt/i);
|
expect(committerProcedure).toContain("git remote get-url origin");
|
||||||
expect(committerProcedure).toMatch(/owner\/repo/i);
|
expect(committerProcedure).toContain("hook_failed");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("committer procedure should include error handling for curl failures", async () => {
|
test("committer procedure should include error handling for curl failures", async () => {
|
||||||
@@ -100,45 +97,42 @@ describe("solve-issue workflow: Gitea API PR creation", () => {
|
|||||||
expect(committedVariant.required).toContain("$status");
|
expect(committedVariant.required).toContain("$status");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("developer procedure should include mandatory verification step", async () => {
|
test("developer procedure should include worktree setup", async () => {
|
||||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||||
const workflow = parse(yamlContent) as WorkflowPayload;
|
const workflow = parse(yamlContent) as WorkflowPayload;
|
||||||
|
|
||||||
const developerProcedure = workflow.roles.developer?.procedure;
|
const developerProcedure = workflow.roles.developer?.procedure;
|
||||||
expect(developerProcedure).toBeDefined();
|
expect(developerProcedure).toBeDefined();
|
||||||
|
|
||||||
// Verify the procedure includes mandatory verification step
|
// Verify the procedure includes worktree setup
|
||||||
expect(developerProcedure).toContain("MANDATORY VERIFICATION");
|
expect(developerProcedure).toContain("IMPORTANT");
|
||||||
expect(developerProcedure).toContain("git branch --show-current");
|
expect(developerProcedure).toContain("git worktree add");
|
||||||
expect(developerProcedure).toContain("git status");
|
expect(developerProcedure).toContain("pnpm install");
|
||||||
expect(developerProcedure).toMatch(/ls -la|verify.*exist/i);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("reviewer procedure should enforce worktree path verification", async () => {
|
test("reviewer procedure should verify branch and run checks", async () => {
|
||||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||||
const workflow = parse(yamlContent) as WorkflowPayload;
|
const workflow = parse(yamlContent) as WorkflowPayload;
|
||||||
|
|
||||||
const reviewerProcedure = workflow.roles.reviewer?.procedure;
|
const reviewerProcedure = workflow.roles.reviewer?.procedure;
|
||||||
expect(reviewerProcedure).toBeDefined();
|
expect(reviewerProcedure).toBeDefined();
|
||||||
|
|
||||||
// Verify the procedure includes critical enforcement
|
// Verify the procedure includes branch verification and build checks
|
||||||
expect(reviewerProcedure).toContain("CRITICAL");
|
expect(reviewerProcedure).toContain("git branch --show-current");
|
||||||
expect(reviewerProcedure).toMatch(/cd.*pwd/);
|
expect(reviewerProcedure).toContain("pnpm run build");
|
||||||
expect(reviewerProcedure).toContain(
|
expect(reviewerProcedure).toContain("pnpm run check");
|
||||||
"Do NOT report results without running the actual commands",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("developer procedure should include test debugging escalation", async () => {
|
test("developer procedure should include changeset and failure handling", async () => {
|
||||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||||
const workflow = parse(yamlContent) as WorkflowPayload;
|
const workflow = parse(yamlContent) as WorkflowPayload;
|
||||||
|
|
||||||
const developerProcedure = workflow.roles.developer?.procedure;
|
const developerProcedure = workflow.roles.developer?.procedure;
|
||||||
expect(developerProcedure).toBeDefined();
|
expect(developerProcedure).toBeDefined();
|
||||||
|
|
||||||
// Verify the procedure includes test failure guidance
|
// Verify the procedure includes changeset requirement and failure path
|
||||||
expect(developerProcedure).toMatch(/tests fail.*first run/i);
|
expect(developerProcedure).toContain(".changeset/");
|
||||||
expect(developerProcedure).toMatch(/3 test cycles|after 3 attempts/i);
|
|
||||||
expect(developerProcedure).toContain("$status=failed");
|
expect(developerProcedure).toContain("$status=failed");
|
||||||
|
expect(developerProcedure).toContain("pnpm test");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@united-workforce/eval",
|
"name": "@united-workforce/eval",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"private": false,
|
"private": false,
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"test:ci": "vitest run __tests__/"
|
"test:ci": "vitest run __tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@ocas/fs": "^0.3.0",
|
"@ocas/fs": "^0.4.0",
|
||||||
"@united-workforce/protocol": "workspace:^",
|
"@united-workforce/protocol": "workspace:^",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"commander": "^14.0.3",
|
"commander": "^14.0.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@united-workforce/protocol",
|
"name": "@united-workforce/protocol",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
"test:ci": "vitest run src/__tests__/"
|
"test:ci": "vitest run src/__tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@ocas/fs": "^0.3.0"
|
"@ocas/fs": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.2 — 2026-06-07
|
||||||
|
|
||||||
|
- fix: decouple session resume from isFirstVisit guard
|
||||||
|
|
||||||
|
When frontmatter validation fails, the step is never written to CAS, so isFirstVisit remains true on the next run. Both adapters now always check the session cache regardless of isFirstVisit. When resuming after a frontmatter-only failure (isFirstVisit + cache hit), a minimal correction prompt is sent via buildFrontmatterRetryPrompt() instead of re-sending the full initial prompt.
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
import { buildFrontmatterRetryPrompt } from "../src/frontmatter-retry-prompt.js";
|
||||||
|
|
||||||
|
describe("buildFrontmatterRetryPrompt", () => {
|
||||||
|
test("includes correction instruction", () => {
|
||||||
|
const result = buildFrontmatterRetryPrompt("Use YAML frontmatter");
|
||||||
|
expect(result).toContain("previous run completed");
|
||||||
|
expect(result).toContain("do NOT need to redo any work");
|
||||||
|
expect(result).toContain("corrected YAML frontmatter");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("includes outputFormatInstruction when provided", () => {
|
||||||
|
const instruction = "---\nstatus: $done | $review\nsummary: string\n---";
|
||||||
|
const result = buildFrontmatterRetryPrompt(instruction);
|
||||||
|
expect(result).toContain(instruction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("works with empty outputFormatInstruction", () => {
|
||||||
|
const result = buildFrontmatterRetryPrompt("");
|
||||||
|
expect(result).not.toContain("\n\n\n");
|
||||||
|
expect(result).toContain("corrected YAML frontmatter");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@united-workforce/util-agent",
|
"name": "@united-workforce/util-agent",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
"test:ci": "vitest run __tests__/ src/__tests__/"
|
"test:ci": "vitest run __tests__/ src/__tests__/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ocas/core": "^0.3.0",
|
"@ocas/core": "^0.4.0",
|
||||||
"@ocas/fs": "^0.3.0",
|
"@ocas/fs": "^0.4.0",
|
||||||
"@united-workforce/protocol": "workspace:^",
|
"@united-workforce/protocol": "workspace:^",
|
||||||
"@united-workforce/util": "workspace:^",
|
"@united-workforce/util": "workspace:^",
|
||||||
"dotenv": "^16.6.1",
|
"dotenv": "^16.6.1",
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Build a minimal prompt for retrying frontmatter output on a resumed session.
|
||||||
|
*
|
||||||
|
* Used when a previous run completed successfully but frontmatter validation
|
||||||
|
* failed — the session already has full context, we just need the agent to
|
||||||
|
* re-output correctly formatted frontmatter without redoing any work.
|
||||||
|
*/
|
||||||
|
export function buildFrontmatterRetryPrompt(outputFormatInstruction: string): string {
|
||||||
|
const parts: string[] = [
|
||||||
|
"Your previous run completed all work successfully, but the output format was incorrect.",
|
||||||
|
"You do NOT need to redo any work — all changes are already in place.",
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
if (outputFormatInstruction !== "") {
|
||||||
|
parts.push(outputFormatInstruction, "");
|
||||||
|
}
|
||||||
|
parts.push(
|
||||||
|
"Please output ONLY the corrected YAML frontmatter block (--- delimited) followed by a brief summary of the work you completed.",
|
||||||
|
);
|
||||||
|
return parts.join("\n");
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ export {
|
|||||||
} from "./extract.js";
|
} from "./extract.js";
|
||||||
export type { FrontmatterFastPathResult } from "./frontmatter.js";
|
export type { FrontmatterFastPathResult } from "./frontmatter.js";
|
||||||
export { tryFrontmatterFastPath } from "./frontmatter.js";
|
export { tryFrontmatterFastPath } from "./frontmatter.js";
|
||||||
|
export { buildFrontmatterRetryPrompt } from "./frontmatter-retry-prompt.js";
|
||||||
export { createAgent, parseArgv } from "./run.js";
|
export { createAgent, parseArgv } from "./run.js";
|
||||||
export { getCachedSessionId, getCachePath, setCachedSessionId } from "./session-cache.js";
|
export { getCachedSessionId, getCachePath, setCachedSessionId } from "./session-cache.js";
|
||||||
export { getConfigPath, getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js";
|
export { getConfigPath, getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js";
|
||||||
|
|||||||
Generated
+38
-36
@@ -18,8 +18,8 @@ importers:
|
|||||||
specifier: ^2.31.0
|
specifier: ^2.31.0
|
||||||
version: 2.31.0(@types/node@25.9.1)
|
version: 2.31.0(@types/node@25.9.1)
|
||||||
'@shazhou/proman':
|
'@shazhou/proman':
|
||||||
specifier: ^0.5.1
|
specifier: ^0.6.3
|
||||||
version: 0.5.1(@biomejs/biome@2.4.16)(typescript@5.9.3)(vite@7.3.5(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vitest@3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0))
|
version: 0.6.3(@biomejs/biome@2.4.16)(typescript@5.9.3)(vite@7.3.5(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vitest@3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0))
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.7.0
|
specifier: ^25.7.0
|
||||||
version: 25.9.1
|
version: 25.9.1
|
||||||
@@ -45,8 +45,8 @@ importers:
|
|||||||
packages/agent-builtin:
|
packages/agent-builtin:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/util':
|
'@united-workforce/util':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../util
|
version: link:../util
|
||||||
@@ -61,8 +61,8 @@ importers:
|
|||||||
packages/agent-claude-code:
|
packages/agent-claude-code:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/protocol':
|
'@united-workforce/protocol':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../protocol
|
version: link:../protocol
|
||||||
@@ -80,8 +80,8 @@ importers:
|
|||||||
packages/agent-hermes:
|
packages/agent-hermes:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/protocol':
|
'@united-workforce/protocol':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../protocol
|
version: link:../protocol
|
||||||
@@ -99,8 +99,8 @@ importers:
|
|||||||
packages/agent-mock:
|
packages/agent-mock:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/protocol':
|
'@united-workforce/protocol':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../protocol
|
version: link:../protocol
|
||||||
@@ -121,11 +121,11 @@ importers:
|
|||||||
packages/cli:
|
packages/cli:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@ocas/fs':
|
'@ocas/fs':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/protocol':
|
'@united-workforce/protocol':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../protocol
|
version: link:../protocol
|
||||||
@@ -231,11 +231,11 @@ importers:
|
|||||||
packages/eval:
|
packages/eval:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@ocas/fs':
|
'@ocas/fs':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/protocol':
|
'@united-workforce/protocol':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../protocol
|
version: link:../protocol
|
||||||
@@ -256,11 +256,11 @@ importers:
|
|||||||
packages/protocol:
|
packages/protocol:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@ocas/fs':
|
'@ocas/fs':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.8.3
|
specifier: ^5.8.3
|
||||||
@@ -275,11 +275,11 @@ importers:
|
|||||||
packages/util-agent:
|
packages/util-agent:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core':
|
'@ocas/core':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@ocas/fs':
|
'@ocas/fs':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.0
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
'@united-workforce/protocol':
|
'@united-workforce/protocol':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../protocol
|
version: link:../protocol
|
||||||
@@ -892,11 +892,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
'@ocas/core@0.3.0':
|
'@ocas/core@0.4.0':
|
||||||
resolution: {integrity: sha512-ejDDZbmQkTj2GoJg+cNjXa3eHlQGybW3PrUZlwERBvBFjjnYBLHOG7AQQYM48bI52UiqucafgZjPEYk9SZd6AQ==}
|
resolution: {integrity: sha512-6JvHd3nr5GncMOBNaZTf9ZTWou/txONTfZbkrblmgqL/H+YuRj1FfeFY+b1ndUlfwR7AuJ6bvoSxR5RP+AbC0w==}
|
||||||
|
engines: {node: '>=22.5.0'}
|
||||||
|
|
||||||
'@ocas/fs@0.3.0':
|
'@ocas/fs@0.4.0':
|
||||||
resolution: {integrity: sha512-/6/nICYVJWXeWx2LcPoHHJAFoqXpJoAtvhLKLS0zpkwtsZX3g0D9X6J5soHCV1QS+BOWybuOJ0+W3cB1FBRkZA==}
|
resolution: {integrity: sha512-AQG6dk1YCL1qpSszUWUgEY+LQhYbTv5hXYrs3J2pHAi2/lY615O2cTgjwEeh6JTcrqHsFwiDsDdKIKMpADchZA==}
|
||||||
|
engines: {node: '>=22.5.0'}
|
||||||
|
|
||||||
'@open-draft/deferred-promise@2.2.0':
|
'@open-draft/deferred-promise@2.2.0':
|
||||||
resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
|
resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
|
||||||
@@ -1152,8 +1154,8 @@ packages:
|
|||||||
'@sec-ant/readable-stream@0.4.1':
|
'@sec-ant/readable-stream@0.4.1':
|
||||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||||
|
|
||||||
'@shazhou/proman@0.5.1':
|
'@shazhou/proman@0.6.3':
|
||||||
resolution: {integrity: sha512-GmFUvd8SAOUW/eaDIEh31pVKSE3XhbgHOZ5vSpX4xS+F8Zl6lAfhgVCjcjRK8w5d43tsH47CVorwyxQcRaJFfA==}
|
resolution: {integrity: sha512-KguWl1xHrWXx1YWYrWj47v4NRbaQuKCm7Hd7T8dzrqnkM8UL8em3R9rC7GeDzI8YDDfriFeLTX+xb03UHkhTDA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@biomejs/biome': ^2.0.0
|
'@biomejs/biome': ^2.0.0
|
||||||
@@ -3896,16 +3898,16 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.20.1
|
fastq: 1.20.1
|
||||||
|
|
||||||
'@ocas/core@0.3.0':
|
'@ocas/core@0.4.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 8.20.0
|
ajv: 8.20.0
|
||||||
cborg: 4.5.8
|
cborg: 4.5.8
|
||||||
liquidjs: 10.27.0
|
liquidjs: 10.27.0
|
||||||
xxhash-wasm: 1.1.0
|
xxhash-wasm: 1.1.0
|
||||||
|
|
||||||
'@ocas/fs@0.3.0':
|
'@ocas/fs@0.4.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ocas/core': 0.3.0
|
'@ocas/core': 0.4.0
|
||||||
cborg: 4.5.8
|
cborg: 4.5.8
|
||||||
|
|
||||||
'@open-draft/deferred-promise@2.2.0': {}
|
'@open-draft/deferred-promise@2.2.0': {}
|
||||||
@@ -4049,7 +4051,7 @@ snapshots:
|
|||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1': {}
|
'@sec-ant/readable-stream@0.4.1': {}
|
||||||
|
|
||||||
'@shazhou/proman@0.5.1(@biomejs/biome@2.4.16)(typescript@5.9.3)(vite@7.3.5(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vitest@3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0))':
|
'@shazhou/proman@0.6.3(@biomejs/biome@2.4.16)(typescript@5.9.3)(vite@7.3.5(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vitest@3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@biomejs/biome': 2.4.16
|
'@biomejs/biome': 2.4.16
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|||||||
@@ -1,329 +0,0 @@
|
|||||||
name: solve-issue
|
|
||||||
description: TDD-driven issue resolution adapted for the workflow monorepo with bun + vitest
|
|
||||||
roles:
|
|
||||||
planner:
|
|
||||||
description: Analyzes issue and outputs a TDD test spec
|
|
||||||
goal: You are a planning agent. You analyze Gitea issues and produce a TDD test specification that downstream roles will implement and verify.
|
|
||||||
capabilities:
|
|
||||||
- issue-analysis
|
|
||||||
- planning
|
|
||||||
procedure: 'On first run (no previous steps):
|
|
||||||
|
|
||||||
1. Read the issue and all comments from Gitea using `tea issues <number> -r <owner/repo>`
|
|
||||||
|
|
||||||
2. Look for project conventions files (CLAUDE.md, CONTRIBUTING.md) in the repo
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
5. If sufficient: produce a detailed TDD test spec in markdown covering all scenarios
|
|
||||||
|
|
||||||
|
|
||||||
On subsequent runs (bounced back by tester with 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
|
|
||||||
|
|
||||||
|
|
||||||
After producing the test spec:
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
3. Set repoPath to the absolute path of the repository root
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
IMPORTANT: Extract the repo remote (owner/repo) from git:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
|
|
||||||
git remote get-url origin | sed ''s|.*[:/]\([^/]*/[^.]*\).*|\1|''
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Store the result as repoRemote in your frontmatter output so downstream roles can use it for tea/API calls.'
|
|
||||||
output: Output a brief summary of the test spec. Set $status to ready (with plan hash and repoPath) or insufficient_info.
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: ready
|
|
||||||
plan:
|
|
||||||
type: string
|
|
||||||
repoPath:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- plan
|
|
||||||
- repoPath
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: insufficient_info
|
|
||||||
reason:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- reason
|
|
||||||
developer:
|
|
||||||
description: TDD implementation per test spec
|
|
||||||
goal: You are a developer agent. You implement code changes following TDD — write tests first, then implementation.
|
|
||||||
capabilities:
|
|
||||||
- coding
|
|
||||||
procedure: "IMPORTANT: Always work in a git worktree, NEVER modify the main working directory directly.\nThe repo path and other details are provided in your task prompt.\n\nBefore starting any work,\
|
|
||||||
\ 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\
|
|
||||||
\ 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: `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.\
|
|
||||||
\ 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"
|
|
||||||
output: List all files changed and provide a summary. Set $status to done (with branch/worktree), or failed (with reason).
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: done
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- branch
|
|
||||||
- worktree
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: failed
|
|
||||||
reason:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- reason
|
|
||||||
reviewer:
|
|
||||||
description: Code standards compliance check
|
|
||||||
goal: You are a code reviewer. You verify code standards compliance — NOT functionality (that's the tester's job).
|
|
||||||
capabilities:
|
|
||||||
- code-review
|
|
||||||
- static-analysis
|
|
||||||
procedure: 'The worktree path is provided in your task prompt. cd into it first.
|
|
||||||
|
|
||||||
|
|
||||||
Before reviewing, verify the git branch:
|
|
||||||
|
|
||||||
1. Run `git branch --show-current` — confirm the branch name references the issue number being worked on
|
|
||||||
|
|
||||||
2. If the branch doesn''t correspond to the issue, flag it in your output and reject
|
|
||||||
|
|
||||||
|
|
||||||
Then perform code review:
|
|
||||||
|
|
||||||
Hard checks (must all pass):
|
|
||||||
|
|
||||||
3. `bun run build` — no build errors
|
|
||||||
|
|
||||||
4. `bunx biome check` — no lint violations
|
|
||||||
|
|
||||||
5. TypeScript strict mode — no type errors
|
|
||||||
|
|
||||||
|
|
||||||
Soft checks (review against project conventions from CLAUDE.md):
|
|
||||||
|
|
||||||
- Functional-first: functions + types, no classes (except for errors or third-party requirements)
|
|
||||||
|
|
||||||
- Named exports only, no default exports
|
|
||||||
|
|
||||||
- No optional properties (use `T | null` instead of `?:`)
|
|
||||||
|
|
||||||
- Folder module discipline: index.ts only re-exports, types in types.ts
|
|
||||||
|
|
||||||
- Crockford Base32 log tags (8-char, unique per call site)
|
|
||||||
|
|
||||||
- No `console.log` in production code (use createLogger from @united-workforce/util)
|
|
||||||
|
|
||||||
- No dynamic imports in production code
|
|
||||||
|
|
||||||
|
|
||||||
Only review standards compliance. Do NOT test functionality.
|
|
||||||
|
|
||||||
If rejecting, you MUST explain the specific reason in your output.
|
|
||||||
|
|
||||||
'
|
|
||||||
output: Explain your decision with specific file/line references. Set $status to approved (with branch/worktree) or rejected (with comments).
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: approved
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- branch
|
|
||||||
- worktree
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: rejected
|
|
||||||
comments:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- comments
|
|
||||||
- worktree
|
|
||||||
tester:
|
|
||||||
description: Functional correctness verification
|
|
||||||
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: `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:\
|
|
||||||
\ 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).
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: passed
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- branch
|
|
||||||
- worktree
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: fix_code
|
|
||||||
report:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- report
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: fix_spec
|
|
||||||
report:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- report
|
|
||||||
committer:
|
|
||||||
description: Commits and creates PR
|
|
||||||
goal: You are a committer agent. You create a clean commit and push a PR linking the original issue.
|
|
||||||
capabilities: []
|
|
||||||
procedure: "The worktree path, branch name, and repo remote (owner/repo) are provided in your task prompt.\ncd into the worktree first.\n\nNote: You inherit the developer's worktree and branch. Do NOT\
|
|
||||||
\ create a new branch.\n1. Stage all changes: `git add -A`\n2. Commit with a descriptive message referencing the issue: `git commit -m \"type: description\\n\\nFixes #N\"`\n3. Push the branch: `git\
|
|
||||||
\ push -u origin <branch-name>`\n4. **Verify push succeeded** — run `git ls-remote origin <branch-name>` and confirm it prints a commit hash.\n - If no output or push failed: capture the error, mark hook_failed\n\
|
|
||||||
5. Create a PR using the Gitea API (do NOT use `tea pr create` — it fails in worktrees):\n ```bash\n GITEA_TOKEN=$(cfg get GITEA_TOKEN)\n curl -s -X POST -H \"Authorization: token $GITEA_TOKEN\" -H \"Content-Type: application/json\" \\\n\
|
|
||||||
\ \"https://git.shazhou.work/api/v1/repos/<owner>/<repo>/pulls\" \\\n -d '{\"title\":\"...\",\"body\":\"...\",\"head\":\"<branch>\",\"base\":\"main\"}'\n ```\n - The repo remote (owner/repo format, e.g. \"shazhou/united-workforce\") is given in your task prompt — use it directly.\n\
|
|
||||||
\ - PR body must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref\n6. **Verify PR was created** — parse the curl response JSON: it must contain a `\"number\"` field. Print the PR URL.\n\
|
|
||||||
\ - If curl returns an error or no number field: capture the response, mark hook_failed\n7. After PR creation, clean up the worktree:\n - cd to the repo root (parent of .worktrees)\n - `git worktree remove <worktree-path>`"
|
|
||||||
output: Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error).
|
|
||||||
frontmatter:
|
|
||||||
oneOf:
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: committed
|
|
||||||
prUrl:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- prUrl
|
|
||||||
- properties:
|
|
||||||
$status:
|
|
||||||
const: hook_failed
|
|
||||||
error:
|
|
||||||
type: string
|
|
||||||
repoRemote:
|
|
||||||
type: string
|
|
||||||
worktree:
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- $status
|
|
||||||
- error
|
|
||||||
graph:
|
|
||||||
$START:
|
|
||||||
new:
|
|
||||||
role: planner
|
|
||||||
prompt: Analyze the issue and produce an implementation plan.
|
|
||||||
resume:
|
|
||||||
role: planner
|
|
||||||
prompt: Review the previous run output and continue the work.
|
|
||||||
planner:
|
|
||||||
insufficient_info:
|
|
||||||
role: $SUSPEND
|
|
||||||
prompt: "信息不足,需要补充:{{{reason}}}"
|
|
||||||
ready:
|
|
||||||
role: developer
|
|
||||||
prompt: 'Implement the TDD test spec (CAS hash: {{{plan}}}) in repo {{{repoPath}}}. Repo remote: {{{repoRemote}}}.'
|
|
||||||
developer:
|
|
||||||
done:
|
|
||||||
role: reviewer
|
|
||||||
prompt: 'Review branch {{{branch}}} at {{{worktree}}} for code standards compliance. Repo remote: {{{repoRemote}}}.'
|
|
||||||
failed:
|
|
||||||
role: $END
|
|
||||||
prompt: 'Developer failed: {{{reason}}}. Ending workflow.'
|
|
||||||
reviewer:
|
|
||||||
rejected:
|
|
||||||
role: developer
|
|
||||||
prompt: 'Reviewer rejected: {{{comments}}}. Fix the issues in repo {{{worktree}}}. Repo remote: {{{repoRemote}}}.'
|
|
||||||
approved:
|
|
||||||
role: tester
|
|
||||||
prompt: 'Review passed. Run tests on branch {{{branch}}} at {{{worktree}}}. Repo remote: {{{repoRemote}}}.'
|
|
||||||
tester:
|
|
||||||
fix_code:
|
|
||||||
role: developer
|
|
||||||
prompt: 'Tests found code issues: {{{report}}}. Fix and re-submit. Worktree: {{{worktree}}}. Repo remote: {{{repoRemote}}}.'
|
|
||||||
fix_spec:
|
|
||||||
role: planner
|
|
||||||
prompt: 'Tests found spec issues: {{{report}}}. Revise the test spec. Repo remote: {{{repoRemote}}}.'
|
|
||||||
passed:
|
|
||||||
role: committer
|
|
||||||
prompt: 'All tests passed. Commit and push branch {{{branch}}} from {{{worktree}}}. Repo remote (owner/repo): {{{repoRemote}}}.'
|
|
||||||
committer:
|
|
||||||
hook_failed:
|
|
||||||
role: developer
|
|
||||||
prompt: 'Push hook failed: {{{error}}}. Fix and re-submit. Worktree: {{{worktree}}}. Repo remote: {{{repoRemote}}}.'
|
|
||||||
committed:
|
|
||||||
role: $END
|
|
||||||
prompt: 'PR created: {{{prUrl}}}. Workflow complete.'
|
|
||||||
Reference in New Issue
Block a user