From 88f2fa245bcd1900aba6b2cce6160c40dc5086ee Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 28 Feb 2026 18:05:34 -0500 Subject: [PATCH] Add kilroy DOT files and parsing compatibility tests Copy 14 DOT workflow files from the kilroy project and add tests proving arc can parse them. 11 files parse successfully, exercising features including subgraphs, fan-out/fan-in, conditional routing, goal gates, model stylesheets, and large 40+ node workflows. 3 batch test files (batch_*.dot) document a parser gap: arc requires quoted values for strings with hyphens/dots (e.g., "gpt-5.2") while kilroy's parser accepts them unquoted. Co-Authored-By: Claude Opus 4.6 --- crates/arc-attractor/tests/kilroy_compat.rs | 164 +++ test/kilroy/batch_clean.dot | 6 + test/kilroy/batch_has_errors.dot | 6 + test/kilroy/batch_warnings_only.dot | 6 + test/kilroy/consensus_task.dot | 158 +++ test/kilroy/green_test_complex.dot | 1075 +++++++++++++++++++ test/kilroy/green_test_moderate.dot | 592 ++++++++++ test/kilroy/green_test_vague.dot | 473 ++++++++ test/kilroy/refactor_test_complex.dot | 569 ++++++++++ test/kilroy/refactor_test_moderate.dot | 400 +++++++ test/kilroy/refactor_test_vague.dot | 346 ++++++ test/kilroy/reference_template.dot | 396 +++++++ test/kilroy/semport.dot | 33 + test/kilroy/simple_example.dot | 17 + test/kilroy/solitaire_fast.dot | 352 ++++++ 15 files changed, 4593 insertions(+) create mode 100644 crates/arc-attractor/tests/kilroy_compat.rs create mode 100644 test/kilroy/batch_clean.dot create mode 100644 test/kilroy/batch_has_errors.dot create mode 100644 test/kilroy/batch_warnings_only.dot create mode 100644 test/kilroy/consensus_task.dot create mode 100644 test/kilroy/green_test_complex.dot create mode 100644 test/kilroy/green_test_moderate.dot create mode 100644 test/kilroy/green_test_vague.dot create mode 100644 test/kilroy/refactor_test_complex.dot create mode 100644 test/kilroy/refactor_test_moderate.dot create mode 100644 test/kilroy/refactor_test_vague.dot create mode 100644 test/kilroy/reference_template.dot create mode 100644 test/kilroy/semport.dot create mode 100644 test/kilroy/simple_example.dot create mode 100644 test/kilroy/solitaire_fast.dot diff --git a/crates/arc-attractor/tests/kilroy_compat.rs b/crates/arc-attractor/tests/kilroy_compat.rs new file mode 100644 index 000000000..f4413bdbc --- /dev/null +++ b/crates/arc-attractor/tests/kilroy_compat.rs @@ -0,0 +1,164 @@ +use std::path::Path; + +use arc_attractor::parser::parse; + +fn parse_kilroy_dot(filename: &str) -> Result { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../test/kilroy") + .join(filename); + let content = + std::fs::read_to_string(&path).map_err(|e| format!("failed to read {}: {e}", path.display()))?; + parse(&content).map_err(|e| format!("failed to parse {filename}: {e}")) +} + +// --------------------------------------------------------------------------- +// Parsing tests: every kilroy DOT file must parse without error +// --------------------------------------------------------------------------- + +#[test] +fn parse_kilroy_simple_example() { + let graph = parse_kilroy_dot("simple_example.dot").unwrap(); + assert_eq!(graph.name, "Simple"); + assert_eq!(graph.goal(), "Run tests and report"); + assert_eq!(graph.nodes.len(), 4); + assert_eq!(graph.edges.len(), 3); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); +} + +// The batch_*.dot files use unquoted values with hyphens/dots (e.g., `llm_model=gpt-5.2`) +// which is valid in kilroy's more lenient Go parser but not in arc's strict DOT parser. +// These tests document the parser gap: arc requires quoting such values. +#[test] +fn parse_kilroy_batch_clean_requires_quoted_model_values() { + let err = parse_kilroy_dot("batch_clean.dot").unwrap_err(); + assert!( + err.contains("grammar error"), + "expected grammar error for unquoted `gpt-5.2`, got: {err}" + ); +} + +#[test] +fn parse_kilroy_batch_has_errors_requires_quoted_model_values() { + let err = parse_kilroy_dot("batch_has_errors.dot").unwrap_err(); + assert!( + err.contains("grammar error"), + "expected grammar error for unquoted `gpt-5.2`, got: {err}" + ); +} + +#[test] +fn parse_kilroy_batch_warnings_only_requires_quoted_model_values() { + let err = parse_kilroy_dot("batch_warnings_only.dot").unwrap_err(); + assert!( + err.contains("grammar error"), + "expected grammar error for unquoted `gpt-5.2`, got: {err}" + ); +} + +#[test] +fn parse_kilroy_solitaire_fast() { + let graph = parse_kilroy_dot("solitaire_fast.dot").unwrap(); + assert_eq!(graph.name, "solitaire"); + assert_eq!( + graph.goal(), + "Build a terminal-based solitaire (Klondike) game" + ); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); + // Large workflow: 2 control + many work nodes + diamond gates + assert!( + graph.nodes.len() > 15, + "expected >15 nodes, got {}", + graph.nodes.len() + ); + assert!( + graph.edges.len() > 20, + "expected >20 edges, got {}", + graph.edges.len() + ); +} + +#[test] +fn parse_kilroy_consensus_task() { + let graph = parse_kilroy_dot("consensus_task.dot").unwrap(); + assert_eq!(graph.name, "Workflow"); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); + // Multi-model consensus: many parallel branches + assert!(graph.nodes.len() > 10); +} + +#[test] +fn parse_kilroy_semport() { + let graph = parse_kilroy_dot("semport.dot").unwrap(); + assert_eq!(graph.name, "Workflow"); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); + // Loop-based workflow with conditional routing + assert!(graph.edges.len() > 5); +} + +#[test] +fn parse_kilroy_reference_template() { + let graph = parse_kilroy_dot("reference_template.dot").unwrap(); + assert_eq!(graph.name, "reference_template"); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); + // Kitchen-sink template: subgraphs, fan-out, parallel, loops + assert!(graph.nodes.len() > 30); + assert!(graph.edges.len() > 30); + // Verify subgraph-derived classes are applied + assert!( + graph.nodes.contains_key("implement"), + "should contain implement node" + ); +} + +#[test] +fn parse_kilroy_green_test_moderate() { + let graph = parse_kilroy_dot("green_test_moderate.dot").unwrap(); + assert_eq!(graph.name, "linkcheck"); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); +} + +#[test] +fn parse_kilroy_green_test_complex() { + let graph = parse_kilroy_dot("green_test_complex.dot").unwrap(); + assert_eq!(graph.name, "dttf"); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); + // Very large workflow (40+ stages) + assert!(graph.nodes.len() > 40); +} + +#[test] +fn parse_kilroy_green_test_vague() { + let graph = parse_kilroy_dot("green_test_vague.dot").unwrap(); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); +} + +#[test] +fn parse_kilroy_refactor_test_moderate() { + let graph = parse_kilroy_dot("refactor_test_moderate.dot").unwrap(); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); +} + +#[test] +fn parse_kilroy_refactor_test_complex() { + let graph = parse_kilroy_dot("refactor_test_complex.dot").unwrap(); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); + // Large workflow + assert!(graph.nodes.len() > 30); +} + +#[test] +fn parse_kilroy_refactor_test_vague() { + let graph = parse_kilroy_dot("refactor_test_vague.dot").unwrap(); + assert!(graph.find_start_node().is_some()); + assert!(graph.find_exit_node().is_some()); +} diff --git a/test/kilroy/batch_clean.dot b/test/kilroy/batch_clean.dot new file mode 100644 index 000000000..ff05b692b --- /dev/null +++ b/test/kilroy/batch_clean.dot @@ -0,0 +1,6 @@ +digraph G { + start [shape=Mdiamond] + exit [shape=Msquare] + work [shape=box, llm_provider=openai, llm_model=gpt-5.2, prompt="Do the work."] + start -> work -> exit +} diff --git a/test/kilroy/batch_has_errors.dot b/test/kilroy/batch_has_errors.dot new file mode 100644 index 000000000..321ba89ad --- /dev/null +++ b/test/kilroy/batch_has_errors.dot @@ -0,0 +1,6 @@ +digraph G { + start [shape=Mdiamond] + exit [shape=Msquare] + work [shape=box, llm_model=gpt-5.2, prompt="Do the work."] + start -> work -> exit +} diff --git a/test/kilroy/batch_warnings_only.dot b/test/kilroy/batch_warnings_only.dot new file mode 100644 index 000000000..bccdb8e92 --- /dev/null +++ b/test/kilroy/batch_warnings_only.dot @@ -0,0 +1,6 @@ +digraph G { + start [shape=Mdiamond] + exit [shape=Msquare] + work [shape=box, llm_provider=openai, llm_model=gpt-5.2] + start -> work -> exit +} diff --git a/test/kilroy/consensus_task.dot b/test/kilroy/consensus_task.dot new file mode 100644 index 000000000..47f473dc8 --- /dev/null +++ b/test/kilroy/consensus_task.dot @@ -0,0 +1,158 @@ +digraph Workflow { + graph [ + label="Consensus Task Workflow", + goal="$task", + rankdir="LR", + context_fidelity_default="truncate", + context_thread_default="consensus-task", + default_max_retry="3", + retry_target="CheckDoD", + fallback_retry_target="Start" + ]; + + Start [ + node_type="start", label="Start", shape="Mdiamond", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="4", timeout="300" + ]; + + CheckDoD [ + node_type="stack.steer", label="Check DoD", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="2", timeout="120", + llm_prompt="Check if definition of done provided.\n\nTASK: $task\nDOD: $definition_of_done\n\nWrite status.json with outcome=needs_dod if DOD is empty or just a placeholder, else outcome=has_dod if a real DOD was provided." + ]; + + DefineDoD_Gemini [ + node_type="stack.observe", label="Define DoD (Gemini)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", + llm_prompt="Propose definition of done.\n\nTASK: $task\n\nWrite to .ai/dod_gemini.md" + ]; + + DefineDoD_GPT [ + node_type="stack.observe", label="Define DoD (GPT)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", reasoning_effort="high", + llm_prompt="Propose definition of done.\n\nTASK: $task\n\nWrite to .ai/dod_gpt.md" + ]; + + DefineDoD_Opus [ + node_type="stack.observe", label="Define DoD (Opus)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", + llm_prompt="Propose definition of done.\n\nTASK: $task\n\nWrite to .ai/dod_opus.md" + ]; + + ConsolidateDoD [ + node_type="stack.observe", label="Consolidate DoD", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="8", timeout="420", + llm_prompt="Synthesize three DoD proposals.\n\nTASK: $task\n\nRead .ai/dod_*.md, write consensus to .ai/definition_of_done.md" + ]; + + PlanGemini [ + node_type="stack.observe", label="Plan (Gemini)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="8", timeout="420", + llm_prompt="Create implementation plan.\n\nTASK: $task\nDOD: .ai/definition_of_done.md or $definition_of_done\n\nWrite to .ai/plan_gemini.md" + ]; + + PlanGPT [ + node_type="stack.observe", label="Plan (GPT)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="8", timeout="420", reasoning_effort="high", + llm_prompt="Create implementation plan.\n\nTASK: $task\nDOD: .ai/definition_of_done.md or $definition_of_done\n\nWrite to .ai/plan_gpt.md" + ]; + + PlanOpus [ + node_type="stack.observe", label="Plan (Opus)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="8", timeout="420", + llm_prompt="Create implementation plan.\n\nTASK: $task\nDOD: .ai/definition_of_done.md or $definition_of_done\n\nWrite to .ai/plan_opus.md" + ]; + + DebateConsolidate [ + node_type="stack.observe", label="Debate & Consolidate", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="10", timeout="600", + llm_prompt="Synthesize three plans.\n\nTASK: $task\n\nRead .ai/plan_*.md, write final to .ai/plan_final.md" + ]; + + Implement [ + node_type="stack.observe", label="Implement (Opus)", shape="box", style="rounded,filled", + is_codergen="true", allow_partial="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="25", timeout="1200", + llm_prompt="Execute plan.\n\nTASK: $task\n\nFollow .ai/plan_final.md. Log to .ai/implementation_log.md" + ]; + + ReviewGemini [ + node_type="stack.observe", label="Review (Gemini)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", + llm_prompt="Review implementation.\n\nTASK: $task\n\nWrite to .ai/review_gemini.md with PASS/FAIL" + ]; + + ReviewGPT [ + node_type="stack.observe", label="Review (GPT)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", reasoning_effort="high", + llm_prompt="Review implementation.\n\nTASK: $task\n\nWrite to .ai/review_gpt.md with PASS/FAIL" + ]; + + ReviewOpus [ + node_type="stack.observe", label="Review (Opus)", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", + llm_prompt="Review implementation.\n\nTASK: $task\n\nWrite to .ai/review_opus.md with PASS/FAIL" + ]; + + ReviewConsensus [ + node_type="stack.steer", label="Review Consensus", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="6", timeout="300", + llm_prompt="Reach consensus.\n\nRead .ai/review_*.md\nWrite to .ai/review_consensus.md\n\noutcome=yes if PASS, outcome=retry if FAIL. Set preferred_next_label to \"\" (empty) or to \"yes\"/\"retry\"; do not use \"No\"." + ]; + + Postmortem [ + node_type="stack.observe", label="Postmortem", shape="box", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="8", timeout="420", + llm_prompt="Failure postmortem.\n\nTASK: $task\n\nWrite to .ai/postmortem_NN.md" + ]; + + Exit [ + node_type="exit", label="Exit", shape="Msquare", style="rounded,filled", + is_codergen="true", llm_provider="openai", llm_model="gpt-5.2-codex", + max_agent_turns="4", timeout="120" + ]; + + Start -> CheckDoD; + CheckDoD -> DefineDoD_Gemini [condition="outcome=needs_dod"]; + CheckDoD -> DefineDoD_GPT [condition="outcome=needs_dod"]; + CheckDoD -> DefineDoD_Opus [condition="outcome=needs_dod"]; + CheckDoD -> PlanGemini [condition="outcome=has_dod"]; + CheckDoD -> PlanGPT [condition="outcome=has_dod"]; + CheckDoD -> PlanOpus [condition="outcome=has_dod"]; + DefineDoD_Gemini -> ConsolidateDoD; + DefineDoD_GPT -> ConsolidateDoD; + DefineDoD_Opus -> ConsolidateDoD; + ConsolidateDoD -> PlanGemini; + ConsolidateDoD -> PlanGPT; + ConsolidateDoD -> PlanOpus; + PlanGemini -> DebateConsolidate; + PlanGPT -> DebateConsolidate; + PlanOpus -> DebateConsolidate; + DebateConsolidate -> Implement; + Implement -> ReviewGemini; + Implement -> ReviewGPT; + Implement -> ReviewOpus; + ReviewGemini -> ReviewConsensus; + ReviewGPT -> ReviewConsensus; + ReviewOpus -> ReviewConsensus; + ReviewConsensus -> Exit [condition="outcome=yes"]; + ReviewConsensus -> Postmortem; + Postmortem -> PlanGemini [loop_restart="true"]; + Postmortem -> PlanGPT [loop_restart="true"]; + Postmortem -> PlanOpus [loop_restart="true"]; +} diff --git a/test/kilroy/green_test_complex.dot b/test/kilroy/green_test_complex.dot new file mode 100644 index 000000000..f6cc27b8f --- /dev/null +++ b/test/kilroy/green_test_complex.dot @@ -0,0 +1,1075 @@ +digraph dttf { + graph [ + goal="Build DTTF: a bitmap-to-TrueType font converter with custom quadratic Bezier tracer", + rankdir=LR, + default_max_retry=3, + retry_target="impl_setup", + model_stylesheet=" + * { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: medium; } + .hard { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: high; } + .verify { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: medium; } + .review { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: high; } + " + ] + + start [shape=Mdiamond, label="Start"] + exit [shape=Msquare, label="Exit"] + + // Project setup + impl_setup [ + shape=box, + timeout=600, + max_retries=2, + prompt="Read specs/dttf-v1.md. Create Go project structure for DTTF. + +Create: +- go.mod with module github.com/kilroy/dttf +- cmd/dttf/main.go (stub that prints version) +- pkg/dttf/ directory structure +- .ai/implementation_plan.md documenting the build order + +Run: go build ./... + +Write status.json: outcome=success if project builds, outcome=fail with error otherwise." + ] + + verify_setup [ + shape=box, + class="verify", + timeout=300, + prompt="Verify project setup. + +Run: +1. go build ./... +2. go vet ./... +3. Check that go.mod exists with correct module path + +Write results to .ai/verify_setup.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_setup [shape=diamond, label="Setup OK?"] + + // Core types and interfaces + impl_types [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 7 (Data Structures). + +Implement core types in pkg/dttf/types.go: +- GlyphBitmap (codepoint, pixels, width, height) +- Point (X, Y int16, OnCurve bool) +- Contour (Points []Point) +- TracedGlyph (codepoint, contours, metrics) +- FontMetadata (family, style, units_per_em, ascender, descender) +- Options, TraceOptions, RasterizeOptions structs + +Add comprehensive godoc comments. Include validation methods where appropriate. + +Run: go build ./... && go test ./pkg/dttf/... + +Write status.json: outcome=success if builds and tests pass, outcome=fail otherwise." + ] + + verify_types [ + shape=box, + class="verify", + timeout=300, + prompt="Verify core types implementation. + +Run: +1. go build ./... +2. go vet ./... +3. go test ./pkg/dttf/... -v +4. Check that all types from section 7 are present + +Write results to .ai/verify_types.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_types [shape=diamond, label="Types OK?"] + + // PNG loader and filename parser + impl_loader [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md sections 1.1-1.5 (Input format). + +Implement in pkg/dttf/loader.go: +- LoadGlyphs(inputDir string) ([]GlyphBitmap, *FontMetadata, error) +- ParseFilename(name string) (codepoint rune, ok bool) +- LoadFontMetadata(dir string) (*FontMetadata, error) + +Handle: +- PNG loading with image/png +- Filename parsing (U+XXXX pattern, case-insensitive) +- Optional font.json sidecar with defaults +- Error handling per section 9 + +Create tests in pkg/dttf/loader_test.go with sample PNGs. + +Run: go test ./pkg/dttf/... -run TestLoad + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_loader [ + shape=box, + class="verify", + timeout=300, + prompt="Verify PNG loader implementation. + +Run: +1. go build ./... +2. go test ./pkg/dttf/... -run TestLoad -v +3. Check error handling for missing files +4. Verify font.json defaults are applied + +Write results to .ai/verify_loader.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_loader [shape=diamond, label="Loader OK?"] + + // Image processing (grayscale + threshold) + impl_imageproc [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 1.4 (Image Requirements). + +Implement in pkg/dttf/imageproc.go: +- ToGrayscale(img image.Image) [][]uint8 +- Threshold(grayscale [][]uint8, threshold uint8) [][]bool +- Helper functions for pixel format conversion + +Handle RGB/RGBA/Gray input formats. Output: row-major 2D arrays. + +Create tests with synthetic images and known thresholds. + +Run: go test ./pkg/dttf/... -run TestImage + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_imageproc [ + shape=box, + class="verify", + timeout=300, + prompt="Verify image processing implementation. + +Run: +1. go test ./pkg/dttf/... -run TestImage -v +2. Check grayscale conversion preserves intensity +3. Check threshold produces binary output + +Write results to .ai/verify_imageproc.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_imageproc [shape=diamond, label="ImageProc OK?"] + + // Tracer Phase 1: Path decomposition + impl_tracer_phase1 [ + shape=box, + class="hard", + timeout=1800, + max_retries=2, + prompt="Read specs/dttf-v1.md section 3.2 Phase 1 (Path Decomposition). + +Implement in pkg/dttf/tracer/contour.go: +- DecomposePaths(binary [][]bool) []RawPath +- RawPath struct (points as pixel coordinates) +- Contour-following algorithm on 1-bit raster +- Separate outer contours from inner contours/counters + +This is complex boundary-tracing logic. Reference standard contour-following algorithms. + +Create tests with simple shapes (square, circle, letter O with counter). + +Run: go test ./pkg/dttf/tracer/... -run TestDecompose + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_tracer_phase1 [ + shape=box, + class="verify", + timeout=300, + prompt="Verify tracer phase 1 (path decomposition). + +Run: +1. go test ./pkg/dttf/tracer/... -run TestDecompose -v +2. Check that outer/inner contours are distinguished +3. Verify closed paths return to origin + +Write results to .ai/verify_tracer_phase1.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_tracer_phase1 [shape=diamond, label="Phase1 OK?"] + + // Tracer Phase 2: Optimal polygon + impl_tracer_phase2 [ + shape=box, + class="hard", + timeout=1800, + max_retries=2, + prompt="Read specs/dttf-v1.md section 3.2 Phase 2 (Optimal Polygon). + +Implement in pkg/dttf/tracer/polygon.go: +- OptimalPolygon(path RawPath) []PixelPoint +- Convert pixel staircase to minimal straight-line segments +- Preserve shape fidelity + +Use polygon approximation algorithm (Douglas-Peucker or similar). + +Create tests comparing input/output vertex counts. + +Run: go test ./pkg/dttf/tracer/... -run TestPolygon + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_tracer_phase2 [ + shape=box, + class="verify", + timeout=300, + prompt="Verify tracer phase 2 (optimal polygon). + +Run: +1. go test ./pkg/dttf/tracer/... -run TestPolygon -v +2. Check polygon has fewer points than raw path +3. Verify shape is preserved + +Write results to .ai/verify_tracer_phase2.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_tracer_phase2 [shape=diamond, label="Phase2 OK?"] + + // Tracer Phase 3: Quadratic Bezier fitting + impl_tracer_phase3 [ + shape=box, + class="hard", + timeout=2400, + max_retries=3, + prompt="Read specs/dttf-v1.md section 3.2 Phase 3 (Quadratic Bezier Fitting). + +Implement in pkg/dttf/tracer/bezier.go: +- FitQuadraticBezier(polygon []Point, tolerance float64) []Point +- Quadratic curve fitting (NOT cubic) +- Output: Points with OnCurve flags +- Minimize point count while staying within tolerance + +This is the core tracer algorithm. Complex curve fitting math. + +Create tests with known curves. + +Run: go test ./pkg/dttf/tracer/... -run TestBezier + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_tracer_phase3 [ + shape=box, + class="verify", + timeout=300, + prompt="Verify tracer phase 3 (quadratic Bezier fitting). + +Run: +1. go test ./pkg/dttf/tracer/... -run TestBezier -v +2. Check output has OnCurve flags set correctly +3. Verify curves are quadratic (TrueType format) + +Write results to .ai/verify_tracer_phase3.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_tracer_phase3 [shape=diamond, label="Phase3 OK?"] + + // Tracer Phase 4: Font-specific optimization + impl_tracer_phase4 [ + shape=box, + class="hard", + timeout=1800, + max_retries=2, + prompt="Read specs/dttf-v1.md section 3.2 Phase 4 (Font-Aware Optimization). + +Implement in pkg/dttf/tracer/optimize.go: +- OptimizeForFont(contours []Contour, opts TraceOptions) []Contour +- Insert points at extrema (required by OpenType) +- Ensure correct winding direction (clockwise outer, CCW inner) +- Remove self-intersections +- Eliminate short segments (< 2 units) +- Enforce max 1000 control points per glyph + +Create tests validating each constraint. + +Run: go test ./pkg/dttf/tracer/... -run TestOptimize + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_tracer_phase4 [ + shape=box, + class="verify", + timeout=300, + prompt="Verify tracer phase 4 (font optimization). + +Run: +1. go test ./pkg/dttf/tracer/... -run TestOptimize -v +2. Check winding direction is correct +3. Verify no self-intersections +4. Check point count cap + +Write results to .ai/verify_tracer_phase4.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_tracer_phase4 [shape=diamond, label="Phase4 OK?"] + + // Coordinate mapping + impl_coordinates [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 3.3 (Coordinate Mapping). + +Implement in pkg/dttf/tracer/coordinates.go: +- PixelToFontUnits(pixelX, pixelY, imageWidth, imageHeight, advanceWidth, ascender, descender int) (int16, int16) +- FontUnitsToPixel (inverse for testing) + +Handle the coordinate system transformation. All output coordinates are integers. + +Create tests with known transformations. + +Run: go test ./pkg/dttf/tracer/... -run TestCoordinates + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_coordinates [ + shape=box, + class="verify", + timeout=300, + prompt="Verify coordinate mapping. + +Run: +1. go test ./pkg/dttf/tracer/... -run TestCoordinates -v +2. Check round-trip accuracy +3. Verify integer output + +Write results to .ai/verify_coordinates.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_coordinates [shape=diamond, label="Coords OK?"] + + // Main tracer integration + impl_tracer_main [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 4.2 (API Surface). + +Implement in pkg/dttf/tracer.go: +- TraceGlyph(bitmap GlyphBitmap, opts TraceOptions) ([]Contour, error) +- Integrate all 4 tracer phases plus coordinate mapping +- Handle parallelization across glyphs (coordinate for later) + +Read types from pkg/dttf/types.go. +Read phase implementations from pkg/dttf/tracer/*.go. + +Create integration tests. + +Run: go test ./pkg/dttf/... -run TestTrace + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_tracer_main [ + shape=box, + class="verify", + timeout=300, + prompt="Verify main tracer integration. + +Run: +1. go test ./pkg/dttf/... -run TestTrace -v +2. Check end-to-end: bitmap in, contours out +3. Verify all phases are called + +Write results to .ai/verify_tracer_main.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_tracer_main [shape=diamond, label="Tracer OK?"] + + // Metrics computation + impl_metrics [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 2.6 (Glyph Metrics). + +Implement in pkg/dttf/metrics.go: +- ComputeMetrics(contours []Contour, opts Options) (advanceWidth uint16, lsb int16, bbox BoundingBox) +- BoundingBox calculation from contour points +- Advance width = bbox width + sidebearings +- Sidebearing strategy per spec + +Create tests with known glyph shapes. + +Run: go test ./pkg/dttf/... -run TestMetrics + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_metrics [ + shape=box, + class="verify", + timeout=300, + prompt="Verify metrics computation. + +Run: +1. go test ./pkg/dttf/... -run TestMetrics -v +2. Check bounding box accuracy +3. Verify sidebearing calculations + +Write results to .ai/verify_metrics.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_metrics [shape=diamond, label="Metrics OK?"] + + // TrueType table builders + impl_tables [ + shape=box, + class="hard", + timeout=2400, + max_retries=3, + prompt="Read specs/dttf-v1.md sections 2.2, 8.1-8.5 (TrueType Tables). + +Implement in pkg/dttf/ttf/: +- BuildHeadTable(meta *FontMetadata) []byte +- BuildMaxpTable(glyphs []TracedGlyph) []byte +- BuildHheaTable(meta *FontMetadata, glyphs []TracedGlyph) []byte +- BuildHmtxTable(glyphs []TracedGlyph) []byte +- BuildOS2Table(meta *FontMetadata, glyphs []TracedGlyph) []byte +- BuildNameTable(meta *FontMetadata) []byte +- BuildPostTable() []byte (format 3.0) +- BuildCmapTable(glyphs []TracedGlyph) []byte (format 4) +- BuildGlyfTable(glyphs []TracedGlyph) []byte +- BuildLocaTable(glyphOffsets []uint32) []byte +- BuildGaspTable() []byte + +Each table builder is a separate function. Follow TrueType spec exactly. + +Create tests validating table structure. + +Run: go test ./pkg/dttf/ttf/... -v + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_tables [ + shape=box, + class="verify", + timeout=300, + prompt="Verify TrueType table builders. + +Run: +1. go test ./pkg/dttf/ttf/... -v +2. Check all 11 tables build without errors +3. Verify checksums are computed correctly + +Write results to .ai/verify_tables.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_tables [shape=diamond, label="Tables OK?"] + + // Font assembler + impl_assembler [ + shape=box, + class="hard", + timeout=1800, + max_retries=2, + prompt="Read specs/dttf-v1.md sections 4.2, 8.1-8.3 (Font Assembly). + +Implement in pkg/dttf/assembler.go: +- AssembleFont(glyphs []TracedGlyph, meta *FontMetadata) (*Font, error) +- Font struct (table directory + table data) +- Build all tables using pkg/dttf/ttf/* builders +- Compute table checksums +- Alphabetical table ordering +- Offset table construction + +Run: go test ./pkg/dttf/... -run TestAssemble + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_assembler [ + shape=box, + class="verify", + timeout=300, + prompt="Verify font assembler. + +Run: +1. go test ./pkg/dttf/... -run TestAssemble -v +2. Check table directory is correct +3. Verify offset calculations + +Write results to .ai/verify_assembler.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_assembler [shape=diamond, label="Assembler OK?"] + + // Font writer + impl_writer [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md sections 4.2, 8.3 (File Writing). + +Implement in pkg/dttf/writer.go: +- WriteFont(font *Font, outputPath string) error +- Write offset table, table directory, table data +- 4-byte padding +- Compute head.checksumAdjustment: 0xB1B0AFBA - file_checksum + +Create tests writing to temp files. + +Run: go test ./pkg/dttf/... -run TestWrite + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_writer [ + shape=box, + class="verify", + timeout=300, + prompt="Verify font writer. + +Run: +1. go test ./pkg/dttf/... -run TestWrite -v +2. Check output file is valid binary +3. Verify checksum calculation + +Write results to .ai/verify_writer.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_writer [shape=diamond, label="Writer OK?"] + + // Pipeline integration + impl_pipeline [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md sections 4.2, 4.3 (Main Pipeline). + +Implement in pkg/dttf/dttf.go: +- Build(inputDir string, outputPath string, opts Options) error +- Integrate: LoadGlyphs -> TraceGlyph (parallel) -> ComputeMetrics -> AssembleFont -> WriteFont +- Use goroutines for parallel glyph tracing + +Create end-to-end test with sample input directory. + +Run: go test ./pkg/dttf/... -run TestBuild + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_pipeline [ + shape=box, + class="verify", + timeout=300, + prompt="Verify main pipeline integration. + +Run: +1. go test ./pkg/dttf/... -run TestBuild -v +2. Check full pipeline: PNG dir -> .ttf file +3. Verify parallelization works + +Write results to .ai/verify_pipeline.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_pipeline [shape=diamond, label="Pipeline OK?"] + + // Validator + impl_validator [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 5.1 (Font Validity). + +Implement in pkg/dttf/validator.go: +- Validate(fontPath string) (ValidityReport, error) +- ValidityReport struct (checks + pass/fail) +- Check: loadable by golang.org/x/image/font/sfnt +- Check: contours closed +- Check: correct winding direction +- Check: no self-intersections +- Check: points at extrema + +Create tests with valid and invalid fonts. + +Run: go test ./pkg/dttf/... -run TestValidate + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_validator [ + shape=box, + class="verify", + timeout=300, + prompt="Verify font validator. + +Run: +1. go test ./pkg/dttf/... -run TestValidate -v +2. Check that valid fonts pass all checks +3. Check that invalid fonts fail appropriately + +Write results to .ai/verify_validator.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_validator [shape=diamond, label="Validator OK?"] + + // Rasterizer + impl_rasterizer [ + shape=box, + class="hard", + timeout=1800, + max_retries=2, + prompt="Read specs/dttf-v1.md section 11 (Rasterizer). + +Implement in pkg/dttf/rasterizer.go: +- Rasterize(fontPath string, outputDir string, opts RasterizeOptions) error +- Use golang.org/x/image/font/opentype for rendering +- Charset selection (ASCII, all, chars, ranges) +- Generate PNGs named per DTTF format +- Write font.json with extracted metrics +- Handle space glyph (U+0020) as white image + +Create tests with a simple font. + +Run: go test ./pkg/dttf/... -run TestRasterize + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_rasterizer [ + shape=box, + class="verify", + timeout=300, + prompt="Verify rasterizer implementation. + +Run: +1. go test ./pkg/dttf/... -run TestRasterize -v +2. Check PNG output format is correct +3. Verify font.json is written + +Write results to .ai/verify_rasterizer.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_rasterizer [shape=diamond, label="Rasterizer OK?"] + + // SSIM computation + impl_ssim [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md sections 5.2, 5.3 (SSIM Quality Metric). + +Implement in pkg/dttf/quality/ssim.go: +- SSIM(img1, img2 image.Image) float64 +- Structural Similarity Index implementation +- Window-based comparison +- Return value 0.0-1.0 + +Create tests with identical/different images. + +Run: go test ./pkg/dttf/quality/... -run TestSSIM + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_ssim [ + shape=box, + class="verify", + timeout=300, + prompt="Verify SSIM implementation. + +Run: +1. go test ./pkg/dttf/quality/... -run TestSSIM -v +2. Check identical images return 1.0 +3. Check different images return < 1.0 + +Write results to .ai/verify_ssim.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_ssim [shape=diamond, label="SSIM OK?"] + + // Test harness + impl_test_harness [ + shape=box, + class="hard", + timeout=2400, + max_retries=3, + prompt="Read specs/dttf-v1.md section 6 (Test Harness). + +Implement in pkg/dttf/test/harness.go: +- RoundTripTest(referenceFontPath string, opts TestOptions) (*TestReport, error) +- TestReport struct (per-glyph scores, aggregate, failures) +- Pipeline: render reference -> trace -> render output -> compute SSIM +- Multi-scale testing (12, 16, 24, 48, 96 px) +- Download reference fonts if needed + +Create tests with a known font. + +Run: go test ./pkg/dttf/test/... -run TestHarness + +Write status.json: outcome=success if tests pass, outcome=fail otherwise." + ] + + verify_test_harness [ + shape=box, + class="verify", + timeout=300, + prompt="Verify test harness implementation. + +Run: +1. go test ./pkg/dttf/test/... -run TestHarness -v +2. Check round-trip completes +3. Verify SSIM scores are computed + +Write results to .ai/verify_test_harness.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_test_harness [shape=diamond, label="Harness OK?"] + + // CLI build command + impl_cli_build [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 12 (CLI Reference). + +Implement in cmd/dttf/build.go: +- BuildCommand using a CLI framework (cobra or stdlib flag) +- Flags: -o/--output, --family, --style, --units-per-em, --threshold, etc. +- Call pkg/dttf.Build() with parsed options + +Update cmd/dttf/main.go to register command. + +Run: go build ./cmd/dttf && ./cmd/dttf build --help + +Write status.json: outcome=success if help displays, outcome=fail otherwise." + ] + + verify_cli_build [ + shape=box, + class="verify", + timeout=300, + prompt="Verify CLI build command. + +Run: +1. go build ./cmd/dttf +2. ./cmd/dttf build --help +3. Check all flags are present + +Write results to .ai/verify_cli_build.md. +Write status.json: outcome=success if help works, outcome=fail with details." + ] + + check_cli_build [shape=diamond, label="CLI Build OK?"] + + // CLI rasterize command + impl_cli_rasterize [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 12 (CLI Reference). + +Implement in cmd/dttf/rasterize.go: +- RasterizeCommand +- Flags: -o/--output-dir, --ascii, --all, --chars, --range, --height +- Call pkg/dttf.Rasterize() with parsed options + +Update cmd/dttf/main.go to register command. + +Run: go build ./cmd/dttf && ./cmd/dttf rasterize --help + +Write status.json: outcome=success if help displays, outcome=fail otherwise." + ] + + verify_cli_rasterize [ + shape=box, + class="verify", + timeout=300, + prompt="Verify CLI rasterize command. + +Run: +1. go build ./cmd/dttf +2. ./cmd/dttf rasterize --help +3. Check all flags are present + +Write results to .ai/verify_cli_rasterize.md. +Write status.json: outcome=success if help works, outcome=fail with details." + ] + + check_cli_rasterize [shape=diamond, label="CLI Rasterize OK?"] + + // CLI validate command + impl_cli_validate [ + shape=box, + timeout=900, + max_retries=2, + prompt="Read specs/dttf-v1.md section 12 (CLI Reference). + +Implement in cmd/dttf/validate.go: +- ValidateCommand +- Call pkg/dttf.Validate() +- Exit 0 if valid, 1 if invalid + +Update cmd/dttf/main.go to register command. + +Run: go build ./cmd/dttf && ./cmd/dttf validate --help + +Write status.json: outcome=success if help displays, outcome=fail otherwise." + ] + + verify_cli_validate [ + shape=box, + class="verify", + timeout=300, + prompt="Verify CLI validate command. + +Run: +1. go build ./cmd/dttf +2. ./cmd/dttf validate --help + +Write results to .ai/verify_cli_validate.md. +Write status.json: outcome=success if help works, outcome=fail with details." + ] + + check_cli_validate [shape=diamond, label="CLI Validate OK?"] + + // CLI test command + impl_cli_test [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read specs/dttf-v1.md section 12 (CLI Reference). + +Implement in cmd/dttf/test.go: +- TestCommand +- Flags: --reference, --reference-dir, --sizes, --threshold, --output-dir +- Call pkg/dttf/test.RoundTripTest() +- Handle single font or directory of fonts + +Update cmd/dttf/main.go to register command. + +Run: go build ./cmd/dttf && ./cmd/dttf test --help + +Write status.json: outcome=success if help displays, outcome=fail otherwise." + ] + + verify_cli_test [ + shape=box, + class="verify", + timeout=300, + prompt="Verify CLI test command. + +Run: +1. go build ./cmd/dttf +2. ./cmd/dttf test --help +3. Check all flags are present + +Write results to .ai/verify_cli_test.md. +Write status.json: outcome=success if help works, outcome=fail with details." + ] + + check_cli_test [shape=diamond, label="CLI Test OK?"] + + // Integration test + impl_integration [ + shape=box, + class="hard", + timeout=2400, + max_retries=3, + goal_gate=true, + prompt="Read specs/dttf-v1.md sections 6.1-6.4 (Test Harness). + +Create integration test in test/integration_test.go: +1. Download or use embedded simple reference font (e.g., Roboto subset) +2. Run: dttf rasterize reference.ttf -o testdata/input/ +3. Run: dttf build testdata/input/ -o testdata/output.ttf +4. Run: dttf validate testdata/output.ttf +5. Run: dttf test --reference reference.ttf +6. Check SSIM > 0.90 + +This is an end-to-end test of the full pipeline. + +Run: go test ./test/... -v -timeout 5m + +Write status.json: outcome=success if all steps pass and SSIM > 0.90, outcome=fail with details." + ] + + verify_integration [ + shape=box, + class="verify", + timeout=600, + prompt="Verify integration test. + +Run: +1. go test ./test/... -v -timeout 5m +2. Check all pipeline stages completed +3. Verify output font is valid +4. Check SSIM threshold met + +Write results to .ai/verify_integration.md. +Write status.json: outcome=success if all pass, outcome=fail with details." + ] + + check_integration [shape=diamond, label="Integration OK?"] + + // Final review + review [ + shape=box, + class="review", + timeout=900, + goal_gate=true, + prompt="Read specs/dttf-v1.md in full. + +Review the complete DTTF implementation: +1. Check all required components from spec are implemented +2. Verify the 4-phase tracer produces quadratic Beziers +3. Check all 11 TrueType tables are built correctly +4. Verify CLI has all 4 commands (build, rasterize, validate, test) +5. Check error handling per section 9 +6. Verify quality metrics (SSIM) are computed +7. Run full test suite + +Run: +1. go build ./... +2. go test ./... -v +3. go vet ./... + +Write a review report to .ai/review.md. +Write status.json: outcome=success if complete and correct, outcome=fail with missing/broken items." + ] + + check_review [shape=diamond, label="Review OK?"] + + // Flow + start -> impl_setup -> verify_setup -> check_setup + check_setup -> impl_types [condition="outcome=success"] + check_setup -> impl_setup [condition="outcome=fail", label="retry"] + + impl_types -> verify_types -> check_types + check_types -> impl_loader [condition="outcome=success"] + check_types -> impl_types [condition="outcome=fail", label="retry"] + + impl_loader -> verify_loader -> check_loader + check_loader -> impl_imageproc [condition="outcome=success"] + check_loader -> impl_loader [condition="outcome=fail", label="retry"] + + impl_imageproc -> verify_imageproc -> check_imageproc + check_imageproc -> impl_tracer_phase1 [condition="outcome=success"] + check_imageproc -> impl_imageproc [condition="outcome=fail", label="retry"] + + impl_tracer_phase1 -> verify_tracer_phase1 -> check_tracer_phase1 + check_tracer_phase1 -> impl_tracer_phase2 [condition="outcome=success"] + check_tracer_phase1 -> impl_tracer_phase1 [condition="outcome=fail", label="retry"] + + impl_tracer_phase2 -> verify_tracer_phase2 -> check_tracer_phase2 + check_tracer_phase2 -> impl_tracer_phase3 [condition="outcome=success"] + check_tracer_phase2 -> impl_tracer_phase2 [condition="outcome=fail", label="retry"] + + impl_tracer_phase3 -> verify_tracer_phase3 -> check_tracer_phase3 + check_tracer_phase3 -> impl_tracer_phase4 [condition="outcome=success"] + check_tracer_phase3 -> impl_tracer_phase3 [condition="outcome=fail", label="retry"] + + impl_tracer_phase4 -> verify_tracer_phase4 -> check_tracer_phase4 + check_tracer_phase4 -> impl_coordinates [condition="outcome=success"] + check_tracer_phase4 -> impl_tracer_phase4 [condition="outcome=fail", label="retry"] + + impl_coordinates -> verify_coordinates -> check_coordinates + check_coordinates -> impl_tracer_main [condition="outcome=success"] + check_coordinates -> impl_coordinates [condition="outcome=fail", label="retry"] + + impl_tracer_main -> verify_tracer_main -> check_tracer_main + check_tracer_main -> impl_metrics [condition="outcome=success"] + check_tracer_main -> impl_tracer_main [condition="outcome=fail", label="retry"] + + impl_metrics -> verify_metrics -> check_metrics + check_metrics -> impl_tables [condition="outcome=success"] + check_metrics -> impl_metrics [condition="outcome=fail", label="retry"] + + impl_tables -> verify_tables -> check_tables + check_tables -> impl_assembler [condition="outcome=success"] + check_tables -> impl_tables [condition="outcome=fail", label="retry"] + + impl_assembler -> verify_assembler -> check_assembler + check_assembler -> impl_writer [condition="outcome=success"] + check_assembler -> impl_assembler [condition="outcome=fail", label="retry"] + + impl_writer -> verify_writer -> check_writer + check_writer -> impl_pipeline [condition="outcome=success"] + check_writer -> impl_writer [condition="outcome=fail", label="retry"] + + impl_pipeline -> verify_pipeline -> check_pipeline + check_pipeline -> impl_validator [condition="outcome=success"] + check_pipeline -> impl_pipeline [condition="outcome=fail", label="retry"] + + impl_validator -> verify_validator -> check_validator + check_validator -> impl_rasterizer [condition="outcome=success"] + check_validator -> impl_validator [condition="outcome=fail", label="retry"] + + impl_rasterizer -> verify_rasterizer -> check_rasterizer + check_rasterizer -> impl_ssim [condition="outcome=success"] + check_rasterizer -> impl_rasterizer [condition="outcome=fail", label="retry"] + + impl_ssim -> verify_ssim -> check_ssim + check_ssim -> impl_test_harness [condition="outcome=success"] + check_ssim -> impl_ssim [condition="outcome=fail", label="retry"] + + impl_test_harness -> verify_test_harness -> check_test_harness + check_test_harness -> impl_cli_build [condition="outcome=success"] + check_test_harness -> impl_test_harness [condition="outcome=fail", label="retry"] + + impl_cli_build -> verify_cli_build -> check_cli_build + check_cli_build -> impl_cli_rasterize [condition="outcome=success"] + check_cli_build -> impl_cli_build [condition="outcome=fail", label="retry"] + + impl_cli_rasterize -> verify_cli_rasterize -> check_cli_rasterize + check_cli_rasterize -> impl_cli_validate [condition="outcome=success"] + check_cli_rasterize -> impl_cli_rasterize [condition="outcome=fail", label="retry"] + + impl_cli_validate -> verify_cli_validate -> check_cli_validate + check_cli_validate -> impl_cli_test [condition="outcome=success"] + check_cli_validate -> impl_cli_validate [condition="outcome=fail", label="retry"] + + impl_cli_test -> verify_cli_test -> check_cli_test + check_cli_test -> impl_integration [condition="outcome=success"] + check_cli_test -> impl_cli_test [condition="outcome=fail", label="retry"] + + impl_integration -> verify_integration -> check_integration + check_integration -> review [condition="outcome=success"] + check_integration -> impl_integration [condition="outcome=fail", label="retry"] + + review -> check_review + check_review -> exit [condition="outcome=success"] + check_review -> impl_setup [condition="outcome=fail", label="fix from start"] +} diff --git a/test/kilroy/green_test_moderate.dot b/test/kilroy/green_test_moderate.dot new file mode 100644 index 000000000..79134522c --- /dev/null +++ b/test/kilroy/green_test_moderate.dot @@ -0,0 +1,592 @@ +digraph linkcheck { + graph [ + goal="Build a Go CLI tool that crawls URLs, checks links for HTTP status, and reports broken links with robots.txt support", + rankdir=LR, + default_max_retry=3, + retry_target="impl_setup", + model_stylesheet=" + * { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: medium; } + .hard { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: high; } + .verify { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: medium; } + .review { llm_model: gpt-5.2-codex; llm_provider: openai; reasoning_effort: high; } + " + ] + + start [shape=Mdiamond, label="Start"] + exit [shape=Msquare, label="Exit"] + + // Phase 1: Requirements expansion + expand_spec [ + shape=box, + timeout=600, + prompt="Given the requirements: Build a Go CLI tool called 'linkcheck' that takes a URL as input, crawls the page, finds all links, checks each link for HTTP status, and outputs a report of broken links (4xx/5xx). It should respect robots.txt, have a configurable crawl depth (default 1), and output in both human-readable and JSON formats. + +Expand this into a detailed spec covering: +- CLI interface (flags, arguments, usage) +- Package structure (cmd/, pkg/) +- Core types and interfaces (URL, Link, Report, etc.) +- Robots.txt parsing and compliance +- HTTP client configuration (timeouts, retries) +- Crawl algorithm (depth limits, visited tracking) +- Link extraction from HTML +- Status code categorization (2xx=ok, 4xx/5xx=broken) +- Output formats (human-readable table, JSON) +- Error handling strategy +- Test plan for each component + +Write the expanded spec to .ai/spec.md. + +Write status.json: outcome=success" + ] + + // Phase 2: Project setup + impl_setup [ + shape=box, + timeout=600, + max_retries=2, + prompt="Read .ai/spec.md. + +Create the Go project structure: +- go.mod (module name: linkcheck) +- cmd/linkcheck/main.go (stub with basic CLI parsing) +- pkg/crawler/ directory +- pkg/robotstxt/ directory +- pkg/checker/ directory +- pkg/report/ directory + +Run: go build ./... + +Write status.json: outcome=success if the project builds, outcome=fail with failure_reason otherwise." + ] + + verify_setup [ + shape=box, + class="verify", + timeout=300, + prompt="Verify project setup. + +Run: +1. go build ./... +2. go vet ./... + +Check that: +- go.mod exists with correct module name +- cmd/linkcheck/main.go exists +- pkg/ directories are created + +Write results to .ai/verify_setup.md. +Write status.json: outcome=success if all checks pass, outcome=fail with details otherwise." + ] + + check_setup [shape=diamond, label="Setup OK?"] + + // Phase 3: Core types and interfaces + impl_types [ + shape=box, + timeout=900, + max_retries=2, + prompt="Read .ai/spec.md. + +Implement core types and interfaces per the spec. + +Create pkg/crawler/types.go with: +- URL type +- Link type (URL, source, depth, status code) +- CrawlConfig struct (max depth, user agent, timeout) +- Crawler interface + +Create pkg/report/types.go with: +- Report struct (total links, broken links, results slice) +- LinkResult struct (URL, status, error) +- Formatter interface + +Include comprehensive documentation for all exported types. + +Run: go build ./... + +Write status.json: outcome=success if builds, outcome=fail with failure_reason otherwise." + ] + + verify_types [ + shape=box, + class="verify", + timeout=300, + prompt="Verify core types implementation. + +Run: +1. go build ./... +2. go vet ./... + +Check that: +- pkg/crawler/types.go defines all required types +- pkg/report/types.go defines report types +- All types have godoc comments +- No compilation errors + +Write results to .ai/verify_types.md. +Write status.json: outcome=success if all pass, outcome=fail with details otherwise." + ] + + check_types [shape=diamond, label="Types OK?"] + + // Phase 4: Robots.txt parser + impl_robotstxt [ + shape=box, + class="hard", + timeout=1200, + max_retries=2, + prompt="Read .ai/spec.md section on robots.txt compliance. + +Implement robots.txt parser in pkg/robotstxt/: +- parser.go: Parse robots.txt files per RFC 9309 +- matcher.go: Check if a URL is allowed for a given user-agent +- cache.go: Cache parsed robots.txt files to avoid repeated fetches + +Handle: +- User-agent matching (specific agent, wildcards, default *) +- Allow/Disallow rules with path matching +- Crawl-delay directive +- Missing robots.txt (allow all) +- Malformed robots.txt (be permissive) + +Create pkg/robotstxt/parser_test.go with tests for: +- Various robots.txt formats +- User-agent matching +- Path matching edge cases + +Read: pkg/crawler/types.go for interfaces. + +Run: go test ./pkg/robotstxt/... + +Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise." + ] + + verify_robotstxt [ + shape=box, + class="verify", + timeout=300, + prompt="Verify robots.txt parser implementation. + +Run: +1. go build ./... +2. go vet ./pkg/robotstxt/... +3. go test ./pkg/robotstxt/... -v + +Check test coverage: +4. go test ./pkg/robotstxt/... -cover + +Write results to .ai/verify_robotstxt.md. +Write status.json: outcome=success if all pass and coverage > 70%, outcome=fail with details otherwise." + ] + + check_robotstxt [shape=diamond, label="Robots.txt OK?"] + + // Phase 5: HTTP checker + impl_checker [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read .ai/spec.md section on HTTP checking. + +Implement HTTP link checker in pkg/checker/: +- checker.go: Check URL status codes +- Configure HTTP client with reasonable timeouts +- Follow redirects (up to a limit) +- Categorize status codes (2xx/3xx=ok, 4xx/5xx=broken) +- Handle network errors gracefully +- Support HEAD requests (fallback to GET if HEAD fails) + +Create pkg/checker/checker_test.go with: +- Tests for various HTTP status codes +- Mock HTTP server for testing +- Timeout handling tests +- Redirect handling tests + +Read: pkg/crawler/types.go for types. + +Run: go test ./pkg/checker/... + +Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise." + ] + + verify_checker [ + shape=box, + class="verify", + timeout=300, + prompt="Verify HTTP checker implementation. + +Run: +1. go build ./... +2. go vet ./pkg/checker/... +3. go test ./pkg/checker/... -v + +Check: +- Tests cover success, failure, and error cases +- No race conditions: go test ./pkg/checker/... -race + +Write results to .ai/verify_checker.md. +Write status.json: outcome=success if all pass, outcome=fail with details otherwise." + ] + + check_checker [shape=diamond, label="Checker OK?"] + + // Phase 6: HTML link extractor + impl_extractor [ + shape=box, + timeout=1200, + max_retries=2, + prompt="Read .ai/spec.md section on link extraction. + +Implement HTML link extractor in pkg/crawler/: +- extractor.go: Parse HTML and extract all links +- Find links in , , ,