@cloverleaf/reference-impl 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/VERSION +1 -1
- package/config/discovery.json +5 -0
- package/config/ui-review.json +2 -1
- package/dist/axe-dedupe.mjs +6 -2
- package/dist/cli.mjs +137 -4
- package/dist/discovery-config.mjs +26 -0
- package/dist/feedback.mjs +1 -1
- package/dist/ids.mjs +26 -1
- package/dist/index.mjs +1 -1
- package/dist/plan.mjs +115 -0
- package/dist/plugin-path.mjs +19 -0
- package/dist/rfc.mjs +38 -0
- package/dist/spike.mjs +37 -0
- package/dist/task.mjs +57 -0
- package/dist/ui-review-config.mjs +5 -1
- package/dist/work-item.mjs +49 -0
- package/lib/axe-dedupe.ts +13 -2
- package/lib/cli.ts +135 -4
- package/lib/discovery-config.ts +35 -0
- package/lib/feedback.ts +1 -1
- package/lib/ids.ts +25 -1
- package/lib/index.ts +1 -1
- package/lib/plan.ts +147 -0
- package/lib/plugin-path.ts +21 -0
- package/lib/rfc.ts +62 -0
- package/lib/spike.ts +60 -0
- package/lib/task.ts +90 -0
- package/lib/ui-review-config.ts +6 -1
- package/lib/work-item.ts +78 -0
- package/package.json +1 -1
- package/prompts/plan.md +63 -0
- package/prompts/researcher.md +74 -0
- package/prompts/ui-reviewer.md +19 -6
- package/skills/cloverleaf-breakdown/SKILL.md +74 -0
- package/skills/cloverleaf-discover/SKILL.md +139 -0
- package/skills/cloverleaf-document/SKILL.md +2 -2
- package/skills/cloverleaf-draft-rfc/SKILL.md +99 -0
- package/skills/cloverleaf-gate/SKILL.md +106 -0
- package/skills/cloverleaf-implement/SKILL.md +3 -3
- package/skills/cloverleaf-merge/SKILL.md +26 -5
- package/skills/cloverleaf-new-rfc/SKILL.md +76 -0
- package/skills/cloverleaf-new-task/SKILL.md +2 -2
- package/skills/cloverleaf-qa/SKILL.md +16 -6
- package/skills/cloverleaf-review/SKILL.md +18 -8
- package/skills/cloverleaf-spike/SKILL.md +66 -0
- package/skills/cloverleaf-ui-review/SKILL.md +17 -7
- package/lib/state.ts +0 -137
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-gate
|
|
3
|
+
description: Human gate action on an RFC (rfc_strategy_gate) or Plan (task_batch_gate) in status gate-pending. Usage — /cloverleaf-gate <item-id> <approve|reject|revise> [reason]. `revise` is valid only at rfc_strategy_gate (RFC only).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — human gate
|
|
7
|
+
|
|
8
|
+
The user has invoked this skill with `<item-id> <action> [reason]`.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Capture arguments:
|
|
13
|
+
- `$ITEM_ID` = first positional arg
|
|
14
|
+
- `$ACTION` = second positional arg (must be `approve`, `reject`, or `revise`)
|
|
15
|
+
- `$REASON` = remaining args joined with spaces (optional)
|
|
16
|
+
|
|
17
|
+
If `ITEM_ID` or `ACTION` is missing, report usage and stop.
|
|
18
|
+
|
|
19
|
+
2. Validate action:
|
|
20
|
+
```bash
|
|
21
|
+
case "$ACTION" in
|
|
22
|
+
approve|reject|revise) ;;
|
|
23
|
+
*) echo "Invalid action: $ACTION. Use approve, reject, or revise." >&2; exit 1 ;;
|
|
24
|
+
esac
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
3. Detect work-item type by checking which directory has the JSON file:
|
|
28
|
+
```bash
|
|
29
|
+
if [ -f "<repo_root>/.cloverleaf/rfcs/$ITEM_ID.json" ]; then
|
|
30
|
+
TYPE=rfc
|
|
31
|
+
GATE=rfc_strategy_gate
|
|
32
|
+
elif [ -f "<repo_root>/.cloverleaf/plans/$ITEM_ID.json" ]; then
|
|
33
|
+
TYPE=plan
|
|
34
|
+
GATE=task_batch_gate
|
|
35
|
+
else
|
|
36
|
+
echo "No RFC or Plan found with ID $ITEM_ID" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
4. Validate that `revise` is only valid at `rfc_strategy_gate` (i.e., on RFCs — revise is only exclusive to rfc_strategy_gate):
|
|
42
|
+
```bash
|
|
43
|
+
if [ "$ACTION" = "revise" ] && [ "$TYPE" != "rfc" ]; then
|
|
44
|
+
echo "revise is only valid at rfc_strategy_gate (RFCs). Use reject on Plans." >&2
|
|
45
|
+
exit 2
|
|
46
|
+
fi
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
5. Verify the item is in `gate-pending` status:
|
|
50
|
+
```bash
|
|
51
|
+
STATUS=$(cloverleaf-cli load-$TYPE <repo_root> $ITEM_ID | jq -r .status)
|
|
52
|
+
if [ "$STATUS" != "gate-pending" ]; then
|
|
53
|
+
echo "$TYPE $ITEM_ID is in status '$STATUS', not gate-pending" >&2
|
|
54
|
+
exit 3
|
|
55
|
+
fi
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
6. Emit the gate-decision event:
|
|
59
|
+
```bash
|
|
60
|
+
if [ -n "$REASON" ]; then
|
|
61
|
+
cloverleaf-cli emit-gate-decision <repo_root> $ITEM_ID $GATE $ACTION human --comment="$REASON"
|
|
62
|
+
else
|
|
63
|
+
cloverleaf-cli emit-gate-decision <repo_root> $ITEM_ID $GATE $ACTION human
|
|
64
|
+
fi
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
7. Advance the work-item state:
|
|
68
|
+
```bash
|
|
69
|
+
case "$ACTION" in
|
|
70
|
+
approve)
|
|
71
|
+
cloverleaf-cli advance-$TYPE <repo_root> $ITEM_ID approved human $GATE
|
|
72
|
+
;;
|
|
73
|
+
reject)
|
|
74
|
+
cloverleaf-cli advance-$TYPE <repo_root> $ITEM_ID rejected human $GATE
|
|
75
|
+
;;
|
|
76
|
+
revise)
|
|
77
|
+
# RFC-only — validated in step 4
|
|
78
|
+
cloverleaf-cli advance-rfc <repo_root> $ITEM_ID drafting human $GATE
|
|
79
|
+
# Persisting the revise reason as a feedback finding is deferred to v0.6.
|
|
80
|
+
# For now, the reason is surfaced on stdout so the caller can capture it
|
|
81
|
+
# and pass it back via the next /cloverleaf-draft-rfc invocation context.
|
|
82
|
+
if [ -n "$REASON" ]; then
|
|
83
|
+
echo "revise reason: $REASON"
|
|
84
|
+
fi
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
8. Commit state files:
|
|
90
|
+
```bash
|
|
91
|
+
git add .cloverleaf/rfcs/ .cloverleaf/plans/ .cloverleaf/events/
|
|
92
|
+
git commit -m "cloverleaf: gate $ITEM_ID $ACTION ($GATE)"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
9. Print the new status:
|
|
96
|
+
```bash
|
|
97
|
+
NEW=$(cloverleaf-cli load-$TYPE <repo_root> $ITEM_ID | jq -r .status)
|
|
98
|
+
echo "$ITEM_ID: $STATUS → $NEW"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Notes
|
|
102
|
+
|
|
103
|
+
- `revise` (RFC only) returns the RFC to `drafting`. The orchestrator loops back to `/cloverleaf-draft-rfc`.
|
|
104
|
+
- On `reject` of an RFC: terminal (enters rejected state; orchestrator halts).
|
|
105
|
+
- On `reject` of a Plan: plan enters rejected state; it can be re-decomposed via a new `/cloverleaf-breakdown` run (rejected → drafting via agent is a legal transition).
|
|
106
|
+
- `[reason]` text is persisted in the gate_decision event's `comment` field. For `revise`, it's also echoed to stdout so the calling orchestrator can feed it back to the Researcher on re-draft.
|
|
@@ -13,20 +13,20 @@ The user has invoked this skill with a TASK-ID (e.g., `DEMO-001`).
|
|
|
13
13
|
|
|
14
14
|
2. Load the task:
|
|
15
15
|
```
|
|
16
|
-
|
|
16
|
+
cloverleaf-cli load-task <repo_root> <TASK-ID>
|
|
17
17
|
```
|
|
18
18
|
Parse the JSON. Verify `status === "pending"` OR `status === "implementing"` (the second case is a re-run after a Reviewer bounce). If neither, report the current status and ask the user to use the correct command for that state.
|
|
19
19
|
|
|
20
20
|
3. Load any outstanding feedback:
|
|
21
21
|
```
|
|
22
|
-
|
|
22
|
+
cloverleaf-cli latest-feedback <repo_root> <TASK-ID>
|
|
23
23
|
```
|
|
24
24
|
Capture the output. If present and the latest verdict is `bounce`, pass it into the subagent.
|
|
25
25
|
|
|
26
26
|
4. Dispatch the Implementer subagent via the Task tool:
|
|
27
27
|
- `subagent_type`: `general-purpose`
|
|
28
28
|
- `model`: `sonnet`
|
|
29
|
-
- Prompt: the contents of
|
|
29
|
+
- Prompt: the contents of `$(cloverleaf-cli plugin-root)/prompts/implementer.md`, with placeholders substituted:
|
|
30
30
|
- `{{task}}` → the full task JSON (pretty-printed)
|
|
31
31
|
- `{{feedback}}` → the feedback JSON if present, else the literal string `null`
|
|
32
32
|
- `{{repo_root}}` → absolute path to the current repo
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cloverleaf-merge
|
|
3
|
-
description: Human gate for merging a Cloverleaf task. Branches on state — from `automated-gates` (fast lane) via `human_merge`, or from `final-gate` (full pipeline) via `final_approval_gate`. Requires explicit user confirmation. Usage — /cloverleaf-merge <TASK-ID>.
|
|
3
|
+
description: Human gate for merging a Cloverleaf task. Branches on state — from `automated-gates` (fast lane) via `human_merge`, or from `final-gate` (full pipeline) via `final_approval_gate`. For full-pipeline tasks, performs a real `git merge --no-ff` of the feature branch into main before advancing state. Requires explicit user confirmation. Usage — /cloverleaf-merge <TASK-ID>.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Cloverleaf — merge
|
|
@@ -53,18 +53,39 @@ description: Human gate for merging a Cloverleaf task. Branches on state — fro
|
|
|
53
53
|
|
|
54
54
|
3B.3. On explicit `y`:
|
|
55
55
|
- `cloverleaf-cli emit-gate-decision <repo_root> <TASK-ID> final_approval_gate approve human`
|
|
56
|
-
-
|
|
57
|
-
|
|
56
|
+
- Verify we are on main with a clean working tree:
|
|
57
|
+
```bash
|
|
58
|
+
cd <repo_root>
|
|
59
|
+
git checkout main
|
|
60
|
+
git status
|
|
61
|
+
# must be clean
|
|
62
|
+
```
|
|
63
|
+
If not clean, stop and report.
|
|
64
|
+
- Perform the real merge — brings the feature branch's Implementer commit, ui-review baseline commit, and any feedback commits into main:
|
|
65
|
+
```bash
|
|
66
|
+
git merge --no-ff cloverleaf/${TASK_ID} -m "cloverleaf: ${TASK_ID} merged (full pipeline)"
|
|
67
|
+
```
|
|
68
|
+
If git reports conflicts: abort and escalate.
|
|
69
|
+
```bash
|
|
70
|
+
git merge --abort
|
|
71
|
+
cloverleaf-cli advance-status <repo_root> ${TASK_ID} escalated agent
|
|
72
|
+
```
|
|
73
|
+
Exit with a human-readable error explaining the conflict.
|
|
74
|
+
- Advance task status on main (commits `.cloverleaf/tasks/${TASK_ID}.json` + event):
|
|
75
|
+
```bash
|
|
76
|
+
cloverleaf-cli advance-status <repo_root> ${TASK_ID} merged agent
|
|
77
|
+
```
|
|
58
78
|
|
|
59
79
|
### 4. Common: report
|
|
60
80
|
|
|
61
81
|
Report:
|
|
62
|
-
- "✓ Merged `<TASK-ID>`. Branch `cloverleaf/<TASK-ID>`
|
|
63
|
-
- "Suggested: `git push origin
|
|
82
|
+
- "✓ Merged `<TASK-ID>`. Branch `cloverleaf/<TASK-ID>` has been merged into main."
|
|
83
|
+
- "Suggested: `git push origin main` to push the merge commit."
|
|
64
84
|
|
|
65
85
|
## Rules
|
|
66
86
|
|
|
67
87
|
- Only proceed on explicit `y`, `Y`, `yes`, `YES`. Anything else: abort without state change.
|
|
68
88
|
- The skill does NOT push the branch or open a PR.
|
|
69
89
|
- Fast lane and full pipeline use distinct gates — the state machine records which path was taken.
|
|
90
|
+
- Full-pipeline merges perform a real `git merge --no-ff` before advancing state — the feature branch's code, baselines, and feedback commits all land on main.
|
|
70
91
|
- If the user declines, no state change and no commit.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-new-rfc
|
|
3
|
+
description: Scaffold a new RFC work item from a brief file. Usage — /cloverleaf-new-rfc <brief-file>. Writes `.cloverleaf/rfcs/<ID>.json` with status=drafting and empty body fields (populated by /cloverleaf-draft-rfc). Returns the new RFC ID.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — new RFC
|
|
7
|
+
|
|
8
|
+
## Steps
|
|
9
|
+
|
|
10
|
+
1. Capture `<brief-file>` (first positional arg). Verify file exists:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
BRIEF_FILE="$1"
|
|
14
|
+
[ -f "$BRIEF_FILE" ] || { echo "Brief file not found: $BRIEF_FILE" >&2; exit 1; }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
2. Load the discovery config to get `projectId`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
CFG=$(cloverleaf-cli discovery-config --repo-root $(pwd))
|
|
21
|
+
PROJECT_ID=$(echo "$CFG" | jq -r .projectId)
|
|
22
|
+
[ -z "$PROJECT_ID" ] && { echo "set projectId in .cloverleaf/config/discovery.json" >&2; exit 2; }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. Compute next work-item ID:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
RFC_ID=$(cloverleaf-cli next-work-item-id $(pwd) "$PROJECT_ID")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
4. Read brief content:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
BRIEF_CONTENT=$(cat "$BRIEF_FILE")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
5. Build the RFC skeleton JSON. Derive a short title from the brief's first non-empty line (truncate if long). Problem = brief content. All other body fields are seeded with schema-conformant placeholders that `/cloverleaf-draft-rfc` will overwrite.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
FIRST_LINE=$(head -n 1 "$BRIEF_FILE" | sed 's/^# *//' | cut -c1-120)
|
|
41
|
+
TMPFILE=$(mktemp --suffix=.json)
|
|
42
|
+
|
|
43
|
+
cat > "$TMPFILE" <<EOF
|
|
44
|
+
{
|
|
45
|
+
"type": "rfc",
|
|
46
|
+
"project": "$PROJECT_ID",
|
|
47
|
+
"id": "$RFC_ID",
|
|
48
|
+
"status": "drafting",
|
|
49
|
+
"owner": { "kind": "agent", "id": "researcher" },
|
|
50
|
+
"title": $(echo "$FIRST_LINE" | jq -Rs .),
|
|
51
|
+
"problem": $(echo "$BRIEF_CONTENT" | jq -Rs .),
|
|
52
|
+
"solution": "TBD — to be populated by /cloverleaf-draft-rfc.",
|
|
53
|
+
"unknowns": [],
|
|
54
|
+
"acceptance_criteria": ["RFC body populated by researcher agent"],
|
|
55
|
+
"out_of_scope": []
|
|
56
|
+
}
|
|
57
|
+
EOF
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
6. Save via CLI (validates against rfc.schema.json):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cloverleaf-cli save-rfc $(pwd) "$TMPFILE"
|
|
64
|
+
rm "$TMPFILE"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
7. Print the new RFC ID to stdout:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
echo "$RFC_ID"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
|
|
75
|
+
- This skill does NOT invoke the Researcher agent. Use `/cloverleaf-draft-rfc <RFC_ID>` to populate body from the brief + doc grounding.
|
|
76
|
+
- To drive the whole Discovery flow end-to-end, use `/cloverleaf-discover <brief-file>` instead.
|
|
@@ -11,13 +11,13 @@ The user has invoked this skill with a brief. Your job: turn the brief into a st
|
|
|
11
11
|
|
|
12
12
|
1. Determine the active project. Run:
|
|
13
13
|
```
|
|
14
|
-
|
|
14
|
+
cloverleaf-cli infer-project <repo_root>
|
|
15
15
|
```
|
|
16
16
|
where `<repo_root>` is the current working directory. On failure (no projects, or multiple projects), report the error and ask the user to specify `--project=<id>` or to create a project config first.
|
|
17
17
|
|
|
18
18
|
2. Allocate the next task ID:
|
|
19
19
|
```
|
|
20
|
-
|
|
20
|
+
cloverleaf-cli next-task-id <repo_root> --project=<project>
|
|
21
21
|
```
|
|
22
22
|
Capture the output (e.g., `DEMO-002`).
|
|
23
23
|
|
|
@@ -7,7 +7,11 @@ description: Run the QA agent on a task in the `qa` state (full pipeline only).
|
|
|
7
7
|
|
|
8
8
|
## Steps
|
|
9
9
|
|
|
10
|
-
0.
|
|
10
|
+
0. Pre-flight: ensure you are on `main` and clean stale feedback temp files from previous runs (prevents /tmp leakage between tasks). If not on main, `git checkout main`. If main has uncommitted changes, stop and report.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
rm -f /tmp/cloverleaf-fb-r.json /tmp/cloverleaf-fb-u.json /tmp/cloverleaf-fb-q.json
|
|
14
|
+
```
|
|
11
15
|
|
|
12
16
|
1. Capture the TASK-ID argument.
|
|
13
17
|
|
|
@@ -30,7 +34,7 @@ description: Run the QA agent on a task in the `qa` state (full pipeline only).
|
|
|
30
34
|
if [ -f "<repo_root>/.cloverleaf/config/qa-rules.json" ]; then
|
|
31
35
|
cat "<repo_root>/.cloverleaf/config/qa-rules.json"
|
|
32
36
|
else
|
|
33
|
-
cat
|
|
37
|
+
cat $(cloverleaf-cli plugin-root)/config/qa-rules.json
|
|
34
38
|
fi
|
|
35
39
|
```
|
|
36
40
|
Capture for the subagent as `qa_rules`.
|
|
@@ -43,7 +47,7 @@ description: Run the QA agent on a task in the `qa` state (full pipeline only).
|
|
|
43
47
|
7. Dispatch the QA subagent via the Task tool:
|
|
44
48
|
- `subagent_type`: `general-purpose`
|
|
45
49
|
- `model`: `sonnet`
|
|
46
|
-
- Prompt: contents of
|
|
50
|
+
- Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/qa.md` with substitutions for `{{task}}`, `{{diff}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{qa_rules}}` (the JSON loaded in step 5).
|
|
47
51
|
|
|
48
52
|
8. Parse response: expect `{"verdict": "pass"|"bounce"|"escalate", "summary", "findings", "results"}`.
|
|
49
53
|
|
|
@@ -59,9 +63,15 @@ description: Run the QA agent on a task in the `qa` state (full pipeline only).
|
|
|
59
63
|
**Bounce:**
|
|
60
64
|
1. Write feedback envelope: `echo '<json>' > /tmp/cloverleaf-fb-q.json`
|
|
61
65
|
2. `cloverleaf-cli write-feedback <repo_root> <TASK-ID> /tmp/cloverleaf-fb-q.json --prefix=q`
|
|
62
|
-
3.
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
3. Commit the persisted feedback file (was missing pre-v0.4.1 — bug #3):
|
|
67
|
+
```bash
|
|
68
|
+
cd <repo_root>
|
|
69
|
+
git add .cloverleaf/feedback/
|
|
70
|
+
git commit -m "cloverleaf: <TASK-ID> qa feedback"
|
|
71
|
+
```
|
|
72
|
+
4. `cloverleaf-cli advance-status <repo_root> <TASK-ID> implementing agent --path=full_pipeline`
|
|
73
|
+
5. Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> qa bounced → implementing"`.
|
|
74
|
+
6. Report: "✗ QA bounced. `<failed>/<total>` tests failed. State → implementing. Next: `/cloverleaf-implement <TASK-ID>`."
|
|
65
75
|
|
|
66
76
|
**Escalate:**
|
|
67
77
|
1. `cloverleaf-cli advance-status <repo_root> <TASK-ID> escalated agent`
|
|
@@ -7,7 +7,7 @@ description: Run the Reviewer agent on a task in the `review` state. Emits a fee
|
|
|
7
7
|
|
|
8
8
|
## Steps
|
|
9
9
|
|
|
10
|
-
0.
|
|
10
|
+
0. Pre-flight: ensure you are on `main` and clean stale feedback temp files from previous runs (prevents /tmp leakage between tasks):
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
cd <repo_root>
|
|
@@ -17,11 +17,15 @@ description: Run the Reviewer agent on a task in the `review` state. Emits a fee
|
|
|
17
17
|
|
|
18
18
|
If main has uncommitted changes, stop and report — the user must clean up first.
|
|
19
19
|
|
|
20
|
+
```bash
|
|
21
|
+
rm -f /tmp/cloverleaf-fb-r.json /tmp/cloverleaf-fb-u.json /tmp/cloverleaf-fb-q.json
|
|
22
|
+
```
|
|
23
|
+
|
|
20
24
|
1. Capture the TASK-ID argument.
|
|
21
25
|
|
|
22
26
|
2. Load the task:
|
|
23
27
|
```
|
|
24
|
-
|
|
28
|
+
cloverleaf-cli load-task <repo_root> <TASK-ID>
|
|
25
29
|
```
|
|
26
30
|
Verify `status === "review"`. If not, report the current status and stop.
|
|
27
31
|
|
|
@@ -36,7 +40,7 @@ description: Run the Reviewer agent on a task in the `review` state. Emits a fee
|
|
|
36
40
|
5. Dispatch the Reviewer subagent via the Task tool:
|
|
37
41
|
- `subagent_type`: `general-purpose`
|
|
38
42
|
- `model`: `sonnet`
|
|
39
|
-
- Prompt: contents of
|
|
43
|
+
- Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/reviewer.md` with substitutions for `{{task}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{diff}}`.
|
|
40
44
|
|
|
41
45
|
6. Parse the subagent's response. Expect a feedback envelope JSON of the form `{"verdict": "pass"|"bounce", "summary": "...", "findings": [...]}`. Validate shape: verdict must be `pass` or `bounce`; if `bounce`, findings must have at least one entry with `severity` (one of `blocker|error|warning|info`) and `message`.
|
|
42
46
|
|
|
@@ -50,11 +54,17 @@ description: Run the Reviewer agent on a task in the `review` state. Emits a fee
|
|
|
50
54
|
Report: "✓ Review passed. State → automated-gates. Next: `/cloverleaf-merge <TASK-ID>`."
|
|
51
55
|
|
|
52
56
|
**Bounce:**
|
|
53
|
-
1. Write the feedback envelope to a temp file: `echo '<envelope-json>' > /tmp/cloverleaf-fb.json`.
|
|
54
|
-
2. `cloverleaf-cli write-feedback <repo_root> <TASK-ID> /tmp/cloverleaf-fb.json` — captures the path like `.cloverleaf/feedback/<TASK-ID>-r<N>.json`.
|
|
55
|
-
3.
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
1. Write the feedback envelope to a temp file: `echo '<envelope-json>' > /tmp/cloverleaf-fb-r.json`.
|
|
58
|
+
2. `cloverleaf-cli write-feedback <repo_root> <TASK-ID> /tmp/cloverleaf-fb-r.json` — captures the path like `.cloverleaf/feedback/<TASK-ID>-r<N>.json`.
|
|
59
|
+
3. Commit the persisted feedback file (was missing pre-v0.4.1 — bug #3):
|
|
60
|
+
```bash
|
|
61
|
+
cd <repo_root>
|
|
62
|
+
git add .cloverleaf/feedback/
|
|
63
|
+
git commit -m "cloverleaf: <TASK-ID> review feedback"
|
|
64
|
+
```
|
|
65
|
+
4. `cloverleaf-cli advance-status <repo_root> <TASK-ID> implementing agent` — loops back.
|
|
66
|
+
5. Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> review bounced → implementing"`.
|
|
67
|
+
6. Report: "✗ Review bounced. Findings: <summarize findings by severity>. State → implementing. Next: `/cloverleaf-implement <TASK-ID>`."
|
|
58
68
|
|
|
59
69
|
## Rules
|
|
60
70
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-spike
|
|
3
|
+
description: Run a single Spike via the Researcher agent (operation=runSpike). Advances pending → running → completed with findings + recommendation. Usage — /cloverleaf-spike <SPIKE-ID>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — run Spike
|
|
7
|
+
|
|
8
|
+
The user has invoked this skill with a SPIKE-ID (e.g., `CLV-010`).
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Capture `<SPIKE-ID>` as `$SPIKE_ID`. If missing, report usage and stop.
|
|
13
|
+
|
|
14
|
+
2. Load the spike:
|
|
15
|
+
```
|
|
16
|
+
cloverleaf-cli load-spike <repo_root> <SPIKE-ID>
|
|
17
|
+
```
|
|
18
|
+
Verify `status === "pending"`. If not, report and stop.
|
|
19
|
+
|
|
20
|
+
3. Transition pending → running:
|
|
21
|
+
```
|
|
22
|
+
cloverleaf-cli advance-spike <repo_root> <SPIKE-ID> running agent
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
4. Load discovery config:
|
|
26
|
+
```bash
|
|
27
|
+
DOC_CTX=$(cloverleaf-cli discovery-config --repo-root <repo_root> | jq -r .docContextUri)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
5. Dispatch the Researcher subagent via the Task tool:
|
|
31
|
+
- `subagent_type`: `general-purpose`
|
|
32
|
+
- `model`: `sonnet`
|
|
33
|
+
- Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/researcher.md`, with placeholders:
|
|
34
|
+
- `{{operation}}` → `runSpike`
|
|
35
|
+
- `{{spike}}` → the full spike JSON (from step 2, with status now `running`)
|
|
36
|
+
- `{{doc_context_uri}}` → `$DOC_CTX`
|
|
37
|
+
- `{{repo_root}}` → absolute path to the current repo
|
|
38
|
+
- `{{brief}}` → `null` (unused for runSpike)
|
|
39
|
+
- `{{prior_rfc}}`, `{{completed_spikes}}` → `null`
|
|
40
|
+
|
|
41
|
+
6. Parse subagent response. Expected: the spike JSON with `status: "completed"`, `findings: string`, `recommendation: string`. Schema: `spike.schema.json` (validated by save-spike).
|
|
42
|
+
|
|
43
|
+
If output fails schema validation: bounce. Budget: 3 bounces. On exhaustion: report and stop without advancing to completed.
|
|
44
|
+
|
|
45
|
+
7. Save the completed spike:
|
|
46
|
+
```
|
|
47
|
+
cloverleaf-cli save-spike <repo_root> /tmp/spike-$SPIKE_ID.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
8. Transition running → completed:
|
|
51
|
+
```
|
|
52
|
+
cloverleaf-cli advance-spike <repo_root> <SPIKE-ID> completed agent
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
9. Commit:
|
|
56
|
+
```bash
|
|
57
|
+
git add .cloverleaf/spikes/ .cloverleaf/events/
|
|
58
|
+
git commit -m "cloverleaf: spike $SPIKE_ID completed"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
10. Report: spike findings summary.
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- Orchestrator (`/cloverleaf-discover`) loops this for every spike in the RFC's `unknowns[]` (materialised as Spike work items by `/cloverleaf-draft-rfc`) before re-drafting the RFC.
|
|
66
|
+
- If `method === "prototype"` or `method === "benchmark"`: the Researcher agent describes what to prototype/benchmark, not implement it. v0.5 does not build prototypes — that's Delivery's job.
|
|
@@ -7,7 +7,7 @@ description: Run the UI Reviewer agent on a task in the `ui-review` state (full
|
|
|
7
7
|
|
|
8
8
|
## Steps
|
|
9
9
|
|
|
10
|
-
0.
|
|
10
|
+
0. Pre-flight: ensure you are on `main` and clean stale feedback temp files from previous runs (prevents /tmp leakage between tasks):
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
cd <repo_root>
|
|
@@ -17,11 +17,15 @@ description: Run the UI Reviewer agent on a task in the `ui-review` state (full
|
|
|
17
17
|
|
|
18
18
|
If main has uncommitted changes, stop and report.
|
|
19
19
|
|
|
20
|
+
```bash
|
|
21
|
+
rm -f /tmp/cloverleaf-fb-r.json /tmp/cloverleaf-fb-u.json /tmp/cloverleaf-fb-q.json
|
|
22
|
+
```
|
|
23
|
+
|
|
20
24
|
1. Capture the TASK-ID argument.
|
|
21
25
|
|
|
22
26
|
2. Load the task:
|
|
23
27
|
```
|
|
24
|
-
|
|
28
|
+
cloverleaf-cli load-task <repo_root> <TASK-ID>
|
|
25
29
|
```
|
|
26
30
|
Verify `status === "ui-review"`. If not, report and stop.
|
|
27
31
|
|
|
@@ -35,7 +39,7 @@ description: Run the UI Reviewer agent on a task in the `ui-review` state (full
|
|
|
35
39
|
|
|
36
40
|
5. Compute affected routes:
|
|
37
41
|
```bash
|
|
38
|
-
AFFECTED=$(
|
|
42
|
+
AFFECTED=$(cloverleaf-cli affected-routes <repo_root> <TASK-ID>)
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
6. **Empty-set early-exit.** If `AFFECTED` is `[]`, skip the subagent entirely:
|
|
@@ -63,7 +67,7 @@ description: Run the UI Reviewer agent on a task in the `ui-review` state (full
|
|
|
63
67
|
10. Dispatch the UI Reviewer subagent via the Task tool:
|
|
64
68
|
- `subagent_type`: `general-purpose`
|
|
65
69
|
- `model`: `sonnet`
|
|
66
|
-
- Prompt: contents of
|
|
70
|
+
- Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/ui-reviewer.md` with substitutions:
|
|
67
71
|
- `{{task}}`, `{{diff}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{preview_port}}`
|
|
68
72
|
- `{{affected_routes}}` → the value of `$AFFECTED` (verbatim — may be `"all"`, a JSON array, or `[]` but step 6 handled `[]` already)
|
|
69
73
|
- `{{ui_review_config}}` → JSON-stringified result of `cloverleaf-cli ui-review-config <repo_root>` (used by the subagent to scope viewport sizes, thresholds, and axe rule overrides)
|
|
@@ -82,9 +86,15 @@ description: Run the UI Reviewer agent on a task in the `ui-review` state (full
|
|
|
82
86
|
**Bounce:**
|
|
83
87
|
1. Write feedback: `echo '<envelope-json>' > /tmp/cloverleaf-fb-u.json`
|
|
84
88
|
2. `cloverleaf-cli write-feedback <repo_root> <TASK-ID> /tmp/cloverleaf-fb-u.json --prefix=u`
|
|
85
|
-
3.
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
3. Commit the persisted feedback file (was missing pre-v0.4.1 — bug #3):
|
|
90
|
+
```bash
|
|
91
|
+
cd <repo_root>
|
|
92
|
+
git add .cloverleaf/feedback/
|
|
93
|
+
git commit -m "cloverleaf: <TASK-ID> ui-review feedback"
|
|
94
|
+
```
|
|
95
|
+
4. `cloverleaf-cli advance-status <repo_root> <TASK-ID> implementing agent '' full_pipeline`
|
|
96
|
+
5. Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> ui-review bounced → implementing"`.
|
|
97
|
+
6. Report: "✗ UI Review bounced. Findings: <summary by severity>. State → implementing. Next: `/cloverleaf-implement <TASK-ID>`."
|
|
88
98
|
|
|
89
99
|
**Escalate:**
|
|
90
100
|
1. `cloverleaf-cli advance-status <repo_root> <TASK-ID> escalated agent`
|
package/lib/state.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
-
import { tasksDir, projectsDir } from './paths.js';
|
|
6
|
-
import { emitStatusTransition, formatReason } from './events.js';
|
|
7
|
-
|
|
8
|
-
// Import validator from @cloverleaf/standard.
|
|
9
|
-
// The standard package ships TypeScript source only with no exports map.
|
|
10
|
-
// Vitest (via vite-node) resolves .js → .ts for workspace symlinked packages,
|
|
11
|
-
// so the .js convention works here. If it ever fails with "module not found",
|
|
12
|
-
// switch the specifier to '@cloverleaf/standard/validators/index.ts'.
|
|
13
|
-
import { validateStatusTransitionLegality } from '@cloverleaf/standard/validators/index.js';
|
|
14
|
-
import type { StatusTransitions, Task as SMTask } from '@cloverleaf/standard/validators/index.js';
|
|
15
|
-
import { validateOrThrow } from './validate.js';
|
|
16
|
-
|
|
17
|
-
const req = createRequire(import.meta.url);
|
|
18
|
-
|
|
19
|
-
export interface TaskDoc {
|
|
20
|
-
type: 'task';
|
|
21
|
-
project: string;
|
|
22
|
-
id: string;
|
|
23
|
-
title: string;
|
|
24
|
-
status: string;
|
|
25
|
-
risk_class: 'low' | 'high';
|
|
26
|
-
owner: { kind: 'agent' | 'human' | 'system'; id: string };
|
|
27
|
-
acceptance_criteria: string[];
|
|
28
|
-
definition_of_done: string[];
|
|
29
|
-
context: Record<string, unknown>;
|
|
30
|
-
[key: string]: unknown;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ProjectDoc {
|
|
34
|
-
key: string;
|
|
35
|
-
name: string;
|
|
36
|
-
[key: string]: unknown;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function loadTask(repoRoot: string, taskId: string): TaskDoc {
|
|
40
|
-
const path = join(tasksDir(repoRoot), `${taskId}.json`);
|
|
41
|
-
if (!existsSync(path)) throw new Error(`Task ${taskId} not found at ${path}`);
|
|
42
|
-
return JSON.parse(readFileSync(path, 'utf-8')) as TaskDoc;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function saveTask(repoRoot: string, task: TaskDoc): void {
|
|
46
|
-
validateOrThrow('https://cloverleaf.example/schemas/task.schema.json', task);
|
|
47
|
-
const path = join(tasksDir(repoRoot), `${task.id}.json`);
|
|
48
|
-
writeFileSync(path, JSON.stringify(task, null, 2) + '\n');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function loadProject(repoRoot: string, projectId: string): ProjectDoc {
|
|
52
|
-
const path = join(projectsDir(repoRoot), `${projectId}.json`);
|
|
53
|
-
if (!existsSync(path)) throw new Error(`Project ${projectId} not found at ${path}`);
|
|
54
|
-
return JSON.parse(readFileSync(path, 'utf-8')) as ProjectDoc;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function loadTaskStateMachine(): StatusTransitions {
|
|
58
|
-
// state-machines/task.json is a static JSON asset. Navigate from standard's
|
|
59
|
-
// package.json — no exports map support needed.
|
|
60
|
-
const pkgPath = req.resolve('@cloverleaf/standard/package.json');
|
|
61
|
-
const pkgDir = pkgPath.replace(/\/package\.json$/, '');
|
|
62
|
-
return JSON.parse(readFileSync(`${pkgDir}/state-machines/task.json`, 'utf-8')) as StatusTransitions;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function advanceStatus(
|
|
66
|
-
repoRoot: string,
|
|
67
|
-
taskId: string,
|
|
68
|
-
toStatus: string,
|
|
69
|
-
actor: 'agent' | 'human',
|
|
70
|
-
options: { gate?: string; path?: 'fast_lane' | 'full_pipeline' } = {}
|
|
71
|
-
): TaskDoc {
|
|
72
|
-
const task = loadTask(repoRoot, taskId);
|
|
73
|
-
const from = task.status;
|
|
74
|
-
const sm = loadTaskStateMachine();
|
|
75
|
-
|
|
76
|
-
// Read risk_class directly from the task (defaulting to 'low' if absent).
|
|
77
|
-
// The validator derives itemPath from workItem.risk_class: low → fast_lane, else full_pipeline.
|
|
78
|
-
// If caller passed options.path, translate it back to risk_class for the validator.
|
|
79
|
-
const riskClass: 'low' | 'high' =
|
|
80
|
-
options.path === 'fast_lane' ? 'low'
|
|
81
|
-
: options.path === 'full_pipeline' ? 'high'
|
|
82
|
-
: (task.risk_class ?? 'low');
|
|
83
|
-
|
|
84
|
-
// Build a minimal Task-shaped object so the validator can resolve path-tagged transitions.
|
|
85
|
-
const workItemForValidator: SMTask = {
|
|
86
|
-
type: 'task',
|
|
87
|
-
id: task.id,
|
|
88
|
-
project: task.project,
|
|
89
|
-
status: task.status,
|
|
90
|
-
risk_class: riskClass,
|
|
91
|
-
context: { rfc: { project: task.project, id: task.id } },
|
|
92
|
-
definition_of_done: task.definition_of_done,
|
|
93
|
-
acceptance_criteria: task.acceptance_criteria,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const reason = formatReason({ gate: options.gate, path: options.path });
|
|
97
|
-
const event = {
|
|
98
|
-
event_id: randomUUID(),
|
|
99
|
-
event_type: 'status_transition' as const,
|
|
100
|
-
occurred_at: new Date().toISOString(),
|
|
101
|
-
work_item_id: { project: task.project, id: task.id },
|
|
102
|
-
work_item_type: 'task' as const,
|
|
103
|
-
from_status: from,
|
|
104
|
-
to_status: toStatus,
|
|
105
|
-
actor: { kind: actor, id: actor },
|
|
106
|
-
...(reason ? { reason } : {}),
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const result = validateStatusTransitionLegality(event, sm, workItemForValidator);
|
|
110
|
-
if (!result.ok) {
|
|
111
|
-
const msgs = result.violations.map((v) => v.message).join('; ');
|
|
112
|
-
throw new Error(`Illegal transition ${from} → ${toStatus}: ${msgs}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// NEW: emit first, save second. validateStatusTransitionLegality stays above.
|
|
116
|
-
const emittedPath = emitStatusTransition(repoRoot, {
|
|
117
|
-
project: task.project,
|
|
118
|
-
workItemType: 'task',
|
|
119
|
-
workItemId: task.id,
|
|
120
|
-
from,
|
|
121
|
-
to: toStatus,
|
|
122
|
-
actor,
|
|
123
|
-
gate: options.gate,
|
|
124
|
-
path: options.path,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const proposed = { ...task, status: toStatus };
|
|
128
|
-
try {
|
|
129
|
-
saveTask(repoRoot, proposed);
|
|
130
|
-
} catch (err) {
|
|
131
|
-
const inner = err instanceof Error ? err.message : String(err);
|
|
132
|
-
throw new Error(
|
|
133
|
-
`orphan event written to ${emittedPath} but task save failed: ${inner}`
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
return proposed;
|
|
137
|
-
}
|