@cloverleaf/reference-impl 0.4.1 → 0.5.1
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/dist/cli.mjs +130 -4
- package/dist/discovery-config.mjs +26 -0
- package/dist/ids.mjs +26 -1
- package/dist/index.mjs +1 -1
- package/dist/plan.mjs +116 -0
- package/dist/rfc.mjs +39 -0
- package/dist/spike.mjs +38 -0
- package/dist/task.mjs +58 -0
- package/dist/work-item.mjs +49 -0
- package/lib/cli.ts +127 -4
- package/lib/discovery-config.ts +35 -0
- package/lib/ids.ts +25 -1
- package/lib/index.ts +1 -1
- package/lib/plan.ts +148 -0
- package/lib/rfc.ts +63 -0
- package/lib/spike.ts +61 -0
- package/lib/task.ts +91 -0
- 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/skills/cloverleaf-breakdown/SKILL.md +74 -0
- package/skills/cloverleaf-discover/SKILL.md +140 -0
- package/skills/cloverleaf-draft-rfc/SKILL.md +99 -0
- package/skills/cloverleaf-gate/SKILL.md +106 -0
- package/skills/cloverleaf-new-rfc/SKILL.md +76 -0
- package/skills/cloverleaf-spike/SKILL.md +66 -0
- package/lib/state.ts +0 -137
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloverleaf/reference-impl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Reference implementation of the Cloverleaf methodology as Claude Code skills. Implements the Tight Loop (Implementer + Reviewer).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/prompts/plan.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Plan Agent
|
|
2
|
+
|
|
3
|
+
**Operation:** breakdown
|
|
4
|
+
|
|
5
|
+
**Repo root:** {{repo_root}}
|
|
6
|
+
**Doc context URI:** {{doc_context_uri}}
|
|
7
|
+
|
|
8
|
+
You are the Plan Agent. Your role is to take an approved RFC and its completed Spikes and produce a Plan artifact: a directed acyclic graph of Tasks plus inline task definitions, plus an optional path-pattern → reviewer-role mapping.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
**RFC:** {{rfc}}
|
|
13
|
+
**Completed spikes:** {{spikes}}
|
|
14
|
+
**Path rules (optional):** {{path_rules}}
|
|
15
|
+
|
|
16
|
+
## Your task
|
|
17
|
+
|
|
18
|
+
1. Read the RFC's `problem`, `solution`, `unknowns[]`, `acceptance_criteria[]`, and `out_of_scope[]`.
|
|
19
|
+
2. Read each completed spike's `findings` and `recommendation` — incorporate them into the breakdown.
|
|
20
|
+
3. Decompose the work into 3–8 tasks. Each task must:
|
|
21
|
+
- Be independently testable and reviewable.
|
|
22
|
+
- Have a clear `definition_of_done` (measurable, not aspirational).
|
|
23
|
+
- Have explicit `acceptance_criteria` (what "done" looks like from the outside).
|
|
24
|
+
- Carry a `risk_class`: `"low"` for trivial / doc-only tasks (fast-lane Delivery), `"high"` otherwise (full-pipeline Delivery).
|
|
25
|
+
- Start at `status: "pending"`. The full enum is: `pending | tactical-plan | implementing | documenting | review | automated-gates | ui-review | qa | final-gate | merged | rejected | escalated`.
|
|
26
|
+
Note: "pending" is the correct initial value — the string "todo" is invalid and will be rejected by schema validation.
|
|
27
|
+
4. Build a `task_dag` using the edge-based shape from `dependency-dag.schema.json`:
|
|
28
|
+
- `nodes: Array<{project, id}>` — one workItemRef per task in `tasks[]`.
|
|
29
|
+
- `edges: Array<{from: {project, id}, to: {project, id}}>` — directed edges from prerequisite to dependent. `to` cannot start until `from` completes. Both endpoints must appear in `nodes`. DAG roots are nodes that appear in no edge's `to` field.
|
|
30
|
+
5. If `path_rules` is non-empty, emit `path_reviewer_map: Array<{pattern, role}>` by mapping the rules' path globs to reviewer roles.
|
|
31
|
+
|
|
32
|
+
## Emit a Plan JSON conforming to `plan.schema.json`
|
|
33
|
+
|
|
34
|
+
Required top-level fields:
|
|
35
|
+
- `type: "plan"`
|
|
36
|
+
- `id` — supplied by orchestrator (the next available ID in the project's work-item namespace)
|
|
37
|
+
- `project` — supplied by orchestrator
|
|
38
|
+
- `status: "drafting"`
|
|
39
|
+
- `owner` — `{ "kind": "agent", "id": "plan" }`
|
|
40
|
+
- `parent_rfc: { project: <rfc.project>, id: <rfc.id> }`
|
|
41
|
+
- `task_dag` — as described above
|
|
42
|
+
- `tasks` — array of full Task docs conforming to `task.schema.json`
|
|
43
|
+
|
|
44
|
+
### Critical runtime invariants
|
|
45
|
+
- The set of `tasks[].id` MUST equal the set of workItemRefs in `task_dag.nodes`. No task without a DAG node; no DAG node without a task.
|
|
46
|
+
- `task_dag.edges` MUST form an acyclic graph. Cycles will be rejected by the orchestrator at materialisation time.
|
|
47
|
+
- `additionalProperties: false` on Plan and on each Task — emit ONLY schema-defined fields.
|
|
48
|
+
|
|
49
|
+
### Important: the Plan has NO `title` field
|
|
50
|
+
Unlike RFC / Spike / Task, the Plan schema does not define a `title` field. Do not add one.
|
|
51
|
+
|
|
52
|
+
## Task IDs
|
|
53
|
+
- Each task's `id` must follow the project's ID convention (typically `<PROJECT>-<N>`).
|
|
54
|
+
- Task IDs must not collide with any existing `.cloverleaf/{rfcs,spikes,plans,tasks}/` work item. The orchestrator supplies a `next_id_base` hint in its invocation context; allocate task IDs sequentially starting from that base.
|
|
55
|
+
- Example: if `next_id_base` is `CLV-13` and you're emitting 3 tasks, use `CLV-13`, `CLV-14`, `CLV-15` in `tasks[]` and in `task_dag.nodes`.
|
|
56
|
+
|
|
57
|
+
## Output format
|
|
58
|
+
Write the Plan JSON to stdout, nothing else. No prose, no markdown fences. The orchestrator captures stdout and validates against `plan.schema.json` (and each task via `task.schema.json`). Budget: 3 bounces per invocation.
|
|
59
|
+
|
|
60
|
+
## General rules
|
|
61
|
+
- **Schema compliance is mandatory.** Each task in `tasks[]` must independently pass `task.schema.json` validation.
|
|
62
|
+
- **No file writes.** Orchestrator persists your output.
|
|
63
|
+
- Gate for Plan approval: `task_batch_gate` (approved by human after `gate-pending`).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Researcher Agent
|
|
2
|
+
|
|
3
|
+
**Operation:** {{operation}} (`draftRfc` or `runSpike`)
|
|
4
|
+
|
|
5
|
+
**Repo root:** {{repo_root}}
|
|
6
|
+
**Doc context URI:** {{doc_context_uri}}
|
|
7
|
+
|
|
8
|
+
You are the Researcher Agent. Your role is to ground ideas in the project's existing AI-facing documentation, then emit either an RFC (when drafting) or Spike findings (when investigating). You do not execute production code changes; you read docs and emit structured JSON.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## If operation = draftRfc
|
|
13
|
+
|
|
14
|
+
**Brief:** {{brief}}
|
|
15
|
+
|
|
16
|
+
**Prior RFC (if this is a re-draft after spikes):** {{prior_rfc}}
|
|
17
|
+
**Completed spikes (if any):** {{completed_spikes}}
|
|
18
|
+
|
|
19
|
+
### Your task
|
|
20
|
+
1. Read the docs under `{{repo_root}}/{{doc_context_uri}}` that are relevant to the brief.
|
|
21
|
+
2. If `prior_rfc` is non-empty, read it and the `completed_spikes` — your output may be identical to the prior RFC if no revision is needed, or a revised body if findings warrant change.
|
|
22
|
+
3. Emit a single JSON document conforming to `rfc.schema.json` (from `@cloverleaf/standard`). Required top-level fields:
|
|
23
|
+
- `type: "rfc"`
|
|
24
|
+
- `id` — supplied by orchestrator via `{{repo_root}}` context; preserve the existing ID if prior_rfc is given, else use the orchestrator's allocated ID
|
|
25
|
+
- `project` — supplied by orchestrator
|
|
26
|
+
- `status: "drafting"`
|
|
27
|
+
- `owner` — object `{ kind: "agent"|"human"|"system", id: string }`; use `{ "kind": "agent", "id": "researcher" }`
|
|
28
|
+
- `title` — short name
|
|
29
|
+
- `problem` — paragraph describing what's wrong or missing
|
|
30
|
+
- `solution` — paragraph describing the proposed approach
|
|
31
|
+
- `unknowns` — **array of strings**, each entry is one uncertainty that should become a Spike (e.g. "What is the webkit install size?"). Empty array if no uncertainties.
|
|
32
|
+
- `acceptance_criteria` — array of strings (minItems: 1), each a measurable condition for RFC approval
|
|
33
|
+
- `out_of_scope` — array of strings; can be empty
|
|
34
|
+
|
|
35
|
+
### Important schema notes
|
|
36
|
+
- **The RFC does NOT contain a `spikes` field.** Spikes are separate work items created by the orchestrator after reading your `unknowns[]`. Each `unknowns[]` entry becomes a Spike's `question`.
|
|
37
|
+
- `additionalProperties: false` — do not emit extra fields. Optional schema fields: `parent`, `relationships`, `extensions`.
|
|
38
|
+
|
|
39
|
+
### Output format
|
|
40
|
+
Write the JSON to stdout, nothing else. No prose, no markdown fences, no explanation. The orchestrator captures stdout and validates against the schema. Budget: 3 bounces per invocation.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## If operation = runSpike
|
|
45
|
+
|
|
46
|
+
**Spike input:** {{spike}}
|
|
47
|
+
|
|
48
|
+
### Your task
|
|
49
|
+
1. Read the spike's `question` and `method` fields.
|
|
50
|
+
2. `method` is one of:
|
|
51
|
+
- `research` — read documentation, synthesise an answer
|
|
52
|
+
- `prototype` — describe a minimal prototype (you don't build it — the Implementer does in Delivery; spike output describes what to prototype)
|
|
53
|
+
- `benchmark` — describe what to measure and expected comparison axes
|
|
54
|
+
3. Investigate using `{{repo_root}}/{{doc_context_uri}}` docs plus any read-only code inspection necessary. Do NOT modify files.
|
|
55
|
+
4. Emit the input spike object with these additions/changes:
|
|
56
|
+
- `status: "completed"`
|
|
57
|
+
- `findings: string` — **a single string** summarising evidence-backed observations. Multi-sentence paragraphs are fine; do NOT emit an array.
|
|
58
|
+
- `recommendation: string` — **a single string** summarising the implication for the RFC / Plan.
|
|
59
|
+
|
|
60
|
+
### Schema compliance
|
|
61
|
+
- Preserve all existing spike fields (`id, type, owner, project, title, parent_rfc, question, method`).
|
|
62
|
+
- Add `findings` and `recommendation`.
|
|
63
|
+
- Output must conform to `spike.schema.json` (from `@cloverleaf/standard`).
|
|
64
|
+
- `additionalProperties: false` — no extra fields.
|
|
65
|
+
|
|
66
|
+
### Output format
|
|
67
|
+
Write the updated spike JSON to stdout, nothing else. Budget: 3 bounces per invocation.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## General rules
|
|
72
|
+
- **Schema compliance is mandatory.** If your output fails AJV validation, you bounce and try again. Budget: 3 bounces per invocation.
|
|
73
|
+
- **No side-effects on the filesystem.** Don't write files. The orchestrator persists your output.
|
|
74
|
+
- **No tool use beyond reading.** v0.5 limits Researcher to doc-reading; web search and external tools are future work.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-breakdown
|
|
3
|
+
description: Invoke the Plan agent (operation=breakdown) to decompose an approved RFC + completed spikes into a Plan with task_dag + inline tasks. Transitions Plan drafting → gate-pending (task_batch_gate). Usage — /cloverleaf-breakdown <RFC-ID>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — Plan breakdown
|
|
7
|
+
|
|
8
|
+
The user has invoked this skill with an RFC-ID (e.g., `CLV-009`).
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Capture `<RFC-ID>` as `$RFC_ID`. If missing, report usage and stop.
|
|
13
|
+
|
|
14
|
+
2. Load and verify the RFC is in status `approved`:
|
|
15
|
+
```
|
|
16
|
+
cloverleaf-cli load-rfc <repo_root> <RFC-ID>
|
|
17
|
+
```
|
|
18
|
+
If `status !== "approved"`, report "RFC must be approved before breakdown" and stop.
|
|
19
|
+
|
|
20
|
+
3. Collect all completed Spikes for this RFC. For each `.cloverleaf/spikes/*.json`:
|
|
21
|
+
- Parse the JSON.
|
|
22
|
+
- If `parent_rfc.id === $RFC_ID` AND `parent_rfc.project === <rfc.project>` AND `status === "completed"`: include in the SPIKES array.
|
|
23
|
+
- If zero matches, SPIKES is `[]`.
|
|
24
|
+
|
|
25
|
+
4. Load discovery config:
|
|
26
|
+
```bash
|
|
27
|
+
CFG=$(cloverleaf-cli discovery-config --repo-root <repo_root>)
|
|
28
|
+
DOC_CTX=$(echo "$CFG" | jq -r .docContextUri)
|
|
29
|
+
PROJECT_ID=$(echo "$CFG" | jq -r .projectId)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
5. Compute the next work-item ID (this is the Plan's ID):
|
|
33
|
+
```
|
|
34
|
+
PLAN_ID=$(cloverleaf-cli next-work-item-id <repo_root> $PROJECT_ID)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
6. Dispatch the Plan subagent via the Task tool:
|
|
38
|
+
- `subagent_type`: `general-purpose`
|
|
39
|
+
- `model`: `sonnet`
|
|
40
|
+
- Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/plan.md`, with placeholders:
|
|
41
|
+
- `{{rfc}}` → the full RFC JSON
|
|
42
|
+
- `{{spikes}}` → the SPIKES JSON array
|
|
43
|
+
- `{{doc_context_uri}}` → `$DOC_CTX`
|
|
44
|
+
- `{{repo_root}}` → absolute path to the current repo
|
|
45
|
+
- `{{path_rules}}` → `null` (v0.5 does not auto-populate path_rules; defer to v0.6)
|
|
46
|
+
|
|
47
|
+
In the subagent context, also supply a hint that `next_id_base === $PLAN_ID`, so task IDs in `tasks[]` start at `PLAN_ID + 1`. The Plan agent allocates its own ID as `next_id_base` and task IDs sequentially after.
|
|
48
|
+
|
|
49
|
+
7. Parse subagent response — expected JSON conforming to `plan.schema.json`. Required fields: `id, type: "plan", status: "drafting", owner, project, parent_rfc, task_dag (edge-based), tasks (inline Task docs, status=pending)`. 3-bounce budget per invocation.
|
|
50
|
+
|
|
51
|
+
8. Ensure output `plan.id === $PLAN_ID`, `project === $PROJECT_ID`, `parent_rfc === { project: <rfc.project>, id: $RFC_ID }`. If the subagent drifted, override these before save.
|
|
52
|
+
|
|
53
|
+
9. Save the Plan:
|
|
54
|
+
```
|
|
55
|
+
cloverleaf-cli save-plan <repo_root> /tmp/plan-$PLAN_ID.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
10. Transition drafting → gate-pending:
|
|
59
|
+
```
|
|
60
|
+
cloverleaf-cli advance-plan <repo_root> $PLAN_ID gate-pending agent task_batch_gate
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
11. Commit:
|
|
64
|
+
```bash
|
|
65
|
+
git add .cloverleaf/plans/ .cloverleaf/events/
|
|
66
|
+
git commit -m "cloverleaf: plan $PLAN_ID for RFC $RFC_ID → gate-pending"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
12. Print the Plan ID.
|
|
70
|
+
|
|
71
|
+
## Notes
|
|
72
|
+
|
|
73
|
+
- Tasks from `plan.tasks[]` are NOT yet materialised on disk. Materialisation happens only after a human approves via `/cloverleaf-gate $PLAN_ID approve`, which transitions the Plan to `approved`, at which point the orchestrator runs `cloverleaf-cli materialise-tasks`.
|
|
74
|
+
- If the RFC has no completed spikes (`unknowns[]` was empty), SPIKES is `[]` and the Plan agent works from the RFC alone.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-discover
|
|
3
|
+
description: End-to-end Discovery orchestrator. Drives RFC → (Spikes) → Plan → gates → task materialisation. Blocks on human gate decisions (rfc_strategy_gate, task_batch_gate). On Plan approval, materialises tasks and asks whether to kick off /cloverleaf-run on the first DAG root. Usage — /cloverleaf-discover <brief-file>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — discover (Discovery orchestrator)
|
|
7
|
+
|
|
8
|
+
The user has invoked this skill with a brief file path (e.g., `docs/briefs/cross-browser-ui-review.md`).
|
|
9
|
+
|
|
10
|
+
## Branch discipline
|
|
11
|
+
|
|
12
|
+
Each sub-skill runs from `main`. Between steps, confirm branch is `main` before proceeding. All sub-skills return the user to `main`.
|
|
13
|
+
|
|
14
|
+
## Per-agent bounce budget (in-session)
|
|
15
|
+
|
|
16
|
+
- **Researcher and Plan agent bounces are budgeted inside the sub-skills** (`/cloverleaf-draft-rfc`, `/cloverleaf-spike`, `/cloverleaf-breakdown`), each with a 3-bounce per-invocation budget. The orchestrator invokes each sub-skill once per step — if a sub-skill exhausts its budget, it halts, and the orchestrator treats that as a hard halt and dumps state.
|
|
17
|
+
- **Revise loops are budgeted in the orchestrator:**
|
|
18
|
+
```
|
|
19
|
+
MAX_REVISE_LOOPS = 3
|
|
20
|
+
```
|
|
21
|
+
Resets on each `/cloverleaf-discover` invocation.
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. Capture `<brief-file>` as `$BRIEF_FILE`. Verify file exists:
|
|
26
|
+
```bash
|
|
27
|
+
[ -f "$BRIEF_FILE" ] || { echo "Brief file not found: $BRIEF_FILE" >&2; exit 1; }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Verify branch is `main`:
|
|
31
|
+
```bash
|
|
32
|
+
[ "$(git rev-parse --abbrev-ref HEAD)" = "main" ] || { echo "Run /cloverleaf-discover from main" >&2; exit 1; }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. **Create the RFC** — invoke `/cloverleaf-new-rfc $BRIEF_FILE`. Capture the printed RFC ID as `$RFC_ID`.
|
|
36
|
+
|
|
37
|
+
Initialise revise loop counter:
|
|
38
|
+
```
|
|
39
|
+
revise_loops = 0
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
4. **Draft RFC (Researcher draftRfc)** — invoke `/cloverleaf-draft-rfc $RFC_ID`. Researcher runs with a per-invocation bounce budget (3 bounces inside draft-rfc).
|
|
43
|
+
- On bounce exhaustion (draft-rfc exits non-zero): dump state to `.cloverleaf/runs/$RFC_ID/discover-crash.json` and halt.
|
|
44
|
+
- Reload the RFC. Its new status is either `spike-in-flight` (unknowns non-empty) or `planning` (unknowns empty).
|
|
45
|
+
|
|
46
|
+
5. **Conditional on RFC status after step 4:**
|
|
47
|
+
- If `planning`: skip the spike loop below; proceed to step 6.
|
|
48
|
+
- If `spike-in-flight`: run every pending spike linked to this RFC:
|
|
49
|
+
```bash
|
|
50
|
+
for each spike in .cloverleaf/spikes/*.json where parent_rfc.id === $RFC_ID AND status === "pending":
|
|
51
|
+
invoke /cloverleaf-spike <SPIKE_ID>.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If `/cloverleaf-spike` exits non-zero or leaves the spike in a non-`completed` status after returning, treat that as bounce exhaustion: dump state to `.cloverleaf/runs/$RFC_ID/discover-crash.json` (including the spike ID) and halt. The orchestrator does NOT resume partially-run spike trees — if any spike is unresolved, the whole Discovery halts.
|
|
55
|
+
|
|
56
|
+
After all spikes complete:
|
|
57
|
+
|
|
58
|
+
1. Transition RFC spike-in-flight → drafting:
|
|
59
|
+
```
|
|
60
|
+
cloverleaf-cli advance-rfc <repo_root> $RFC_ID drafting agent
|
|
61
|
+
```
|
|
62
|
+
2. Re-invoke `/cloverleaf-draft-rfc $RFC_ID` — Researcher sees the completed spikes and may revise the RFC body. draft-rfc itself re-transitions the RFC to `spike-in-flight` (if new unknowns emerge — should not happen the second time since spikes were answered) or `planning` (expected path).
|
|
63
|
+
|
|
64
|
+
Rationale: redundant redraft is cheap; avoids introducing an off-contract "rfc_revision_required" boolean. The human gate at step 6 catches any missed revisions.
|
|
65
|
+
|
|
66
|
+
6. **Gate: rfc_strategy_gate** — transition RFC to gate-pending:
|
|
67
|
+
```
|
|
68
|
+
cloverleaf-cli advance-rfc <repo_root> $RFC_ID gate-pending agent rfc_strategy_gate
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Prompt human (blocking readline):
|
|
72
|
+
```
|
|
73
|
+
RFC $RFC_ID at rfc_strategy_gate: approve / reject / revise [reason]? > _
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Parse input. On:
|
|
77
|
+
- `approve` → invoke `/cloverleaf-gate $RFC_ID approve`. Continue to step 7.
|
|
78
|
+
- `reject [reason]` → invoke `/cloverleaf-gate $RFC_ID reject [reason]`. Exit with summary.
|
|
79
|
+
- `revise [reason]` → invoke `/cloverleaf-gate $RFC_ID revise [reason]`. `revise_loops += 1`. If `revise_loops >= MAX_REVISE_LOOPS`, dump state and halt. Else loop back to step 4.
|
|
80
|
+
|
|
81
|
+
7. **Plan breakdown** — invoke `/cloverleaf-breakdown $RFC_ID`. Per-invocation bounce budget (3 bounces inside breakdown). Capture `$PLAN_ID`. On bounce exhaustion: dump state and halt.
|
|
82
|
+
|
|
83
|
+
8. **Gate: task_batch_gate** — Plan is already in `gate-pending` (set by breakdown). Prompt:
|
|
84
|
+
```
|
|
85
|
+
PLN $PLAN_ID at task_batch_gate: approve / reject [reason]? > _
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
On:
|
|
89
|
+
- `approve` → invoke `/cloverleaf-gate $PLAN_ID approve`. Continue.
|
|
90
|
+
- `reject [reason]` → invoke `/cloverleaf-gate $PLAN_ID reject [reason]`. Exit with summary.
|
|
91
|
+
|
|
92
|
+
9. **Materialise tasks**:
|
|
93
|
+
```bash
|
|
94
|
+
OUT=$(cloverleaf-cli materialise-tasks <repo_root> $PLAN_ID)
|
|
95
|
+
TASK_IDS=$(echo "$OUT" | jq -r '.task_ids[]')
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
On materialise-tasks failure (cycle detected, or AJV validation error):
|
|
99
|
+
- The error message from `cloverleaf-cli` identifies the failing task.
|
|
100
|
+
- `materialiseTasksFromPlan` is atomic: the cycle-check and AJV pre-validation run BEFORE any file write, so no task files were created.
|
|
101
|
+
- The Plan remains in `approved` status (the gate was legitimately approved; the materialisation issue is with the Plan's own task DAG, not the gate decision).
|
|
102
|
+
- Dump state to `.cloverleaf/runs/$PLAN_ID/materialise-crash.json` and halt.
|
|
103
|
+
- Operator investigation typically leads to a new `/cloverleaf-breakdown` run to produce a corrected Plan (the rejected path is via the ORIGINAL gate, not a post-approval state correction).
|
|
104
|
+
|
|
105
|
+
10. **Compute DAG roots**:
|
|
106
|
+
```bash
|
|
107
|
+
PLAN_JSON=$(cloverleaf-cli load-plan <repo_root> $PLAN_ID)
|
|
108
|
+
|
|
109
|
+
# DAG roots = nodes whose id never appears as `to.id` in any edge.
|
|
110
|
+
# v0.5 picks the FIRST root only; multi-root concurrent Delivery is v0.6.
|
|
111
|
+
FIRST_ROOT=$(echo "$PLAN_JSON" | jq -r '
|
|
112
|
+
(.task_dag.edges | map(.to.id)) as $targets
|
|
113
|
+
| [.task_dag.nodes[] | select(.id as $n | ($targets | index($n)) | not) | .id][0]
|
|
114
|
+
')
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
v0.5 simplified rule: pick the FIRST root only (first `nodes[]` entry whose `id` does not appear in any `edges[].to.id`). Defer multi-root walk to v0.6.
|
|
118
|
+
|
|
119
|
+
11. **Prompt to kick off first root** (blocking readline):
|
|
120
|
+
```
|
|
121
|
+
N tasks materialised: $TASK_IDS.
|
|
122
|
+
DAG roots: $FIRST_ROOT.
|
|
123
|
+
Run first root via /cloverleaf-run now? [y/N] > _
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
On `y` or `yes` (case-insensitive): invoke `/cloverleaf-run <FIRST_ROOT>`. On anything else: exit with summary.
|
|
127
|
+
|
|
128
|
+
12. **Exit summary**:
|
|
129
|
+
- RFC ID + final status
|
|
130
|
+
- Spike IDs + findings summary (if any)
|
|
131
|
+
- Plan ID + task count
|
|
132
|
+
- Materialised task IDs
|
|
133
|
+
- Whether first root was invoked (and its outcome if so)
|
|
134
|
+
|
|
135
|
+
## Notes
|
|
136
|
+
|
|
137
|
+
- v0.5 prompts only for the FIRST DAG root. Multi-root concurrent Delivery is v0.6 scope.
|
|
138
|
+
- All bounces and revise loops halt cleanly with a state dump at `.cloverleaf/runs/<RFC_ID>/discover-crash.json`. No partial work is left in an inconsistent state — the gate_decision events and work-item status are always coherent.
|
|
139
|
+
- The readline prompts are compatible with the user's session-bridging tool for automated dogfooding.
|
|
140
|
+
- "Invoke `/cloverleaf-X`" means spawn the sub-skill via the Skill tool — do NOT re-enter `/cloverleaf-discover` recursively. Sub-skills run in-process (same session) and return control to this orchestrator.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-draft-rfc
|
|
3
|
+
description: Invoke the Researcher agent (operation=draftRfc) to populate an RFC body from its brief. Emits zero or more Spike work items from the RFC's unknowns[]. Transitions the RFC drafting → spike-in-flight (if unknowns exist) or drafting → planning (if no unknowns). Usage — /cloverleaf-draft-rfc <RFC-ID>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — draft RFC
|
|
7
|
+
|
|
8
|
+
The user has invoked this skill with an RFC-ID (e.g., `CLV-009`).
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Capture `<RFC-ID>` as `$RFC_ID`. If missing, report usage and stop.
|
|
13
|
+
|
|
14
|
+
2. Load the RFC:
|
|
15
|
+
```
|
|
16
|
+
cloverleaf-cli load-rfc <repo_root> <RFC-ID>
|
|
17
|
+
```
|
|
18
|
+
Parse the JSON. Verify `status === "drafting"`. If not, report and stop.
|
|
19
|
+
|
|
20
|
+
3. Load the discovery config:
|
|
21
|
+
```bash
|
|
22
|
+
CFG=$(cloverleaf-cli discovery-config --repo-root <repo_root>)
|
|
23
|
+
DOC_CTX=$(echo "$CFG" | jq -r .docContextUri)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
4. Gather re-draft context (optional — used when re-invoked after spikes):
|
|
27
|
+
- `PRIOR_RFC`: the current RFC JSON (for a re-draft, same content)
|
|
28
|
+
- `COMPLETED_SPIKES`: for each spike ref linked to this RFC (via spike's `parent_rfc` field), if `status === "completed"`, include its findings/recommendation. Build a JSON array. If none, use `[]`.
|
|
29
|
+
|
|
30
|
+
Find all spikes for this RFC by scanning `.cloverleaf/spikes/*.json` for files whose `parent_rfc.id === $RFC_ID`. Use jq. If none, the array is empty.
|
|
31
|
+
|
|
32
|
+
5. Dispatch the Researcher subagent via the Task tool:
|
|
33
|
+
- `subagent_type`: `general-purpose`
|
|
34
|
+
- `model`: `sonnet`
|
|
35
|
+
- Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/researcher.md`, with placeholders substituted:
|
|
36
|
+
- `{{operation}}` → `draftRfc`
|
|
37
|
+
- `{{brief}}` → the RFC's `problem` field (seeded by `/cloverleaf-new-rfc`)
|
|
38
|
+
- `{{doc_context_uri}}` → `$DOC_CTX`
|
|
39
|
+
- `{{repo_root}}` → absolute path to the current repo
|
|
40
|
+
- `{{prior_rfc}}` → `PRIOR_RFC` JSON (or the literal string `null` for first draft)
|
|
41
|
+
- `{{completed_spikes}}` → `COMPLETED_SPIKES` JSON array (or `[]`)
|
|
42
|
+
- `{{spike}}` → (unused for draftRfc; substitute `null`)
|
|
43
|
+
|
|
44
|
+
6. Parse subagent's response: expected JSON conforming to `rfc.schema.json`. Required fields: `id`, `type: "rfc"`, `status: "drafting"`, `owner`, `project`, `title`, `problem`, `solution`, `unknowns` (array of strings), `acceptance_criteria`, `out_of_scope`.
|
|
45
|
+
|
|
46
|
+
If output fails schema validation: bounce. Budget: 3 bounces per invocation. On budget exhaustion: report and stop without advancing state.
|
|
47
|
+
|
|
48
|
+
7. Ensure output `id === $RFC_ID` and `project === <original>`. If the subagent changed them, override back.
|
|
49
|
+
|
|
50
|
+
8. Save the populated RFC:
|
|
51
|
+
```bash
|
|
52
|
+
cloverleaf-cli save-rfc <repo_root> /tmp/rfc-draft-$RFC_ID.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
9. Inspect `rfc.unknowns[]`:
|
|
56
|
+
|
|
57
|
+
**If `unknowns.length > 0`:** create one Spike per unknown:
|
|
58
|
+
```bash
|
|
59
|
+
for unknown in rfc.unknowns:
|
|
60
|
+
SPIKE_ID=$(cloverleaf-cli next-work-item-id <repo_root> <project>)
|
|
61
|
+
cat > /tmp/spike-$SPIKE_ID.json <<EOF
|
|
62
|
+
{
|
|
63
|
+
"type": "spike",
|
|
64
|
+
"project": "<project>",
|
|
65
|
+
"id": "$SPIKE_ID",
|
|
66
|
+
"status": "pending",
|
|
67
|
+
"owner": { "kind": "agent", "id": "researcher" },
|
|
68
|
+
"title": "<first 80 chars of unknown>",
|
|
69
|
+
"parent_rfc": { "project": "<project>", "id": "$RFC_ID" },
|
|
70
|
+
"question": "<unknown>",
|
|
71
|
+
"method": "research"
|
|
72
|
+
}
|
|
73
|
+
EOF
|
|
74
|
+
cloverleaf-cli save-spike <repo_root> /tmp/spike-$SPIKE_ID.json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then transition RFC:
|
|
78
|
+
```
|
|
79
|
+
cloverleaf-cli advance-rfc <repo_root> <RFC-ID> spike-in-flight agent
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**If `unknowns.length === 0`:** transition RFC directly to planning:
|
|
83
|
+
```
|
|
84
|
+
cloverleaf-cli advance-rfc <repo_root> <RFC-ID> planning agent
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
10. Clean up temp files. Commit state files under `.cloverleaf/`:
|
|
88
|
+
```bash
|
|
89
|
+
git add .cloverleaf/rfcs/ .cloverleaf/spikes/ .cloverleaf/events/
|
|
90
|
+
git commit -m "cloverleaf: draft RFC $RFC_ID + spike emission"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
11. Report: RFC body populated; N spikes created (list IDs); RFC status now `spike-in-flight` or `planning`.
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- This skill does NOT prompt for human input. It's a pure agent step.
|
|
98
|
+
- The orchestrator (`/cloverleaf-discover`) invokes this plus manages bounces and gates.
|
|
99
|
+
- For a re-draft after spikes complete, invoke this skill again with the same RFC-ID — it will pick up completed spikes and may revise the RFC body.
|
|
@@ -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.
|
|
@@ -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": $(printf '%s' "$FIRST_LINE" | jq -Rs .),
|
|
51
|
+
"problem": $(printf '%s' "$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.
|