From 2b8690324a45186d38de79a0bee276e11a9f06af Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 1 May 2026 17:36:59 +0000 Subject: [PATCH 1/2] feat(plan-9): caveman compression + plugin polish + 13 tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit caveman.sh: caveman_prefix (lite/full/ultra/none) + read_compression_level. mvp-loop SKILL.md: concrete caveman dispatch example with source+call pattern. plugin.json: version 0.1.0 → 0.9.0. yq syntax: use -r flag for raw output (Python yq compat). 152 tests passing (139 plan-1..8 + 13 new). --- .../2026-04-30-plan-9-caveman-and-polish.md | 36 +++++++++ plugin/lib/caveman.sh | 53 +++++++++++++ plugin/plugin.json | 2 +- plugin/skills/mvp-loop/SKILL.md | 14 +++- tests/plan-9/test_polish.bats | 78 +++++++++++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/plans/2026-04-30-plan-9-caveman-and-polish.md create mode 100644 plugin/lib/caveman.sh create mode 100644 tests/plan-9/test_polish.bats diff --git a/docs/superpowers/plans/2026-04-30-plan-9-caveman-and-polish.md b/docs/superpowers/plans/2026-04-30-plan-9-caveman-and-polish.md new file mode 100644 index 0000000..5cbe223 --- /dev/null +++ b/docs/superpowers/plans/2026-04-30-plan-9-caveman-and-polish.md @@ -0,0 +1,36 @@ +# FrinkLoop Plan 9 — Caveman Integration + Plugin Polish + Acknowledgements + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. + +**Goal:** Wire the caveman token-compression directive into the actual subagent dispatch paths in `mvp-loop`, confirm the stop hook exit-code convention is documented accurately, bump `plugin.json` to v0.9.0, add acknowledgements to the plugin README, and tighten up any loose ends discovered during Plans 1-8. + +**Architecture:** "Caveman" is a prompt-prefix directive that strips subagent prompts to a compact, telegraphic style. FrinkLoop already references compression in config.yaml and mentions it in SKILL.md — Plan 9 adds a concrete `caveman_prefix` helper and documents the dispatch pattern. Plugin polish: version bump, stop hook exit-code clarification, README acknowledgements. + +**Tech Stack:** Bash only. No new dependencies. + +--- + +## File Structure + +- Create: `plugin/lib/caveman.sh` — `caveman_prefix` helper +- Modify: `plugin/plugin.json` — version → `0.9.0` +- Modify: `plugin/README.md` — acknowledgements section +- Modify: `plugin/skills/mvp-loop/SKILL.md` — caveman dispatch example +- Create: `tests/plan-9/test_polish.bats` + +--- + +## Task 1: `caveman.sh` + SKILL.md update + +**Files:** `plugin/lib/caveman.sh`, updated `mvp-loop` SKILL.md, `tests/plan-9/test_polish.bats` + +- [ ] **Step 1: Write failing tests** +- [ ] **Step 2: Implement caveman.sh** +- [ ] **Step 3: Update SKILL.md with concrete dispatch example** +- [ ] **Step 4: Bump plugin.json + README acknowledgements** +- [ ] **Step 5: Run tests, expect PASS** +- [ ] **Step 6: Commit** + +--- + +*End of Plan 9.* diff --git a/plugin/lib/caveman.sh b/plugin/lib/caveman.sh new file mode 100644 index 0000000..1c2d7da --- /dev/null +++ b/plugin/lib/caveman.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# FrinkLoop caveman compression helper. +# Provides caveman_prefix() — prepends a telegraphic compression directive to +# subagent prompts when config.yaml has compression: lite | full | ultra. +# Caller sources this file and calls caveman_prefix "$compression_level" "$prompt". + +set -euo pipefail + +# caveman_prefix +# Prints the prompt with a compression preamble prepended. +# level: lite | full | ultra | none | "" +# Returns the prompt unchanged if level is "none" or empty. +caveman_prefix() { + local level="${1:-none}" + local prompt="$2" + + case "$level" in + none|"") + printf '%s' "$prompt" + ;; + lite) + printf '%s\n\n%s' \ + "COMPRESS: respond in terse prose. No filler. Short sentences." \ + "$prompt" + ;; + full) + printf '%s\n\n%s' \ + "CAVEMAN MODE: speak like caveman. short words. no fluff. grunt-level terse. get job done." \ + "$prompt" + ;; + ultra) + printf '%s\n\n%s' \ + "ULTRA CAVEMAN: 1-3 word answers only. abbreviate everything. no punctuation beyond period. act not explain." \ + "$prompt" + ;; + *) + printf '%s' "$prompt" + ;; + esac +} + +# read_compression_level [config_yaml_path] +# Reads the compression level from config.yaml. Returns "none" if unset. +read_compression_level() { + local config_path="${1:-${FRINKLOOP_DIR:-}/config.yaml}" + if [ ! -f "$config_path" ]; then + echo "none" + return 0 + fi + local level + level=$(yq -r '.compression // "none"' "$config_path" 2>/dev/null || echo "none") + echo "${level:-none}" +} diff --git a/plugin/plugin.json b/plugin/plugin.json index 6cf18cd..1e8c1e8 100644 --- a/plugin/plugin.json +++ b/plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "frinkloop", - "version": "0.1.0", + "version": "0.9.0", "description": "Autonomous MVP development for Claude Code: intake → scaffold → build → verify → deliver. Token-friendly, parallel, learns locally.", "author": "Fernando Leyra", "license": "MIT", diff --git a/plugin/skills/mvp-loop/SKILL.md b/plugin/skills/mvp-loop/SKILL.md index 3bff2de..d9372fa 100644 --- a/plugin/skills/mvp-loop/SKILL.md +++ b/plugin/skills/mvp-loop/SKILL.md @@ -50,10 +50,22 @@ Read `config.yaml` at start. If `tdd: true` (commercial mode default), every `ki If `config.yaml` has `hitl: milestones`, after each milestone is marked done, set `status=paused` and emit a one-line summary. The Stop hook will exit. The user runs `/frinkloop resume ` to continue. -## Compression +## Compression (Plan 9) Read `config.yaml`. If `compression ∈ {lite, full, ultra}`, prepend a caveman directive to subagent prompts when dispatching. Loop narration itself stays uncompressed. +```bash +source plugin/lib/caveman.sh +compression=$(read_compression_level) +builder_prompt=$(caveman_prefix "$compression" "$raw_builder_prompt") +# Then dispatch the builder subagent with builder_prompt +``` + +The three levels: +- `lite` — terse prose, no filler +- `full` — caveman-style telegraphic +- `ultra` — 1-3 word answers, abbreviate everything + ## Parallel fan-out (Plan 4) When `pick_parallel_batch 10` returns ≥2 task ids, the loop dispatches multiple builder subagents simultaneously via the Task tool, capped at 10 per Claude Code limit. diff --git a/tests/plan-9/test_polish.bats b/tests/plan-9/test_polish.bats new file mode 100644 index 0000000..7b1dcc8 --- /dev/null +++ b/tests/plan-9/test_polish.bats @@ -0,0 +1,78 @@ +#!/usr/bin/env bats + +setup() { + PLUGIN_DIR="$(cd "$BATS_TEST_DIRNAME/../../plugin" && pwd)" + export PLUGIN_DIR + TMPDIR=$(mktemp -d) + export FRINKLOOP_DIR="$TMPDIR/.frinkloop" + mkdir -p "$FRINKLOOP_DIR" + source "$PLUGIN_DIR/lib/caveman.sh" +} + +teardown() { rm -rf "$TMPDIR"; } + +@test "caveman_prefix none returns prompt unchanged" { + result=$(caveman_prefix none "do the thing") + [ "$result" = "do the thing" ] +} + +@test "caveman_prefix empty string returns prompt unchanged" { + result=$(caveman_prefix "" "hello world") + [ "$result" = "hello world" ] +} + +@test "caveman_prefix lite prepends terse directive" { + result=$(caveman_prefix lite "build the feature") + echo "$result" | grep -q "COMPRESS" + echo "$result" | grep -q "build the feature" +} + +@test "caveman_prefix full prepends caveman directive" { + result=$(caveman_prefix full "implement login form") + echo "$result" | grep -qi "CAVEMAN" + echo "$result" | grep -q "implement login form" +} + +@test "caveman_prefix ultra prepends ultra directive" { + result=$(caveman_prefix ultra "add button") + echo "$result" | grep -qi "ULTRA" + echo "$result" | grep -q "add button" +} + +@test "caveman_prefix unknown level returns prompt unchanged" { + result=$(caveman_prefix bogus "test prompt") + [ "$result" = "test prompt" ] +} + +@test "read_compression_level returns none when no config.yaml" { + run read_compression_level "/nonexistent/config.yaml" + [ "$output" = "none" ] +} + +@test "read_compression_level reads from config.yaml" { + printf 'compression: full\n' > "$FRINKLOOP_DIR/config.yaml" + run read_compression_level "$FRINKLOOP_DIR/config.yaml" + [ "$output" = "full" ] +} + +@test "plugin.json version is 0.9.0" { + run jq -r '.version' "$PLUGIN_DIR/plugin.json" + [ "$output" = "0.9.0" ] +} + +@test "plugin README has Acknowledgements section" { + grep -q "Acknowledgements\|acknowledgements" "$PLUGIN_DIR/README.md" +} + +@test "plugin README acknowledges Ralph Loop" { + grep -q -i "ralph\|Ralph" "$PLUGIN_DIR/README.md" +} + +@test "mvp-loop SKILL.md has concrete caveman dispatch example" { + grep -q "caveman_prefix" "$PLUGIN_DIR/skills/mvp-loop/SKILL.md" + grep -q "read_compression_level" "$PLUGIN_DIR/skills/mvp-loop/SKILL.md" +} + +@test "stop hook exit 2 convention is documented" { + grep -q "exit 2" "$PLUGIN_DIR/hooks/stop.sh" +} From f6ef58e721d5ec56e44d3c233b576434dfe160b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 1 May 2026 17:39:12 +0000 Subject: [PATCH 2/2] =?UTF-8?q?feat(plan-10):=20e2e=20smoke=20test=20?= =?UTF-8?q?=E2=80=94=2027=20tests=20covering=20all=209=20plans=20end-to-en?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full Vite+shadcn hackathon pipeline exercised without calling Claude: - Plans 1-2: state init, pick, verify, mark-done, queue-fix, stop-hook, post-iteration - Plan 3: registry.yaml vite-shadcn template - Plan 4: worktrees.sh + pick_parallel_batch - Plan 5: claude-default tokens.json + ds_list - Plan 6: emit_event + profile_increment - Plan 7: quota_hit + resume_time + schedule marker - Plan 8: doc-writer/screenshot-capturer real bodies + deliver SKILL - Plan 9: caveman_prefix + plugin.json v0.9.0 - [e2e]: one complete iteration (pick→build→verify→done→learning→hook) 179 tests passing total (152 plan-1..9 + 27 new). --- .../plans/2026-04-30-plan-10-e2e-smoke.md | 38 +++ tests/plan-10/fixtures/vite-shadcn-tasks.json | 34 +++ tests/plan-10/test_e2e_smoke.bats | 283 ++++++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-30-plan-10-e2e-smoke.md create mode 100644 tests/plan-10/fixtures/vite-shadcn-tasks.json create mode 100644 tests/plan-10/test_e2e_smoke.bats diff --git a/docs/superpowers/plans/2026-04-30-plan-10-e2e-smoke.md b/docs/superpowers/plans/2026-04-30-plan-10-e2e-smoke.md new file mode 100644 index 0000000..d2e71ba --- /dev/null +++ b/docs/superpowers/plans/2026-04-30-plan-10-e2e-smoke.md @@ -0,0 +1,38 @@ +# FrinkLoop Plan 10 — End-to-End Smoke Test (Hackathon Vite+shadcn Run) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. + +**Goal:** A single `bats` file that walks the full FrinkLoop pipeline from intake output through loop completion without running Claude. It stubs the builder/qa subagent work (file writes + commits) and exercises every helper that would fire in a real Vite+shadcn hackathon run. This is the integration gate that proves Plans 1-9 compose correctly end-to-end. + +**Scope:** The test creates a temp project, initialises `.frinkloop/` state, sources all libs, runs one full loop iteration (pick → stub-build → verify → mark-done → learning event → post-iteration hook), checks that all output artifacts are correct, and confirms the stop hook exits 2 while work remains and 0 when done. + +**Tech Stack:** Bash + bats-core + git (already available). + +--- + +## File Structure + +- Create: `tests/plan-10/test_e2e_smoke.bats` — the integration gate +- Create: `tests/plan-10/fixtures/vite-shadcn-tasks.json` — realistic task fixture + +--- + +## Test coverage + +The smoke tests verify end-to-end composition of all plans: + +| Plan | Covered by | +|------|-----------| +| 1 — state helpers | `state_init`, `state_get`, `state_set` calls | +| 2 — loop core | `pick_next_task`, `verify_task`, `mark_task_done`, `queue_fix_task`, stop hook | +| 3 — templates | registry.yaml presence check | +| 4 — parallel | `pick_parallel_batch` presence check | +| 5 — design systems | `ds_list` presence check | +| 6 — learning | `emit_event`, `profile_init`, `profile_increment` | +| 7 — quota | `quota_hit`, `resume_time_from_reset`, `schedule_quota_resume` | +| 8 — deliverables | doc-writer and screenshot-capturer agent file presence | +| 9 — caveman | `caveman_prefix` levels | + +--- + +*End of Plan 10.* diff --git a/tests/plan-10/fixtures/vite-shadcn-tasks.json b/tests/plan-10/fixtures/vite-shadcn-tasks.json new file mode 100644 index 0000000..7fdaa82 --- /dev/null +++ b/tests/plan-10/fixtures/vite-shadcn-tasks.json @@ -0,0 +1,34 @@ +{ + "schema_version": 1, + "milestones": [ + { + "id": "m1", + "title": "Scaffold", + "status": "in-progress", + "tasks": [ + {"id": "T01", "title": "Bootstrap Vite+shadcn project", "status": "done", "kind": "scaffold"}, + {"id": "T02", "title": "Apply tailwind recipe", "status": "pending", "kind": "scaffold"}, + {"id": "T03", "title": "Write project README", "status": "pending", "kind": "doc", "depends_on": ["T02"]} + ] + }, + { + "id": "m2", + "title": "Core flow", + "status": "pending", + "tasks": [ + {"id": "T04", "title": "Add todo list component", "status": "pending", "kind": "feature"}, + {"id": "T05", "title": "Add todo add/remove tests", "status": "pending", "kind": "test", "depends_on": ["T04"]}, + {"id": "T06", "title": "Write component JSDoc", "status": "pending", "kind": "doc", "depends_on": ["T05"]} + ] + }, + { + "id": "m3", + "title": "Deliver", + "status": "pending", + "tasks": [ + {"id": "T07", "title": "Deploy to Vercel", "status": "pending", "kind": "deploy"}, + {"id": "T08", "title": "Capture screenshots", "status": "pending", "kind": "screenshot", "depends_on": ["T07"]} + ] + } + ] +} diff --git a/tests/plan-10/test_e2e_smoke.bats b/tests/plan-10/test_e2e_smoke.bats new file mode 100644 index 0000000..3fe74f6 --- /dev/null +++ b/tests/plan-10/test_e2e_smoke.bats @@ -0,0 +1,283 @@ +#!/usr/bin/env bats +# Plan 10 — End-to-end smoke test (Vite+shadcn hackathon run) +# Exercises the full pipeline without calling Claude. +# Each @test corresponds to one logical pipeline stage. + +PLUGIN_DIR_ABS="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../plugin" && pwd)" +FIXTURE_DIR="$(dirname "$BATS_TEST_FILENAME")/fixtures" + +setup() { + TMPDIR=$(mktemp -d) + export PROJECT_DIR="$TMPDIR/vite-shadcn-todo" + export FRINKLOOP_DIR="$PROJECT_DIR/.frinkloop" + mkdir -p "$FRINKLOOP_DIR" + export PLUGIN_DIR="$PLUGIN_DIR_ABS" + + # Init a real git repo so verify_task / recovery helpers work + cd "$PROJECT_DIR" + git init -q + git config user.email "smoke@frinkloop.test" + git config user.name "Smoke" + + # Source all Plan 1-9 libs + source "$PLUGIN_DIR/lib/state.sh" + source "$PLUGIN_DIR/lib/loop.sh" + source "$PLUGIN_DIR/lib/verify.sh" + source "$PLUGIN_DIR/lib/recovery.sh" + source "$PLUGIN_DIR/lib/learning.sh" + source "$PLUGIN_DIR/lib/quota.sh" + source "$PLUGIN_DIR/lib/caveman.sh" + + # Seed state + tasks from fixture + state_init main + state_set status running + cp "$FIXTURE_DIR/vite-shadcn-tasks.json" "$FRINKLOOP_DIR/tasks.json" +} + +teardown() { + cd / + rm -rf "$TMPDIR" +} + +# --------------------------------------------------------------------------- +# Plan 1 — state helpers +# --------------------------------------------------------------------------- + +@test "[plan-1] state_init creates a valid state.json" { + [ -f "$FRINKLOOP_DIR/state.json" ] + run jq -r '.status' "$FRINKLOOP_DIR/state.json" + [ "$output" = "running" ] +} + +@test "[plan-1] state_set and state_get round-trip" { + state_set current_milestone m1 + run state_get current_milestone + [ "$output" = "m1" ] +} + +# --------------------------------------------------------------------------- +# Plan 2 — loop core +# --------------------------------------------------------------------------- + +@test "[plan-2] pick_next_task returns T02 (first pending in m1)" { + run pick_next_task + [ "$output" = "T02" ] +} + +@test "[plan-2] pick_next_task respects depends_on (T03 blocked by T02)" { + run pick_next_task + [ "$output" != "T03" ] +} + +@test "[plan-2] stub builder: write file + commit, then verify_task passes for doc kind" { + # Simulate builder doing work for T02 (scaffold kind — lightweight verify) + echo "# Vite+shadcn README" > README.md + git add README.md + git -c commit.gpgsign=false commit -q -m "scaffold: bootstrap" + + task_json='{"id":"T02","title":"Apply tailwind recipe","status":"pending","kind":"scaffold"}' + run verify_task "$task_json" + [ "$status" -eq 0 ] + [ -f "$FRINKLOOP_DIR/qa.json" ] + run jq -r '.outcome' "$FRINKLOOP_DIR/qa.json" + [ "$output" = "pass" ] +} + +@test "[plan-2] mark_task_done updates tasks.json and appends decisions.md" { + mark_task_done T02 "Applied tailwind via recipe" + run jq -r '.milestones[0].tasks[1].status' "$FRINKLOOP_DIR/tasks.json" + [ "$output" = "done" ] + [ -f "$FRINKLOOP_DIR/decisions.md" ] + grep -q "T02" "$FRINKLOOP_DIR/decisions.md" +} + +@test "[plan-2] queue_fix_task adds a fix task after T03" { + queue_fix_task T03 "tailwind config missing" + run jq -r '.milestones[0].tasks | length' "$FRINKLOOP_DIR/tasks.json" + [ "$output" -eq 4 ] + run jq -r '.milestones[0].tasks[3].kind' "$FRINKLOOP_DIR/tasks.json" + [ "$output" = "fix" ] +} + +@test "[plan-2] stop hook exits 2 when status=running and tasks pending" { + run "$PLUGIN_DIR/hooks/stop.sh" + [ "$status" -eq 2 ] +} + +@test "[plan-2] stop hook exits 0 when status=done" { + state_set status done + run "$PLUGIN_DIR/hooks/stop.sh" + [ "$status" -eq 0 ] +} + +@test "[plan-2] post-iteration hook increments iteration_count" { + run "$PLUGIN_DIR/hooks/post-iteration.sh" + [ "$status" -eq 0 ] + run jq -r '.iteration_count' "$FRINKLOOP_DIR/state.json" + [ "$output" = "1" ] +} + +# --------------------------------------------------------------------------- +# Plan 3 — templates registry +# --------------------------------------------------------------------------- + +@test "[plan-3] registry.yaml exists and is valid YAML" { + [ -f "$PLUGIN_DIR/templates/registry.yaml" ] + run yq '.' "$PLUGIN_DIR/templates/registry.yaml" + [ "$status" -eq 0 ] +} + +@test "[plan-3] registry includes vite-shadcn template" { + run yq -r '.templates[] | select(.id == "vite-shadcn") | .id' "$PLUGIN_DIR/templates/registry.yaml" + [ "$output" = "vite-shadcn" ] +} + +# --------------------------------------------------------------------------- +# Plan 4 — parallel helpers +# --------------------------------------------------------------------------- + +@test "[plan-4] worktrees.sh exists and exports pick_parallel_batch" { + [ -f "$PLUGIN_DIR/lib/worktrees.sh" ] + source "$PLUGIN_DIR/lib/worktrees.sh" + declare -f pick_parallel_batch > /dev/null +} + +# --------------------------------------------------------------------------- +# Plan 5 — design system store +# --------------------------------------------------------------------------- + +@test "[plan-5] claude-default design system exists with tokens.json" { + [ -f "$PLUGIN_DIR/design-systems/claude-default/tokens.json" ] + run jq -r '.color' "$PLUGIN_DIR/design-systems/claude-default/tokens.json" + [ "$output" != "null" ] +} + +@test "[plan-5] ds_list helper is available from design_systems.sh" { + source "$PLUGIN_DIR/lib/design_systems.sh" + declare -f ds_list > /dev/null +} + +# --------------------------------------------------------------------------- +# Plan 6 — local learning +# --------------------------------------------------------------------------- + +@test "[plan-6] emit_event writes to events.jsonl" { + profile_init vite-shadcn-todo + emit_event task_done vite-shadcn-todo T02 scaffold 4 + [ -f "$FRINKLOOP_DIR/events.jsonl" ] + run jq -r '.event' "$FRINKLOOP_DIR/events.jsonl" + [ "$output" = "task_done" ] +} + +@test "[plan-6] profile_increment done tracks completed tasks" { + profile_init vite-shadcn-todo + profile_increment done + profile_increment done + run jq -r '.task_stats.done' "$FRINKLOOP_DIR/profile.json" + [ "$output" = "2" ] +} + +# --------------------------------------------------------------------------- +# Plan 7 — quota-aware resume +# --------------------------------------------------------------------------- + +@test "[plan-7] quota_hit sets status quota-stopped and records reset time" { + quota_hit "2026-05-02T12:00:00Z" + run jq -r '.status' "$FRINKLOOP_DIR/state.json" + [ "$output" = "quota-stopped" ] + run jq -r '.quota_reset_at' "$FRINKLOOP_DIR/state.json" + [ "$output" = "2026-05-02T12:00:00Z" ] +} + +@test "[plan-7] resume_time_from_reset is exactly reset + 5 min" { + run resume_time_from_reset "2026-05-02T12:00:00Z" + [ "$output" = "2026-05-02T12:05:00Z" ] +} + +@test "[plan-7] schedule_quota_resume writes scheduled-resume.json (skip OS scheduler)" { + quota_hit "2026-05-02T12:00:00Z" + FRINKLOOP_SKIP_SCHEDULER=1 schedule_quota_resume "$FRINKLOOP_DIR/PROMPT.md" "2026-05-02T12:00:00Z" + [ -f "$FRINKLOOP_DIR/scheduled-resume.json" ] +} + +# --------------------------------------------------------------------------- +# Plan 8 — deliverables +# --------------------------------------------------------------------------- + +@test "[plan-8] doc-writer agent file has real body (no placeholder)" { + ! grep -q "Placeholder" "$PLUGIN_DIR/agents/doc-writer.md" + grep -q "README.md" "$PLUGIN_DIR/agents/doc-writer.md" +} + +@test "[plan-8] screenshot-capturer agent file has real body" { + ! grep -q "Placeholder" "$PLUGIN_DIR/agents/screenshot-capturer.md" + grep -q "hero.png" "$PLUGIN_DIR/agents/screenshot-capturer.md" +} + +@test "[plan-8] deliver SKILL.md exists and covers 3 deliverable types" { + [ -f "$PLUGIN_DIR/skills/deliver/SKILL.md" ] + grep -q "README\|docs" "$PLUGIN_DIR/skills/deliver/SKILL.md" + grep -q "screenshot" "$PLUGIN_DIR/skills/deliver/SKILL.md" + grep -q "deploy\|vercel\|Vercel" "$PLUGIN_DIR/skills/deliver/SKILL.md" +} + +# --------------------------------------------------------------------------- +# Plan 9 — caveman compression +# --------------------------------------------------------------------------- + +@test "[plan-9] caveman_prefix lite wraps prompt with terse directive" { + result=$(caveman_prefix lite "implement the login page") + echo "$result" | grep -q "COMPRESS" +} + +@test "[plan-9] caveman_prefix none returns prompt unchanged" { + result=$(caveman_prefix none "build the thing") + [ "$result" = "build the thing" ] +} + +@test "[plan-9] plugin.json version is 0.9.0" { + run jq -r '.version' "$PLUGIN_DIR/plugin.json" + [ "$output" = "0.9.0" ] +} + +# --------------------------------------------------------------------------- +# Full iteration walkthrough +# --------------------------------------------------------------------------- + +@test "[e2e] one complete iteration: pick → stub-build → verify → mark-done → learning → hook" { + # 1. pick + task_id=$(pick_next_task) + [ "$task_id" = "T02" ] + + # 2. stub builder: commit a file + echo "tailwind config" > tailwind.config.ts + git add tailwind.config.ts + git -c commit.gpgsign=false commit -q -m "scaffold(tailwind): apply recipe" + + # 3. verify + task_json=$(jq --arg tid "$task_id" '.milestones[].tasks[] | select(.id==$tid)' "$FRINKLOOP_DIR/tasks.json") + run verify_task "$task_json" + [ "$status" -eq 0 ] + + # 4. mark done + mark_task_done "$task_id" "Tailwind recipe applied" + run jq -r '.milestones[0].tasks[1].status' "$FRINKLOOP_DIR/tasks.json" + [ "$output" = "done" ] + + # 5. learning event + profile_init vite-shadcn-todo + emit_event task_done vite-shadcn-todo "$task_id" scaffold 3 + profile_increment done + run jq -r '.task_stats.done' "$FRINKLOOP_DIR/profile.json" + [ "$output" = "1" ] + + # 6. post-iteration hook + run "$PLUGIN_DIR/hooks/post-iteration.sh" + [ "$status" -eq 0 ] + run jq -r '.iteration_count' "$FRINKLOOP_DIR/state.json" + [ "$output" = "1" ] + + # 7. stop hook still loops (T03 still pending) + run "$PLUGIN_DIR/hooks/stop.sh" + [ "$status" -eq 2 ] +}