@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.
Files changed (48) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/VERSION +1 -1
  3. package/config/discovery.json +5 -0
  4. package/config/ui-review.json +2 -1
  5. package/dist/axe-dedupe.mjs +6 -2
  6. package/dist/cli.mjs +137 -4
  7. package/dist/discovery-config.mjs +26 -0
  8. package/dist/feedback.mjs +1 -1
  9. package/dist/ids.mjs +26 -1
  10. package/dist/index.mjs +1 -1
  11. package/dist/plan.mjs +115 -0
  12. package/dist/plugin-path.mjs +19 -0
  13. package/dist/rfc.mjs +38 -0
  14. package/dist/spike.mjs +37 -0
  15. package/dist/task.mjs +57 -0
  16. package/dist/ui-review-config.mjs +5 -1
  17. package/dist/work-item.mjs +49 -0
  18. package/lib/axe-dedupe.ts +13 -2
  19. package/lib/cli.ts +135 -4
  20. package/lib/discovery-config.ts +35 -0
  21. package/lib/feedback.ts +1 -1
  22. package/lib/ids.ts +25 -1
  23. package/lib/index.ts +1 -1
  24. package/lib/plan.ts +147 -0
  25. package/lib/plugin-path.ts +21 -0
  26. package/lib/rfc.ts +62 -0
  27. package/lib/spike.ts +60 -0
  28. package/lib/task.ts +90 -0
  29. package/lib/ui-review-config.ts +6 -1
  30. package/lib/work-item.ts +78 -0
  31. package/package.json +1 -1
  32. package/prompts/plan.md +63 -0
  33. package/prompts/researcher.md +74 -0
  34. package/prompts/ui-reviewer.md +19 -6
  35. package/skills/cloverleaf-breakdown/SKILL.md +74 -0
  36. package/skills/cloverleaf-discover/SKILL.md +139 -0
  37. package/skills/cloverleaf-document/SKILL.md +2 -2
  38. package/skills/cloverleaf-draft-rfc/SKILL.md +99 -0
  39. package/skills/cloverleaf-gate/SKILL.md +106 -0
  40. package/skills/cloverleaf-implement/SKILL.md +3 -3
  41. package/skills/cloverleaf-merge/SKILL.md +26 -5
  42. package/skills/cloverleaf-new-rfc/SKILL.md +76 -0
  43. package/skills/cloverleaf-new-task/SKILL.md +2 -2
  44. package/skills/cloverleaf-qa/SKILL.md +16 -6
  45. package/skills/cloverleaf-review/SKILL.md +18 -8
  46. package/skills/cloverleaf-spike/SKILL.md +66 -0
  47. package/skills/cloverleaf-ui-review/SKILL.md +17 -7
  48. 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
- ~/.claude/plugins/cloverleaf/bin/cloverleaf-cli load-task <repo_root> <TASK-ID>
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
- ~/.claude/plugins/cloverleaf/bin/cloverleaf-cli latest-feedback <repo_root> <TASK-ID>
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 `~/.claude/plugins/cloverleaf/prompts/implementer.md`, with placeholders substituted:
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
- - `cloverleaf-cli advance-status <repo_root> <TASK-ID> merged human final_approval_gate full_pipeline`
57
- - Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> merged (full pipeline)"`.
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>` is ready for you to push and open a PR."
63
- - "Suggested: `git push origin cloverleaf/<TASK-ID>` then open a PR against `main`."
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
- ~/.claude/plugins/cloverleaf/bin/cloverleaf-cli infer-project <repo_root>
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
- ~/.claude/plugins/cloverleaf/bin/cloverleaf-cli next-task-id <repo_root> --project=<project>
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. Ensure you are on `main`. If not, `git checkout main`. If main has uncommitted changes, stop and report.
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 ~/.claude/plugins/cloverleaf/config/qa-rules.json
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 `~/.claude/plugins/cloverleaf/prompts/qa.md` with substitutions for `{{task}}`, `{{diff}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{qa_rules}}` (the JSON loaded in step 5).
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. `cloverleaf-cli advance-status <repo_root> <TASK-ID> implementing agent --path=full_pipeline`
63
- 4. Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> qa bounced → implementing"`.
64
- 5. Report: "✗ QA bounced. `<failed>/<total>` tests failed. State → implementing. Next: `/cloverleaf-implement <TASK-ID>`."
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. Ensure you are on `main`. State is authoritative on main. Run:
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
- ~/.claude/plugins/cloverleaf/bin/cloverleaf-cli load-task <repo_root> <TASK-ID>
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 `~/.claude/plugins/cloverleaf/prompts/reviewer.md` with substitutions for `{{task}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{diff}}`.
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. `cloverleaf-cli advance-status <repo_root> <TASK-ID> implementing agent`loops back.
56
- 4. Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> review bounced → implementing"`.
57
- 5. Report: "✗ Review bounced. Findings: <summarize findings by severity>. State → implementing. Next: `/cloverleaf-implement <TASK-ID>`."
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. Ensure you are on `main`. State is authoritative on main. Run:
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
- ~/.claude/plugins/cloverleaf/bin/cloverleaf-cli load-task <repo_root> <TASK-ID>
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=$(~/.claude/plugins/cloverleaf/bin/cloverleaf-cli affected-routes <repo_root> <TASK-ID>)
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 `~/.claude/plugins/cloverleaf/prompts/ui-reviewer.md` with substitutions:
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. `cloverleaf-cli advance-status <repo_root> <TASK-ID> implementing agent '' full_pipeline`
86
- 4. Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> ui-review bounced → implementing"`.
87
- 5. Report: "✗ UI Review bounced. Findings: <summary by severity>. State → implementing. Next: `/cloverleaf-implement <TASK-ID>`."
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
- }