| name | tree-sitter-tdd |
| description | Use when adding a new F# language feature to the tree-sitter parser with TDD workflow |
Tree-Sitter TDD Skill
Overview
Use this skill when adding a new F# language feature to the tree-sitter parser. This skill automates the TDD workflow: write test first, get Fantomas AST, map to tree-sitter format, verify with user, and then strictly orchestrate multi-agent iteration—spawning per-test subagents for parse fixes until all test cases pass. Full green suite is mandatory.
Critical: This workflow loops—using delegated subagents—until ALL tests pass. Adding a feature may break existing tests; every breakage is isolated and fixed before success is declared.
When to Use
- Adding a new language feature (e.g., new expression type, pattern, keyword)
- Fixing a parsing issue with existing syntax
Prerequisite
- The script
ast_parser.fsx at project root must accept the code example as a command-line argument. If it does not, update the script before continuing. See Step 1 for the exact required change.
Input
The user provides:
- F# code snippet to parse
- Test name (e.g., "nullable type")
- Corpus file (e.g.,
test/corpus/type_defn.txt)
Workflow
Multi-Agent Orchestration Pattern
After a new test is written and (expectedly) fails, use the following orchestration:
- Spawn a subagent (with the
tree-sitter-parse-testing skill) to iterate on parser and grammar only for the failing example until it parses as required.
- IMPORTANT: The subagent must focus on fixing that single test case. The subagent should never call
npx tree-sitter test or attempt to fix multiple cases. It is strictly one subagent per failing test.
- Once that test parses, run the entire test suite (
npx tree-sitter test).
- For every other test that fails, spawn a new subagent, each focusing solely on its failing case.
- Continue this loop, always re-running and spawning as needed, until all tests pass.
- Never “fix everything at once”; maintain one-failure-at-a-time discipline per subagent.
This coordination ensures robust TDD and protects against missed breakages elsewhere in the corpus. (See Step 7 for concrete mechanics.)
Step 1: Get Fantomas AST
ALWAYS use the helper script .opencode/ast_parser.fsx in the repository to obtain the Fantomas AST for your F# code example.
If the script does not accept input as a command-line parameter, update it so that it sets the sample code from the command line argument instead of any hardcoded value.
Then run:
dotnet fsi .opencode/ast_parser.fsx -- "<F# code example to test>"
This ensures that your code snippet is always parsed accurately for each test, avoids manual copy-paste errors, and standardizes the workflow.
Step 2: Read Grammar
Read the parser's src/grammar.json to get valid node names:
cat fsharp/src/grammar.json | jq '.rules | keys'
Step 3: Map AST to Tree-Sitter Format
Convert Fantomas AST to tree-sitter lisp format:
(file
(declaration_expression
(function_or_value_defn
(value_declaration_left
(identifier_pattern
(long_identifier_or_op
(identifier))))
(const
(string)))))
Use these mappings (add as needed):
Let → declaration_expression
SynIdent → identifier
Const → constant
Int32 → integer
String → string
Step 4: Ask User for Unknown Nodes
If you encounter a Fantomas AST node that doesn't map to any node in grammar.json:
- Propose a new node name based on F# language spec
- Use
question tool to ask user:
header: "New node name"
question: "Found Fantomas node 'X' that doesn't map to grammar. What's the tree-sitter node name?"
options:
- label: "proposed_name"
description: "Use proposed name based on F# spec"
- label: "custom"
description: "Enter custom name"
Step 5: Verify Parse Tree
Show the user the expected parse tree and ask for verification:
header: "Verify parse tree"
question: "Does this parse tree look correct?"
options:
- label: "Yes, add test"
description: "Proceed to add test to corpus"
- label: "No, fix mapping"
description: "Revise node mappings"
Step 6: Add Test to Corpus
Add the test to the specified corpus file:
================================================================================
test-name
================================================================================
<user's F# code>
--------------------------------------------------------------------------------
(expected parse tree)
Step 6: Run Tests
npx tree-sitter test
- Always run the full suite after each fix/change; never trust partial green results. See below for full orchestration.
Step 7: Multi-Agent Test Fixing and Orchestration (Loop Until All Pass)
IMPORTANT: Adding a new feature may break existing tests. You MUST loop until every test passes. ALL fixes should follow multi-agent TDD:
- After confirming the new test fails, the main agent spawns a subagent (using the
tree-sitter-parse-testing skill) assigned to that single failing case. The subagent iterates on grammar/debugging until that test parses as expected (no ERROR nodes).
- When the case parses, the main agent runs:
npx tree-sitter test
This executes the full test suite.
- If further tests fail (including old ones):
- The main agent spawns a new subagent for one of the failing test (using
tree-sitter-parse-testing, providing the specific code and expected tree).
- the Subagent only work on ONE failing test at a time—never batch.
- When the subagent report their test passes, the main agent re-runs the full test suite.
- Repeat this loop, spawning new subagents for any failures, until ALL TESTS PASS.
- Only when the entire suite is green is the feature considered implemented.
Multi-Agent Orchestration Flowchart (pseudo-dot)
digraph tdd_multiagent {
"Add/confirm new failing test"
-> "Spawn subagent (parse-testing, failing case)"
-> "Subagent: Fix until passes"
-> "Agent: Run npx tree-sitter test"
-> "Any failing tests?"
-> "Yes" -> "Spawn subagent (one/test); fixing that case"
-> "Repeat: run all tests"
-> "No" -> "DONE: all tests pass!"
}
Quick Reference Table
| Action | Agent/Skill | Description |
|---|
| Add test | Main agent | Write new corpus case, confirm it fails |
| Debug/fixone | Subagent (tree-sitter-parse-testing) | Fix parsing for that individual test |
| Test suite | Main agent | npx tree-sitter test - check all cases |
| Handle fail | Main agent | Spawn subagents per failing test, loop until green |
Common Mistakes & Red Flags
- Stopping when only the new or one test passes (must loop until suite is fully green)
- Not re-running full suite after every fix
- Fixing errors for multiple failing tests in one subagent (fan-out—each subagent is one-to-one)
- Rationalizing partial fix (“close enough”)—never allowed
- Declaring done when any case still fails
Step 8: Verify Complete
Confirm all tests pass and the new feature parses correctly.
Notes on Code Example Input
- Always ensure the exact code sample for your test is supplied as a command-line argument to
ast_parser.fsx. This eliminates discrepancies between test and AST.
Key Rule (Discipline-Enforcement)
NO CASES LEFT FAILING: Never declare the feature/fix complete until the main agent runs the FULL test suite and every single test is green. ALWAYS use per-case subagents for debugging, never batch fix. Any attempt to declare partial results "done" is a violation of TDD discipline and must be explicitly rejected.
Test Output Handling and Automation
Automated Parsing of Test Output
After running npx tree-sitter test, you MUST automatically and robustly parse all CLI output to:
- Identify each test's name, pass/fail status, and summary results.
- For every \✗ (fail), extract the test name and spawn a dedicated subagent/
action to fix ONLY that failing case (never batch).
- For edge cases (infinite loop, crash, "Killed", incomplete/timed-out output),
halt and alert, do NOT declare pass/fail—require intervention for investigation.
- Never pause to request user input after test execution; always proceed immediately according to test results.
Sample npx tree-sitter test Output
Passing case:
✓ simple string
test/corpus/constants.txt
Failing case:
✗ simple string
test/corpus/constants.txt
--- Expected
+++ Actual
...
(tree diff here)
Summary example:
60/61 tests passed (1 failing)
Infinite loop/truncation warning (edge case CLI output):
Killed
<no summary>
Recommended Output Parsing (Pseudocode)
for each line in output:
if line.startswith('✓ '):
mark_test_passed(test_name)
elif line.startswith('✗ '):
failing_tests.append(test_name)
if 'Killed' in output or 'timed out' in output:
handle_infinite_loop_or_crash()
if summary_line_reports_failures:
for test in failing_tests:
spawn_agent_for_failure(test)
else:
declare_suite_green()
- Use regular expressions or simple prefix checks to robustly parse lines.
- Always cross-check summary (e.g., "X/Y tests passed") for confirmation.
- On any unclear/edge output, escalate immediately; do NOT risk false success/failure.
Edge Case Handling
- If output is truncated, contains 'Killed', 'timed out', or otherwise incomplete, flag as test infrastructure issue.
- Never declare TDD suite complete unless 100% of cases are green and summary confirms zero failures.
Key Rules for Output Automation
- After every test run, parse all output and immediately act (trigger new subagents or declare pass). Never pause.
- No fix or iteration can skip this output step.
Test Format Example
================================================================================
nullable type
================================================================================
let x: int? = 1
--------------------------------------------------------------------------------
(file
(declaration_expression
(function_or_value_defn
(value_declaration_left
(identifier_pattern
(long_identifier_or_op
(identifier))))
(type
(nullable_type
(type)))
(const
(integer)))))
Parser Selection
.fs / .fsx files → use fsharp/ parser
.fsi files → use fsharp_signature/ parser
Key Commands and Workflow Summary
| Command | Description |
|---|
npx tree-sitter test | Run all tests |
cd fsharp && npx tree-sitter generate | Regenerate parser after grammar changes |
echo 'code' | npx tree-sitter parse | Parse code snippet |
echo 'code' | npx tree-sitter parse -d | Debug parse (see tree-sitter-parse-testing) |
Workflow Recap:
- For every failing test: spawn a single subagent to fix that case. Never batch.
- Run all tests after every fix; repeat subagent assignment/fixing as needed.
- You may only declare success when 0 failures remain. Enforce this at every iteration.