Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…

\\n'\n fi\n\n # Get top-level functions, classes, and decorators\n local top_level\n top_level=$(grep -nE '^(def [a-zA-Z][a-zA-Z0-9_]*|class [a-zA-Z][a-zA-Z0-9_]*|async def [a-zA-Z][a-zA-Z0-9_]*|@(property|staticmethod|classmethod|dataclass|runtime_checkable))' \"$file\" 2>/dev/null | grep -v '^\\s*def _' || true)\n if [[ -n \"$top_level\" ]]; then\n output+=\"$top_level\"

Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…

\\n'\n fi\n\n # Get class methods (1-level indent: 4 spaces or 1 tab)\n local methods\n methods=$(grep -nE '^( |\\t)(def [a-zA-Z][a-zA-Z0-9_]*|async def [a-zA-Z][a-zA-Z0-9_]*)' \"$file\" 2>/dev/null | grep -v 'def _[a-zA-Z]' || true)\n if [[ -n \"$methods\" ]]; then\n output+=\"$methods\"

Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…

\\n'\n fi\n\n output=$(echo \"$output\" | sort -t: -k1,1n | uniq)\n\n if [[ -n \"$output\" ]]; then\n echo \"## $rel\"\n while IFS= read -r line; do\n [[ -z \"$line\" ]] && continue\n echo \" $line\"\n done \u003c\u003c\u003c \"$output\"\n echo \"\"\n fi\n done\n}\n\nextract_go() {\n local dir=\"$1\"\n echo \"# Go API Surface\"\n echo \"# Source: $dir\"\n echo \"\"\n\n find \"$dir\" -type f -name '*.go' \\\n -not -name '*_test.go' \\\n -not -path '*/vendor/*' \\\n -print0 2>/dev/null | sort -z | while IFS= read -r -d '' file; do\n\n local rel=\"${file#$dir/}\"\n\n # Exported: functions/types starting with uppercase\n local signatures\n signatures=$(grep -nE '^(func [A-Z]|type [A-Z]|var [A-Z]|const [A-Z])' \"$file\" 2>/dev/null || true)\n\n if [[ -n \"$signatures\" ]]; then\n echo \"## $rel\"\n while IFS= read -r line; do\n echo \" $line\"\n done \u003c\u003c\u003c \"$signatures\"\n echo \"\"\n fi\n done\n}\n\nextract_rust() {\n local dir=\"$1\"\n echo \"# Rust API Surface\"\n echo \"# Source: $dir\"\n echo \"\"\n\n find \"$dir\" -type f -name '*.rs' \\\n -not -path '*/target/*' \\\n -print0 2>/dev/null | sort -z | while IFS= read -r -d '' file; do\n\n local rel=\"${file#$dir/}\"\n\n local signatures\n signatures=$(grep -nE '^pub (fn|struct|enum|trait|type|const|static|mod|use)' \"$file\" 2>/dev/null || true)\n\n if [[ -n \"$signatures\" ]]; then\n echo \"## $rel\"\n while IFS= read -r line; do\n echo \" $line\"\n done \u003c\u003c\u003c \"$signatures\"\n echo \"\"\n fi\n done\n}\n\nextract_ruby() {\n local dir=\"$1\"\n echo \"# Ruby API Surface\"\n echo \"# Source: $dir\"\n echo \"\"\n\n find \"$dir\" -type f -name '*.rb' \\\n -not -name '*_spec.rb' -not -name '*_test.rb' \\\n -not -path '*/spec/*' -not -path '*/test/*' -not -path '*/vendor/*' \\\n -print0 2>/dev/null | sort -z | while IFS= read -r -d '' file; do\n\n local rel=\"${file#$dir/}\"\n\n local signatures\n signatures=$(grep -nE '^\\s*(class |module |def [a-z]|def self\\.)' \"$file\" 2>/dev/null || true)\n\n if [[ -n \"$signatures\" ]]; then\n echo \"## $rel\"\n while IFS= read -r line; do\n echo \" $line\"\n done \u003c\u003c\u003c \"$signatures\"\n echo \"\"\n fi\n done\n}\n\nextract_php() {\n local dir=\"$1\"\n echo \"# PHP API Surface\"\n echo \"# Source: $dir\"\n echo \"\"\n\n find \"$dir\" -type f -name '*.php' \\\n -not -name '*Test.php' \\\n -not -path '*/vendor/*' -not -path '*/tests/*' \\\n -print0 2>/dev/null | sort -z | while IFS= read -r -d '' file; do\n\n local rel=\"${file#$dir/}\"\n\n local signatures\n signatures=$(grep -nE '^\\s*(public |protected |private )?(static )?(function |class |interface |trait |enum )' \"$file\" 2>/dev/null || true)\n\n if [[ -n \"$signatures\" ]]; then\n echo \"## $rel\"\n while IFS= read -r line; do\n echo \" $line\"\n done \u003c\u003c\u003c \"$signatures\"\n echo \"\"\n fi\n done\n}\n\n# Dispatch\ncase \"$LANG\" in\n typescript|javascript) extract_typescript \"$SOURCE_DIR\" ;;\n python) extract_python \"$SOURCE_DIR\" ;;\n go) extract_go \"$SOURCE_DIR\" ;;\n rust) extract_rust \"$SOURCE_DIR\" ;;\n ruby) extract_ruby \"$SOURCE_DIR\" ;;\n php) extract_php \"$SOURCE_DIR\" ;;\n *)\n echo \"# Unknown language: $LANG\"\n echo \"# Could not auto-detect from files in $SOURCE_DIR\"\n echo \"# Use --lang to specify: typescript, python, go, rust, ruby, php\"\n exit 1\n ;;\nesac\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7726,"content_sha256":"4818ec870932b36b3c01ec3d44076f38feb5107d8bf094b04ef1e8d676b8c153"},{"filename":"scripts/run_tests.sh","content":"#!/usr/bin/env bash\n# Universal test runner — wraps framework output into structured JSON.\n# Usage: run_tests.sh \u003cframework> \u003ctest_command> [--all] [--timeout \u003cseconds>]\n#\n# Examples:\n# run_tests.sh jest \"npx jest src/sum.test.ts\"\n# run_tests.sh pytest \"pytest tests/test_sum.py -v\"\n# run_tests.sh jest \"npx jest\" --all\n# run_tests.sh jest \"npx jest\" --timeout 120\n#\n# Output: single JSON object on stdout:\n# {\"status\":\"pass|fail|error\",\"total\":N,\"passed\":N,\"failed\":N,\n# \"failures\":[{\"test_name\":\"...\",\"message\":\"...\",\"stack\":\"...\"}],\n# \"raw_tail\":\"last 30 lines of output\"}\n#\n# Status values:\n# pass — all tests passed (exit 0)\n# fail — one or more tests failed (exit non-zero, parseable output)\n# error — script/compilation/infra error (exit non-zero, no parseable test results)\n\nset -uo pipefail\n# NOTE: intentionally NOT using set -e. Parsing steps may fail on unexpected\n# output formats; we always want to produce JSON, even degraded.\n\nFRAMEWORK=\"${1:?Usage: run_tests.sh \u003cframework> \u003ctest_command> [--all] [--timeout \u003cseconds>]}\"\nTEST_CMD=\"${2:?Usage: run_tests.sh \u003cframework> \u003ctest_command> [--all] [--timeout \u003cseconds>]}\"\n\nshift 2\nTIMEOUT=300 # default 5 minutes\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --all) shift ;; # informational only, doesn't change behavior\n --timeout) TIMEOUT=\"$2\"; shift 2 ;;\n *) shift ;;\n esac\ndone\n\nTMPFILE=$(mktemp)\ntrap 'rm -f \"$TMPFILE\"' EXIT\n\n# Run the test command with timeout, capture output and exit code\nEXIT_CODE=0\nif command -v timeout &>/dev/null; then\n timeout \"$TIMEOUT\" bash -c \"$TEST_CMD\" > \"$TMPFILE\" 2>&1 || EXIT_CODE=$?\nelif command -v gtimeout &>/dev/null; then\n gtimeout \"$TIMEOUT\" bash -c \"$TEST_CMD\" > \"$TMPFILE\" 2>&1 || EXIT_CODE=$?\nelse\n # No timeout command available — run directly\n bash -c \"$TEST_CMD\" > \"$TMPFILE\" 2>&1 || EXIT_CODE=$?\nfi\n\n# Exit code 124 = timeout killed the process\nif [[ \"$EXIT_CODE\" -eq 124 ]]; then\n echo '{\"status\":\"error\",\"total\":0,\"passed\":0,\"failed\":0,\"failures\":[{\"test_name\":\"TIMEOUT\",\"message\":\"Test command exceeded '\"$TIMEOUT\"'s timeout\",\"stack\":\"\"}],\"raw_tail\":\"killed by timeout after '\"$TIMEOUT\"' seconds\"}'\n exit 0\nfi\n\n# Escape raw_tail safely via python3 (handles all JSON-special chars)\nRAW_TAIL=$(tail -30 \"$TMPFILE\" | python3 -c '\nimport sys, json\ntext = sys.stdin.read()\n# json.dumps produces a quoted string with all escaping handled\nprint(json.dumps(text))\n' 2>/dev/null || echo '\"(could not read output)\"')\n# RAW_TAIL is now a JSON-quoted string like \"\\\"line1\\\\nline2\\\"\"\n\n# Emit valid JSON. Uses python3 for safe assembly to avoid printf % issues.\nemit_json() {\n local status=\"$1\" total=\"$2\" passed=\"$3\" failed=\"$4\" failures=\"$5\"\n python3 -c \"\nimport json, sys\nobj = {\n 'status': sys.argv[1],\n 'total': int(sys.argv[2]),\n 'passed': int(sys.argv[3]),\n 'failed': int(sys.argv[4]),\n 'failures': json.loads(sys.argv[5]),\n 'raw_tail': json.loads(sys.argv[6])\n}\nprint(json.dumps(obj))\n\" \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\" \"$RAW_TAIL\" 2>/dev/null || \\\n echo '{\"status\":\"error\",\"total\":0,\"passed\":0,\"failed\":0,\"failures\":[],\"raw_tail\":\"JSON assembly failed\"}'\n}\n\n# Parse based on framework\nparse_jest_vitest() {\n local total=0 passed=0 failed=0 failures=\"[]\"\n\n local summary_line\n summary_line=$(grep -E '(Tests|Test Suites):.*total' \"$TMPFILE\" | tail -1 || true)\n\n if [[ -n \"$summary_line\" ]]; then\n total=$(echo \"$summary_line\" | grep -oE '[0-9]+ total' | grep -oE '[0-9]+' || echo 0)\n passed=$(echo \"$summary_line\" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' || echo 0)\n failed=$(echo \"$summary_line\" | grep -oE '[0-9]+ failed' | grep -oE '[0-9]+' || echo 0)\n fi\n\n if [[ \"$failed\" -gt 0 ]] || [[ \"$EXIT_CODE\" -ne 0 ]]; then\n failures=$(python3 -c \"\nimport re, json, sys\n\ntext = open(sys.argv[1]).read()\npattern = r'● (.+?)(?:\\n\\n|\\n\\s*\\n)([\\s\\S]*?)(?=\\n\\s*●|\\n\\s*Test Suites:|\\Z)'\nmatches = re.findall(pattern, text)\nresults = []\nfor name, body in matches[:10]:\n msg_lines = body.strip().split('\\n')\n msg = msg_lines[0] if msg_lines else ''\n stack = '\\n'.join(msg_lines[1:4]) if len(msg_lines) > 1 else ''\n results.append({'test_name': name.strip(), 'message': msg.strip(), 'stack': stack.strip()})\n\nif not results:\n for line in text.split('\\n'):\n if line.strip().startswith('FAIL'):\n results.append({'test_name': line.strip(), 'message': 'See raw output', 'stack': ''})\n\nprint(json.dumps(results))\n\" \"$TMPFILE\" 2>/dev/null || echo '[]')\n fi\n\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n [[ \"$total\" -eq 0 && \"$EXIT_CODE\" -ne 0 ]] && status=\"error\"\n\n emit_json \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\"\n}\n\nparse_pytest() {\n local total=0 passed=0 failed=0 failures=\"[]\"\n\n local summary_line\n summary_line=$(grep -E '=+ .*(passed|failed|error).*=+' \"$TMPFILE\" | tail -1 || true)\n\n if [[ -n \"$summary_line\" ]]; then\n passed=$(echo \"$summary_line\" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' || echo 0)\n failed=$(echo \"$summary_line\" | grep -oE '[0-9]+ failed' | grep -oE '[0-9]+' || echo 0)\n local errors\n errors=$(echo \"$summary_line\" | grep -oE '[0-9]+ error' | grep -oE '[0-9]+' || echo 0)\n total=$((passed + failed + errors))\n fi\n\n if [[ \"$failed\" -gt 0 ]] || [[ \"$EXIT_CODE\" -ne 0 ]]; then\n failures=$(python3 -c \"\nimport re, json, sys\n\ntext = open(sys.argv[1]).read()\npattern = r'FAILED (.+?)(?:\\s*-\\s*(.+))?

Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…

\nresults = []\nfor m in re.finditer(pattern, text, re.MULTILINE):\n name = m.group(1).strip()\n msg = m.group(2).strip() if m.group(2) else 'See raw output'\n results.append({'test_name': name, 'message': msg, 'stack': ''})\n\nif not results:\n pattern2 = r'___+ (.+?) ___+\\n([\\s\\S]*?)(?=___+|\\Z)'\n for m in re.finditer(pattern2, text):\n name = m.group(1).strip()\n body = m.group(2).strip().split('\\n')\n msg = next((l for l in body if 'assert' in l.lower() or 'Error' in l), body[0] if body else '')\n results.append({'test_name': name, 'message': msg.strip(), 'stack': ''})\n\nprint(json.dumps(results[:10]))\n\" \"$TMPFILE\" 2>/dev/null || echo '[]')\n fi\n\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n [[ \"$total\" -eq 0 && \"$EXIT_CODE\" -ne 0 ]] && status=\"error\"\n\n emit_json \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\"\n}\n\nparse_go() {\n local total=0 passed=0 failed=0 failures=\"[]\"\n\n passed=$(grep -cE '^--- PASS:' \"$TMPFILE\" 2>/dev/null) || passed=0\n failed=$(grep -cE '^--- FAIL:' \"$TMPFILE\" 2>/dev/null) || failed=0\n total=$((passed + failed))\n\n if [[ \"$failed\" -gt 0 ]] || [[ \"$EXIT_CODE\" -ne 0 ]]; then\n failures=$(python3 -c \"\nimport re, json, sys\n\ntext = open(sys.argv[1]).read()\nresults = []\nfor m in re.finditer(r'^--- FAIL: (\\S+)', text, re.MULTILINE):\n name = m.group(1)\n start = m.end()\n end_match = re.search(r'^---', text[start:], re.MULTILINE)\n block = text[start:start + end_match.start()] if end_match else text[start:start+500]\n lines = [l.strip() for l in block.strip().split('\\n') if l.strip()]\n msg = lines[0] if lines else 'See raw output'\n results.append({'test_name': name, 'message': msg, 'stack': ''})\nprint(json.dumps(results[:10]))\n\" \"$TMPFILE\" 2>/dev/null || echo '[]')\n fi\n\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n [[ \"$total\" -eq 0 && \"$EXIT_CODE\" -ne 0 ]] && status=\"error\"\n\n emit_json \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\"\n}\n\nparse_cargo() {\n local total=0 passed=0 failed=0 failures=\"[]\"\n\n local summary_line\n summary_line=$(grep -E '^test result:' \"$TMPFILE\" | tail -1 || true)\n\n if [[ -n \"$summary_line\" ]]; then\n passed=$(echo \"$summary_line\" | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' || echo 0)\n failed=$(echo \"$summary_line\" | grep -oE '[0-9]+ failed' | grep -oE '[0-9]+' || echo 0)\n total=$((passed + failed))\n fi\n\n if [[ \"$failed\" -gt 0 ]] || [[ \"$EXIT_CODE\" -ne 0 ]]; then\n failures=$(python3 -c \"\nimport re, json, sys\n\ntext = open(sys.argv[1]).read()\nresults = []\nfor m in re.finditer(r'^---- (.+?) stdout ----\\n([\\s\\S]*?)(?=^----|\\Z)', text, re.MULTILINE):\n name = m.group(1).strip()\n body = m.group(2).strip().split('\\n')\n msg = next((l for l in body if 'panicked' in l or 'assert' in l), body[0] if body else '')\n results.append({'test_name': name, 'message': msg.strip(), 'stack': ''})\nprint(json.dumps(results[:10]))\n\" \"$TMPFILE\" 2>/dev/null || echo '[]')\n fi\n\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n [[ \"$total\" -eq 0 && \"$EXIT_CODE\" -ne 0 ]] && status=\"error\"\n\n emit_json \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\"\n}\n\nparse_rspec() {\n local total=0 passed=0 failed=0 failures=\"[]\"\n\n local summary_line\n summary_line=$(grep -E '[0-9]+ examples' \"$TMPFILE\" | tail -1 || true)\n\n if [[ -n \"$summary_line\" ]]; then\n total=$(echo \"$summary_line\" | grep -oE '[0-9]+ examples' | grep -oE '[0-9]+' || echo 0)\n failed=$(echo \"$summary_line\" | grep -oE '[0-9]+ failures?' | grep -oE '[0-9]+' || echo 0)\n passed=$((total - failed))\n fi\n\n if [[ \"$failed\" -gt 0 ]] || [[ \"$EXIT_CODE\" -ne 0 ]]; then\n failures=$(python3 -c \"\nimport re, json, sys\n\ntext = open(sys.argv[1]).read()\nresults = []\nfor m in re.finditer(r'^\\s+\\d+\\) (.+?)\\n\\s+Failure/Error: (.+?)

Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…

, text, re.MULTILINE):\n results.append({'test_name': m.group(1).strip(), 'message': m.group(2).strip(), 'stack': ''})\nprint(json.dumps(results[:10]))\n\" \"$TMPFILE\" 2>/dev/null || echo '[]')\n fi\n\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n\n emit_json \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\"\n}\n\nparse_phpunit() {\n local total=0 passed=0 failed=0 failures=\"[]\"\n\n if grep -qE '^OK \\(' \"$TMPFILE\"; then\n total=$(grep -oE 'OK \\([0-9]+ tests' \"$TMPFILE\" | grep -oE '[0-9]+' || echo 0)\n passed=$total\n elif grep -qE 'Tests: [0-9]+' \"$TMPFILE\"; then\n total=$(grep -oE 'Tests: [0-9]+' \"$TMPFILE\" | grep -oE '[0-9]+' || echo 0)\n failed=$(grep -oE 'Failures: [0-9]+' \"$TMPFILE\" | grep -oE '[0-9]+' || echo 0)\n passed=$((total - failed))\n fi\n\n if [[ \"$failed\" -gt 0 ]] || [[ \"$EXIT_CODE\" -ne 0 ]]; then\n failures=$(python3 -c \"\nimport re, json, sys\n\ntext = open(sys.argv[1]).read()\nresults = []\nfor m in re.finditer(r'^\\d+\\) (.+?)$\\n(.+?)

Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…

, text, re.MULTILINE):\n results.append({'test_name': m.group(1).strip(), 'message': m.group(2).strip(), 'stack': ''})\nprint(json.dumps(results[:10]))\n\" \"$TMPFILE\" 2>/dev/null || echo '[]')\n fi\n\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n\n emit_json \"$status\" \"$total\" \"$passed\" \"$failed\" \"$failures\"\n}\n\n# Generic fallback for unknown frameworks\nparse_generic() {\n local status=\"pass\"\n [[ \"$EXIT_CODE\" -ne 0 ]] && status=\"fail\"\n\n emit_json \"$status\" \"0\" \"0\" \"0\" \"[]\"\n}\n\n# Dispatch\ncase \"$FRAMEWORK\" in\n jest|vitest) parse_jest_vitest ;;\n pytest) parse_pytest ;;\n go) parse_go ;;\n cargo) parse_cargo ;;\n rspec) parse_rspec ;;\n phpunit) parse_phpunit ;;\n *) parse_generic ;;\nesac\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":11000,"content_sha256":"082ded072db72e3acd384f5bdfc1f6e5601a659eba687ffa09ced4cd19dcb7ee"},{"filename":"tests/conftest.py","content":"\"\"\"Shared fixtures for TDD skill script tests.\"\"\"\n\nimport os\nfrom pathlib import Path\n\nimport pytest\n\nSKILL_DIR = Path(__file__).parent.parent\nSCRIPTS_DIR = SKILL_DIR / \"scripts\"\nFIXTURES_DIR = Path(__file__).parent / \"fixtures\"\n\n\[email protected]\ndef scripts_dir():\n return SCRIPTS_DIR\n\n\[email protected]\ndef python_project():\n return FIXTURES_DIR / \"python_project\"\n\n\[email protected]\ndef ts_project():\n return FIXTURES_DIR / \"ts_project\"\n\n\[email protected]\ndef go_project():\n return FIXTURES_DIR / \"go_project\"\n\n\[email protected]\ndef empty_project():\n return FIXTURES_DIR / \"empty_project\"\n\n\[email protected]\ndef tmp_project(tmp_path):\n \"\"\"A writable temp directory for tests that need to create files.\"\"\"\n return tmp_path\n","content_type":"text/x-python; charset=utf-8","language":"python","size":741,"content_sha256":"cfecb743af2b641acf49bf42f75e41cfec0d640ee11c3e103fc1a5fb2ec0d718"},{"filename":"tests/fixtures/go_project/calculator.go","content":"package calculator\n\n// Add returns the sum of two integers.\n// It handles overflow by wrapping.\nfunc Add(a, b int) int {\n\treturn a + b\n}\n\n// Divide returns a/b. Returns error if b is zero.\nfunc Divide(a, b int) (int, error) {\n\tif b == 0 {\n\t\treturn 0, fmt.Errorf(\"division by zero\")\n\t}\n\treturn a / b, nil\n}\n\n// Calculator holds state for chained operations.\ntype Calculator struct {\n\tResult int\n}\n\nfunc internal() {}\n","content_type":"text/plain; charset=utf-8","language":"go","size":416,"content_sha256":"bc634ccf050396b6822c06dd4aef28b0080a38acb71a699a1d38272ac338210c"},{"filename":"tests/fixtures/python_project/docs/API.md","content":"# API Reference\n\n## Calculator\n\n### Methods\n\n- `add(a, b)` — Returns sum of a and b\n- `subtract(a, b)` — Returns a minus b\n- `divide(a, b)` — Returns a divided by b, raises ValueError on zero\n\n## Standalone Functions\n\n- `factorial(n)` — Returns n!, raises ValueError for negative input\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":294,"content_sha256":"df181178812d9ae9fc9ff04564645da13327a8efa26a2891214ba8bd246b815e"},{"filename":"tests/fixtures/python_project/docs/openapi.yaml","content":"openapi: \"3.0.0\"\ninfo:\n title: Calculator API\n version: \"1.0.0\"\npaths:\n /calculate:\n post:\n summary: Perform a calculation\n requestBody:\n content:\n application/json:\n schema:\n type: object\n properties:\n operation:\n type: string\n a:\n type: number\n b:\n type: number\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":429,"content_sha256":"cf06da3856e16d20a5c7b89a946f73c2be2999d664db6f9cbd85b5d37c9933f1"},{"filename":"tests/fixtures/python_project/README.md","content":"# Calculator Project\n\nA simple calculator library for arithmetic operations.\n\n## Features\n\n- Basic arithmetic: add, subtract, multiply, divide\n- Factorial computation\n- Input validation with descriptive errors\n\n## Usage\n\n```python\nfrom src.calculator import Calculator\n\ncalc = Calculator()\nresult = calc.add(2, 3) # returns 5\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":331,"content_sha256":"13b54ef4e21278cbb646ea170fc35f416bb99e036b2b9ff6c7953c0c6c038e5c"},{"filename":"tests/fixtures/python_project/src/calculator.py","content":"\"\"\"Calculator module for arithmetic operations.\"\"\"\n\n\nclass Calculator:\n \"\"\"A simple calculator with basic arithmetic.\n\n Supports add, subtract, multiply, divide with\n error handling for division by zero.\n \"\"\"\n\n def add(self, a: float, b: float) -> float:\n \"\"\"Add two numbers and return the result.\"\"\"\n return a + b\n\n def subtract(self, a: float, b: float) -> float:\n \"\"\"Subtract b from a.\"\"\"\n return a - b\n\n def divide(self, a: float, b: float) -> float:\n \"\"\"Divide a by b.\n\n Raises:\n ValueError: If b is zero.\n \"\"\"\n if b == 0:\n raise ValueError(\"Cannot divide by zero\")\n return a / b\n\n\ndef factorial(n: int) -> int:\n \"\"\"Compute factorial of n.\n\n Args:\n n: Non-negative integer.\n\n Returns:\n n! as an integer.\n \"\"\"\n if n \u003c 0:\n raise ValueError(\"n must be non-negative\")\n if n \u003c= 1:\n return 1\n return n * factorial(n - 1)\n\n\ndef _internal_helper():\n \"\"\"This is private and should not appear in API surface.\"\"\"\n pass\n","content_type":"text/x-python; charset=utf-8","language":"python","size":1074,"content_sha256":"cc861e01f8550ce886f37f9955cd1e3ae31607508246cd813f97399d64c60d5b"},{"filename":"tests/fixtures/python_project/src/models.py","content":"from dataclasses import dataclass\n\n\n@dataclass\nclass User:\n \"\"\"Represents a user in the system.\"\"\"\n\n name: str\n email: str\n\n def validate_email(self) -> bool:\n \"\"\"Check if email contains @ symbol.\"\"\"\n return \"@\" in self.email\n","content_type":"text/x-python; charset=utf-8","language":"python","size":252,"content_sha256":"21e8ae91454c71817f5b26e6d08ed42f3eedbea8244a23a222fd0cf74e1cff25"},{"filename":"tests/fixtures/ts_project/README.md","content":"# TypeScript Calculator\n\nSimple calculator with type safety.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":61,"content_sha256":"c411f61d3270d033d4d64f1fb3f4cd2586ad52ed02845ae9b7440fbb6e7ac776"},{"filename":"tests/fixtures/ts_project/src/calculator.ts","content":"/**\n * Add two numbers together.\n * @param a - First operand\n * @param b - Second operand\n * @returns The sum of a and b\n */\nexport function add(a: number, b: number): number {\n return a + b;\n}\n\n/**\n * Divide a by b with zero-check.\n * @throws Error if b is zero\n */\nexport function divide(a: number, b: number): number {\n if (b === 0) throw new Error(\"Division by zero\");\n return a / b;\n}\n\nexport class Calculator {\n private history: number[] = [];\n\n add(a: number, b: number): number {\n const result = a + b;\n this.history.push(result);\n return result;\n }\n}\n\nexport type Operation = \"add\" | \"subtract\" | \"multiply\" | \"divide\";\n\nexport interface CalculatorConfig {\n precision: number;\n strict: boolean;\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":723,"content_sha256":"e7ce9a8db25c562223d4df7373e363f37f1cba4b90593832f1fef4fa920ae2b5"},{"filename":"tests/snapshots/implementer.txt","content":"You are a TDD Implementer. Your ONLY job is to write the MINIMUM code to make a failing test pass.\n\n## Language: python\n\n## Failing test code\ndef test_should_reject_email_without_at():\n user = User(name='Test', email='invalid')\n assert user.validate_email() is False\n\n\n## Test failure output\nFAILED tests/test_user.py::test_should_reject_email_without_at\nModuleNotFoundError: No module named 'src.models'\n\n\n\n\n## File tree (source files only)\nsrc/\nsrc/models.py\nsrc/__init__.py\n\n\n## Existing source code (files relevant to the failing test)\n# src/models.py\nclass User:\n pass\n\n\n## Architectural layer\nThis code belongs to the **domain** layer.\n\nThis is the innermost layer. It MUST NOT import anything from domain-service, application, or infrastructure layers. No ORM imports, no HTTP clients, no framework imports. Only standard library and domain types.\n\n## Rules\n1. Write the MINIMUM code to make the failing test pass\n2. No code beyond what the test requires\n3. No premature abstractions or extra error handling\n4. No optimization -- simple and direct\n5. Hardcoded values are acceptable if they satisfy the test\n6. Do NOT modify the test file\n7. Do NOT add features or behaviors not tested\n8. ALWAYS return the COMPLETE file content for every file you change or create\n9. Respect the layer dependency constraint above -- do NOT import from outer layers\n\n## Output\nReturn a single JSON object. Do NOT wrap in markdown fences. Do NOT include any text before or after the JSON.\n\n{\"files\": [{\"path\": \"relative/path/to/file.ext\", \"action\": \"create or overwrite\", \"content\": \"COMPLETE file content -- the entire file from first line to last\", \"description\": \"what this file does\"}], \"explanation\": \"brief explanation of the implementation approach\"}","content_type":"text/plain; charset=utf-8","language":null,"size":1755,"content_sha256":"d5eef8ab33678553c14b73a30a76acc7a102b14fa846b352633e45a63317c5f8"},{"filename":"tests/snapshots/refactorer.txt","content":"You are a TDD Refactorer. All tests are currently passing. Your job is to suggest code improvements that preserve behavior.\n\n## Language: python\n\n## Current test results (all green)\n======================== 2 passed in 0.03s ========================\n\n\n## All test code\ndef test_should_reject_email_without_at():\n user = User(name='Test', email='invalid')\n assert user.validate_email() is False\n\ndef test_should_accept_valid_email():\n user = User(name='Test', email='[email protected]')\n assert user.validate_email() is True\n\n\n## All implementation code\nclass User:\n def __init__(self, name: str, email: str):\n self.name = name\n self.email = email\n\n def validate_email(self) -> bool:\n return '@' in self.email\n\n\n## Layers touched in this session\ndomain\n\n## Rules\n1. Do NOT change behavior -- all existing tests must continue to pass\n2. Focus on: extracting duplication, improving naming, simplifying logic, applying appropriate patterns\n3. Do NOT add new features, new tests, or new error handling\n4. Each suggestion must be independently applicable (revert-safe)\n5. Prefer small, targeted improvements over large restructurings\n6. Apply the Rule of Three -- don't extract abstractions unless a pattern appears 3+ times\n7. If no meaningful refactoring is needed, say so -- that's a valid outcome\n8. Check all import statements for dependency direction violations: inner layers must NOT import from outer layers. Direction: domain → domain-service → application → infrastructure. Flag any violation as a HIGH priority suggestion.\n9. Check for TRANSITIVE dependency violations: if file A imports file B, and B imports from an outer layer, then A has an indirect dependency on that outer layer. Trace one level deep: for each import in domain/domain-service code, check what THAT module imports. Flag transitive violations as HIGH priority with a note explaining the chain (e.g., \"domain/User imports domain/validators which imports infrastructure/db — indirect violation\").\n10. Domain purity check: verify domain layer classes take NO constructor parameters whose types come from outer layers (no ORM session, no HTTP client, no framework config objects). Flag as HIGH priority.\n\n## Output\nReturn a single JSON object. Do NOT wrap in markdown fences. Do NOT include any text before or after the JSON.\n\nIf refactoring is suggested:\n{\"suggestions\": [{\"description\": \"what this refactoring does\", \"priority\": \"high or medium or low\", \"files\": [{\"path\": \"relative/path/to/file.ext\", \"old_code\": \"exact code to find and replace\", \"new_code\": \"replacement code\"}]}], \"summary\": \"overall assessment of code quality\"}\n\nIf no refactoring is needed:\n{\"suggestions\": [], \"summary\": \"Code is clean. No refactoring needed at this stage.\"}","content_type":"text/plain; charset=utf-8","language":null,"size":2768,"content_sha256":"832cce92847aef0d3808d1680e9d52ad76d24c77daeb18dce7199bf44e084329"},{"filename":"tests/snapshots/test_writer.txt","content":"You are a TDD Test Writer. Your ONLY job is to write ONE failing test for a specific behavior.\n\n## Specification for this slice\nUser email validation: should reject emails without @ symbol\n\n## Language and framework\n- Language: python\n- Framework: pytest\n- Test file location: tests/test_user.py\n\n## Public API surface (signatures only, no implementations)\n# Python API Surface\n## src/models.py\n 5:class User:\n 10:def validate_email(self) -> bool:\n\n\nNote: If the API surface is empty, the function/class does not exist yet. Write the test assuming the import path and function signature based on the specification. The Implementer will create the code.\n\n## Project documentation (relevant excerpts)\n# Project Documentation\n## README.md\nUser model validates email format on creation.\n# Source Docstrings\n## models.py\n User: Represents a user in the system.\n validate_email: Check if email contains @ symbol.\n\n\nUse this documentation to understand intended behavior, API contracts, edge cases, and validation rules. Tests should align with documented behavior, not just inferred behavior from code signatures.\n\n## Existing test file content (if any)\nNo test file exists yet.\n\n## Framework-specific test skeleton\ndef test_should_behavior():\n # Arrange\n ...\n # Act\n result = function_under_test()\n # Assert\n assert result == expected\n\n\n## Architectural layer for this slice\nThis slice belongs to the **domain** layer.\n\nWrite tests using only domain types. NO database mocks, NO HTTP mocks, NO file system. Test pure business logic through public methods. Construct real domain objects directly — never mock them.\n\n## Rules\n1. Write EXACTLY ONE test function for the specified behavior\n2. The test MUST fail because the implementation does not yet exist\n3. Test through the public interface only -- no internal/private access\n4. Use descriptive test names that read as behavior specs\n5. Do NOT plan or think about implementation -- reason only from the specification\n6. Do NOT write implementation code\n7. Do NOT write helper functions beyond minimal test setup\n8. Include all necessary imports in the test code\n9. Follow the layer-specific test constraints above\n\n## Output\nReturn a single JSON object. Do NOT wrap in markdown fences. Do NOT include any text before or after the JSON.\n\n{\"test_code\": \"the COMPLETE test code to add (including describe/it blocks, not just the assertion)\", \"test_name\": \"name of the test function\", \"test_description\": \"what behavior this test verifies\", \"imports_needed\": \"any import statements needed at the top of the file, or empty string if none\"}","content_type":"text/plain; charset=utf-8","language":null,"size":2603,"content_sha256":"2ccae95ee6544cc6070a3767856df96157d953594e6009bca736da9180ede829"},{"filename":"tests/test_discover_docs.py","content":"\"\"\"Tests for discover_docs.sh — project documentation discovery.\"\"\"\n\nimport subprocess\n\nimport pytest\n\n\ndef run_discover(scripts_dir, project_dir, lang=None):\n cmd = [\"bash\", str(scripts_dir / \"discover_docs.sh\"), str(project_dir)]\n if lang:\n cmd += [\"--lang\", lang]\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)\n return result\n\n\nclass TestDocumentationFiles:\n def test_finds_readme(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project)\n assert result.returncode == 0\n assert \"README.md\" in result.stdout\n assert \"Calculator Project\" in result.stdout\n\n def test_finds_docs_folder(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project)\n assert \"API.md\" in result.stdout\n assert \"Calculator\" in result.stdout\n\n def test_ts_readme(self, scripts_dir, ts_project):\n result = run_discover(scripts_dir, ts_project)\n assert \"TypeScript Calculator\" in result.stdout\n\n\nclass TestAPISpecifications:\n def test_finds_openapi_yaml(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project)\n assert \"openapi\" in result.stdout.lower()\n assert \"/calculate\" in result.stdout\n\n def test_no_specs_in_ts_project(self, scripts_dir, ts_project):\n result = run_discover(scripts_dir, ts_project)\n assert \"No API specification files found\" in result.stdout\n\n\nclass TestPythonDocstrings:\n def test_extracts_class_docstrings(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project, lang=\"python\")\n assert \"Calculator\" in result.stdout\n assert \"arithmetic\" in result.stdout.lower()\n\n def test_extracts_function_docstrings(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project, lang=\"python\")\n assert \"factorial\" in result.stdout\n\n def test_excludes_private_docstrings(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project, lang=\"python\")\n assert \"_internal_helper\" not in result.stdout\n\n def test_extracts_model_docstrings(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project, lang=\"python\")\n assert \"User\" in result.stdout\n\n\nclass TestTypeScriptDocstrings:\n def test_extracts_jsdoc(self, scripts_dir, ts_project):\n result = run_discover(scripts_dir, ts_project, lang=\"typescript\")\n assert \"Source Docstrings\" in result.stdout\n # JSDoc for exported functions\n assert \"add\" in result.stdout.lower() or \"calculator\" in result.stdout.lower()\n\n\nclass TestGoDocstrings:\n def test_extracts_doc_comments(self, scripts_dir, go_project):\n result = run_discover(scripts_dir, go_project, lang=\"go\")\n assert \"Source Docstrings\" in result.stdout\n\n\nclass TestEmptyProject:\n def test_handles_empty_gracefully(self, scripts_dir, empty_project):\n result = run_discover(scripts_dir, empty_project)\n assert result.returncode == 0\n assert \"No documentation files found\" in result.stdout\n assert \"No API specification files found\" in result.stdout\n\n\nclass TestOutputStructure:\n def test_has_three_sections(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project)\n assert \"# Project Documentation\" in result.stdout\n assert \"# API Specifications\" in result.stdout\n assert \"# Source Docstrings\" in result.stdout\n\n def test_output_not_empty(self, scripts_dir, python_project):\n result = run_discover(scripts_dir, python_project)\n assert len(result.stdout) > 100\n\n\nclass TestTruncation:\n def test_respects_char_limit(self, scripts_dir, tmp_project):\n \"\"\"Create a project with very large docs to test truncation.\"\"\"\n readme = tmp_project / \"README.md\"\n readme.write_text(\"# Big Doc\\n\" + (\"x\" * 200 + \"\\n\") * 100)\n result = run_discover(scripts_dir, tmp_project, lang=\"python\")\n assert len(result.stdout) \u003c= 16000 # 15k + some overhead\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4140,"content_sha256":"21ac626562e985b9c351dcb1252bb644a6978792036d71d1bd05d22eef9bc9e6"},{"filename":"tests/test_extract_api.py","content":"\"\"\"Tests for extract_api.sh — public API signature extraction.\n\nNote: Python/TS fixtures are copied to tmp_path to avoid extract_api.sh's\n`-not -path '*/tests/*'` exclusion (fixtures live under tests/).\n\"\"\"\n\nimport shutil\nimport subprocess\n\nimport pytest\n\nfrom conftest import FIXTURES_DIR\n\n\ndef run_extract(scripts_dir, source_dir, lang=None):\n cmd = [\"bash\", str(scripts_dir / \"extract_api.sh\"), str(source_dir)]\n if lang:\n cmd += [\"--lang\", lang]\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)\n return result\n\n\[email protected]\ndef py_src(tmp_path):\n \"\"\"Copy Python fixture to tmp_path so it's not under tests/.\"\"\"\n src = tmp_path / \"src\"\n shutil.copytree(FIXTURES_DIR / \"python_project\" / \"src\", src)\n return src\n\n\[email protected]\ndef ts_src(tmp_path):\n src = tmp_path / \"src\"\n shutil.copytree(FIXTURES_DIR / \"ts_project\" / \"src\", src)\n return src\n\n\[email protected]\ndef go_src(tmp_path):\n dst = tmp_path / \"go_project\"\n shutil.copytree(FIXTURES_DIR / \"go_project\", dst)\n return dst\n\n\nclass TestPythonExtraction:\n def test_extracts_class_signatures(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n assert result.returncode == 0\n assert \"class Calculator\" in result.stdout\n\n def test_extracts_function_signatures(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n # extract_api.sh matches top-level definitions only (^def)\n # methods inside classes are indented and won't match\n assert \"def factorial\" in result.stdout\n\n def test_extracts_dataclass_decorator(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n assert \"@dataclass\" in result.stdout\n\n def test_excludes_private_functions(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n assert \"_internal_helper\" not in result.stdout\n\n def test_shows_file_paths(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n assert \"calculator.py\" in result.stdout\n assert \"models.py\" in result.stdout\n\n def test_header_present(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n assert \"Python API Surface\" in result.stdout\n\n\nclass TestTypeScriptExtraction:\n def test_extracts_exports(self, scripts_dir, ts_src):\n result = run_extract(scripts_dir, ts_src)\n assert result.returncode == 0\n assert \"export function add\" in result.stdout\n assert \"export function divide\" in result.stdout\n\n def test_extracts_class_exports(self, scripts_dir, ts_src):\n result = run_extract(scripts_dir, ts_src)\n assert \"export class Calculator\" in result.stdout\n\n def test_extracts_type_exports(self, scripts_dir, ts_src):\n result = run_extract(scripts_dir, ts_src)\n assert \"export type Operation\" in result.stdout\n assert \"export interface CalculatorConfig\" in result.stdout\n\n def test_header_present(self, scripts_dir, ts_src):\n result = run_extract(scripts_dir, ts_src)\n assert \"TypeScript\" in result.stdout\n\n\nclass TestGoExtraction:\n def test_extracts_exported_functions(self, scripts_dir, go_src):\n result = run_extract(scripts_dir, go_src)\n assert result.returncode == 0\n assert \"func Add\" in result.stdout\n assert \"func Divide\" in result.stdout\n\n def test_extracts_exported_types(self, scripts_dir, go_src):\n result = run_extract(scripts_dir, go_src)\n assert \"type Calculator\" in result.stdout\n\n def test_excludes_unexported(self, scripts_dir, go_src):\n result = run_extract(scripts_dir, go_src)\n assert \"internal\" not in result.stdout\n\n def test_header_present(self, scripts_dir, go_src):\n result = run_extract(scripts_dir, go_src)\n assert \"Go API Surface\" in result.stdout\n\n\nclass TestLanguageDetection:\n def test_auto_detects_python(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src)\n assert \"Python\" in result.stdout\n\n def test_auto_detects_typescript(self, scripts_dir, ts_src):\n result = run_extract(scripts_dir, ts_src)\n assert \"TypeScript\" in result.stdout\n\n def test_lang_override(self, scripts_dir, py_src):\n result = run_extract(scripts_dir, py_src, lang=\"go\")\n assert \"Go API Surface\" in result.stdout\n\n\nclass TestEmptyProject:\n def test_empty_returns_successfully(self, scripts_dir, empty_project):\n result = run_extract(scripts_dir, empty_project)\n assert result.returncode in (0, 1)\n\n def test_empty_no_crash(self, scripts_dir, empty_project):\n result = run_extract(scripts_dir, empty_project, lang=\"python\")\n assert result.returncode == 0\n assert \"Python API Surface\" in result.stdout\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4843,"content_sha256":"b046d9b28c842bbd974f4ed722de2a0c4ac61189057166b7c80c0ecb3353c811"},{"filename":"tests/test_prompts.py","content":"\"\"\"Snapshot tests for agent prompt templates.\n\nRenders each agent prompt (Test Writer, Implementer, Refactorer) with\nknown sample variables and compares against stored snapshots. Catches\naccidental regressions in prompt templates without any LLM calls.\n\nUpdate snapshots: pytest tests/test_prompts.py --snapshot-update\n\"\"\"\n\nimport re\nfrom pathlib import Path\n\nimport pytest\n\nSKILL_DIR = Path(__file__).parent.parent\nPROMPTS_FILE = SKILL_DIR / \"references\" / \"agent_prompts.md\"\nSNAPSHOTS_DIR = Path(__file__).parent / \"snapshots\"\n\n\n# ── Template parsing ────────────────────────────────────────────────\n\n\ndef load_prompt_templates():\n \"\"\"Extract agent prompt templates from agent_prompts.md.\"\"\"\n content = PROMPTS_FILE.read_text()\n\n templates = {}\n # Match ``` blocks that follow \"## \u003cAgent Name> Agent\" headers\n pattern = r\"## (Test Writer|Implementer|Refactorer) Agent.*?```\\n(.*?)```\"\n for match in re.finditer(pattern, content, re.DOTALL):\n name = match.group(1).lower().replace(\" \", \"_\")\n templates[name] = match.group(2).strip()\n\n return templates\n\n\ndef render_template(template, variables):\n \"\"\"Substitute {VARIABLE} placeholders and handle {?SECTION}...{/SECTION}.\"\"\"\n result = template\n\n # Handle optional sections: {?SECTION}...{/SECTION}\n # Include section content if variable exists and is non-empty\n optional_pattern = r\"\\{(\\?[A-Z_]+)\\}(.*?)\\{/([A-Z_]+)\\}\"\n for match in re.finditer(optional_pattern, result, re.DOTALL):\n section_open = match.group(1) # e.g. ?DOC_CONTEXT\n section_body = match.group(2)\n section_close = match.group(3) # e.g. DOC_CONTEXT\n var_name = section_open[1:] # strip leading ?\n\n if var_name in variables and variables[var_name]:\n # Include the section with variables substituted\n rendered_body = section_body\n for k, v in variables.items():\n rendered_body = rendered_body.replace(f\"{{{k}}}\", v)\n result = result.replace(match.group(0), rendered_body.strip())\n else:\n # Remove the entire optional section\n result = result.replace(match.group(0), \"\")\n\n # Substitute remaining {VARIABLE} placeholders\n for k, v in variables.items():\n result = result.replace(f\"{{{k}}}\", v)\n\n return result.strip()\n\n\n# ── Sample variables for each agent ─────────────────────────────────\n\nTEST_WRITER_VARS = {\n \"SLICE_SPEC\": \"User email validation: should reject emails without @ symbol\",\n \"LANGUAGE\": \"python\",\n \"FRAMEWORK\": \"pytest\",\n \"API_SURFACE\": (\n \"# Python API Surface\\n\"\n \"## src/models.py\\n\"\n \" 5:class User:\\n\"\n \" 10:def validate_email(self) -> bool:\\n\"\n ),\n \"DOC_CONTEXT\": (\n \"# Project Documentation\\n\"\n \"## README.md\\n\"\n \"User model validates email format on creation.\\n\"\n \"# Source Docstrings\\n\"\n \"## models.py\\n\"\n \" User: Represents a user in the system.\\n\"\n \" validate_email: Check if email contains @ symbol.\\n\"\n ),\n \"TEST_FILE_PATH\": \"tests/test_user.py\",\n \"EXISTING_TEST_CONTENT\": \"No test file exists yet.\",\n \"FRAMEWORK_SKELETON\": (\n \"def test_should_behavior():\\n\"\n \" # Arrange\\n\"\n \" ...\\n\"\n \" # Act\\n\"\n \" result = function_under_test()\\n\"\n \" # Assert\\n\"\n \" assert result == expected\\n\"\n ),\n \"LAYER\": \"domain\",\n \"LAYER_TEST_CONSTRAINTS\": (\n \"Write tests using only domain types. NO database mocks, NO HTTP mocks, \"\n \"NO file system. Test pure business logic through public methods. \"\n \"Construct real domain objects directly — never mock them.\"\n ),\n}\n\nIMPLEMENTER_VARS = {\n \"LANGUAGE\": \"python\",\n \"FAILING_TEST_CODE\": (\n \"def test_should_reject_email_without_at():\\n\"\n \" user = User(name='Test', email='invalid')\\n\"\n \" assert user.validate_email() is False\\n\"\n ),\n \"TEST_FAILURE_OUTPUT\": (\n \"FAILED tests/test_user.py::test_should_reject_email_without_at\\n\"\n \"ModuleNotFoundError: No module named 'src.models'\\n\"\n ),\n \"FILE_TREE\": \"src/\\nsrc/models.py\\nsrc/__init__.py\\n\",\n \"EXISTING_SOURCE\": (\n \"# src/models.py\\n\"\n \"class User:\\n\"\n \" pass\\n\"\n ),\n \"LAYER\": \"domain\",\n \"LAYER_DEPENDENCY_CONSTRAINT\": (\n \"This is the innermost layer. It MUST NOT import anything from \"\n \"domain-service, application, or infrastructure layers. No ORM imports, \"\n \"no HTTP clients, no framework imports. Only standard library and domain types.\"\n ),\n \"PREVIOUS_ATTEMPT\": \"\",\n \"PREVIOUS_ATTEMPT_DESCRIPTION\": \"\",\n \"PREVIOUS_ATTEMPT_ERROR\": \"\",\n}\n\nREFACTORER_VARS = {\n \"LANGUAGE\": \"python\",\n \"GREEN_TEST_OUTPUT\": (\n \"======================== 2 passed in 0.03s ========================\\n\"\n ),\n \"ALL_TEST_CODE\": (\n \"def test_should_reject_email_without_at():\\n\"\n \" user = User(name='Test', email='invalid')\\n\"\n \" assert user.validate_email() is False\\n\\n\"\n \"def test_should_accept_valid_email():\\n\"\n \" user = User(name='Test', email='[email protected]')\\n\"\n \" assert user.validate_email() is True\\n\"\n ),\n \"ALL_IMPLEMENTATION_CODE\": (\n \"class User:\\n\"\n \" def __init__(self, name: str, email: str):\\n\"\n \" self.name = name\\n\"\n \" self.email = email\\n\\n\"\n \" def validate_email(self) -> bool:\\n\"\n \" return '@' in self.email\\n\"\n ),\n \"SLICE_LAYERS\": \"domain\",\n}\n\n\n# ── Snapshot management ─────────────────────────────────────────────\n\n\ndef read_snapshot(name):\n path = SNAPSHOTS_DIR / f\"{name}.txt\"\n if path.exists():\n return path.read_text()\n return None\n\n\ndef write_snapshot(name, content):\n SNAPSHOTS_DIR.mkdir(parents=True, exist_ok=True)\n path = SNAPSHOTS_DIR / f\"{name}.txt\"\n path.write_text(content)\n\n\n# ── Tests ───────────────────────────────────────────────────────────\n\n\[email protected](scope=\"session\")\ndef templates():\n return load_prompt_templates()\n\n\[email protected]\ndef update_snapshots(request):\n return request.config.getoption(\"--snapshot-update\", default=False)\n\n\ndef pytest_addoption(parser):\n parser.addoption(\n \"--snapshot-update\",\n action=\"store_true\",\n default=False,\n help=\"Update prompt snapshots\",\n )\n\n\nclass TestPromptTemplatesParsing:\n def test_loads_three_templates(self, templates):\n assert \"test_writer\" in templates\n assert \"implementer\" in templates\n assert \"refactorer\" in templates\n\n def test_templates_not_empty(self, templates):\n for name, tmpl in templates.items():\n assert len(tmpl) > 100, f\"{name} template too short\"\n\n def test_test_writer_has_required_placeholders(self, templates):\n tmpl = templates[\"test_writer\"]\n required = [\n \"{SLICE_SPEC}\",\n \"{LANGUAGE}\",\n \"{FRAMEWORK}\",\n \"{API_SURFACE}\",\n \"{TEST_FILE_PATH}\",\n \"{LAYER}\",\n ]\n for placeholder in required:\n assert placeholder in tmpl, f\"Missing {placeholder} in test_writer\"\n\n def test_implementer_has_required_placeholders(self, templates):\n tmpl = templates[\"implementer\"]\n required = [\n \"{LANGUAGE}\",\n \"{FAILING_TEST_CODE}\",\n \"{TEST_FAILURE_OUTPUT}\",\n \"{FILE_TREE}\",\n \"{LAYER}\",\n ]\n for placeholder in required:\n assert placeholder in tmpl, f\"Missing {placeholder} in implementer\"\n\n def test_refactorer_has_required_placeholders(self, templates):\n tmpl = templates[\"refactorer\"]\n required = [\n \"{LANGUAGE}\",\n \"{GREEN_TEST_OUTPUT}\",\n \"{ALL_TEST_CODE}\",\n \"{ALL_IMPLEMENTATION_CODE}\",\n \"{SLICE_LAYERS}\",\n ]\n for placeholder in required:\n assert placeholder in tmpl, f\"Missing {placeholder} in refactorer\"\n\n def test_test_writer_has_doc_context_section(self, templates):\n tmpl = templates[\"test_writer\"]\n assert \"{?DOC_CONTEXT}\" in tmpl or \"DOC_CONTEXT\" in tmpl\n\n\nclass TestPromptRendering:\n def test_test_writer_renders_without_unresolved_vars(self, templates):\n rendered = render_template(templates[\"test_writer\"], TEST_WRITER_VARS)\n # No unresolved {VARIABLE} patterns (except JSON examples like {\"test_code\":...)\n unresolved = re.findall(r\"\\{[A-Z][A-Z_]+\\}\", rendered)\n assert not unresolved, f\"Unresolved variables: {unresolved}\"\n\n def test_implementer_renders_without_unresolved_vars(self, templates):\n rendered = render_template(templates[\"implementer\"], IMPLEMENTER_VARS)\n unresolved = re.findall(r\"\\{[A-Z][A-Z_]+\\}\", rendered)\n assert not unresolved, f\"Unresolved variables: {unresolved}\"\n\n def test_refactorer_renders_without_unresolved_vars(self, templates):\n rendered = render_template(templates[\"refactorer\"], REFACTORER_VARS)\n unresolved = re.findall(r\"\\{[A-Z][A-Z_]+\\}\", rendered)\n assert not unresolved, f\"Unresolved variables: {unresolved}\"\n\n def test_doc_context_included_when_present(self, templates):\n rendered = render_template(templates[\"test_writer\"], TEST_WRITER_VARS)\n assert \"Project Documentation\" in rendered\n assert \"validate_email\" in rendered\n\n def test_doc_context_omitted_when_empty(self, templates):\n vars_no_docs = {**TEST_WRITER_VARS, \"DOC_CONTEXT\": \"\"}\n rendered = render_template(templates[\"test_writer\"], vars_no_docs)\n assert \"Project Documentation\" not in rendered\n\n def test_previous_attempt_omitted_when_empty(self, templates):\n rendered = render_template(templates[\"implementer\"], IMPLEMENTER_VARS)\n assert \"Previous attempt\" not in rendered\n\n def test_previous_attempt_included_when_present(self, templates):\n vars_with_retry = {\n **IMPLEMENTER_VARS,\n \"PREVIOUS_ATTEMPT\": \"yes\",\n \"PREVIOUS_ATTEMPT_DESCRIPTION\": \"Tried adding @property\",\n \"PREVIOUS_ATTEMPT_ERROR\": \"AttributeError: can't set attribute\",\n }\n rendered = render_template(templates[\"implementer\"], vars_with_retry)\n assert \"Tried adding @property\" in rendered\n\n\nclass TestPromptSnapshots:\n \"\"\"Compare rendered prompts against stored snapshots.\n\n Run with --snapshot-update to regenerate snapshots.\n \"\"\"\n\n def _check_snapshot(self, name, rendered, update_snapshots):\n existing = read_snapshot(name)\n if existing is None or update_snapshots:\n write_snapshot(name, rendered)\n if existing is None:\n pytest.skip(f\"Snapshot {name} created (first run)\")\n else:\n assert rendered == existing, (\n f\"Prompt snapshot '{name}' changed. \"\n f\"Run with --snapshot-update to accept changes.\"\n )\n\n def test_test_writer_snapshot(self, templates, update_snapshots):\n rendered = render_template(templates[\"test_writer\"], TEST_WRITER_VARS)\n self._check_snapshot(\"test_writer\", rendered, update_snapshots)\n\n def test_implementer_snapshot(self, templates, update_snapshots):\n rendered = render_template(templates[\"implementer\"], IMPLEMENTER_VARS)\n self._check_snapshot(\"implementer\", rendered, update_snapshots)\n\n def test_refactorer_snapshot(self, templates, update_snapshots):\n rendered = render_template(templates[\"refactorer\"], REFACTORER_VARS)\n self._check_snapshot(\"refactorer\", rendered, update_snapshots)\n","content_type":"text/x-python; charset=utf-8","language":"python","size":11997,"content_sha256":"b8bec79e50bef3096866083b8f41cb6561a63aadc6c10d64fbc5477bd91ac683"},{"filename":"tests/test_run_tests.py","content":"\"\"\"Tests for run_tests.sh — universal test runner JSON output.\"\"\"\n\nimport json\nimport shutil\nimport subprocess\n\nimport pytest\n\n\ndef run_tests_sh(scripts_dir, framework, test_cmd, extra_args=None):\n cmd = [\"bash\", str(scripts_dir / \"run_tests.sh\"), framework, test_cmd]\n if extra_args:\n cmd += extra_args\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)\n return result\n\n\ndef parse_output(result):\n \"\"\"Parse the JSON from run_tests.sh stdout.\"\"\"\n stdout = result.stdout.strip()\n assert stdout, f\"Empty stdout. stderr={result.stderr}\"\n return json.loads(stdout)\n\n\nHAS_TIMEOUT = shutil.which(\"timeout\") or shutil.which(\"gtimeout\")\n\n\nclass TestPytestParsing:\n def test_passing_tests(self, scripts_dir):\n cmd = (\n \"echo '============================= test session starts =============================='; \"\n \"echo 'collected 3 items'; \"\n \"echo ''; \"\n \"echo 'tests/test_calc.py ... [100%]'; \"\n \"echo ''; \"\n \"echo '============================== 3 passed in 0.02s ==============================='; \"\n \"exit 0\"\n )\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"pass\"\n assert data[\"passed\"] == 3\n assert data[\"failed\"] == 0\n assert data[\"total\"] == 3\n\n def test_failing_tests(self, scripts_dir):\n cmd = (\n \"echo '============================= test session starts =============================='; \"\n \"echo 'collected 2 items'; \"\n \"echo ''; \"\n \"echo 'FAILED tests/test_calc.py::test_add - assert 4 == 5'; \"\n \"echo ''; \"\n \"echo '=========================== 1 failed, 1 passed in 0.03s ========================'; \"\n \"exit 1\"\n )\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"fail\"\n assert data[\"passed\"] == 1\n assert data[\"failed\"] == 1\n assert len(data[\"failures\"]) >= 1\n\n def test_error_no_tests(self, scripts_dir):\n cmd = \"echo 'ERROR: not found'; exit 1\"\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"error\"\n assert data[\"total\"] == 0\n\n\nclass TestJestParsing:\n def test_passing_tests(self, scripts_dir):\n cmd = (\n \"echo 'PASS src/sum.test.ts'; \"\n \"echo ' sum'; \"\n \"echo ' ✓ adds 1 + 2 to equal 3 (2 ms)'; \"\n \"echo ''; \"\n \"echo 'Tests: 2 passed, 2 total'; \"\n \"echo 'Time: 0.5 s'; \"\n \"exit 0\"\n )\n result = run_tests_sh(scripts_dir, \"jest\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"pass\"\n assert data[\"passed\"] == 2\n assert data[\"total\"] == 2\n\n def test_failing_tests(self, scripts_dir):\n cmd = (\n \"echo 'FAIL src/sum.test.ts'; \"\n \"echo ' ● sum › adds 1 + 2 to equal 3'; \"\n \"echo ''; \"\n \"echo ' expect(received).toBe(expected)'; \"\n \"echo ''; \"\n \"echo ' Expected: 3'; \"\n \"echo ' Received: 4'; \"\n \"echo ''; \"\n \"echo 'Tests: 1 failed, 1 passed, 2 total'; \"\n \"echo 'Time: 0.8 s'; \"\n \"exit 1\"\n )\n result = run_tests_sh(scripts_dir, \"jest\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"fail\"\n assert data[\"failed\"] == 1\n assert data[\"total\"] == 2\n\n\nclass TestGoParsing:\n def test_passing_tests(self, scripts_dir):\n cmd = (\n \"echo '--- PASS: TestAdd (0.00s)'; \"\n \"echo '--- PASS: TestSubtract (0.00s)'; \"\n \"echo 'PASS'; \"\n \"echo 'ok calculator 0.003s'; \"\n \"exit 0\"\n )\n result = run_tests_sh(scripts_dir, \"go\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"pass\"\n assert data[\"passed\"] == 2\n assert data[\"total\"] == 2\n\n def test_failing_tests(self, scripts_dir):\n cmd = (\n \"echo '--- FAIL: TestDivide (0.00s)'; \"\n \"echo ' calculator_test.go:15: expected 5, got 0'; \"\n \"echo '--- PASS: TestAdd (0.00s)'; \"\n \"echo 'FAIL'; \"\n \"exit 1\"\n )\n result = run_tests_sh(scripts_dir, \"go\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"fail\"\n assert data[\"failed\"] == 1\n assert data[\"passed\"] == 1\n\n\nclass TestJSONStructure:\n def test_always_returns_valid_json(self, scripts_dir):\n \"\"\"Even on weird output, should produce valid JSON.\"\"\"\n cmd = \"echo 'garbage output'; exit 1\"\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert \"status\" in data\n assert \"total\" in data\n assert \"passed\" in data\n assert \"failed\" in data\n assert \"failures\" in data\n assert \"raw_tail\" in data\n\n def test_raw_tail_captures_output(self, scripts_dir):\n cmd = \"echo 'line1'; echo 'line2'; echo 'line3'; exit 0\"\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert \"line1\" in data[\"raw_tail\"]\n assert \"line3\" in data[\"raw_tail\"]\n\n def test_json_escapes_special_chars(self, scripts_dir):\n \"\"\"Ensure quotes and newlines in output don't break JSON.\"\"\"\n cmd = \"\"\"echo 'he said \"hello\"'; echo \"line with 'quotes'\"; exit 0\"\"\"\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert isinstance(data[\"raw_tail\"], str)\n\n def test_all_fields_have_correct_types(self, scripts_dir):\n cmd = \"echo 'some output'; exit 0\"\n result = run_tests_sh(scripts_dir, \"pytest\", cmd)\n data = parse_output(result)\n assert isinstance(data[\"status\"], str)\n assert isinstance(data[\"total\"], int)\n assert isinstance(data[\"passed\"], int)\n assert isinstance(data[\"failed\"], int)\n assert isinstance(data[\"failures\"], list)\n assert isinstance(data[\"raw_tail\"], str)\n\n\nclass TestTimeout:\n @pytest.mark.skipif(not HAS_TIMEOUT, reason=\"timeout/gtimeout not installed\")\n def test_timeout_produces_json(self, scripts_dir):\n result = run_tests_sh(\n scripts_dir, \"pytest\", \"sleep 10\", [\"--timeout\", \"1\"]\n )\n data = parse_output(result)\n assert data[\"status\"] == \"error\"\n assert \"timeout\" in data[\"raw_tail\"].lower() or \"TIMEOUT\" in str(\n data[\"failures\"]\n )\n\n\nclass TestGenericFramework:\n def test_unknown_framework_returns_json(self, scripts_dir):\n cmd = \"echo 'some output'; exit 0\"\n result = run_tests_sh(scripts_dir, \"unknown_framework\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"pass\"\n\n def test_unknown_framework_failure(self, scripts_dir):\n cmd = \"echo 'error'; exit 1\"\n result = run_tests_sh(scripts_dir, \"unknown_framework\", cmd)\n data = parse_output(result)\n assert data[\"status\"] == \"fail\"\n","content_type":"text/x-python; charset=utf-8","language":"python","size":7311,"content_sha256":"02411a8f25ff4cca5117eaea1b0880d2b9bd135eb4e56336bac1faf093f425d7"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Test-Driven Development — Multi-Agent Orchestration","type":"text"}]},{"type":"paragraph","content":[{"text":"Enforce disciplined RED-GREEN-REFACTOR cycles using ","type":"text"},{"text":"separate subagents","type":"text","marks":[{"type":"strong"}]},{"text":" for test writing and implementation. The core innovation: ","type":"text"},{"text":"the Test Writer never sees implementation code, and the Implementer never sees the specification.","type":"text","marks":[{"type":"strong"}]},{"text":" This prevents the LLM from leaking implementation intent into test design.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User requests TDD, test-first, or red-green-refactor workflow","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User says ","type":"text"},{"text":"/tdd","type":"text","marks":[{"type":"code_inline"}]},{"text":" with a feature description or bug report","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User wants to add a feature with test coverage enforced from the start","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User wants to fix a bug by first writing a reproducing test","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Invocation Modes","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invocation","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Behavior","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/tdd \u003cfeature>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Interactive mode — pause for approval at slices and each RED checkpoint","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/tdd --auto \u003cfeature>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Autonomous mode — run all slices without pausing; stop ONLY on unrecoverable errors","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/tdd --resume","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Resume from ","type":"text"},{"text":".tdd-state.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" in project root","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/tdd --dry-run \u003cfeature>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation mode — runs Phase 0 + Phase 1 fully, renders all prompts, but skips ","type":"text"},{"text":"Task()","type":"text","marks":[{"type":"code_inline"}]},{"text":" calls. No code is written.","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"In ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, skip all ","type":"text"},{"text":"[HUMAN CHECKPOINT]","type":"text","marks":[{"type":"code_inline"}]},{"text":" steps. Print status lines instead:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"[auto] RED slice 1/4: \"validates email format\" — test failing as expected\n[auto] GREEN slice 1/4: passing (attempt 1)\n[auto] REFACTOR slice 1/4: 1 suggestion applied, 0 skipped","type":"text"}]},{"type":"paragraph","content":[{"text":"Stop and ask the user ONLY when:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implementation fails after 5 attempts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Regressions cannot be auto-fixed after 3 attempts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A script error makes it impossible to continue (missing binary, permission denied, etc.)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"In ","type":"text"},{"text":"--dry-run","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, validate the entire orchestration pipeline without executing any subagents or writing any code:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 0 runs fully","type":"text","marks":[{"type":"strong"}]},{"text":": detect framework, verify baseline, extract API, discover docs, create state file","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 1 runs fully","type":"text","marks":[{"type":"strong"}]},{"text":": decompose into slices (still requires user approval)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each slice","type":"text","marks":[{"type":"strong"}]},{"text":": render all three agent prompts (Test Writer, Implementer, Refactorer) with actual variables. Print rendered prompts to the user with character counts.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No ","type":"text","marks":[{"type":"strong"}]},{"text":"Task()","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" calls are made","type":"text","marks":[{"type":"strong"}]},{"text":". No test files are written. No implementation code is generated.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate","type":"text","marks":[{"type":"strong"}]},{"text":": check that all template variables resolve (no ","type":"text"},{"text":"{UNRESOLVED}","type":"text","marks":[{"type":"code_inline"}]},{"text":" placeholders), all scripts execute without error, and the state file is well-formed.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Report summary","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"DRY RUN COMPLETE: {feature name}\n\nPhase 0:\n Framework: {framework}\n Language: {language}\n Baseline: {pass|greenfield}\n API surface: {line count} lines\n Doc context: {line count} lines (or \"none\")\n\nPhase 1:\n Slices: {N} ({layer breakdown})\n\nPrompts rendered: {N * 3} (all variables resolved)\n Test Writer: {char count} chars\n Implementer: {char count} chars\n Refactorer: {char count} chars\n\nState file: .tdd-state.json written\nNo code was modified.","type":"text"}]},{"type":"paragraph","content":[{"text":"This mode is useful for:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validating that scripts work in the project's environment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reviewing prompt content before committing to a full TDD run","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing skill changes without side effects","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Architecture Overview","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"ORCHESTRATOR (you, reading this file)\n├─ Phase 0: Setup — detect framework, extract API, create state file\n├─ Phase 1: Decompose into vertical slices → user approves\n│\n├─ FOR EACH SLICE:\n│ ├─ Phase 2 (RED): Task(Test Writer) ← spec + API only\n│ ├─ Phase 3 (GREEN): Task(Implementer) ← failing test + error only\n│ └─ Phase 4 (REFACTOR): Task(Refactorer) ← all code + green results\n│\n└─ Summary","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Context Boundaries (the key constraint)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Agent","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sees","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Does NOT See","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test Writer","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Slice spec, public API signatures, framework conventions, layer constraints","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Implementation code, other slices, implementation plans","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Implementer","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Failing test code, test failure output, file tree, existing source, layer constraints","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Original spec, slice descriptions, future plans","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Refactorer","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All implementation + all tests + green results, layers touched","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Original spec, decomposition rationale","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 0: Setup (once per session)","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 1","type":"text","marks":[{"type":"strong"}]},{"text":": Detect framework and test runner.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Check for: package.json (jest/vitest), pyproject.toml/pytest.ini (pytest),\ngo.mod (go test), Cargo.toml (cargo test), Gemfile (rspec), composer.json (phpunit)","type":"text"}]},{"type":"paragraph","content":[{"text":"If ambiguous, ask: \"What command runs your tests? (e.g., ","type":"text"},{"text":"npm test","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pytest","type":"text","marks":[{"type":"code_inline"}]},{"text":")\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 2","type":"text","marks":[{"type":"strong"}]},{"text":": Detect language from source files (for agent prompts):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"TypeScript (.ts/.tsx), JavaScript (.js/.jsx), Python (.py), Go (.go), Rust (.rs), Ruby (.rb), PHP (.php)","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 3","type":"text","marks":[{"type":"strong"}]},{"text":": Verify green baseline.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} \"{TEST_COMMAND}\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Parse the JSON output.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"pass\"","type":"text","marks":[{"type":"code_inline"}]},{"text":": proceed.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"fail\"","type":"text","marks":[{"type":"code_inline"}]},{"text":": stop — \"Existing tests are failing. TDD starts from a green baseline.\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"error\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" AND ","type":"text"},{"text":"total","type":"text","marks":[{"type":"code_inline"}]},{"text":" is 0: ","type":"text"},{"text":"greenfield project","type":"text","marks":[{"type":"strong"}]},{"text":" — no tests exist yet. This is fine. Proceed.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 4","type":"text","marks":[{"type":"strong"}]},{"text":": Extract the public API surface.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/extract_api.sh {SOURCE_DIR}","type":"text"}]},{"type":"paragraph","content":[{"text":"Save the output — this is what the Test Writer will see. If empty (greenfield), that's expected.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 5","type":"text","marks":[{"type":"strong"}]},{"text":": Discover project documentation.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/discover_docs.sh {PROJECT_ROOT} --lang {LANGUAGE}","type":"text"}]},{"type":"paragraph","content":[{"text":"This searches for:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Documentation files","type":"text","marks":[{"type":"strong"}]},{"text":": README, ARCHITECTURE.md, docs/ folder, DESIGN.md, SPEC files, ADRs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API specifications","type":"text","marks":[{"type":"strong"}]},{"text":": OpenAPI/Swagger, GraphQL schemas, .proto files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Source docstrings","type":"text","marks":[{"type":"strong"}]},{"text":": JSDoc, Python docstrings, Go doc comments, Rust ","type":"text"},{"text":"///","type":"text","marks":[{"type":"code_inline"}]},{"text":" comments","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Save the output as ","type":"text"},{"text":"{DOC_CONTEXT}","type":"text","marks":[{"type":"code_inline"}]},{"text":". This feeds into:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 1","type":"text","marks":[{"type":"strong"}]},{"text":" — so slice decomposition is informed by documented behavior and API contracts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 2","type":"text","marks":[{"type":"strong"}]},{"text":" — so the Test Writer writes tests aligned with documented intent, not just code signatures","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If empty (no docs found), that's fine — proceed without doc context.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 6","type":"text","marks":[{"type":"strong"}]},{"text":": Create the state file ","type":"text"},{"text":".tdd-state.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the project root:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"feature\": \"user's feature description\",\n \"framework\": \"jest|vitest|pytest|go|cargo|rspec|phpunit\",\n \"language\": \"typescript|javascript|python|go|rust|ruby|php\",\n \"test_command\": \"the full test command\",\n \"source_dir\": \"src/\",\n \"doc_context\": \"output from discover_docs.sh (or empty string)\",\n \"auto_mode\": false,\n \"dry_run\": false,\n \"slices\": [],\n \"current_slice\": 0,\n \"phase\": \"setup\",\n \"layer_map\": {},\n \"files_modified\": [],\n \"test_files_created\": []\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Each slice in the ","type":"text"},{"text":"slices","type":"text","marks":[{"type":"code_inline"}]},{"text":" array includes a ","type":"text"},{"text":"layer","type":"text","marks":[{"type":"code_inline"}]},{"text":" field: ","type":"text"},{"text":"\"domain\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"domain-service\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"application\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"\"infrastructure\"","type":"text","marks":[{"type":"code_inline"}]},{"text":". See Phase 1 for how layers are assigned.","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" maps directory prefixes to layers. Built during Phase 1 from project structure:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"layer_map\": {\n \"src/domain/\": \"domain\",\n \"src/services/\": \"domain-service\",\n \"src/application/\": \"application\",\n \"src/infrastructure/\": \"infrastructure\",\n \"src/adapters/\": \"infrastructure\",\n \"src/controllers/\": \"infrastructure\"\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"If the project has no clear directory-layer mapping (flat structure), set ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":"{}","type":"text","marks":[{"type":"code_inline"}]},{"text":" and skip path-based validation.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 5a","type":"text","marks":[{"type":"strong"}]},{"text":" (auto-detect layer_map): If ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" is empty, scan the source directory for common DDD/layered architecture directory names and auto-populate:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Common directory → layer mappings (check if directories exist):\n */domain/ → \"domain\"\n */models/ → \"domain\" (ORM models often serve as domain entities)\n */entities/ → \"domain\"\n */value_objects/ → \"domain\"\n */services/ → \"application\" (unless clearly infrastructure)\n */application/ → \"application\"\n */use_cases/ → \"application\"\n */core/ → \"application\"\n */infrastructure/ → \"infrastructure\"\n */adapters/ → \"infrastructure\"\n */controllers/ → \"infrastructure\"\n */api/ → \"infrastructure\"\n */bot/ → \"infrastructure\" (Telegram/Discord bot handlers)\n */handlers/ → \"infrastructure\"\n */repositories/ → \"infrastructure\" (concrete repo implementations)","type":"text"}]},{"type":"paragraph","content":[{"text":"Only add entries for directories that actually exist in the source tree. If fewer than 2 directories match, leave ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" empty (flat project). Present the auto-detected map to the user for confirmation:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Auto-detected layer map from directory structure:\n src/models/ → domain\n src/services/ → application\n src/core/ → application\n src/bot/ → infrastructure\n src/api/ → infrastructure\n\nDoes this mapping look correct? (adjust if needed)","type":"text"}]},{"type":"paragraph","content":[{"text":"Update state","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"\"phase\": \"setup\"","type":"text","marks":[{"type":"code_inline"}]},{"text":". Write state file immediately.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 1: Specification Decomposition","type":"text"}]},{"type":"paragraph","content":[{"text":"Take the user's feature request and decompose into ","type":"text"},{"text":"ordered vertical slices","type":"text","marks":[{"type":"strong"}]},{"text":". Each slice is one testable behavior.","type":"text"}]},{"type":"paragraph","content":[{"text":"Use doc context","type":"text","marks":[{"type":"strong"}]},{"text":": When decomposing, cross-reference ","type":"text"},{"text":"{DOC_CONTEXT}","type":"text","marks":[{"type":"code_inline"}]},{"text":" from Phase 0 Step 5. Documentation often describes intended behaviors, edge cases, and API contracts that should inform slice boundaries. If docs mention specific error cases, validation rules, or behavioral requirements, consider them as slice candidates.","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Inside-Out Slice Ordering","type":"text"}]},{"type":"paragraph","content":[{"text":"After identifying all slices, ","type":"text"},{"text":"sort them inside-out by architectural layer","type":"text","marks":[{"type":"strong"}]},{"text":". This ensures each slice can build on real (not mocked) implementations from previous slices:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Domain model","type":"text","marks":[{"type":"strong"}]},{"text":" slices first — pure logic, no dependencies, no mocks needed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Domain service","type":"text","marks":[{"type":"strong"}]},{"text":" slices — cross-aggregate operations using real domain objects","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Application service / use case","type":"text","marks":[{"type":"strong"}]},{"text":" slices — orchestration using in-memory fakes for ports","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Infrastructure adapter","type":"text","marks":[{"type":"strong"}]},{"text":" slices last — repos, external APIs, framework adapters","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Assign each slice a ","type":"text"},{"text":"layer","type":"text","marks":[{"type":"code_inline"}]},{"text":" tag: ","type":"text"},{"text":"domain","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"domain-service","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"application","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"infrastructure","type":"text","marks":[{"type":"code_inline"}]},{"text":". Use the heuristics from ","type":"text"},{"text":"references/layer_guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" to classify.","type":"text"}]},{"type":"paragraph","content":[{"text":"Why inside-out?","type":"text","marks":[{"type":"strong"}]},{"text":" Domain slices produce real objects that later slices use directly. This minimizes mocking and catches integration issues early. It also ensures business rules are implemented and tested before any infrastructure decisions are made.","type":"text"}]},{"type":"paragraph","content":[{"text":"For simple projects where all code lives in one layer, all slices get ","type":"text"},{"text":"layer: \"application\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the ordering doesn't change — the guidance degrades gracefully.","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Edge Cases in Slice Ordering","type":"text"}]},{"type":"paragraph","content":[{"text":"Infrastructure-only features","type":"text","marks":[{"type":"strong"}]},{"text":" (e.g., \"add email provider retry logic\", \"switch from Postgres to MySQL\"):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If a feature has NO domain or application behavior changes, all slices may be ","type":"text"},{"text":"infrastructure","type":"text","marks":[{"type":"code_inline"}]},{"text":". This is valid — skip the inner layers entirely.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Present as: \"This is a pure infrastructure change. All slices are infrastructure-layer.\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Missing port interface","type":"text","marks":[{"type":"strong"}]},{"text":" (domain-service needs a port that doesn't exist yet):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The first slice that needs the port should create the interface as part of its implementation. The Implementer is allowed to create files in inner layers (domain/domain-service can define their own ports).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Example: a ","type":"text"},{"text":"domain-service","type":"text","marks":[{"type":"code_inline"}]},{"text":" slice for ","type":"text"},{"text":"RegistrationService","type":"text","marks":[{"type":"code_inline"}]},{"text":" creates ","type":"text"},{"text":"domain/ports/UserRepository","type":"text","marks":[{"type":"code_inline"}]},{"text":" interface as part of GREEN.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Cross-cutting slices","type":"text","marks":[{"type":"strong"}]},{"text":" (a slice touches multiple layers):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tag with the INNERMOST layer it touches. The Implementer may create files in that layer and any inner layers.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Example: a use case that also introduces a new domain event is tagged ","type":"text"},{"text":"application","type":"text","marks":[{"type":"code_inline"}]},{"text":" but creates a file in ","type":"text"},{"text":"domain/events/","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Present to the user:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"I've broken this into N vertical slices (ordered inside-out):\n\nDomain:\n1. [behavior] — [what the test verifies]\n\nDomain Services:\n2. [behavior] — [what the test verifies]\n\nApplication:\n3. [behavior] — [what the test verifies]\n\nInfrastructure:\n4. [behavior] — [what the test verifies]\n\nEach slice follows RED -> GREEN -> REFACTOR before moving to the next.\nDoes this decomposition look right?","type":"text"}]},{"type":"paragraph","content":[{"text":"If all slices fall in one layer, skip the layer headings and present as a flat list.","type":"text"}]},{"type":"paragraph","content":[{"text":"Wait for user approval","type":"text","marks":[{"type":"strong"}]},{"text":" (even in ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode — slice decomposition always needs sign-off).","type":"text"}]},{"type":"paragraph","content":[{"text":"Update state","type":"text","marks":[{"type":"strong"}]},{"text":": Write slices array (each with ","type":"text"},{"text":"layer","type":"text","marks":[{"type":"code_inline"}]},{"text":" field), set ","type":"text"},{"text":"\"phase\": \"decomposed\"","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Dry-Run Phase Override (Phase 2–4)","type":"text"}]},{"type":"paragraph","content":[{"text":"In ","type":"text"},{"text":"--dry-run","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, ","type":"text"},{"text":"replace Phases 2–4 entirely","type":"text","marks":[{"type":"strong"}]},{"text":" with the following for each slice:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refresh API surface (","type":"text"},{"text":"extract_api.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Render the ","type":"text"},{"text":"Test Writer prompt","type":"text","marks":[{"type":"strong"}]},{"text":" with all variables filled in. Print it under a ","type":"text"},{"text":"### Test Writer Prompt (slice N)","type":"text","marks":[{"type":"code_inline"}]},{"text":" heading.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Render the ","type":"text"},{"text":"Implementer prompt","type":"text","marks":[{"type":"strong"}]},{"text":" using placeholder test code: ","type":"text"},{"text":"\"(dry-run: test code would be generated by Test Writer)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":"{FAILING_TEST_CODE}","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"\"(dry-run: no test output)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":"{TEST_FAILURE_OUTPUT}","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Render the ","type":"text"},{"text":"Refactorer prompt","type":"text","marks":[{"type":"strong"}]},{"text":" using placeholder values: ","type":"text"},{"text":"\"(dry-run: no green output)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":"{GREEN_TEST_OUTPUT}","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"(dry-run: code from Test Writer)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":"{ALL_TEST_CODE}","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"(dry-run: code from Implementer)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":"{ALL_IMPLEMENTATION_CODE}","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each rendered prompt, verify no ","type":"text"},{"text":"{UNRESOLVED_VARIABLE}","type":"text","marks":[{"type":"code_inline"}]},{"text":" patterns remain (regex: ","type":"text"},{"text":"\\{[A-Z][A-Z_]+\\}","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Report any unresolved variables as errors.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Print character counts for each prompt.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Move to next slice (no ","type":"text"},{"text":"Task()","type":"text","marks":[{"type":"code_inline"}]},{"text":" calls, no file writes, no test runs).","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"After all slices are processed, print the dry-run summary and exit. Do NOT clean up the state file — it's useful for subsequent ","type":"text"},{"text":"--resume","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 2: RED — Write One Failing Test","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 1","type":"text","marks":[{"type":"strong"}]},{"text":": Refresh the API surface (it changes as slices are implemented):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/extract_api.sh {SOURCE_DIR}","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 2","type":"text","marks":[{"type":"strong"}]},{"text":": Read the prompt template from ","type":"text"},{"text":"references/agent_prompts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> \"Test Writer Agent\" section. Construct the prompt by filling in:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{SLICE_SPEC}","type":"text","marks":[{"type":"code_inline"}]},{"text":": The current slice's behavior description","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LANGUAGE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Detected language from Phase 0","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{FRAMEWORK}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Detected framework name","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{API_SURFACE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Output from extract_api.sh","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{DOC_CONTEXT}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Output from discover_docs.sh (Phase 0 Step 5). Include only sections relevant to the current slice — filter by keyword match if the full output is large.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{TEST_FILE_PATH}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Where the test should go (follow project conventions)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{EXISTING_TEST_CONTENT}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Current content of the test file (if it exists), or \"No test file exists yet.\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{FRAMEWORK_SKELETON}","type":"text","marks":[{"type":"code_inline"}]},{"text":": The relevant skeleton from ","type":"text"},{"text":"references/framework_configs.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LAYER}","type":"text","marks":[{"type":"code_inline"}]},{"text":": The slice's layer tag from Phase 1","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LAYER_TEST_CONSTRAINTS}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Layer-specific test constraints (see agent_prompts.md -> Layer-Specific Constraint Lookup)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 3","type":"text","marks":[{"type":"strong"}]},{"text":": Launch the Test Writer agent:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Task(subagent_type=\"general-purpose\", prompt=\u003cconstructed prompt>)","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 4","type":"text","marks":[{"type":"strong"}]},{"text":": Parse the JSON response using the ","type":"text"},{"text":"parse_agent_json","type":"text","marks":[{"type":"code_inline"}]},{"text":" logic from ","type":"text"},{"text":"agent_prompts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Strip markdown fences if present","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Try direct JSON parse","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If that fails, find first ","type":"text"},{"text":"{","type":"text","marks":[{"type":"code_inline"}]},{"text":" and last ","type":"text"},{"text":"}","type":"text","marks":[{"type":"code_inline"}]},{"text":", try that substring","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If still invalid: retry the Task call once with appended \"Return ONLY a JSON object.\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If still failing: extract test code manually from the raw response","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 5","type":"text","marks":[{"type":"strong"}]},{"text":": Write the test code to the test file. If the file exists, append the test function (and merge imports). If new, create with the agent's ","type":"text"},{"text":"imports_needed","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"test_code","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 5a","type":"text","marks":[{"type":"strong"}]},{"text":" (post-write test smell scan): Scan the test code for common smells before running:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Smell","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Detection","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Assertion Roulette","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multiple bare ","type":"text"},{"text":"assert","type":"text","marks":[{"type":"code_inline"}]},{"text":" statements without messages in the same test function (3+)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Warn the user (don't block): \"Test has N bare assertions — consider adding failure messages for easier debugging.\"","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unknown Test","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test name is generic: matches ","type":"text"},{"text":"test_1","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"test_it","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"test_works","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"test_example","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"test_thing","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-launch Test Writer with appended: \"Use a descriptive test name that reads as a behavior spec (e.g., test_rejects_empty_email).\"","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tautological assertion","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assert True","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"assert result is not None","type":"text","marks":[{"type":"code_inline"}]},{"text":" when function has no None return path, ","type":"text"},{"text":"assert isinstance(result, X)","type":"text","marks":[{"type":"code_inline"}]},{"text":" as sole assertion","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-launch Test Writer with appended: \"The assertion is tautological — test the actual behavior/value, not just that the function returns something.\"","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Step 5b","type":"text","marks":[{"type":"strong"}]},{"text":" (post-write layer lint): Scan the test code for layer-violating patterns:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Layer","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Forbidden patterns in test code","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"domain","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"jest.mock(","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"vi.mock(","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Mock(","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"mock.patch","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"unittest.mock","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"gomock","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"mockery","type":"text","marks":[{"type":"code_inline"}]},{"text":" — domain tests must not use mocking libraries","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"domain-service","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Same mocking patterns for domain objects (mocking ports/repos is OK)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"application","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No forbidden patterns (mocking ports is expected)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"infrastructure","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No forbidden patterns","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"If forbidden patterns found:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remove the offending mock/pattern from the test","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Re-launch Test Writer with appended: \"Do NOT use mocking libraries. This is a {LAYER} layer test. Use real domain objects.\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If second attempt still uses forbidden patterns, ask user","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 6","type":"text","marks":[{"type":"strong"}]},{"text":": Run the test to confirm it FAILS (expect an assertion failure, not a setup error):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} \"{TEST_COMMAND_FOR_SPECIFIC_TEST}\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 7","type":"text","marks":[{"type":"strong"}]},{"text":": Evaluate the result with semantic validation:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Result","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"status: \"fail\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", assertion error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Proper RED","type":"text","marks":[{"type":"strong"}]},{"text":" — test fails because the expected behavior doesn't exist yet. Proceed.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"status: \"fail\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ImportError","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"ModuleNotFoundError","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Setup problem, not a proper RED.","type":"text","marks":[{"type":"strong"}]},{"text":" The test can't even import the module under test. Fix: create a minimal stub (empty class/function) so the import resolves, then re-run. The test should now fail on the assertion instead.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"status: \"fail\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"AttributeError","type":"text","marks":[{"type":"code_inline"}]},{"text":" on missing method","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Similar to import error — the class exists but the method doesn't. This is an acceptable RED if the assertion would also fail. Proceed.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"status: \"pass\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Behavior already exists. Log: \"Test passes — skipping slice (already implemented).\" Increment ","type":"text"},{"text":"current_slice","type":"text","marks":[{"type":"code_inline"}]},{"text":", move to next slice.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"status: \"error\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"SyntaxError","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix: the test has a typo. Read the ","type":"text"},{"text":"raw_tail","type":"text","marks":[{"type":"code_inline"}]},{"text":", fix the test file directly. Re-run. If still erroring after 2 fix attempts, ask user.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"status: \"error\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", compile/framework error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix: bad import, missing fixture, or framework misconfiguration. Read the ","type":"text"},{"text":"raw_tail","type":"text","marks":[{"type":"code_inline"}]},{"text":", fix the test file directly. Re-run. If still erroring after 2 fix attempts, ask user.","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Step 8","type":"text","marks":[{"type":"strong"}]},{"text":" (interactive mode only — skip in ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":"): Present to the user:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"RED: Test written and failing as expected.\n\nTest: {test_name}\nFile: {test_file_path}\nFailure: {failure message from JSON}\n\nThis test verifies: {test_description from agent response}\n\nProceed to GREEN phase? (or adjust the test?)","type":"text"}]},{"type":"paragraph","content":[{"text":"Wait for user approval before proceeding to GREEN.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Update state","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"\"phase\": \"red\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", add test file to ","type":"text"},{"text":"test_files_created","type":"text","marks":[{"type":"code_inline"}]},{"text":". Write state immediately.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 3: GREEN — Minimal Implementation","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 1","type":"text","marks":[{"type":"strong"}]},{"text":": Read the failing test file and the test failure output (the full ","type":"text"},{"text":"raw_tail","type":"text","marks":[{"type":"code_inline"}]},{"text":" from the RED phase run_tests.sh result).","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 2","type":"text","marks":[{"type":"strong"}]},{"text":": Build the file tree of source files (not test files, not node_modules, etc.):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"find {SOURCE_DIR} -type f \\( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.go' -o -name '*.rs' -o -name '*.rb' -o -name '*.php' \\) | grep -v test | grep -v spec | grep -v node_modules | grep -v __pycache__ | grep -v vendor | grep -v target | grep -v dist | grep -v build | head -50","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 3","type":"text","marks":[{"type":"strong"}]},{"text":": Read existing source files that the test imports or references.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 4","type":"text","marks":[{"type":"strong"}]},{"text":": Read the prompt template from ","type":"text"},{"text":"references/agent_prompts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> \"Implementer Agent\" section. Fill in:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LANGUAGE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Detected language","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{FAILING_TEST_CODE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": The complete test file content","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{TEST_FAILURE_OUTPUT}","type":"text","marks":[{"type":"code_inline"}]},{"text":": The ","type":"text"},{"text":"raw_tail","type":"text","marks":[{"type":"code_inline"}]},{"text":" from run_tests.sh JSON output","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{FILE_TREE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Source file listing from Step 2","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{EXISTING_SOURCE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Content of relevant source files (if any — may be empty for greenfield)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LAYER}","type":"text","marks":[{"type":"code_inline"}]},{"text":": The slice's layer tag from Phase 1","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LAYER_DEPENDENCY_CONSTRAINT}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Layer-specific dependency constraint (see agent_prompts.md -> Layer-Specific Constraint Lookup)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"On retries (attempt > 1), also fill in the ","type":"text"},{"text":"{?PREVIOUS_ATTEMPT}","type":"text","marks":[{"type":"code_inline"}]},{"text":" section:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{PREVIOUS_ATTEMPT_DESCRIPTION}","type":"text","marks":[{"type":"code_inline"}]},{"text":": the ","type":"text"},{"text":"explanation","type":"text","marks":[{"type":"code_inline"}]},{"text":" field from the failed attempt","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{PREVIOUS_ATTEMPT_ERROR}","type":"text","marks":[{"type":"code_inline"}]},{"text":": the ","type":"text"},{"text":"raw_tail","type":"text","marks":[{"type":"code_inline"}]},{"text":" from the test run after the failed attempt","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"CRITICAL","type":"text","marks":[{"type":"strong"}]},{"text":": Do NOT include the slice specification, feature description, or any future plans. The Implementer works from the test alone.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 5","type":"text","marks":[{"type":"strong"}]},{"text":": Launch the Implementer agent:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Task(subagent_type=\"general-purpose\", prompt=\u003cconstructed prompt>)","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 6","type":"text","marks":[{"type":"strong"}]},{"text":": Parse the JSON response. ","type":"text"},{"text":"Validate layer boundaries","type":"text","marks":[{"type":"strong"}]},{"text":", then apply file changes.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 6a","type":"text","marks":[{"type":"strong"}]},{"text":" (Layer path validation): If ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" is not empty, check each file path in the response against the current slice's layer:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"For each file in response.files:\n inferred_layer = lookup file.path against layer_map (longest prefix match)\n if inferred_layer exists AND inferred_layer != current_slice.layer:\n if inferred_layer is OUTER relative to current_slice.layer:\n REJECT: \"Implementer created/modified {file.path} which belongs to\n the {inferred_layer} layer, but this is a {current_slice.layer} slice.\n Inner layers must not depend on outer layers.\"\n → Re-launch Implementer with appended constraint:\n \"Do NOT create or modify files in {inferred_layer} directories.\n This slice is {current_slice.layer} only.\"\n if inferred_layer is INNER relative to current_slice.layer:\n ALLOW: outer layers may touch inner-layer files (e.g., adding a port interface)","type":"text"}]},{"type":"paragraph","content":[{"text":"Layer ordering for \"outer\" check: domain \u003c domain-service \u003c application \u003c infrastructure.","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" is empty (flat project), skip this validation.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 6b","type":"text","marks":[{"type":"strong"}]},{"text":": Apply validated file changes:","type":"text"}]},{"type":"paragraph","content":[{"text":"For each file in the response ","type":"text"},{"text":"files","type":"text","marks":[{"type":"code_inline"}]},{"text":" array:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"action","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"create\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"\"overwrite\"","type":"text","marks":[{"type":"code_inline"}]},{"text":": Use the Write tool to create or overwrite the file with the complete content","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"action","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"\"edit\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (used for existing files over 200 lines): Use the Edit tool with ","type":"text"},{"text":"old_string","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"new_string","type":"text","marks":[{"type":"code_inline"}]},{"text":" to apply the changes. The Implementer returns only the changed functions with surrounding context — identify the insertion point or the function being replaced, and use Edit tool accordingly. If the edit target is ambiguous, fall back to reading the full file and using Write.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For existing files over 200 lines where the Implementer returned full content anyway (action = \"overwrite\"), prefer using Edit tool to apply only the diff — this prevents accidental reformatting of untouched code","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 7","type":"text","marks":[{"type":"strong"}]},{"text":": Run the specific test:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} \"{TEST_COMMAND_FOR_SPECIFIC_TEST}\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 8","type":"text","marks":[{"type":"strong"}]},{"text":": RETRY LOOP (if test still fails):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"attempt = 1\nmax_attempts = 5\nprevious_explanation = null\nprevious_error = null\n\nwhile status != \"pass\" AND attempt \u003c= max_attempts:\n previous_explanation = explanation from last Implementer response\n previous_error = raw_tail from last test run\n\n Launch FRESH Task(Implementer) with:\n - same test code + file tree + existing source (re-read!)\n - NEW failure output\n - PREVIOUS_ATTEMPT section filled in\n\n Apply changes (Write tool for each file)\n Re-run test\n attempt += 1\n\nif still failing after max_attempts:\n STOP. Present to user:\n \"Implementation failed after 5 attempts. Last error: {raw_tail}\"\n Ask: \"Adjust the test, try a different approach, or debug manually?\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Each retry is a ","type":"text"},{"text":"fresh","type":"text","marks":[{"type":"strong"}]},{"text":" Task call with only the previous attempt's explanation and error. This prevents the Implementer from going down rabbit holes while giving it enough context to try a different strategy.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 9","type":"text","marks":[{"type":"strong"}]},{"text":": Once the specific test passes, run the FULL test suite:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} \"{FULL_TEST_COMMAND}\" --all","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 10","type":"text","marks":[{"type":"strong"}]},{"text":": Handle regressions:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Result","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All pass","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Proceed to REFACTOR","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Regressions found","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-fix: launch a fresh Implementer with the regression test failures. Apply. Re-run full suite. Repeat up to 3 times. If still failing after 3 regression-fix attempts, STOP and present to user.","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Step 11","type":"text","marks":[{"type":"strong"}]},{"text":" (interactive mode only — skip in ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":"): Present to the user:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"GREEN: Test passing with minimal implementation.\n\nImplementation: {explanation from agent response}\nFiles changed: {list}\nAll tests: {passed} passing, {failed} failing\n\nProceed to REFACTOR phase? (or adjust?)","type":"text"}]},{"type":"paragraph","content":[{"text":"Update state","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"\"phase\": \"green\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", update ","type":"text"},{"text":"files_modified","type":"text","marks":[{"type":"code_inline"}]},{"text":". Write state immediately.","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 12","type":"text","marks":[{"type":"strong"}]},{"text":" (domain/domain-service slices only): Layer purity check before REFACTOR:","type":"text"}]},{"type":"paragraph","content":[{"text":"For each new/modified file in a ","type":"text"},{"text":"domain","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"domain-service","type":"text","marks":[{"type":"code_inline"}]},{"text":" layer slice:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Import scan","type":"text","marks":[{"type":"strong"}]},{"text":": Read all import/require statements. Check each imported module against ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":". Flag any import from an outer layer as a violation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Constructor check","type":"text","marks":[{"type":"strong"}]},{"text":": Verify constructor takes NO parameters typed from outer layers (no ORM sessions, HTTP clients, framework configs)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static call check","type":"text","marks":[{"type":"strong"}]},{"text":": No static method calls to outer-layer code","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If violations found, fix them now (move the dependency to a port interface) before entering REFACTOR","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 13","type":"text","marks":[{"type":"strong"}]},{"text":": Full-repo import scan (all layers, runs once per slice):","type":"text"}]},{"type":"paragraph","content":[{"text":"Scan ALL source files (not just session-modified) for dependency direction violations:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# For each source file, extract imports and check against layer_map\n# Language-specific patterns:\n# Python: from X import Y, import X\n# TypeScript/JS: import ... from 'X', require('X')\n# Go: import \"X\"","type":"text"}]},{"type":"paragraph","content":[{"text":"For each file:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Determine its layer from ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]},{"text":" (skip if no match)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each import, determine the imported module's layer from ","type":"text"},{"text":"layer_map","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If imported layer is OUTER relative to file's layer → violation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Report violations to the user before REFACTOR:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Layer scan found N dependency direction violation(s):\n- domain/user.py imports infrastructure/db.py (domain → infrastructure)\n- domain/services/registration.py imports adapters/email.py (domain-service → infrastructure)","type":"text"}]},{"type":"paragraph","content":[{"text":"In ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode: attempt auto-fix (replace concrete import with port interface). In interactive mode: present violations and ask user how to proceed.","type":"text"}]},{"type":"paragraph","content":[{"text":"This supplements the Refactorer's import checking (which only sees session files) with a repo-wide scan. Static tools miss ~23% of violations (Pruijt et al., 2017) — combining textual + structural checks improves coverage.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 4: REFACTOR","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 1","type":"text","marks":[{"type":"strong"}]},{"text":": Gather all context:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"All test files created/modified during this session","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"All source files modified during this session","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The green test output","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 2","type":"text","marks":[{"type":"strong"}]},{"text":": Read the prompt template from ","type":"text"},{"text":"references/agent_prompts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> \"Refactorer Agent\" section. Fill in:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{LANGUAGE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Detected language","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{GREEN_TEST_OUTPUT}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Full test output showing all green","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{ALL_TEST_CODE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Content of all test files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{ALL_IMPLEMENTATION_CODE}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Content of all modified source files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"{SLICE_LAYERS}","type":"text","marks":[{"type":"code_inline"}]},{"text":": Comma-separated list of unique layers from all slices completed so far","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 3","type":"text","marks":[{"type":"strong"}]},{"text":": Launch the Refactorer agent:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Task(subagent_type=\"general-purpose\", prompt=\u003cconstructed prompt>)","type":"text"}]},{"type":"paragraph","content":[{"text":"Step 4","type":"text","marks":[{"type":"strong"}]},{"text":": Parse the JSON response. If ","type":"text"},{"text":"suggestions","type":"text","marks":[{"type":"code_inline"}]},{"text":" is empty, skip to Step 6.","type":"text"}]},{"type":"paragraph","content":[{"text":"Apply suggestions ","type":"text"},{"text":"one at a time","type":"text","marks":[{"type":"strong"}]},{"text":", in priority order (high first):","type":"text"}]},{"type":"paragraph","content":[{"text":"For each suggestion:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply the code change (Edit tool, using ","type":"text"},{"text":"old_code","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> ","type":"text"},{"text":"new_code","type":"text","marks":[{"type":"code_inline"}]},{"text":" for each file)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run the project linter/formatter check (detect from project config):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Python","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"python -m black --check {files} && python -m flake8 {files} && python -m mypy {files}","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TypeScript/JS","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"npx eslint {files}","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"npx tsc --noEmit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Go","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"go vet ./...","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rust","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"cargo clippy","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If lint fails -> ","type":"text"},{"text":"revert immediately","type":"text","marks":[{"type":"strong"}]},{"text":" and skip this suggestion (same as test failure)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run the full test suite","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If any test fails -> ","type":"text"},{"text":"revert immediately","type":"text","marks":[{"type":"strong"}]},{"text":" (re-read the file from before the edit and Write it back) and skip this suggestion","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If all tests pass and lint passes -> keep the change","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Step 5","type":"text","marks":[{"type":"strong"}]},{"text":" (interactive mode only — skip in ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":"): Present:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"REFACTOR: Code improved, all tests still passing.\n\nApplied: {list of accepted suggestions}\nSkipped: {list of reverted suggestions, if any}\nAll tests: {count} passing\n\n[Moving to slice N of M] or [All slices complete]","type":"text"}]},{"type":"paragraph","content":[{"text":"In ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, print one-liner:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"[auto] REFACTOR slice N/M: {applied_count} applied, {skipped_count} skipped","type":"text"}]},{"type":"paragraph","content":[{"text":"Update state","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"\"phase\": \"refactor\"","type":"text","marks":[{"type":"code_inline"}]},{"text":". Write state immediately.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 5: Next Slice or Complete","type":"text"}]},{"type":"paragraph","content":[{"text":"If more slices remain -> increment ","type":"text"},{"text":"current_slice","type":"text","marks":[{"type":"code_inline"}]},{"text":" in state, return to Phase 2.","type":"text"}]},{"type":"paragraph","content":[{"text":"If all slices complete -> present summary:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"TDD Complete: {feature name}\n\nSlices implemented: N\nTests written: N\nFiles created/modified: {list}\nAll tests passing: yes","type":"text"}]},{"type":"paragraph","content":[{"text":"Clean up: remove ","type":"text"},{"text":".tdd-state.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" (in ","type":"text"},{"text":"--auto","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, remove silently; in interactive, ask user).","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resume Support","type":"text"}]},{"type":"paragraph","content":[{"text":"When user invokes ","type":"text"},{"text":"/tdd --resume","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":".tdd-state.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" from project root","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Report current state: \"Found TDD session for '{feature}'. Currently at slice {N}/{total}, phase: {phase}.\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resume from the current phase of the current slice","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"auto_mode","type":"text","marks":[{"type":"code_inline"}]},{"text":" is true in state, continue in auto mode","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Edge Cases","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Greenfield Projects","type":"text"}]},{"type":"paragraph","content":[{"text":"No source files, no tests, no test configuration. Handle gracefully:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 0 Step 3","type":"text","marks":[{"type":"strong"}]},{"text":": If run_tests.sh returns ","type":"text"},{"text":"status: \"error\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"total: 0","type":"text","marks":[{"type":"code_inline"}]},{"text":", check if any test files exist. If none, this is greenfield — proceed.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 0 Step 4","type":"text","marks":[{"type":"strong"}]},{"text":": extract_api.sh will return empty output. Pass ","type":"text"},{"text":"\"(No existing API — this is a new project)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" to the Test Writer.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase 2","type":"text","marks":[{"type":"strong"}]},{"text":": The Test Writer will create test files from scratch. May need to set up the test framework config (e.g., ","type":"text"},{"text":"jest.config.js","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pytest.ini","type":"text","marks":[{"type":"code_inline"}]},{"text":"). If the first test run fails with a framework error (not a test failure), create minimal framework config and retry.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bug Fix TDD","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write a test demonstrating the bug (should FAIL showing the bug exists)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm failure matches the reported bug — human checkpoint","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fix: minimal code to make test pass (GREEN phase as normal)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify: no regressions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Existing Code (Characterization Tests)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write a test for CURRENT behavior (should PASS — this is a characterization test)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modify the test for DESIRED behavior (should FAIL)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Proceed with GREEN -> REFACTOR","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"User-Provided Tests","type":"text"}]},{"type":"paragraph","content":[{"text":"If user provides test code:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run to confirm it fails (RED confirmed)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skip to Phase 3 (GREEN) — user-provided tests are authoritative","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do not modify without asking","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Flaky Tests","type":"text"}]},{"type":"paragraph","content":[{"text":"If a test sometimes passes/fails: stop, report, fix the flaky test before continuing.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Failure Recovery Reference","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Failure","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Phase","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recovery","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test Writer returns invalid JSON","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Parse with fence-stripping + substring extraction. Retry once with \"Return ONLY JSON.\" Fall back to manual extraction.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test passes when it should fail","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Log \"already implemented\", skip slice, move to next.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test has syntax/compile error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read raw_tail, fix test file directly. Retry up to 2 times. Then ask user.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Implementer returns invalid JSON","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GREEN","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Same JSON recovery as Test Writer.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test still fails after implementation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GREEN","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Retry loop: up to 5 fresh Implementer calls with previous-attempt context. Then ask user.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Full suite has regressions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GREEN","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-fix: fresh Implementer with regression failures. Up to 3 attempts. Then ask user.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Refactorer suggestion breaks tests","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"REFACTOR","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Revert immediately, skip suggestion, continue with next.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"run_tests.sh timeout","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Any","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Increase timeout. If persistent, ask user about test performance.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"run_tests.sh returns ","type":"text"},{"text":"\"error\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Any","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read raw_tail for cause. Script error (missing binary, bad path) -> fix and retry. Compilation error -> treat as implementation error.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"extract_api.sh returns empty","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Normal for greenfield. Pass \"(No existing API)\" message.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Agent response is completely empty","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Any","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Retry the Task call once. If still empty, ask user.","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Layer Reference","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/layer_guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for layer definitions, dependency rules, test strategies by layer, and detection heuristics.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Anti-Patterns to Avoid","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/anti_patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Critical ones:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never modify a test to make it pass (change implementation, not tests)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never write implementation before tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never write all tests at once (vertical slicing)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never test implementation details","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never skip the RED phase","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never let domain code import infrastructure (dependency direction violation)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never mock domain objects — construct real instances instead","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Framework Quick Reference","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/framework_configs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for setup details.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Framework","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run single test","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run all","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Watch mode","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Jest","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"npx jest --testPathPattern=\u003cfile> -t \"\u003cname>\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"npx jest","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"npx jest --watch","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Vitest","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"npx vitest run \u003cfile> -t \"\u003cname>\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"npx vitest run","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"npx vitest","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pytest","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pytest \u003cfile>::\u003ctest_name> -v","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pytest -v","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pytest-watch","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Go","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"go test -run \u003cTestName> ./...","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"go test ./...","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cargo","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cargo test \u003ctest_name>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cargo test","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cargo watch -x test","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSpec","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"rspec \u003cfile>:\u003cline>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"rspec","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"guard","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PHPUnit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"phpunit --filter \u003ctest_name>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"phpunit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"tdd","author":"@skillopedia","source":{"stars":237,"repo_name":"claude-skills","origin_url":"https://github.com/glebis/claude-skills/blob/HEAD/tdd/SKILL.md","repo_owner":"glebis","body_sha256":"ea5fcd49ed1c952966ec0e21c8965119d40e7bfeab8bdf0fad226607b68ad967","cluster_key":"7ecae1702e2905e2a7a2670bc42cd204da630259df70216b891ca8dbe998beca","clean_bundle":{"format":"clean-skill-bundle-v1","source":"glebis/claude-skills/tdd/SKILL.md","attachments":[{"id":"21caeadb-e5ec-5391-9db9-a30e667db42a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/21caeadb-e5ec-5391-9db9-a30e667db42a/attachment.md","path":"README.md","size":6293,"sha256":"4ab2777c44362fb8efc141d8b9c9da76baac3fe32757ce953769e8836649c2e9","contentType":"text/markdown; charset=utf-8"},{"id":"581e174f-9585-55f6-a0ac-0b7eee9702cf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/581e174f-9585-55f6-a0ac-0b7eee9702cf/attachment.md","path":"references/agent_prompts.md","size":15544,"sha256":"830030ad2b0cd12af560f287f0636a1fc9825185c9eb072b35c2e4ba7f6a8715","contentType":"text/markdown; charset=utf-8"},{"id":"d139d46f-ddb8-5f9e-9db9-dfa8b9755989","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d139d46f-ddb8-5f9e-9db9-dfa8b9755989/attachment.md","path":"references/anti_patterns.md","size":11502,"sha256":"29dba73b06e0731fc5f3693b8b3a0afd48ab056553fbc4789b50fa539402b572","contentType":"text/markdown; charset=utf-8"},{"id":"5cc8da79-9226-571a-aa58-b06567f630e9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5cc8da79-9226-571a-aa58-b06567f630e9/attachment.md","path":"references/framework_configs.md","size":8144,"sha256":"3e99d4927d5d6ce9c76838d09634942c5aa4f5f737a6fcc063f06c13f036c88c","contentType":"text/markdown; charset=utf-8"},{"id":"aee322a3-c7bf-56f3-903e-40f22f460a8b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aee322a3-c7bf-56f3-903e-40f22f460a8b/attachment.md","path":"references/layer_guide.md","size":4137,"sha256":"688830b5de59bf2218968fbd9e2b72379e4fb3fc076ec87ce567230eb6a72096","contentType":"text/markdown; charset=utf-8"},{"id":"73132eb1-bef1-5214-a3a3-f79d8d2bfebe","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/73132eb1-bef1-5214-a3a3-f79d8d2bfebe/attachment.sh","path":"scripts/discover_docs.sh","size":10301,"sha256":"0c64edbd76172fea2230f2a0c611e5c566bfd91c90778e203c1fc8de552b59c0","contentType":"application/x-sh; charset=utf-8"},{"id":"c4a12d53-4fb6-53e9-a9cc-f4b43087c636","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c4a12d53-4fb6-53e9-a9cc-f4b43087c636/attachment.sh","path":"scripts/extract_api.sh","size":7726,"sha256":"4818ec870932b36b3c01ec3d44076f38feb5107d8bf094b04ef1e8d676b8c153","contentType":"application/x-sh; charset=utf-8"},{"id":"34ce5110-2441-55fb-8fc4-cf2a76421250","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/34ce5110-2441-55fb-8fc4-cf2a76421250/attachment.sh","path":"scripts/run_tests.sh","size":11000,"sha256":"082ded072db72e3acd384f5bdfc1f6e5601a659eba687ffa09ced4cd19dcb7ee","contentType":"application/x-sh; charset=utf-8"},{"id":"6e9f06ee-e9c3-5889-b958-b8e27a1dcdb9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6e9f06ee-e9c3-5889-b958-b8e27a1dcdb9/attachment.py","path":"tests/conftest.py","size":741,"sha256":"cfecb743af2b641acf49bf42f75e41cfec0d640ee11c3e103fc1a5fb2ec0d718","contentType":"text/x-python; charset=utf-8"},{"id":"1f373578-fa72-5b6c-a95a-7770da29faef","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1f373578-fa72-5b6c-a95a-7770da29faef/attachment.go","path":"tests/fixtures/go_project/calculator.go","size":416,"sha256":"bc634ccf050396b6822c06dd4aef28b0080a38acb71a699a1d38272ac338210c","contentType":"text/plain; charset=utf-8"},{"id":"88303984-0543-548b-8113-d28d82efa3fa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/88303984-0543-548b-8113-d28d82efa3fa/attachment.md","path":"tests/fixtures/python_project/README.md","size":331,"sha256":"13b54ef4e21278cbb646ea170fc35f416bb99e036b2b9ff6c7953c0c6c038e5c","contentType":"text/markdown; charset=utf-8"},{"id":"d1ca70d9-b7b1-5e86-90b3-962c947a9883","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d1ca70d9-b7b1-5e86-90b3-962c947a9883/attachment.md","path":"tests/fixtures/python_project/docs/API.md","size":294,"sha256":"df181178812d9ae9fc9ff04564645da13327a8efa26a2891214ba8bd246b815e","contentType":"text/markdown; charset=utf-8"},{"id":"847cbf2a-7f61-5737-823f-b2b4017df6bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/847cbf2a-7f61-5737-823f-b2b4017df6bd/attachment.yaml","path":"tests/fixtures/python_project/docs/openapi.yaml","size":429,"sha256":"cf06da3856e16d20a5c7b89a946f73c2be2999d664db6f9cbd85b5d37c9933f1","contentType":"application/yaml; charset=utf-8"},{"id":"06576ed6-0f14-5022-bbf1-40653473e4ca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/06576ed6-0f14-5022-bbf1-40653473e4ca/attachment.py","path":"tests/fixtures/python_project/src/calculator.py","size":1074,"sha256":"cc861e01f8550ce886f37f9955cd1e3ae31607508246cd813f97399d64c60d5b","contentType":"text/x-python; charset=utf-8"},{"id":"c195f3f1-6bc9-561c-903a-5e6264e30595","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c195f3f1-6bc9-561c-903a-5e6264e30595/attachment.py","path":"tests/fixtures/python_project/src/models.py","size":252,"sha256":"21e8ae91454c71817f5b26e6d08ed42f3eedbea8244a23a222fd0cf74e1cff25","contentType":"text/x-python; charset=utf-8"},{"id":"c6b9ec7d-2532-5bc8-9241-a3f681b94e93","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c6b9ec7d-2532-5bc8-9241-a3f681b94e93/attachment.md","path":"tests/fixtures/ts_project/README.md","size":61,"sha256":"c411f61d3270d033d4d64f1fb3f4cd2586ad52ed02845ae9b7440fbb6e7ac776","contentType":"text/markdown; charset=utf-8"},{"id":"ecad1b62-2cad-5ce8-8319-2b2a42d60c77","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ecad1b62-2cad-5ce8-8319-2b2a42d60c77/attachment.ts","path":"tests/fixtures/ts_project/src/calculator.ts","size":723,"sha256":"e7ce9a8db25c562223d4df7373e363f37f1cba4b90593832f1fef4fa920ae2b5","contentType":"text/typescript; charset=utf-8"},{"id":"1b9cb714-4bca-5063-b5a2-79d9d5d4ca6a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1b9cb714-4bca-5063-b5a2-79d9d5d4ca6a/attachment.txt","path":"tests/snapshots/implementer.txt","size":1755,"sha256":"d5eef8ab33678553c14b73a30a76acc7a102b14fa846b352633e45a63317c5f8","contentType":"text/plain; charset=utf-8"},{"id":"66704d4e-4e07-518b-99b4-e5811dd5e4bb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/66704d4e-4e07-518b-99b4-e5811dd5e4bb/attachment.txt","path":"tests/snapshots/refactorer.txt","size":2768,"sha256":"832cce92847aef0d3808d1680e9d52ad76d24c77daeb18dce7199bf44e084329","contentType":"text/plain; charset=utf-8"},{"id":"f4bc5c63-e770-567c-9a1b-443662f681c0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f4bc5c63-e770-567c-9a1b-443662f681c0/attachment.txt","path":"tests/snapshots/test_writer.txt","size":2603,"sha256":"2ccae95ee6544cc6070a3767856df96157d953594e6009bca736da9180ede829","contentType":"text/plain; charset=utf-8"},{"id":"aaedd8d7-6372-56d5-ac29-21c37ed5a775","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aaedd8d7-6372-56d5-ac29-21c37ed5a775/attachment.py","path":"tests/test_discover_docs.py","size":4140,"sha256":"21ac626562e985b9c351dcb1252bb644a6978792036d71d1bd05d22eef9bc9e6","contentType":"text/x-python; charset=utf-8"},{"id":"26a2bb6b-39b0-5a08-bffa-b5dd36e8d189","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/26a2bb6b-39b0-5a08-bffa-b5dd36e8d189/attachment.py","path":"tests/test_extract_api.py","size":4843,"sha256":"b046d9b28c842bbd974f4ed722de2a0c4ac61189057166b7c80c0ecb3353c811","contentType":"text/x-python; charset=utf-8"},{"id":"f38bf2f3-6df8-55c1-b07b-364d6ae06238","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f38bf2f3-6df8-55c1-b07b-364d6ae06238/attachment.py","path":"tests/test_prompts.py","size":11997,"sha256":"b8bec79e50bef3096866083b8f41cb6561a63aadc6c10d64fbc5477bd91ac683","contentType":"text/x-python; charset=utf-8"},{"id":"6b590269-2b3b-50c1-bc0c-cc7f94beae7f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6b590269-2b3b-50c1-bc0c-cc7f94beae7f/attachment.py","path":"tests/test_run_tests.py","size":7311,"sha256":"02411a8f25ff4cca5117eaea1b0880d2b9bd135eb4e56336bac1faf093f425d7","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"5b1e9f9559434b6868d523363d47f9b7d2bbffa1d109a866995aa0c492b89c88","attachment_count":24,"text_attachments":24,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"tdd/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"testing-qa","import_tag":"clean-skills-v1","description":"This skill should be used when the user wants to implement features or fix bugs using test-driven development. Enforces the RED-GREEN-REFACTOR cycle with vertical slicing, context isolation between test writing and implementation, human checkpoints, and auto-test feedback loops. Uses multi-agent orchestration with the Task tool for architecturally enforced context isolation. Supports Jest, Vitest, pytest, Go test, cargo test, PHPUnit, and RSpec."}},"renderedAt":1782987135739}

Test-Driven Development — Multi-Agent Orchestration Enforce disciplined RED-GREEN-REFACTOR cycles using separate subagents for test writing and implementation. The core innovation: the Test Writer never sees implementation code, and the Implementer never sees the specification. This prevents the LLM from leaking implementation intent into test design. When to Use - User requests TDD, test-first, or red-green-refactor workflow - User says with a feature description or bug report - User wants to add a feature with test coverage enforced from the start - User wants to fix a bug by first writing…