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 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-28 18:05:34 -05:00
parent 2a82a4e147
commit 88f2fa245b
15 changed files with 4593 additions and 0 deletions

View file

@ -0,0 +1,164 @@
use std::path::Path;
use arc_attractor::parser::parse;
fn parse_kilroy_dot(filename: &str) -> Result<arc_attractor::graph::types::Graph, String> {
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());
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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"];
}

File diff suppressed because it is too large Load diff

View file

@ -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 <a href>, <link href>, <img src>, <script src>
- Convert relative URLs to absolute using base URL
- Filter out non-http(s) schemes (mailto:, javascript:, etc.)
- Handle malformed HTML gracefully
- Use golang.org/x/net/html for parsing
Create pkg/crawler/extractor_test.go with:
- Tests for relative URL resolution
- Tests for various HTML structures
- Tests for malformed HTML
- Tests for filtering non-http links
Read: pkg/crawler/types.go for types.
Run: go test ./pkg/crawler/...
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_extractor [
shape=box,
class="verify",
timeout=300,
prompt="Verify HTML extractor implementation.
Run:
1. go build ./...
2. go vet ./pkg/crawler/...
3. go test ./pkg/crawler/... -v
Check:
- All test cases pass
- Relative URL conversion is correct
- Non-http schemes are filtered
Write results to .ai/verify_extractor.md.
Write status.json: outcome=success if all pass, outcome=fail with details otherwise."
]
check_extractor [shape=diamond, label="Extractor OK?"]
// Phase 7: Crawler algorithm
impl_crawler [
shape=box,
class="hard",
timeout=1500,
max_retries=2,
prompt="Read .ai/spec.md section on crawling algorithm.
Implement the main crawler in pkg/crawler/:
- crawler.go: Orchestrate crawling with depth limits
- Track visited URLs to avoid cycles
- Respect robots.txt using pkg/robotstxt
- Extract links using extractor.go
- Check each link using pkg/checker
- Implement breadth-first crawl up to configured depth
- Handle concurrent checking with rate limiting
- Collect results into a report
Key algorithm:
1. Start with seed URL at depth 0
2. Fetch and parse page
3. Extract all links
4. For each link:
- Check if allowed by robots.txt
- Check if already visited
- Check HTTP status
- If depth < max_depth and status ok, add to queue
5. Continue until queue empty or max depth reached
Create pkg/crawler/crawler_test.go with:
- Tests for depth limiting
- Tests for visited tracking
- Tests for robots.txt integration
- Mock HTTP responses for testing
Read: pkg/crawler/types.go, pkg/crawler/extractor.go, pkg/robotstxt/, pkg/checker/ for dependencies.
Run: go test ./pkg/crawler/...
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_crawler [
shape=box,
class="verify",
timeout=300,
prompt="Verify crawler implementation.
Run:
1. go build ./...
2. go vet ./pkg/crawler/...
3. go test ./pkg/crawler/... -v
4. go test ./pkg/crawler/... -race
Check:
- Depth limiting works correctly
- No infinite loops
- Robots.txt is respected
- No race conditions
Write results to .ai/verify_crawler.md.
Write status.json: outcome=success if all pass, outcome=fail with details otherwise."
]
check_crawler [shape=diamond, label="Crawler OK?"]
// Phase 8: Output formatters
impl_report [
shape=box,
timeout=900,
max_retries=2,
prompt="Read .ai/spec.md section on output formats.
Implement output formatters in pkg/report/:
- formatter.go: Format interface
- text.go: Human-readable table format showing broken links
- json.go: JSON format with all details
Human-readable format should show:
- Summary: X total links, Y broken
- Table of broken links: URL, Status Code, Source Page
- Color coding (red for errors) if terminal supports it
JSON format should output:
- Structured data with all checked links
- Status codes, timestamps, error messages
- Easy to parse for automation
Create pkg/report/formatter_test.go with:
- Tests for both formats
- Tests for empty results
- Tests for large result sets
Read: pkg/report/types.go for types.
Run: go test ./pkg/report/...
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_report [
shape=box,
class="verify",
timeout=300,
prompt="Verify report formatting implementation.
Run:
1. go build ./...
2. go vet ./pkg/report/...
3. go test ./pkg/report/... -v
Check:
- Both text and JSON formats work
- Output is properly formatted
- All test cases pass
Write results to .ai/verify_report.md.
Write status.json: outcome=success if all pass, outcome=fail with details otherwise."
]
check_report [shape=diamond, label="Report OK?"]
// Phase 9: CLI integration
impl_cli [
shape=box,
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md section on CLI interface.
Implement the CLI in cmd/linkcheck/main.go:
- Use flag package for argument parsing
- Required argument: URL to check
- Optional flags:
--depth int (default 1): maximum crawl depth
--format string (default text): output format (text or json)
--user-agent string (default linkcheck/1.0): HTTP user agent
--timeout int (default 10): HTTP timeout in seconds
--verbose: enable verbose logging
- Wire together: crawler, checker, robots.txt, formatter
- Print results to stdout
- Exit code 0 if no broken links, 1 if broken links found, 2 on error
Usage example:
linkcheck https://example.com
linkcheck --depth 2 --format json https://example.com
Read: all pkg/ directories for integration.
Run: go build ./cmd/linkcheck
Write status.json: outcome=success if builds, outcome=fail with failure_reason otherwise."
]
verify_cli [
shape=box,
class="verify",
timeout=300,
prompt="Verify CLI integration.
Run:
1. go build ./cmd/linkcheck
2. ./cmd/linkcheck --help (check help text)
3. Test with a real URL: ./cmd/linkcheck https://example.com
4. Test JSON output: ./cmd/linkcheck --format json https://example.com
5. Test depth flag: ./cmd/linkcheck --depth 0 https://example.com
Check:
- Binary builds successfully
- Help text is clear
- Flags work correctly
- Output is properly formatted
- Exit codes are correct
Write results to .ai/verify_cli.md.
Write status.json: outcome=success if all manual tests pass, outcome=fail with details otherwise."
]
check_cli [shape=diamond, label="CLI OK?"]
// Phase 10: Integration tests
impl_integration [
shape=box,
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md section on testing.
Create integration tests in test/:
- integration_test.go: End-to-end tests
- Set up local HTTP test server with known structure
- Create test pages with working and broken links
- Create test robots.txt
- Run linkcheck against test server
- Verify correct broken links are detected
- Test depth limiting
- Test robots.txt compliance
- Test both output formats
Run: go test ./test/...
Write status.json: outcome=success if integration tests pass, outcome=fail with failure_reason otherwise."
]
verify_integration [
shape=box,
class="verify",
timeout=300,
goal_gate=true,
prompt="Verify integration tests.
Run:
1. go test ./test/... -v
2. go test ./... (all tests)
3. go build ./...
Check:
- All integration tests pass
- All unit tests still pass
- Project builds cleanly
Write results to .ai/verify_integration.md.
Write status.json: outcome=success if all pass, outcome=fail with details otherwise."
]
check_integration [shape=diamond, label="Integration OK?"]
// Phase 11: Final review
review [
shape=box,
class="review",
timeout=600,
goal_gate=true,
prompt="Read .ai/spec.md.
Perform final review of the complete linkcheck implementation:
1. Verify all requirements are met:
- CLI takes URL as input
- Crawls pages and finds all links
- Checks each link for HTTP status
- Reports broken links (4xx/5xx)
- Respects robots.txt
- Configurable crawl depth (default 1)
- Outputs both human-readable and JSON formats
2. Code quality:
- All packages have tests
- Code is well-documented
- Error handling is comprehensive
- No obvious bugs or race conditions
3. Functionality:
- Run: go build ./cmd/linkcheck
- Test with real URLs
- Verify robots.txt compliance
- Verify depth limiting works
- Verify both output formats work
4. Run complete test suite:
- go test ./...
- go vet ./...
- go build ./...
Write detailed review to .ai/final_review.md.
Write status.json: outcome=success if complete and working, outcome=fail with specific issues that need fixing."
]
check_review [shape=diamond, label="Review OK?"]
// Graph flow
start -> expand_spec -> 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_robotstxt [condition="outcome=success"]
check_types -> impl_types [condition="outcome=fail", label="retry"]
impl_robotstxt -> verify_robotstxt -> check_robotstxt
check_robotstxt -> impl_checker [condition="outcome=success"]
check_robotstxt -> impl_robotstxt [condition="outcome=fail", label="retry"]
impl_checker -> verify_checker -> check_checker
check_checker -> impl_extractor [condition="outcome=success"]
check_checker -> impl_checker [condition="outcome=fail", label="retry"]
impl_extractor -> verify_extractor -> check_extractor
check_extractor -> impl_crawler [condition="outcome=success"]
check_extractor -> impl_extractor [condition="outcome=fail", label="retry"]
impl_crawler -> verify_crawler -> check_crawler
check_crawler -> impl_report [condition="outcome=success"]
check_crawler -> impl_crawler [condition="outcome=fail", label="retry"]
impl_report -> verify_report -> check_report
check_report -> impl_cli [condition="outcome=success"]
check_report -> impl_report [condition="outcome=fail", label="retry"]
impl_cli -> verify_cli -> check_cli
check_cli -> impl_integration [condition="outcome=success"]
check_cli -> impl_cli [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_cli [condition="outcome=fail", label="fix"]
}

View file

@ -0,0 +1,473 @@
digraph solitaire {
graph [
goal="Build a terminal-based Klondike Solitaire game in Go with TUI",
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,
prompt="Read .ai/spec.md. Create Go project structure for solitaire game.
Create:
- go.mod with module name 'solitaire'
- cmd/solitaire/main.go (stub with hello world)
- pkg/game/ directory
- pkg/ui/ directory
- pkg/storage/ directory
- README.md with brief description
Run: go build ./...
Write status.json: outcome=success if project builds, outcome=fail with failure_reason otherwise."
]
verify_setup [
shape=box,
class="verify",
timeout=300,
prompt="Verify project setup was completed correctly.
Run:
1. go build ./...
2. go vet ./...
3. Check that all required directories exist
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 (cards and deck)
impl_core_types [
shape=box,
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md sections on Game Logic and Package Structure.
Implement card and deck types in pkg/game/:
Create pkg/game/card.go with:
- Card type (Rank, Suit)
- Suit constants (Spades, Hearts, Diamonds, Clubs) with Unicode symbols
- Rank constants (Ace through King)
- String() method for card display
- NewDeck() function that creates standard 52-card deck
- Shuffle() method using crypto/rand for secure shuffling
Create pkg/game/card_test.go with tests for:
- Card string representation
- Deck creation (52 unique cards)
- Shuffle produces different order
Acceptance:
- go build ./...
- go test ./pkg/game/
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_core_types [
shape=box,
class="verify",
timeout=300,
prompt="Verify core card types implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/game/ -v
4. Check test coverage: go test -cover ./pkg/game/
Write results to .ai/verify_core_types.md.
Write status.json: outcome=success if ALL pass and coverage >70%, outcome=fail with details."
]
check_core_types [shape=diamond, label="Types OK?"]
// Game logic
impl_game_logic [
shape=box,
class="hard",
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md sections on Game Logic and Core Features.
Read pkg/game/card.go for Card and Deck types.
Implement Klondike Solitaire game logic:
Create pkg/game/game.go with:
- Game struct with:
- Tableau (7 piles, [][]Card)
- Foundation (4 piles, [][]Card)
- Stock pile ([]Card)
- Waste pile ([]Card)
- Move history for undo
- NewGame() initializes properly (7 tableau piles with 1-7 cards, stock gets rest)
- IsWon() checks if all cards in foundations
- GetValidMoves() returns legal moves from current state
Create pkg/game/move.go with:
- Move type (source, destination, card count)
- MoveType constants (TableauToTableau, TableauToFoundation, WasteToTableau, etc.)
- ValidateMove() checks Klondike rules:
- Tableau: descending rank, alternating colors
- Foundation: ascending rank, same suit, starts with Ace
- Kings only to empty tableau
- ExecuteMove() applies move and updates game state
- UndoMove() reverts last move
Create pkg/game/game_test.go with tests for:
- Game initialization (correct card distribution)
- Move validation (legal and illegal moves)
- Move execution and undo
- Win detection
Acceptance:
- go build ./...
- go test ./pkg/game/ -v (all tests pass)
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_game_logic [
shape=box,
class="verify",
timeout=300,
prompt="Verify game logic implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/game/ -v
4. go test -cover ./pkg/game/ (check for >80% coverage)
Write results to .ai/verify_game_logic.md.
Write status.json: outcome=success if ALL pass with good coverage, outcome=fail with details."
]
check_game_logic [shape=diamond, label="Logic OK?"]
// UI Rendering
impl_ui_render [
shape=box,
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md sections on User Interface and Input/Output.
Read pkg/game/game.go and pkg/game/card.go for game state types.
Implement TUI rendering using bubbletea framework:
First, add dependencies:
- go get github.com/charmbracelet/bubbletea
- go get github.com/charmbracelet/lipgloss
Create pkg/ui/render.go with:
- RenderGame() function that takes Game state and returns formatted string
- Display layout: foundations (top), tableau (middle), stock/waste (bottom)
- Card display: face-up shows rank+suit, face-down shows \"##\"
- Highlight selected pile/card
- Status line showing move count and messages
Create pkg/ui/tui.go with:
- model struct embedding Game and UI state (selected pile, cursor position)
- Init() bubbletea init function
- Update() stub (minimal, just quit on 'q')
- View() calls RenderGame()
Create basic test in pkg/ui/render_test.go for RenderGame output format.
Acceptance:
- go build ./...
- go test ./pkg/ui/
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_ui_render [
shape=box,
class="verify",
timeout=300,
prompt="Verify UI rendering implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/ui/ -v
4. Check that bubbletea dependency is properly added to go.mod
Write results to .ai/verify_ui_render.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_ui_render [shape=diamond, label="Render OK?"]
// UI Input
impl_ui_input [
shape=box,
class="hard",
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md section on User Interface keyboard navigation.
Read pkg/game/game.go, pkg/game/move.go for game operations.
Read pkg/ui/tui.go for existing model struct.
Implement keyboard input handling:
Update pkg/ui/tui.go:
- Extend model with:
- selectedPile (which pile is selected)
- selectedCardIndex (which card in pile)
- message (for feedback)
- Update() handles:
- Arrow keys / hjkl for navigation
- Space/Enter to select source, then destination (execute move)
- 'd' to draw from stock
- 'u' to undo
- 'n' for new game
- 'q' to quit
- Call game.ValidateMove() before executing
- Show feedback messages for invalid moves
- Detect and display win condition
Create pkg/ui/input.go with helper functions for input processing.
Update cmd/solitaire/main.go to:
- Create new game
- Initialize bubbletea program with TUI model
- Run program
Acceptance:
- go build ./cmd/solitaire
- Binary runs and displays game (manual check)
- All keyboard controls work (manual check)
Write status.json: outcome=success if builds and runs, outcome=fail with failure_reason otherwise."
]
verify_ui_input [
shape=box,
class="verify",
timeout=300,
prompt="Verify UI input handling.
Run:
1. go build ./...
2. go vet ./...
3. go test ./...
4. go build ./cmd/solitaire
5. Check that binary exists at ./cmd/solitaire/solitaire
Write results to .ai/verify_ui_input.md.
Write status.json: outcome=success if ALL pass and binary exists, outcome=fail with details."
]
check_ui_input [shape=diamond, label="Input OK?"]
// Persistence
impl_persistence [
shape=box,
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md section on Game State Management and Input/Output.
Read pkg/game/game.go for Game struct.
Implement save/load functionality:
Create pkg/storage/save.go with:
- SaveGame(game *Game, filename string) error
- Serialize game state to JSON
- Write to file
- LoadGame(filename string) (*Game, error)
- Read from file
- Deserialize JSON to Game struct
- Validate loaded state
- Make sure Game struct fields are exported for JSON marshaling
Update cmd/solitaire/main.go to:
- Add --load flag
- Add 's' key to save game
- Auto-save on quit (to .solitaire-save.json)
- Load on startup if save exists
Create pkg/storage/save_test.go with:
- Test save/load cycle
- Test invalid file handling
Acceptance:
- go build ./...
- go test ./pkg/storage/
- Manual test: save game, quit, restart with --load
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_persistence [
shape=box,
class="verify",
timeout=300,
prompt="Verify persistence implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/storage/ -v
4. go test ./... (all tests)
Write results to .ai/verify_persistence.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_persistence [shape=diamond, label="Persist OK?"]
// Integration and polish
impl_integration [
shape=box,
timeout=1200,
max_retries=2,
prompt="Read .ai/spec.md for full acceptance criteria.
Read all existing code to understand current state.
Polish and integrate the complete solitaire game:
Tasks:
1. Add command-line flags:
- --draw (1 or 3 cards, default 3)
- --seed (for reproducible games)
- --load (load saved game)
2. Add statistics display on quit (moves, time)
3. Improve error messages and user feedback
4. Add help screen ('h' or '?' key)
5. Ensure all tests pass
6. Update README.md with build instructions and how to play
Create integration test that:
- Initializes game
- Executes a sequence of moves
- Verifies game state
- Tests save/load cycle
Acceptance:
- go build ./...
- go test ./...
- go vet ./...
- ./cmd/solitaire/solitaire runs successfully
- All keyboard commands work
- Game is winnable
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_integration [
shape=box,
class="verify",
timeout=300,
prompt="Verify integration and completeness.
Run:
1. go build ./...
2. go vet ./...
3. go test ./... -v
4. go test -cover ./...
5. go build ./cmd/solitaire
6. Test all command-line flags work
7. Verify README exists and has instructions
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=600,
goal_gate=true,
prompt="Read .ai/spec.md in full.
Review the complete solitaire implementation against all requirements.
Verify:
1. Core Gameplay:
- Game initializes correctly (proper card distribution)
- All legal moves accepted
- All illegal moves rejected
- Win condition works
2. User Experience:
- Full keyboard control
- Clear display
- Undo works
- Save/load works
3. Code Quality:
- go build ./... succeeds
- go test ./... succeeds
- go vet ./... clean
- Test coverage >80% for pkg/game/
4. Binary Works:
- Runs with ./cmd/solitaire/solitaire
- All features functional
- Can complete a game
Run full test suite and manual gameplay check.
Write comprehensive review to .ai/final_review.md.
Write status.json: outcome=success if COMPLETE per spec, outcome=fail with specific missing items."
]
check_review [shape=diamond, label="Review OK?"]
// Wire up the graph
start -> impl_setup
impl_setup -> verify_setup
verify_setup -> check_setup
check_setup -> impl_core_types [condition="outcome=success"]
check_setup -> impl_setup [condition="outcome=fail", label="retry"]
impl_core_types -> verify_core_types
verify_core_types -> check_core_types
check_core_types -> impl_game_logic [condition="outcome=success"]
check_core_types -> impl_core_types [condition="outcome=fail", label="retry"]
impl_game_logic -> verify_game_logic
verify_game_logic -> check_game_logic
check_game_logic -> impl_ui_render [condition="outcome=success"]
check_game_logic -> impl_game_logic [condition="outcome=fail", label="retry"]
impl_ui_render -> verify_ui_render
verify_ui_render -> check_ui_render
check_ui_render -> impl_ui_input [condition="outcome=success"]
check_ui_render -> impl_ui_render [condition="outcome=fail", label="retry"]
impl_ui_input -> verify_ui_input
verify_ui_input -> check_ui_input
check_ui_input -> impl_persistence [condition="outcome=success"]
check_ui_input -> impl_ui_input [condition="outcome=fail", label="retry"]
impl_persistence -> verify_persistence
verify_persistence -> check_persistence
check_persistence -> impl_integration [condition="outcome=success"]
check_persistence -> impl_persistence [condition="outcome=fail", label="retry"]
impl_integration -> verify_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_integration [condition="outcome=fail", label="fix"]
}

View file

@ -0,0 +1,569 @@
digraph dttf {
graph [
goal="Build DTTF: a tool that converts bitmap glyph images to valid TrueType fonts",
rankdir=LR,
default_max_retry=3,
retry_target="impl_setup",
fallback_retry_target="impl_loader",
model_stylesheet="
* { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: medium; }
.hard { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: high; }
.verify { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: medium; }
.review { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: high; }
"
]
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
// Project setup
impl_setup [
shape=box,
prompt="Goal: $goal
Read specs/dttf-v1.md sections 4 (Architecture) and 7 (Data Structures).
Create Go project structure:
- go.mod with module github.com/kilroy/dttf
- pkg/dttf/ directory for core library
- cmd/dttf/ directory for CLI
- pkg/dttf/types.go with core data structures from section 7.1: GlyphBitmap, Point, Contour, TracedGlyph, FontMetadata
Run: go build ./...
Write status.json: outcome=success if builds, outcome=fail with failure_reason otherwise."
]
verify_setup [
shape=box,
class="verify",
prompt="Verify project setup was completed correctly.
Run:
1. go build ./...
2. go vet ./...
3. Check that pkg/dttf/types.go exists and contains all required types from specs/dttf-v1.md section 7.1
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?"]
// PNG loader implementation
impl_loader [
shape=box,
prompt="Goal: $goal
Read specs/dttf-v1.md sections 1 (Input) and 4.3 (Pipeline).
Implement PNG loading and parsing:
- Create pkg/dttf/loader.go
- Implement LoadGlyphs(inputDir string) ([]GlyphBitmap, *FontMetadata, error)
- Parse filename pattern: [FontName-GlyphLabel-]U+XXXX.png (section 1.2)
- Load PNGs, convert to grayscale (section 1.4)
- Load optional font.json metadata (section 1.5)
- Apply threshold conversion to 1-bit (section 1.4)
Create pkg/dttf/loader_test.go with tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_loader [
shape=box,
class="verify",
prompt="Verify PNG loader implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Loader
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?"]
// Tracer implementation (complex, uses Opus)
impl_tracer [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Read specs/dttf-v1.md section 3 (Tracer) completely.
Implement custom bitmap-to-vector tracer:
- Create pkg/dttf/tracer.go
- Implement TraceGlyph(bitmap GlyphBitmap, opts TraceOptions) ([]Contour, error)
- Phase 1: Path decomposition (boundary following, section 3.2)
- Phase 2: Optimal polygon approximation (section 3.2)
- Phase 3: Quadratic Bezier fitting (section 3.2)
- Phase 4: Font-aware optimization (extrema, winding, intersections, section 3.2)
- Coordinate mapping per section 3.3
- Configuration parameters from section 3.4
Create pkg/dttf/tracer_test.go with unit tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v -run Tracer
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_tracer [
shape=box,
class="verify",
prompt="Verify tracer implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Tracer
4. Check that tracer outputs quadratic Beziers (not cubic)
5. Verify winding direction enforcement (section 3.2 Phase 4)
Write results to .ai/verify_tracer.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_tracer [shape=diamond, label="Tracer OK?"]
// Metrics computation
impl_metrics [
shape=box,
prompt="Goal: $goal
Read specs/dttf-v1.md section 2.6 (Glyph Metrics).
Implement glyph metrics computation:
- Create pkg/dttf/metrics.go
- Compute bounding boxes from traced contours
- Compute advanceWidth, leftSideBearing per section 2.6
- Implement sidebearing strategy (proportional to UPEm)
Create pkg/dttf/metrics_test.go with tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v -run Metrics
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_metrics [
shape=box,
class="verify",
prompt="Verify metrics computation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Metrics
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 assembly (complex, uses Opus)
impl_tables [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Read specs/dttf-v1.md sections 2.2 (Required Tables), 2.5 (Vertical Metrics), and 8 (File Assembly).
Implement TrueType table assembly:
- Create pkg/dttf/tables.go
- Implement AssembleFont(glyphs []TracedGlyph, meta *FontMetadata) (*Font, error)
- Build all 10 required tables: head, maxp, hhea, hmtx, OS/2, name, post, cmap, loca, glyf (section 2.2)
- Add gasp table (section 2.3)
- Implement vertical metrics per section 2.5
- Implement glyf encoding per section 8.4
- Implement cmap format 4 per section 8.5
- Compute checksums per section 8.3
Create pkg/dttf/tables_test.go with tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v -run Tables
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_tables [
shape=box,
class="verify",
prompt="Verify TrueType table assembly.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Tables
4. Verify all 10 required tables are present
5. 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 writer
impl_writer [
shape=box,
prompt="Goal: $goal
Read specs/dttf-v1.md section 8 (File Assembly).
Implement font file writer:
- Create pkg/dttf/writer.go
- Implement WriteFont(font *Font, outputPath string) error
- Write offset table, table directory, table data (section 8.1)
- Order tables alphabetically by tag (section 8.2)
- Pad tables to 4-byte boundaries (section 8.1)
- Set checksumAdjustment in head table (section 8.3)
Implement main Build function:
- Create pkg/dttf/build.go
- Implement Build(inputDir string, outputPath string, opts Options) error
- Orchestrate: LoadGlyphs -> TraceGlyph (parallel) -> compute metrics -> AssembleFont -> WriteFont
Create pkg/dttf/build_test.go with integration tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_writer [
shape=box,
class="verify",
prompt="Verify font writer and Build function.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Writer
4. go test ./pkg/dttf/... -v -run Build
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?"]
// Validator
impl_validator [
shape=box,
prompt="Goal: $goal
Read specs/dttf-v1.md section 5.1 (Font Validity) and section 9 (Error Handling).
Implement font validation:
- Create pkg/dttf/validator.go
- Implement Validate(fontPath string) (ValidationResult, error)
- Check: loadable by golang.org/x/image/font/sfnt parser
- Check: contours closed
- Check: correct winding direction (signed area test)
- Check: no self-intersections (segment-segment test)
- Check: points at extrema (derivative roots)
Create pkg/dttf/validator_test.go with tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v -run Validator
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_validator [
shape=box,
class="verify",
prompt="Verify validator implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Validator
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,
prompt="Goal: $goal
Read specs/dttf-v1.md section 11 (Rasterizer).
Implement font-to-bitmap rasterizer:
- Create pkg/dttf/rasterizer.go
- Implement Rasterize(fontPath string, outputDir string, opts RasterizeOptions) error
- Use golang.org/x/image/font/opentype for rendering
- Character set selection: ASCII, All, Chars, Ranges (section 11.3)
- Render configuration from section 11.4
- Extract and write font.json with real metrics (section 11.5)
- PNG naming per section 1.2
Create pkg/dttf/rasterizer_test.go with tests.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v -run Rasterizer
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_rasterizer [
shape=box,
class="verify",
prompt="Verify rasterizer implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run Rasterizer
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?"]
// CLI implementation
impl_cli [
shape=box,
prompt="Goal: $goal
Read specs/dttf-v1.md sections 4.2 (API Surface) and 12 (CLI Reference).
Implement CLI commands:
- Create cmd/dttf/main.go
- Use cobra or similar for CLI structure
- Implement: dttf build (section 12)
- Implement: dttf rasterize (section 12)
- Implement: dttf validate (section 12)
- Implement: dttf test (section 12)
- All flags and options from section 12
Acceptance:
- go build ./cmd/dttf
- ./cmd/dttf/dttf --help
- Test each command with --help
Write status.json: outcome=success if all commands work, outcome=fail with failure_reason otherwise."
]
verify_cli [
shape=box,
class="verify",
prompt="Verify CLI implementation.
Run:
1. go build ./cmd/dttf
2. go vet ./cmd/dttf/...
3. ./cmd/dttf/dttf --help
4. ./cmd/dttf/dttf build --help
5. ./cmd/dttf/dttf rasterize --help
6. ./cmd/dttf/dttf validate --help
7. ./cmd/dttf/dttf test --help
Write results to .ai/verify_cli.md.
Write status.json: outcome=success if ALL commands exist and show help, outcome=fail with details."
]
check_cli [shape=diamond, label="CLI OK?"]
// Test harness (complex, uses Opus)
impl_test_harness [
shape=box,
class="hard",
max_retries=2,
goal_gate=true,
prompt="Goal: $goal
Read specs/dttf-v1.md section 6 (Test Harness) and section 5 (Quality Function).
Implement round-trip test harness:
- Create pkg/dttf/testharness.go
- Implement round-trip test per section 6.1: render -> trace -> compare
- Reference fonts from section 6.2 (Roboto, Open Sans, Noto Serif, Source Code Pro, Playfair Display, Roboto Slab)
- SSIM computation per section 5.2
- Multi-scale testing per section 5.3
- Quality metrics from sections 5.4, 5.5, 5.6
- Composite score per section 5.7
- Auto-download reference fonts from Google Fonts API
Create pkg/dttf/testharness_test.go with tests.
Integrate with dttf test CLI command.
Acceptance:
- go build ./...
- go test ./pkg/dttf/... -v -run TestHarness
- dttf test --help shows correct options
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_test_harness [
shape=box,
class="verify",
prompt="Verify test harness implementation.
Run:
1. go build ./...
2. go vet ./...
3. go test ./pkg/dttf/... -v -run TestHarness
4. Verify SSIM computation is implemented
5. Verify reference font download works
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="Test Harness OK?"]
// Integration test
impl_integration [
shape=box,
goal_gate=true,
prompt="Goal: $goal
Read specs/dttf-v1.md completely.
Run full integration test:
1. Use dttf rasterize to create test input from a simple reference font
2. Run dttf build on the rasterized glyphs
3. Run dttf validate on the output font
4. Run dttf test to compare output vs reference
5. Verify SSIM scores meet thresholds from section 5.2
Document results in .ai/integration_test.md.
Acceptance:
- All commands succeed
- Output font is valid
- SSIM > 0.90 (acceptable threshold from section 5.2)
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason and specific metrics otherwise."
]
verify_integration [
shape=box,
class="verify",
prompt="Verify integration test results.
Run:
1. Check .ai/integration_test.md exists and shows passing results
2. Verify output font file exists and is valid
3. Verify SSIM scores are documented and meet threshold
4. go test ./... (full test suite)
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",
goal_gate=true,
prompt="Goal: $goal
Read specs/dttf-v1.md in full.
Review complete DTTF implementation against spec:
1. Input handling (section 1): PNG loading, filename parsing, metadata, thresholding
2. Output format (section 2): All required tables, vertical metrics, glyph metrics
3. Tracer (section 3): Custom quadratic Bezier tracer with all 4 phases
4. Architecture (section 4): Go library + CLI, correct pipeline
5. Quality function (section 5): All 6 layers implemented
6. Test harness (section 6): Round-trip testing with reference fonts
7. Data structures (section 7): All core types present
8. File assembly (section 8): Correct TrueType structure
9. Error handling (section 9): Fail loudly and early
10. Dependencies (section 10): Only Go stdlib + x/image packages
11. Rasterizer (section 11): Font-to-bitmap with all features
12. CLI (section 12): All commands with correct flags
Run:
- go build ./...
- go test ./... -v
- dttf test --reference-dir <test-fonts>/ if available
Write comprehensive review to .ai/final_review.md.
Write status.json: outcome=success if complete and spec-compliant, outcome=fail with specific missing/incorrect features otherwise."
]
check_review [shape=diamond, label="Review OK?"]
// Flow
start -> impl_setup -> verify_setup -> check_setup
check_setup -> impl_loader [condition="outcome=success"]
check_setup -> impl_setup [condition="outcome=fail", label="retry"]
impl_loader -> verify_loader -> check_loader
check_loader -> impl_tracer [condition="outcome=success"]
check_loader -> impl_loader [condition="outcome=fail", label="retry"]
impl_tracer -> verify_tracer -> check_tracer
check_tracer -> impl_metrics [condition="outcome=success"]
check_tracer -> impl_tracer [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_writer [condition="outcome=success"]
check_tables -> impl_tables [condition="outcome=fail", label="retry"]
impl_writer -> verify_writer -> check_writer
check_writer -> impl_validator [condition="outcome=success"]
check_writer -> impl_writer [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_cli [condition="outcome=success"]
check_rasterizer -> impl_rasterizer [condition="outcome=fail", label="retry"]
impl_cli -> verify_cli -> check_cli
check_cli -> impl_test_harness [condition="outcome=success"]
check_cli -> impl_cli [condition="outcome=fail", label="retry"]
impl_test_harness -> verify_test_harness -> check_test_harness
check_test_harness -> impl_integration [condition="outcome=success"]
check_test_harness -> impl_test_harness [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_integration [condition="outcome=fail", label="fix"]
}

View file

@ -0,0 +1,400 @@
digraph linkcheck {
graph [
goal="Build a Go CLI tool that checks URLs for broken links with configurable depth and multiple output formats",
rankdir=LR,
default_max_retry=3,
retry_target="impl_setup",
fallback_retry_target="impl_crawler",
model_stylesheet="
* { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: medium; }
.hard { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: high; }
.verify { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: medium; }
.review { llm_model: gemini-3-flash-preview; llm_provider: google; reasoning_effort: high; }
"
]
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
// Spec expansion (vague input — bootstraps .ai/spec.md into existence)
expand_spec [
shape=box,
auto_status=true,
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 into a detailed spec covering:
- CLI interface (flags, arguments, exit codes)
- Package structure (cmd/, pkg/ organization)
- Core data types (Link, CrawlResult, Report, etc.)
- robots.txt parsing and respect
- HTTP client configuration (timeouts, user agent, redirects)
- Link extraction from HTML
- Status code categorization (broken vs. OK)
- Depth control mechanism
- Output formatters (text and JSON)
- Error handling
- Test plan (unit tests for each package, integration test)
Write the spec to .ai/spec.md.
Write status.json: outcome=success"
]
// Project setup
impl_setup [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md. Create the Go project structure:
- Initialize go.mod for linkcheck
- Create cmd/linkcheck/main.go with CLI stub
- Create pkg/ directories: pkg/crawler/, pkg/checker/, pkg/robots/, pkg/formatter/
- Add basic README.md with build/usage instructions
Acceptance:
- `go mod init` must succeed
- `go build ./...` must pass
- Directory structure matches spec
Write status.json: outcome=success if all pass, outcome=fail with failure_reason otherwise."
]
verify_setup [
shape=box,
class="verify",
prompt="Verify project setup was completed correctly.
Run:
1. `go build ./...`
2. `go vet ./...`
3. Check that go.mod exists
4. Check that cmd/linkcheck/main.go exists
5. Check that pkg/ subdirectories exist
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?"]
// Crawler implementation
impl_crawler [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Spec: .ai/spec.md, crawler section.
Read: cmd/linkcheck/main.go for understanding the entry point.
Implement the web crawler in pkg/crawler/:
- Crawler struct with configurable depth, user agent
- FetchPage function (HTTP GET with proper headers)
- ExtractLinks function (parse HTML, find all <a href> tags)
- Depth tracking and queue management
- URL normalization and deduplication
- Tests for all functions
Create/modify:
- pkg/crawler/crawler.go
- pkg/crawler/parser.go
- pkg/crawler/crawler_test.go
Acceptance:
- `go build ./...` must pass
- `go test ./pkg/crawler/...` must pass with all tests
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_crawler [
shape=box,
class="verify",
prompt="Verify crawler implementation was completed correctly.
Run:
1. `go build ./...`
2. `go vet ./...`
3. `go test ./pkg/crawler/... -v`
4. Check that pkg/crawler/crawler.go exists
5. Check that tests cover FetchPage and ExtractLinks
Write results to .ai/verify_crawler.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_crawler [shape=diamond, label="Crawler OK?"]
// Robots.txt implementation
impl_robots [
shape=box,
prompt="Goal: $goal
Spec: .ai/spec.md, robots.txt section.
Read: pkg/crawler/crawler.go for types you need.
Implement robots.txt parser and checker in pkg/robots/:
- RobotsParser struct
- FetchRobotsTxt function
- IsAllowed function (check if URL path is allowed for user agent)
- Parse user-agent, disallow, allow directives
- Tests
Create/modify:
- pkg/robots/robots.go
- pkg/robots/robots_test.go
Acceptance:
- `go build ./...` must pass
- `go test ./pkg/robots/...` must pass
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_robots [
shape=box,
class="verify",
prompt="Verify robots.txt implementation was completed correctly.
Run:
1. `go build ./...`
2. `go vet ./...`
3. `go test ./pkg/robots/... -v`
4. Check that pkg/robots/robots.go exists
Write results to .ai/verify_robots.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_robots [shape=diamond, label="Robots OK?"]
// Link checker implementation
impl_checker [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Spec: .ai/spec.md, link checker section.
Read: pkg/crawler/crawler.go, pkg/robots/robots.go for types.
Implement HTTP status checker in pkg/checker/:
- CheckLink function (HEAD or GET request, return status code)
- Categorize status codes (2xx=OK, 3xx=redirect, 4xx/5xx=broken)
- Timeout and retry handling
- Report struct with URL, status, error message
- Tests with mock HTTP server
Create/modify:
- pkg/checker/checker.go
- pkg/checker/status.go
- pkg/checker/checker_test.go
Acceptance:
- `go build ./...` must pass
- `go test ./pkg/checker/...` must pass
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_checker [
shape=box,
class="verify",
prompt="Verify link checker implementation was completed correctly.
Run:
1. `go build ./...`
2. `go vet ./...`
3. `go test ./pkg/checker/... -v`
4. Check that pkg/checker/checker.go exists
Write results to .ai/verify_checker.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_checker [shape=diamond, label="Checker OK?"]
// Output formatter implementation
impl_formatter [
shape=box,
prompt="Goal: $goal
Spec: .ai/spec.md, output section.
Read: pkg/checker/checker.go for Report type.
Implement output formatters in pkg/formatter/:
- TextFormatter (human-readable table or list)
- JSONFormatter (structured JSON output)
- Format function that takes Report slice and format type
- Tests for both formatters
Create/modify:
- pkg/formatter/text.go
- pkg/formatter/json.go
- pkg/formatter/formatter.go
- pkg/formatter/formatter_test.go
Acceptance:
- `go build ./...` must pass
- `go test ./pkg/formatter/...` must pass
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_formatter [
shape=box,
class="verify",
prompt="Verify formatter implementation was completed correctly.
Run:
1. `go build ./...`
2. `go vet ./...`
3. `go test ./pkg/formatter/... -v`
4. Check that both text.go and json.go exist
Write results to .ai/verify_formatter.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_formatter [shape=diamond, label="Formatter OK?"]
// CLI integration
impl_cli [
shape=box,
prompt="Goal: $goal
Spec: .ai/spec.md, CLI section.
Read: pkg/crawler/, pkg/robots/, pkg/checker/, pkg/formatter/ for all functions.
Wire up the CLI in cmd/linkcheck/main.go:
- Parse flags: --depth (default 1), --format (text/json), --user-agent
- Parse URL argument
- Orchestrate: check robots.txt, crawl, check links, format output
- Exit codes: 0 if no broken links, 1 if broken links found, 2 on error
- Help text
Acceptance:
- `go build ./cmd/linkcheck` must pass
- `./linkcheck --help` shows usage
- `./linkcheck https://example.com` runs without error
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_cli [
shape=box,
class="verify",
prompt="Verify CLI integration was completed correctly.
Run:
1. `go build ./cmd/linkcheck`
2. `./linkcheck --help` (must show help)
3. `go vet ./...`
4. Check that main.go wires all packages together
Write results to .ai/verify_cli.md.
Write status.json: outcome=success if ALL pass, outcome=fail with details."
]
check_cli [shape=diamond, label="CLI OK?"]
// Integration test
impl_integration [
shape=box,
goal_gate=true,
prompt="Goal: $goal
Spec: .ai/spec.md, test plan section.
Create integration test:
- Test script or test/integration_test.go
- Test with real HTTP calls (or mock server)
- Test depth=1 vs depth=2
- Test text vs JSON output
- Test robots.txt respect
Acceptance:
- `go build ./...` must pass
- `go test ./...` must pass (all tests)
- Integration test validates end-to-end functionality
Write status.json: outcome=success if all criteria pass, outcome=fail with failure_reason otherwise."
]
verify_integration [
shape=box,
class="verify",
prompt="Verify integration test was completed correctly.
Run:
1. `go build ./...`
2. `go test ./... -v`
3. Check that integration test covers depth, format, robots.txt
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",
goal_gate=true,
prompt="Goal: $goal
Read .ai/spec.md. Review the full linkcheck implementation against the spec.
Check:
- All features implemented (crawling, link checking, robots.txt, depth, formats)
- All tests pass
- CLI works as specified
- Error handling is correct
- Code quality (no warnings, good structure)
- Documentation complete
Run:
1. `go build ./cmd/linkcheck`
2. `go test ./...`
3. Manual test: `./linkcheck https://example.com --depth 1 --format json`
Write review to .ai/final_review.md.
Write status.json: outcome=success if complete and correct, outcome=fail with what's missing or broken."
]
check_review [shape=diamond, label="Review OK?"]
// Flow
start -> expand_spec -> impl_setup -> verify_setup -> check_setup
check_setup -> impl_crawler [condition="outcome=success"]
check_setup -> impl_setup [condition="outcome=fail", label="retry"]
impl_crawler -> verify_crawler -> check_crawler
check_crawler -> impl_robots [condition="outcome=success"]
check_crawler -> impl_crawler [condition="outcome=fail", label="retry"]
impl_robots -> verify_robots -> check_robots
check_robots -> impl_checker [condition="outcome=success"]
check_robots -> impl_robots [condition="outcome=fail", label="retry"]
impl_checker -> verify_checker -> check_checker
check_checker -> impl_formatter [condition="outcome=success"]
check_checker -> impl_checker [condition="outcome=fail", label="retry"]
impl_formatter -> verify_formatter -> check_formatter
check_formatter -> impl_cli [condition="outcome=success"]
check_formatter -> impl_formatter [condition="outcome=fail", label="retry"]
impl_cli -> verify_cli -> check_cli
check_cli -> impl_integration [condition="outcome=success"]
check_cli -> impl_cli [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_integration [condition="outcome=fail", label="fix"]
}

View file

@ -0,0 +1,346 @@
digraph solitaire {
graph [
goal="Build a terminal-based solitaire (Klondike) game",
rankdir=LR,
default_max_retry=3,
retry_target="impl_setup",
fallback_retry_target="impl_game_logic",
model_stylesheet="
* { llm_model: gemini-3-flash-preview; llm_provider: google; }
"
]
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
// Spec expansion - bootstraps .ai/spec.md into existence
expand_spec [
shape=box,
auto_status=true,
prompt="Given the requirements: Build a terminal-based solitaire game (Klondike variant). The game should display cards using ASCII art, support standard solitaire rules (7 tableau piles, 4 foundation piles, stock and waste), allow keyboard-based moves, detect win/loss conditions, and provide a pleasant terminal UI with clear instructions.
Expand into a detailed spec covering:
- Language choice (Python recommended for terminal UI libraries)
- Game rules and data structures (Card, Deck, Pile types)
- Terminal rendering approach (curses or rich library)
- Input handling and move validation
- Win/loss detection
- User interface layout
- Test strategy
Write the spec to .ai/spec.md.
Write status.json: outcome=success"
]
// Project setup
impl_setup [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md. Set up the project structure based on the chosen language:
- If Python: Create pyproject.toml or requirements.txt, src/ directory, main.py stub
- If Go: Create go.mod, cmd/solitaire/main.go, pkg/ structure
- If Rust: Create Cargo.toml, src/main.rs
Create the basic directory structure and configuration files needed.
Run the appropriate build command:
- Python: python3 -m py_compile src/*.py (or pytest --collect-only if tests exist)
- Go: go build ./...
- Rust: cargo build
Write status.json: outcome=success if project structure is created and builds, outcome=fail with failure_reason otherwise."
]
verify_setup [
shape=box,
class="verify",
prompt="Verify project setup was completed correctly.
Run:
1. Check that project configuration file exists (pyproject.toml/requirements.txt, go.mod, or Cargo.toml)
2. Check that source directories exist
3. Run build command appropriate to language (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
4. Verify no syntax errors
Write results to .ai/verify_setup.md.
Write status.json: outcome=success if ALL checks pass, outcome=fail with failure_reason otherwise."
]
check_setup [shape=diamond, label="Setup OK?"]
// Core data structures
impl_data_structures [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md for the data structure design.
Implement the core game data structures:
- Card (suit, rank, face up/down)
- Deck (collection of cards with shuffle)
- Pile types (Tableau, Foundation, Stock, Waste)
- GameState (tracks all piles, move history)
Include basic validation methods and unit tests for each structure.
Run appropriate test command:
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_data_structures [
shape=box,
class="verify",
prompt="Verify data structures implementation.
Run:
1. Build command for the language (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all unit tests for the chosen language (ONLY one):
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
3. Check that Card, Deck, and Pile types are defined
4. Verify basic operations work (create deck, shuffle, deal)
Write results to .ai/verify_data_structures.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_data_structures [shape=diamond, label="Data structures OK?"]
// Game logic
impl_game_logic [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Read .ai/spec.md for Klondike solitaire rules.
Read the data structure files for available types and methods.
Implement the core game logic:
- Initial deal (7 tableau piles, stock)
- Move validation (tableau to tableau, tableau to foundation, waste to tableau, etc.)
- Auto-complete detection
- Win condition checking
- Undo functionality
Create comprehensive tests covering:
- Legal and illegal moves
- Win detection
- Edge cases (empty piles, king placement)
Run tests for the chosen language ONLY (do not run other language commands):
- Python: python3 -m pytest tests/ -v
- Go: go test ./... -v
- Rust: cargo test -- --nocapture
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_game_logic [
shape=box,
class="verify",
prompt="Verify game logic implementation.
Run:
1. Build command (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all tests with verbose output for the chosen language (ONLY one):
- Python: python3 -m pytest tests/ -v
- Go: go test ./... -v
- Rust: cargo test -- --nocapture
3. Verify move validation works correctly
4. Check win condition detection
5. Test undo functionality
Write results to .ai/verify_game_logic.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_game_logic [shape=diamond, label="Game logic OK?"]
// Terminal UI
impl_terminal_ui [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Read .ai/spec.md for UI requirements.
Read the game logic files to understand the GameState interface.
Implement the terminal UI:
- Card rendering (ASCII art for suits and ranks)
- Game board layout (tableau, foundation, stock, waste)
- Keyboard input handling (arrow keys, enter, undo)
- Move selection interface
- Status messages and help text
- Graceful exit handling
Use appropriate library:
- Python: curses or rich
- Go: termui or bubbletea
- Rust: crossterm or tui-rs
Create integration tests that verify UI components render without crashing.
Run for the chosen language ONLY (do not run other language commands):
- Python: python3 -m pytest tests/ && python3 -m mypy src/ (if using type hints)
- Go: go build ./... && go test ./...
- Rust: cargo build && cargo test
Write status.json: outcome=success if builds and tests pass, outcome=fail with failure_reason otherwise."
]
verify_terminal_ui [
shape=box,
class="verify",
prompt="Verify terminal UI implementation.
Run:
1. Build the executable (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all tests for the chosen language (ONLY one):
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
3. Verify UI components exist (renderer, input handler)
4. Check that game can be instantiated
5. Test that rendering doesn't crash with empty game state
Write results to .ai/verify_terminal_ui.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_terminal_ui [shape=diamond, label="Terminal UI OK?"]
// Integration and polish
impl_integration [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md for complete requirements.
Integrate all components into a working game:
- Wire up main.go/main.py/main.rs to start game loop
- Connect UI input to game logic
- Add game over screen with win/loss message
- Include help menu (accessible with 'h' key)
- Add README with build and run instructions
Test the complete game:
- Build the executable
- Play through at least one successful game (can auto-win or test with a fixed seed)
- Verify all keyboard controls work
- Check that undo works across multiple moves
Run:
IMPORTANT: Run for the chosen language ONLY (do not run other language commands):
- Python: python3 src/main.py (manual test) && python3 -m pytest tests/
- Go: go build ./cmd/solitaire && go test ./...
- Rust: cargo build --release && cargo test
Write status.json: outcome=success if game runs and all tests pass, outcome=fail with failure_reason otherwise."
]
verify_integration [
shape=box,
class="verify",
prompt="Verify integration is complete.
Run:
1. Build the final executable (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all tests for the chosen language (ONLY one):
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
3. Check README exists with instructions
4. Verify the game binary can be executed
5. Test that game starts without errors
Write results to .ai/verify_integration.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_integration [shape=diamond, label="Integration OK?"]
// Final review
review [
shape=box,
class="review",
goal_gate=true,
prompt="Goal: $goal
Read .ai/spec.md in full.
Review the complete implementation against the spec:
- All game rules correctly implemented (Klondike solitaire)
- Terminal UI works and is intuitive
- Keyboard controls responsive
- Win/loss detection accurate
- Tests comprehensive and passing
- README clear and accurate
- Code quality good (organized, readable)
Test the game:
- Build and run the executable
- Play through a few moves
- Verify UI renders correctly
- Check that illegal moves are rejected
- Test undo functionality
- Run full test suite
Write detailed review to .ai/final_review.md including:
- What works well
- Any issues found
- Compliance with spec
Write status.json: outcome=success if the game is complete and playable per spec, outcome=fail with what's missing or broken."
]
check_review [shape=diamond, label="Review OK?"]
// Flow
start -> expand_spec -> impl_setup -> verify_setup -> check_setup
check_setup -> impl_data_structures [condition="outcome=success"]
check_setup -> impl_setup [condition="outcome=fail", label="retry"]
impl_data_structures -> verify_data_structures -> check_data_structures
check_data_structures -> impl_game_logic [condition="outcome=success"]
check_data_structures -> impl_data_structures [condition="outcome=fail", label="retry"]
impl_game_logic -> verify_game_logic -> check_game_logic
check_game_logic -> impl_terminal_ui [condition="outcome=success"]
check_game_logic -> impl_game_logic [condition="outcome=fail", label="retry"]
impl_terminal_ui -> verify_terminal_ui -> check_terminal_ui
check_terminal_ui -> impl_integration [condition="outcome=success"]
check_terminal_ui -> impl_terminal_ui [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_terminal_ui [condition="outcome=fail", label="fix"]
}

View file

@ -0,0 +1,396 @@
digraph reference_template {
graph [
goal="$goal",
rankdir=LR,
default_max_retry=3,
retry_target="implement",
fallback_retry_target="debate_consolidate",
provenance_version="1",
model_stylesheet="
* { llm_model: DEFAULT_MODEL; llm_provider: DEFAULT_PROVIDER; }
.hard { llm_model: HARD_MODEL; llm_provider: HARD_PROVIDER; }
.verify { llm_model: VERIFY_MODEL; llm_provider: VERIFY_PROVIDER; }
.branch-a { llm_model: BRANCH_A_MODEL; llm_provider: BRANCH_A_PROVIDER; }
.branch-b { llm_model: BRANCH_B_MODEL; llm_provider: BRANCH_B_PROVIDER; }
.branch-c { llm_model: BRANCH_C_MODEL; llm_provider: BRANCH_C_PROVIDER; }
"
]
// =======================================================================
// TEMPLATE USAGE
//
// This template defines TOPOLOGY ONLY: node shapes, edges, routing,
// and structural patterns. It contains NO prompt text.
//
// The ingestor must compose every prompt from scratch based on the
// actual project spec, DoD, and repo contents. Prompt requirements
// are listed in comments above each shape=box node. See Phase 4 of
// the create-dotfile skill for the full prompt contract.
//
// Common requirements for ALL shape=box prompts (do not repeat per node):
// - Reference $goal
// - Full status contract: write to $KILROY_STAGE_STATUS_PATH,
// fall back to $KILROY_STAGE_STATUS_FALLBACK_PATH, do not write
// nested status.json after cd, use schema {"status":"..."}
// - For status=fail or status=retry: include failure_reason, details,
// and failure_class
// =======================================================================
exit [shape=Msquare, label="Exit"]
subgraph cluster_bootstrap {
label="Bootstrap"
start [shape=Mdiamond, label="Start"]
// Toolchain gate — fail fast before LLM stages.
// Replace tool_command with project-specific checks.
check_toolchain [
shape=parallelogram,
max_retries=0,
tool_command="echo 'Replace with project-specific toolchain checks'; exit 0"
]
// PROMPT: expand_spec
// Role: create or reuse canonical spec at .ai/spec.md
// Must address:
// - Reuse existing .ai/spec.md or repo spec if adequate
// - Copy user-declared verbatim specs without rewriting
// - Expand from $goal only when no adequate spec exists
// - Spec must include: scope, constraints, assumptions, acceptance
// criteria, verification approach, non-goals
// Reads: $goal, existing .ai/spec.md (if any), repo docs
// Writes: .ai/spec.md
// Note: auto_status=true — no explicit status write needed
expand_spec [
shape=box,
auto_status=true
]
// PROMPT: check_dod
// Role: determine if .ai/definition_of_done.md is adequate
// Must address:
// - Check file existence and content (not placeholder)
// - Apply DoD rubric: scope, deliverables, AC, verification,
// quality/safety gates, non-goals
// - Apply coverage checklist: build, tests, lint, docs,
// compatibility, security, ops, perf
// Reads: .ai/definition_of_done.md
// Outcomes: has_dod or needs_dod
check_dod [
shape=box,
label="DoD exists?"
]
}
subgraph cluster_dod {
label="DoD Fanout"
node [shape=box]
dod_fanout [shape=component, label="DoD Fan-Out"]
// PROMPT: dod_a, dod_b, dod_c
// (identical prompt — diversity comes from different models)
// Role: propose a project DoD from the spec
// Must address:
// - Read .ai/spec.md
// - DoD is outcomes/evidence, not a plan
// - Each item verifiable (pass/fail)
// - Don't prescribe implementation approach
// - Include: scope, deliverables, AC, verification, non-goals
// - Apply coverage checklist
// Writes: .ai/dod_a.md (or _b, _c)
dod_a [class="branch-a"]
dod_b [class="branch-b"]
dod_c [class="branch-c"]
// PROMPT: consolidate_dod
// Role: synthesize dod_a/b/c into consensus DoD
// Must address:
// - Read branch outputs via parallel_results.json + worktree_dir
// - Fall back to current worktree if parallel_results.json missing
// - Read .ai/spec.md for context
// - Resolve contradictions, apply DoD rubric + coverage checklist
// Writes: .ai/definition_of_done.md
consolidate_dod [auto_status=true]
}
subgraph cluster_planning {
label="Planning Fanout"
node [shape=box]
plan_fanout [shape=component, label="Plan Fan-Out"]
// PROMPT: plan_a, plan_b, plan_c
// (identical prompt — diversity comes from different models)
// Role: create implementation plan from spec + DoD
// Must address:
// - Read .ai/spec.md and .ai/definition_of_done.md
// - If .ai/postmortem_latest.md exists, incorporate its lessons
// - Plan must cover all deliverables and acceptance criteria
// from the DoD, with project-specific implementation detail
// - Plan must be grounded in the actual project domain,
// technology stack, and repo structure
// Writes: .ai/plan_a.md (or _b, _c)
plan_a [class="branch-a"]
plan_b [class="branch-b"]
plan_c [class="branch-c"]
// PROMPT: debate_consolidate
// Role: synthesize plan_a/b/c into best-of-breed final plan
// Must address:
// - Read branch outputs via parallel_results.json + worktree_dir
// - Fall back to current worktree if parallel_results.json missing
// - If .ai/postmortem_latest.md exists, verify plan addresses
// every identified issue
// - Resolve conflicts, ensure dependency order
// Writes: .ai/plan_final.md
debate_consolidate [auto_status=true]
}
// OPTIONAL: For porting/reading-existing-source tasks — add analyze cluster here.
// analyze_fanout [shape=component, label="Analyze Fan-Out"]
// analyze_module_a [shape=box, auto_status=true, prompt="...read source, write .ai/design_a.md..."]
// analyze_module_b [shape=box, auto_status=true, prompt="..."]
// merge_analysis [shape=box, auto_status=true, prompt="...verify all design docs..."]
// See reference_template.dot OPTIONAL stubs below for pattern.
// OPTIONAL: For 5+ discrete deliverable files — use worker pool instead of flat fan-out.
// plan_work [shape=box, auto_status=true, label="Plan Work Queue", prompt="...write .ai/work_queue.json..."]
// work_pool [shape=component, label="Worker Pool"]
// worker_0 [shape=box, auto_status=true, label="Worker 0", prompt="...id%3==0..."]
// worker_1 [shape=box, auto_status=true, label="Worker 1", prompt="...id%3==1..."]
// worker_2 [shape=box, auto_status=true, label="Worker 2", prompt="...id%3==2..."]
// check_work_complete [shape=box, auto_status=true, label="Work Complete?", prompt="...pass counter..."]
subgraph cluster_implement_verify {
label="Implement And Verify"
// PROMPT: implement
// Role: single-writer code implementation (fresh or repair)
// Must address:
// - REPAIR FIRST: if .ai/postmortem_latest.md exists, read it
// FIRST, fix ONLY identified gaps, do NOT regenerate working
// systems, preserve all passing code and tests
// - FRESH: if no postmortem, execute .ai/plan_final.md
// - Read .ai/spec.md and .ai/definition_of_done.md
// - Implementation instructions must be specific to the project's
// deliverables, domain, technology, and constraints — derived
// from the ingestor's reading of the spec and DoD
// - Incremental implementation: each module complete before next
// - Log progress to .ai/implementation_log.md
// Failure: also include failure_signature in meta
implement [
shape=box,
class="hard",
max_retries=2
]
check_implement [shape=diamond, label="Implement OK?"]
// Auto-fix formatting before verify gate.
// Replace tool_command with project-specific auto-formatter.
fix_fmt [
shape=parallelogram,
max_retries=0,
tool_command="echo 'Replace with project-specific auto-formatter'; exit 0"
]
// Replace tool_command with project-specific formatter check.
verify_fmt [
shape=parallelogram,
max_retries=0,
tool_command="echo 'Replace with project-specific formatter check'; exit 0"
]
check_fmt [shape=diamond, label="Fmt OK?"]
// Replace tool_command with project-specific build command.
verify_build [
shape=parallelogram,
tool_command="echo 'Replace with project-specific build check'; exit 0"
]
check_build [shape=diamond, label="Build OK?"]
// Replace tool_command with project-specific test command.
verify_test [
shape=parallelogram,
tool_command="echo 'Replace with project-specific test check'; exit 0"
]
check_test [shape=diamond, label="Tests OK?"]
// Replace tool_command with artifact hygiene check.
// Confirm deliverables meet their interface contract (exports, endpoints,
// CLI behavior, observable outputs); file existence alone is insufficient.
verify_artifacts [
shape=parallelogram,
max_retries=0,
tool_command="echo 'Replace with artifact hygiene check'; exit 0"
]
check_artifacts [shape=diamond, label="Artifacts OK?"]
// PROMPT: verify_fidelity
// Role: semantic review after all deterministic checks pass
// Must address:
// - Read implementation outputs and verify against
// .ai/definition_of_done.md and .ai/spec.md
// - Verification must be specific to the project's acceptance
// criteria — enumerate the actual areas to check, derived from
// the ingestor's reading of the DoD
// - Write results to .ai/verify_fidelity.md
// Failure: also include failure_signature in meta — sorted
// comma-separated list of specific failed criteria identifiers
verify_fidelity [
shape=box,
class="verify"
]
check_impl [shape=diamond, label="Impl OK?"]
}
subgraph cluster_review {
label="Review Fanout"
node [shape=box]
review_fanout [shape=component, label="Review Fan-Out"]
// PROMPT: review_a, review_b, review_c
// (identical prompt — diversity comes from different models)
// Role: review implementation against DoD
// Must address:
// - Read .ai/definition_of_done.md for acceptance criteria
// - Read implementation outputs
// - Check build, completeness, correctness, tests against all
// DoD criteria — enumerate what to check, derived from the
// ingestor's reading of the DoD
// - Verdict: APPROVED or REJECTED with specific evidence
// Failure: include specific gaps with criteria identifiers
// Writes: .ai/review_a.md (or _b, _c)
review_a [class="branch-a"]
review_b [class="branch-b"]
review_c [class="branch-c"]
// PROMPT: review_consensus
// Role: synthesize reviews into consensus verdict
// Must address:
// - Read branch outputs via parallel_results.json + worktree_dir
// - Fall back to current worktree if parallel_results.json missing
// - Read .ai/definition_of_done.md for criteria
// - Consensus: 2+ APPROVED with no critical gaps -> success;
// otherwise -> retry with specific issues
// Writes: .ai/review_consensus.md
review_consensus [auto_status=true, goal_gate=true, retry_target="postmortem"]
}
subgraph cluster_postmortem {
label="Postmortem"
node [shape=box]
// PROMPT: postmortem
// Role: analyze failure and guide next repair iteration
// Must address:
// - Read .ai/review_consensus.md (if review stage reached)
// - Read .ai/verify_fidelity.md (if semantic verify ran)
// - Read branch review outputs via parallel_results.json +
// worktree_dir if available
// - Read .ai/implementation_log.md
// - Output: root causes, what worked (preserve), what failed
// (fix), concrete next changes
// - Must NOT direct from-scratch restart — preserve working code
// Outcome classification (recovery routing):
// - impl_repair: code repair needed; plan/toolchain still valid
// - needs_replan: plan/approach is inadequate; regenerate plan branches
// - needs_toolchain: environment/bootstrap/toolchain issue detected
// - When uncertain, default to impl_repair
// Writes: .ai/postmortem_latest.md (overwrite previous)
// Note: status reflects analysis completion, not implementation state
postmortem [auto_status=true]
}
// =========================================================================
// Flow
// =========================================================================
// Linear start: toolchain gate -> spec -> DoD check
start -> check_toolchain
check_toolchain -> expand_spec [condition="outcome=success"]
check_toolchain -> check_toolchain [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_toolchain -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_toolchain -> postmortem
expand_spec -> check_dod
// DoD fan-out (if needed)
check_dod -> dod_fanout [condition="outcome=needs_dod"]
check_dod -> dod_fanout
dod_fanout -> dod_a
dod_fanout -> dod_b
dod_fanout -> dod_c
dod_a -> consolidate_dod
dod_b -> consolidate_dod
dod_c -> consolidate_dod
consolidate_dod -> plan_fanout
// Skip to planning if DoD exists
check_dod -> plan_fanout [condition="outcome=has_dod"]
// Planning fan-in -> debate -> implement
plan_fanout -> plan_a
plan_fanout -> plan_b
plan_fanout -> plan_c
plan_a -> debate_consolidate
plan_b -> debate_consolidate
plan_c -> debate_consolidate
debate_consolidate -> implement
// Verify/check inner loop (tool gates first, semantic review last)
implement -> check_implement
check_implement -> fix_fmt [condition="outcome=success"]
fix_fmt -> verify_fmt
check_implement -> implement [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_implement -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_implement -> postmortem
verify_fmt -> check_fmt
check_fmt -> verify_build [condition="outcome=success"]
check_fmt -> implement [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_fmt -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_fmt -> postmortem
verify_build -> check_build
check_build -> verify_test [condition="outcome=success"]
check_build -> implement [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_build -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_build -> postmortem
verify_test -> check_test
check_test -> verify_artifacts [condition="outcome=success"]
check_test -> implement [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_test -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_test -> postmortem
verify_artifacts -> check_artifacts
check_artifacts -> verify_fidelity [condition="outcome=success"]
check_artifacts -> implement [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_artifacts -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_artifacts -> postmortem
verify_fidelity -> check_impl
check_impl -> review_fanout [condition="outcome=success"]
review_fanout -> review_a
review_fanout -> review_b
review_fanout -> review_c
check_impl -> implement [condition="outcome=fail && context.failure_class=transient_infra", loop_restart=true]
check_impl -> postmortem [condition="outcome=fail && context.failure_class!=transient_infra"]
check_impl -> postmortem
// Review fan-in -> consensus
review_a -> review_consensus
review_b -> review_consensus
review_c -> review_consensus
// Consensus routing: success -> exit, anything else -> postmortem
review_consensus -> exit [condition="outcome=success"]
review_consensus -> postmortem
// Domain-routed recovery: classify failure and choose the right re-entry
postmortem -> check_toolchain [condition="outcome=fail && context.failure_class=transient_infra"]
postmortem -> implement [condition="outcome=impl_repair"]
postmortem -> plan_fanout [condition="outcome=needs_replan"]
postmortem -> check_toolchain [condition="outcome=needs_toolchain"]
postmortem -> implement
}

33
test/kilroy/semport.dot Normal file
View file

@ -0,0 +1,33 @@
digraph Workflow {
graph [ label="Semantic Port Tracking Loop", goal="We want to intelligently track and port semantic changes from the upstream openai-agents-python repository to our Go implementation.\n\nWe want to fetch the latest commits from inspiration/openai-agents-python, analyze each new commit for semantic changes (not just syntax), and intelligently coalesce/merge those changes into our Go codebase while respecting Go idioms and our existing architecture.\n\nWe want to track the disposition of each upstream commit in semport/ledger.tsv with three states: 'new' (unprocessed), 'implemented' (changes made), or 'acknowledged' (reviewed but no changes needed).\n\nWe want to make sure we are surgical in this monorepo and follow pre-existing coding conventions and standards.", rankdir="LR", context_fidelity_default="truncate", context_thread_default="semport-tracking", default_max_retry="4" ];
FinalizeAndUpdateLedger [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="6) Finalize & update ledger", llm_model="gpt-5.2-codex", llm_prompt="**Finalize implementation and update ledger in one step.**\\n\\n1. Synthesize the port plan from .ai/semport_plan_sonnet.md and implementation results into .ai/semport_implementation_summary.md. List which upstream commits were processed, what changes were made (with file:line references), and the disposition ('implemented').\\n\\n2. Update the ledger using:\\n```\\npython3 semport/ledger.py update <shortsha> implemented\\npython3 semport/ledger.py sort\\n```\\n\\n3. Verify with `python3 semport/ledger.py stats` to see progress.\\n\\n4. **Commit all changes** (implementation + ledger update) with a clear message:\\n ```\\n git add -A\\n git commit -m \"semport: implement <shortsha> - <brief description of what was ported>\"\\n ```\\n Example: `git commit -m \"semport: implement a776d80 - nest handoff history by default\"`\\n\\nKeep our goal $goal in mind. Then loop back to process the next commit.", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.observe", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="1200"];
TestValidate [allow_partial="true", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="5) Test/Validate changes", llm_model="gpt-5.2-codex", llm_prompt="Keeping our goal in mind: $goal. From repo root, validate that all ported changes work correctly. Run relevant tests (go test ./...), ensure compilation succeeds, and verify the ported functionality matches the upstream semantic intent (not necessarily syntax). Write validation results to .ai/semport_validation_report_NN.md. Use outcome=yes if all tests pass and changes are semantically correct; otherwise use outcome=retry with concrete failure details.", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.steer", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="1800"];
AnalyzeFailureSonnet [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="6a) Analyze failure (sonnet)", llm_model="gpt-5.2-codex", llm_prompt="When tests or validation fail, inspect .ai/semport_validation_report_*.md, logs, diffs, and error messages. Write .ai/semport_failure_sonnet.md summarizing root causes, impacted files (with line references), and what needs to be fixed. Clearly note where failure artifacts are located. Keep our goal $goal in mind and be subjective.", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.observe", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="1200"];
FetchUpstreamSonnet [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="1) Fetch upstream & identify next commit (sonnet)", llm_model="gpt-5.2-codex", llm_prompt="Our goal is: $goal\\n\\n---\\n\\n**CRITICAL: Use semport/ledger.py for all ledger operations to ensure proper chronological ordering. Ledger entries MUST always use short git hashes (7 characters, e.g. from `git rev-parse --short`).**\\n\\n1. Run `python3 semport/ledger.py earliest` to get the chronologically earliest commit with disposition='new'\\n2. If a commit is found, write ONLY that single commit (shortsha, iso8601, and full commit message from git show) to .ai/semport_new_commits.md and use outcome=process\\n3. If NO 'new' commits exist:\\n a. Ensure inspiration/openai-agents-python exists (clone if missing)\\n b. Run git fetch && git pull in that directory\\n c. Use git log to find commits newer than the latest in ledger.tsv, capturing a short hash for each commit (e.g. `git log --format='%h %cI' ...`)\\n d. Add new commits using `python3 semport/ledger.py add <shortsha> <timestamp>`\\n e. Run `python3 semport/ledger.py sort` to maintain chronological order\\n f. Then run `python3 semport/ledger.py earliest` to get the first new commit\\n g. If a new commit is found after fetching, write it to .ai/semport_new_commits.md and use outcome=process\\n h. If still no new commits after fetching, write a completion report to .ai/semport_completion.md and use outcome=done\\n\\n**IMPORTANT**: You MUST end with exactly one of these outcomes:\\n- outcome=process (when there is a commit to process)\\n- outcome=done (when fully caught up with no new commits)", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.steer", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="1200"];
ImplementPort [allow_partial="true", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="4) Implement port (gpt-5.1)", llm_model="gpt-5.2-codex", llm_prompt="Follow the port plan in .ai/semport_plan_finalized.md. For each upstream commit, port the semantic changes to the Go codebase. Focus on semantic equivalence, not literal translation. Use Go idioms, respect existing architecture, and reference specific files/line ranges. Log all changes and commands to .ai/semport_impl.log.", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.observe", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="2400"];
FinalizePlanGPT [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="3) Finalize port plan (gpt-5.1)", llm_model="gpt-5.2-codex", llm_prompt="Keeping our goal $goal in mind. Perform a final editorial pass over .ai/semport_plan_sonnet.md and write .ai/semport_plan_finalized.md. Ensure each port task has concrete file:line references, clear acceptance criteria, and is directly executable. Remove vague language and ensure the plan is actionable.", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.observe", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="1200"];
Exit [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="Exit", llm_model="gpt-5.2-codex", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="exit", penwidth="1.2", reasoning_effort="high", shape="doublecircle", style="rounded,filled", timeout="1200"];
Start [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="Start", llm_model="gpt-5.2-codex", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="start", penwidth="1.2", reasoning_effort="high", shape="circle", style="rounded,filled", timeout="1200"];
AnalyzePlanSonnet [allow_partial="false", color="#94a3b8", fillcolor="white", fontname="Helvetica", fontsize="12", is_codergen="true", label="2) Analyze & plan port (sonnet)", llm_model="gpt-5.2-codex", llm_prompt="Keeping our goal $goal in mind. Read .ai/semport_new_commits.md which contains a SINGLE commit to process. Examine this one commit in inspiration/openai-agents-python using git show. Analyze the semantic changes (what functionality changed, not just syntax). Decide if this change is relevant to our Go implementation or if it's Python-specific/docs-only/not-applicable.\\n\\nWrite .ai/semport_plan_sonnet.md with sections: Commit Being Processed (shortsha and summary), Semantic Analysis (what changed functionally), DECISION (port or acknowledge with clear reasoning), Port Plan (if porting: concrete tasks with file:line references for Go code), and Disposition Recommendation.\\n\\n**If decision is to ACKNOWLEDGE (skip):**\\n1. Update the ledger: `python3 semport/ledger.py update <shortsha> acknowledged && python3 semport/ledger.py sort`\\n2. Verify with `python3 semport/ledger.py stats`\\n3. **Commit the ledger change** with a clear message summarizing why this commit was acknowledged:\\n ```\\n git add semport/ledger.tsv\\n git commit -m \"semport: acknowledge <shortsha> - <brief reason>\"\\n ```\\n Example: `git commit -m \"semport: acknowledge e3fe4f4 - docs typo fix, no Go changes needed\"`\\n4. Use outcome=skip to loop back for next commit\\n\\n**If decision is to PORT:**\\nUse outcome=port to proceed to implementation.", llm_provider="openai", margin="0.1,0.08", max_agent_turns="8", node_type="stack.steer", penwidth="1.2", reasoning_effort="high", shape="box", style="rounded,filled", timeout="1200"];
FinalizeAndUpdateLedger -> FetchUpstreamSonnet [loop_restart="true"];
Start -> FetchUpstreamSonnet;
AnalyzeFailureSonnet -> FinalizeAndUpdateLedger;
FinalizePlanGPT -> ImplementPort;
TestValidate -> FinalizeAndUpdateLedger [condition="outcome=yes", label="pass"];
TestValidate -> AnalyzeFailureSonnet [condition="outcome=retry", label="fail"];
FetchUpstreamSonnet -> AnalyzePlanSonnet [condition="outcome=process", label="process"];
FetchUpstreamSonnet -> Exit [condition="outcome=done", label="done"];
ImplementPort -> TestValidate;
AnalyzePlanSonnet -> FinalizePlanGPT [condition="outcome=port", label="port"];
AnalyzePlanSonnet -> FetchUpstreamSonnet [condition="outcome=skip", label="skip", loop_restart="true"];
}

View file

@ -0,0 +1,17 @@
digraph Simple {
graph [
goal="Run tests and report",
rankdir=LR,
model_stylesheet="
* { llm_model: gpt-5.2-codex; llm_provider: openai; }
"
]
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
run_tests [shape=box, label="Run Tests", prompt="Run the test suite and report results"]
report [shape=box, label="Report", prompt="Summarize the test results"]
start -> run_tests -> report -> exit
}

View file

@ -0,0 +1,352 @@
digraph solitaire {
graph [
goal="Build a terminal-based solitaire (Klondike) game",
rankdir=LR,
default_max_retry=3,
retry_target="impl_setup",
fallback_retry_target="impl_game_logic",
model_stylesheet="
* { llm_model: gpt-5.3-codex-spark; llm_provider: openai; }
"
]
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
// Spec expansion - bootstraps .ai/spec.md into existence
expand_spec [
shape=box,
auto_status=true,
prompt="Given the requirements: Build a terminal-based solitaire game (Klondike variant). The game should display cards using ASCII art, support standard solitaire rules (7 tableau piles, 4 foundation piles, stock and waste), allow keyboard-based moves, detect win/loss conditions, and provide a pleasant terminal UI with clear instructions.
Expand into a detailed spec covering:
- Language choice (Python recommended for terminal UI libraries)
- Game rules and data structures (Card, Deck, Pile types)
- Terminal rendering approach (curses or rich library)
- Input handling and move validation
- Win/loss detection
- User interface layout
- Test strategy
Write the spec to .ai/spec.md.
Write status.json: outcome=success"
]
// Project setup
impl_setup [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md. Set up the project structure based on the chosen language:
- If Python: Create pyproject.toml or requirements.txt, src/ directory, main.py stub
- If Go: Create go.mod, cmd/solitaire/main.go, pkg/ structure
- If Rust: Create Cargo.toml, src/main.rs
Create the basic directory structure and configuration files needed.
Run the appropriate build command:
- Python: python3 -m py_compile src/*.py (or pytest --collect-only if tests exist)
- Go: go build ./...
- Rust: cargo build
Write status.json: outcome=success if project structure is created and builds, outcome=fail with failure_reason otherwise."
]
verify_setup [
shape=box,
class="verify",
prompt="Verify project setup was completed correctly.
Run:
1. Check that project configuration file exists (pyproject.toml/requirements.txt, go.mod, or Cargo.toml)
2. Check that source directories exist
3. Run build command appropriate to language (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
4. Verify no syntax errors
Write results to .ai/verify_setup.md.
Write status.json: outcome=success if ALL checks pass, outcome=fail with failure_reason otherwise."
]
check_setup [shape=diamond, label="Setup OK?"]
// Core data structures
impl_data_structures [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md for the data structure design.
Implement the core game data structures:
- Card (suit, rank, face up/down)
- Deck (collection of cards with shuffle)
- Pile types (Tableau, Foundation, Stock, Waste)
- GameState (tracks all piles, move history)
Include basic validation methods and unit tests for each structure.
Run appropriate test command:
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_data_structures [
shape=box,
class="verify",
prompt="Verify data structures implementation.
Run:
1. Build command for the language (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all unit tests for the chosen language (ONLY one):
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
3. Check that Card, Deck, and Pile types are defined
4. Verify basic operations work (create deck, shuffle, deal)
Write results to .ai/verify_data_structures.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_data_structures [shape=diamond, label="Data structures OK?"]
// Game logic
impl_game_logic [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Read .ai/spec.md for Klondike solitaire rules.
Read the data structure files for available types and methods.
Implement the core game logic:
- Initial deal (7 tableau piles, stock)
- Move validation (tableau to tableau, tableau to foundation, waste to tableau, etc.)
- Auto-complete detection
- Win condition checking
- Undo functionality
Create comprehensive tests covering:
- Legal and illegal moves
- Win detection
- Edge cases (empty piles, king placement)
Run tests for the chosen language ONLY (do not run other language commands):
- Python: python3 -m pytest tests/ -v
- Go: go test ./... -v
- Rust: cargo test -- --nocapture
Write status.json: outcome=success if all tests pass, outcome=fail with failure_reason otherwise."
]
verify_game_logic [
shape=box,
class="verify",
prompt="Verify game logic implementation.
Run:
1. Build command (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all tests with verbose output for the chosen language (ONLY one):
- Python: python3 -m pytest tests/ -v
- Go: go test ./... -v
- Rust: cargo test -- --nocapture
3. Verify move validation works correctly
4. Check win condition detection
5. Test undo functionality
Write results to .ai/verify_game_logic.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_game_logic [shape=diamond, label="Game logic OK?"]
// Terminal UI
impl_terminal_ui [
shape=box,
class="hard",
max_retries=2,
prompt="Goal: $goal
Read .ai/spec.md for UI requirements.
Read the game logic files to understand the GameState interface.
Implement the terminal UI:
- Card rendering (ASCII art for suits and ranks)
- Game board layout (tableau, foundation, stock, waste)
- Keyboard input handling (arrow keys, enter, undo)
- Move selection interface
- Status messages and help text
- Graceful exit handling
Use appropriate library:
- Python: curses or rich
- Go: termui or bubbletea
- Rust: crossterm or tui-rs
Create integration tests that verify UI components render without crashing.
Run for the chosen language ONLY (do not run other language commands):
- Python: python3 -m pytest tests/ && python3 -m mypy src/ (if using type hints)
- Go: go build ./... && go test ./...
- Rust: cargo build && cargo test
Write status.json: outcome=success if builds and tests pass, outcome=fail with failure_reason otherwise."
]
verify_terminal_ui [
shape=box,
class="verify",
prompt="Verify terminal UI implementation.
Run:
1. Build the executable (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all tests for the chosen language (ONLY one):
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
3. Verify UI components exist (renderer, input handler)
4. Check that game can be instantiated
5. Test that rendering doesn't crash with empty game state
Write results to .ai/verify_terminal_ui.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_terminal_ui [shape=diamond, label="Terminal UI OK?"]
// Integration and polish
impl_integration [
shape=box,
prompt="Goal: $goal
Read .ai/spec.md for complete requirements.
Integrate all components into a working game:
- Wire up main.go/main.py/main.rs to start game loop
- Connect UI input to game logic
- Add game over screen with win/loss message
- Include help menu (accessible with 'h' key)
- Add README with build and run instructions
Test the complete game:
- Build the executable
- Play through at least one successful game (can auto-win or test with a fixed seed)
- Verify all keyboard controls work
- Check that undo works across multiple moves
Run:
IMPORTANT: Run for the chosen language ONLY (do not run other language commands):
- Python: python3 src/main.py (manual test) && python3 -m pytest tests/
- Go: go build ./cmd/solitaire && go test ./...
- Rust: cargo build --release && cargo test
Write status.json: outcome=success if game runs and all tests pass, outcome=fail with failure_reason otherwise."
]
verify_integration [
shape=box,
class="verify",
prompt="Verify integration is complete.
Run:
1. Build the final executable (ONLY one):
- Python: python3 -m py_compile src/*.py
- Go: go build ./...
- Rust: cargo build
2. Run all tests for the chosen language (ONLY one):
- Python: python3 -m pytest tests/
- Go: go test ./...
- Rust: cargo test
3. Check README exists with instructions
4. Verify the game binary can be executed
5. Test that game starts without errors
Write results to .ai/verify_integration.md.
Write status.json: outcome=success if all pass, outcome=fail with failure_reason."
]
check_integration [shape=diamond, label="Integration OK?"]
// Final review
review [
shape=box,
class="review",
goal_gate=true,
prompt="Goal: $goal
Read .ai/spec.md in full.
Review the complete implementation against the spec:
- All game rules correctly implemented (Klondike solitaire)
- Terminal UI works and is intuitive
- Keyboard controls responsive
- Win/loss detection accurate
- Tests comprehensive and passing
- README clear and accurate
- Code quality good (organized, readable)
Test the game:
- Build and run the executable
- Play through a few moves
- Verify UI renders correctly
- Check that illegal moves are rejected
- Test undo functionality
- Run full test suite
Write detailed review to .ai/final_review.md including:
- What works well
- Any issues found
- Compliance with spec
Write status.json: outcome=success if the game is complete and playable per spec, outcome=fail with what's missing or broken."
]
check_review [shape=diamond, label="Review OK?"]
// Flow
start -> expand_spec -> impl_setup -> verify_setup -> check_setup
check_setup -> impl_data_structures [condition="outcome=success"]
check_setup -> impl_setup [condition="outcome=fail", label="retry"]
check_setup -> impl_setup [label="fallback"]
impl_data_structures -> verify_data_structures -> check_data_structures
check_data_structures -> impl_game_logic [condition="outcome=success"]
check_data_structures -> impl_data_structures [condition="outcome=fail", label="retry"]
check_data_structures -> impl_data_structures [label="fallback"]
impl_game_logic -> verify_game_logic -> check_game_logic
check_game_logic -> impl_terminal_ui [condition="outcome=success"]
check_game_logic -> impl_game_logic [condition="outcome=fail", label="retry"]
check_game_logic -> impl_game_logic [label="fallback"]
impl_terminal_ui -> verify_terminal_ui -> check_terminal_ui
check_terminal_ui -> impl_integration [condition="outcome=success"]
check_terminal_ui -> impl_terminal_ui [condition="outcome=fail", label="retry"]
check_terminal_ui -> impl_terminal_ui [label="fallback"]
impl_integration -> verify_integration -> check_integration
check_integration -> review [condition="outcome=success"]
check_integration -> impl_integration [condition="outcome=fail", label="retry"]
check_integration -> impl_integration [label="fallback"]
review -> check_review
check_review -> exit [condition="outcome=success"]
check_review -> impl_terminal_ui [condition="outcome=fail", label="fix"]
check_review -> impl_terminal_ui [label="fallback"]
}