diff --git a/java/src/test/java/io/cucumber/testngxmlformatter/MessagesToTestngXmlWriterAcceptanceTest.java b/java/src/test/java/io/cucumber/testngxmlformatter/MessagesToTestngXmlWriterAcceptanceTest.java
index 4d14b38..347f0bf 100644
--- a/java/src/test/java/io/cucumber/testngxmlformatter/MessagesToTestngXmlWriterAcceptanceTest.java
+++ b/java/src/test/java/io/cucumber/testngxmlformatter/MessagesToTestngXmlWriterAcceptanceTest.java
@@ -49,7 +49,7 @@ void test(TestCase testCase) throws IOException {
@ParameterizedTest
@MethodSource("acceptance")
@Disabled
- void updateExpectedXmlReportFiles(TestCase testCase) throws IOException {
+ void updateExpectedFiles(TestCase testCase) throws IOException {
try (OutputStream out = Files.newOutputStream(testCase.expected)) {
writeTestngXmlReport(testCase, out);
}
diff --git a/testdata/package-lock.json b/testdata/package-lock.json
index 2625e20..0fb4b14 100644
--- a/testdata/package-lock.json
+++ b/testdata/package-lock.json
@@ -10,14 +10,14 @@
"hasInstallScript": true,
"license": "ISC",
"devDependencies": {
- "@cucumber/compatibility-kit": "^22.0.0",
+ "@cucumber/compatibility-kit": "^23.0.0",
"shx": "^0.4.0"
}
},
"node_modules/@cucumber/compatibility-kit": {
- "version": "22.0.1",
- "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-22.0.1.tgz",
- "integrity": "sha512-3wvLc+JL+O/zZby5wKthjoOwGgJNIX7DVb2pODMpGx8ZWMGUm/OnlTq5gXDRQgGqQAdtoEhcYfr9eV56HgRdmg==",
+ "version": "23.0.0",
+ "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-23.0.0.tgz",
+ "integrity": "sha512-+XapEOpxPm2c5iOD+eiWgRQLAN6MMal22dOeicm6Z4Vewp1XpEoPaS/G5l0ODMcIxhKi9GrUlyCsQ1Hd84M45w==",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
@@ -565,9 +565,9 @@
},
"dependencies": {
"@cucumber/compatibility-kit": {
- "version": "22.0.1",
- "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-22.0.1.tgz",
- "integrity": "sha512-3wvLc+JL+O/zZby5wKthjoOwGgJNIX7DVb2pODMpGx8ZWMGUm/OnlTq5gXDRQgGqQAdtoEhcYfr9eV56HgRdmg==",
+ "version": "23.0.0",
+ "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-23.0.0.tgz",
+ "integrity": "sha512-+XapEOpxPm2c5iOD+eiWgRQLAN6MMal22dOeicm6Z4Vewp1XpEoPaS/G5l0ODMcIxhKi9GrUlyCsQ1Hd84M45w==",
"dev": true
},
"@nodelib/fs.scandir": {
diff --git a/testdata/package.json b/testdata/package.json
index e49e625..46a8787 100644
--- a/testdata/package.json
+++ b/testdata/package.json
@@ -9,7 +9,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
- "@cucumber/compatibility-kit": "^22.0.0",
+ "@cucumber/compatibility-kit": "^23.0.0",
"shx": "^0.4.0"
}
}
diff --git a/testdata/src/ambiguous.ndjson b/testdata/src/ambiguous.ndjson
index 213af10..4e6b887 100644
--- a/testdata/src/ambiguous.ndjson
+++ b/testdata/src/ambiguous.ndjson
@@ -1,11 +1,11 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Ambiguous steps\n Multiple step definitions that match a pickle step result in an AMBIGUOUS status, since Cucumnber cannot determine\n which one to execute.\n\n Scenario: Multiple step definitions for a step\n Given a step with multiple definitions\n","uri":"samples/ambiguous/ambiguous.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Ambiguous steps","description":" Multiple step definitions that match a pickle step result in an AMBIGUOUS status, since Cucumnber cannot determine\n which one to execute.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"Multiple step definitions for a step","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step with multiple definitions"}],"examples":[]}}]},"comments":[],"uri":"samples/ambiguous/ambiguous.feature"}}
{"pickle":{"id":"3","uri":"samples/ambiguous/ambiguous.feature","astNodeIds":["1"],"tags":[],"name":"Multiple step definitions for a step","language":"en","steps":[{"id":"2","text":"a step with multiple definitions","type":"Context","astNodeIds":["0"]}]}}
-{"stepDefinition":{"id":"4","pattern":{"type":"REGULAR_EXPRESSION","source":"a (.*?) with (.*?)"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":3}}}}
-{"stepDefinition":{"id":"5","pattern":{"type":"REGULAR_EXPRESSION","source":"a step with (.*)"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":7}}}}
+{"stepDefinition":{"id":"4","pattern":{"type":"REGULAR_EXPRESSION","source":"^a (.*?) with (.*?)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":3}}}}
+{"stepDefinition":{"id":"5","pattern":{"type":"REGULAR_EXPRESSION","source":"^a step with (.*)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":7}}}}
{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}}
-{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"step","children":[]}},{"group":{"start":12,"value":"","children":[]}}]},{"stepMatchArguments":[{"group":{"start":12,"value":"multiple definitions","children":[]},"parameterTypeName":""}]}]}],"testRunStartedId":"6"}}
+{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"step","children":[]}},{"group":{"start":12,"value":"multiple definitions","children":[]}}]},{"stepMatchArguments":[{"group":{"start":12,"value":"multiple definitions","children":[]},"parameterTypeName":""}]}]}],"testRunStartedId":"6"}}
{"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}}
{"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"AMBIGUOUS","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
diff --git a/testdata/src/attachments.ndjson b/testdata/src/attachments.ndjson
index 21b015c..8949657 100644
--- a/testdata/src/attachments.ndjson
+++ b/testdata/src/attachments.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Attachments\n It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.\n\n Scenario: Strings can be attached with a media type\n Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.\n\n When the string \"hello\" is attached as \"application/octet-stream\"\n\n Scenario: Log text\n When the string \"hello\" is logged\n\n Scenario: Log ANSI coloured text\n When text with ANSI escapes is logged\n\n Scenario: Log JSON\n When the following string is attached as \"application/json\":\n ```\n {\"message\": \"The big question\", \"foo\": \"bar\"}\n ```\n\n Scenario: Byte arrays are base64-encoded regardless of media type\n When an array with 10 bytes is attached as \"text/plain\"\n\n Scenario: Attaching PDFs with a different filename\n When a PDF document is attached and renamed\n\n Scenario: Attaching URIs\n When a link to \"https://cucumber.io\" is attached\n","uri":"samples/attachments/attachments.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Attachments","description":" It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Strings can be attached with a media type","description":" Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.","steps":[{"id":"0","location":{"line":15,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is attached as \"application/octet-stream\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Log text","description":"","steps":[{"id":"2","location":{"line":18,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is logged"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":20,"column":3},"keyword":"Scenario","name":"Log ANSI coloured text","description":"","steps":[{"id":"4","location":{"line":21,"column":5},"keyword":"When ","keywordType":"Action","text":"text with ANSI escapes is logged"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"Log JSON","description":"","steps":[{"id":"6","location":{"line":24,"column":6},"keyword":"When ","keywordType":"Action","text":"the following string is attached as \"application/json\":","docString":{"location":{"line":25,"column":8},"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}","delimiter":"```"}}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":29,"column":3},"keyword":"Scenario","name":"Byte arrays are base64-encoded regardless of media type","description":"","steps":[{"id":"8","location":{"line":30,"column":5},"keyword":"When ","keywordType":"Action","text":"an array with 10 bytes is attached as \"text/plain\""}],"examples":[]}},{"scenario":{"id":"11","tags":[],"location":{"line":32,"column":3},"keyword":"Scenario","name":"Attaching PDFs with a different filename","description":"","steps":[{"id":"10","location":{"line":33,"column":5},"keyword":"When ","keywordType":"Action","text":"a PDF document is attached and renamed"}],"examples":[]}},{"scenario":{"id":"13","tags":[],"location":{"line":35,"column":3},"keyword":"Scenario","name":"Attaching URIs","description":"","steps":[{"id":"12","location":{"line":36,"column":5},"keyword":"When ","keywordType":"Action","text":"a link to \"https://cucumber.io\" is attached"}],"examples":[]}}]},"comments":[],"uri":"samples/attachments/attachments.feature"}}
{"pickle":{"id":"15","uri":"samples/attachments/attachments.feature","astNodeIds":["1"],"tags":[],"name":"Strings can be attached with a media type","language":"en","steps":[{"id":"14","text":"the string \"hello\" is attached as \"application/octet-stream\"","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/backgrounds.ndjson b/testdata/src/backgrounds.ndjson
index 81414e3..311b3ae 100644
--- a/testdata/src/backgrounds.ndjson
+++ b/testdata/src/backgrounds.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Backgrounds\n Though not recommended, Backgrounds can be used to share context steps between Scenarios. The Background steps\n are prepended to the steps in each Scenario when they are compiled to Pickles. Only one Background at the Feature\n level is supported.\n\n Background:\n Given an order for \"eggs\"\n And an order for \"milk\"\n And an order for \"bread\"\n\n Scenario: one scenario\n When an action\n Then an outcome\n\n Scenario: another scenario\n When an action\n Then an outcome","uri":"samples/backgrounds/backgrounds.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Backgrounds","description":" Though not recommended, Backgrounds can be used to share context steps between Scenarios. The Background steps\n are prepended to the steps in each Scenario when they are compiled to Pickles. Only one Background at the Feature\n level is supported.","children":[{"background":{"id":"3","location":{"line":6,"column":3},"keyword":"Background","name":"","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""},{"id":"1","location":{"line":8,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"milk\""},{"id":"2","location":{"line":9,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"bread\""}]}},{"scenario":{"id":"6","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"one scenario","description":"","steps":[{"id":"4","location":{"line":12,"column":5},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"5","location":{"line":13,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":15,"column":3},"keyword":"Scenario","name":"another scenario","description":"","steps":[{"id":"7","location":{"line":16,"column":5},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"8","location":{"line":17,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}}]},"comments":[],"uri":"samples/backgrounds/backgrounds.feature"}}
{"pickle":{"id":"15","uri":"samples/backgrounds/backgrounds.feature","astNodeIds":["6"],"tags":[],"name":"one scenario","language":"en","steps":[{"id":"10","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]},{"id":"11","text":"an order for \"milk\"","type":"Context","astNodeIds":["1"]},{"id":"12","text":"an order for \"bread\"","type":"Context","astNodeIds":["2"]},{"id":"13","text":"an action","type":"Action","astNodeIds":["4"]},{"id":"14","text":"an outcome","type":"Outcome","astNodeIds":["5"]}]}}
diff --git a/testdata/src/cdata.ndjson b/testdata/src/cdata.ndjson
index 58ed605..5c2d6d2 100644
--- a/testdata/src/cdata.ndjson
+++ b/testdata/src/cdata.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: cdata\n Cucumber xml formatters should be able to handle xml cdata elements.\n\n Scenario: cdata\n Given I have 42 in my belly\n","uri":"samples/cdata/cdata.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"cdata","description":" Cucumber xml formatters should be able to handle xml cdata elements.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"cdata","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"Given ","keywordType":"Context","text":"I have 42 in my belly"}],"examples":[]}}]},"comments":[],"uri":"samples/cdata/cdata.feature"}}
{"pickle":{"id":"3","uri":"samples/cdata/cdata.feature","astNodeIds":["1"],"tags":[],"name":"cdata","language":"en","steps":[{"id":"2","text":"I have 42 in my belly","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/data-tables.ndjson b/testdata/src/data-tables.ndjson
index 598a042..1b43475 100644
--- a/testdata/src/data-tables.ndjson
+++ b/testdata/src/data-tables.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Data Tables\n Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.\n\n Scenario: transposed table\n When the following table is transposed:\n | a | b |\n | 1 | 2 |\n Then it should be:\n | a | 1 |\n | b | 2 |\n","uri":"samples/data-tables/data-tables.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Data Tables","description":" Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.","children":[{"scenario":{"id":"6","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"transposed table","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"the following table is transposed:","dataTable":{"location":{"line":9,"column":7},"rows":[{"id":"0","location":{"line":9,"column":7},"cells":[{"location":{"line":9,"column":9},"value":"a"},{"location":{"line":9,"column":13},"value":"b"}]},{"id":"1","location":{"line":10,"column":7},"cells":[{"location":{"line":10,"column":9},"value":"1"},{"location":{"line":10,"column":13},"value":"2"}]}]}},{"id":"5","location":{"line":11,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"it should be:","dataTable":{"location":{"line":12,"column":7},"rows":[{"id":"3","location":{"line":12,"column":7},"cells":[{"location":{"line":12,"column":9},"value":"a"},{"location":{"line":12,"column":13},"value":"1"}]},{"id":"4","location":{"line":13,"column":7},"cells":[{"location":{"line":13,"column":9},"value":"b"},{"location":{"line":13,"column":13},"value":"2"}]}]}}],"examples":[]}}]},"comments":[],"uri":"samples/data-tables/data-tables.feature"}}
{"pickle":{"id":"9","uri":"samples/data-tables/data-tables.feature","astNodeIds":["6"],"tags":[],"name":"transposed table","language":"en","steps":[{"id":"7","text":"the following table is transposed:","type":"Action","argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"b"}]},{"cells":[{"value":"1"},{"value":"2"}]}]}},"astNodeIds":["2"]},{"id":"8","text":"it should be:","type":"Outcome","argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"1"}]},{"cells":[{"value":"b"},{"value":"2"}]}]}},"astNodeIds":["5"]}]}}
diff --git a/testdata/src/doc-strings.ndjson b/testdata/src/doc-strings.ndjson
index fde3d5b..f529b32 100644
--- a/testdata/src/doc-strings.ndjson
+++ b/testdata/src/doc-strings.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Doc strings\n Doc strings are a way to supply long, sometimes multi-line, text to a step. They are passed as the last argument\n to the step definition.\n\n Scenario: a doc string with standard delimiter\n Three double quotes above and below are the standard delimiter for doc strings.\n\n Given a doc string:\n \"\"\"\n Here is some content\n And some more on another line\n \"\"\"\n\n Scenario: a doc string with backticks delimiter\n Backticks can also be used, like Markdown, but are less widely supported by editors.\n\n Given a doc string:\n ```\n Here is some content\n And some more on another line\n ```\n\n Scenario: a doc string with media type\n The media type can be optionally specified too, following the opening delimiter.\n\n Given a doc string:\n \"\"\"application/json\n {\n \"foo\": \"bar\"\n }\n \"\"\"","uri":"samples/doc-strings/doc-strings.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Doc strings","description":" Doc strings are a way to supply long, sometimes multi-line, text to a step. They are passed as the last argument\n to the step definition.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"a doc string with standard delimiter","description":" Three double quotes above and below are the standard delimiter for doc strings.","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"a doc string:","docString":{"location":{"line":9,"column":5},"content":"Here is some content\nAnd some more on another line","delimiter":"\"\"\""}}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":14,"column":3},"keyword":"Scenario","name":"a doc string with backticks delimiter","description":" Backticks can also be used, like Markdown, but are less widely supported by editors.","steps":[{"id":"2","location":{"line":17,"column":5},"keyword":"Given ","keywordType":"Context","text":"a doc string:","docString":{"location":{"line":18,"column":5},"content":"Here is some content\nAnd some more on another line","delimiter":"```"}}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"a doc string with media type","description":" The media type can be optionally specified too, following the opening delimiter.","steps":[{"id":"4","location":{"line":26,"column":5},"keyword":"Given ","keywordType":"Context","text":"a doc string:","docString":{"location":{"line":27,"column":5},"content":"{\n \"foo\": \"bar\"\n}","delimiter":"\"\"\"","mediaType":"application/json"}}],"examples":[]}}]},"comments":[],"uri":"samples/doc-strings/doc-strings.feature"}}
{"pickle":{"id":"7","uri":"samples/doc-strings/doc-strings.feature","astNodeIds":["1"],"tags":[],"name":"a doc string with standard delimiter","language":"en","steps":[{"id":"6","text":"a doc string:","type":"Context","argument":{"docString":{"content":"Here is some content\nAnd some more on another line"}},"astNodeIds":["0"]}]}}
diff --git a/testdata/src/empty.ndjson b/testdata/src/empty.ndjson
index 9e2b6bd..7aefb40 100644
--- a/testdata/src/empty.ndjson
+++ b/testdata/src/empty.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","uri":"samples/empty/empty.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Empty Scenarios","description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","children":[{"scenario":{"id":"0","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"Blank Scenario","description":"","steps":[],"examples":[]}}]},"comments":[],"uri":"samples/empty/empty.feature"}}
{"pickle":{"id":"1","uri":"samples/empty/empty.feature","astNodeIds":["0"],"tags":[],"name":"Blank Scenario","language":"en","steps":[]}}
diff --git a/testdata/src/examples-tables-attachment.ndjson b/testdata/src/examples-tables-attachment.ndjson
index 77ad2df..ad29127 100644
--- a/testdata/src/examples-tables-attachment.ndjson
+++ b/testdata/src/examples-tables-attachment.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Examples Tables - With attachments\n It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Scenario Outline: Attaching images in an examples table\n When a image is attached\n\n Examples:\n | type |\n | JPEG |\n | PNG |\n","uri":"samples/examples-tables-attachment/examples-tables-attachment.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables - With attachments","description":" It is sometimes useful to take a screenshot while a scenario runs or capture some logs.","children":[{"scenario":{"id":"5","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario Outline","name":"Attaching images in an examples table","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a image is attached"}],"examples":[{"id":"4","tags":[],"location":{"line":7,"column":5},"keyword":"Examples","name":"","description":"","tableHeader":{"id":"1","location":{"line":8,"column":7},"cells":[{"location":{"line":8,"column":9},"value":"type"}]},"tableBody":[{"id":"2","location":{"line":9,"column":7},"cells":[{"location":{"line":9,"column":9},"value":"JPEG"}]},{"id":"3","location":{"line":10,"column":7},"cells":[{"location":{"line":10,"column":9},"value":"PNG"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables-attachment/examples-tables-attachment.feature"}}
{"pickle":{"id":"7","uri":"samples/examples-tables-attachment/examples-tables-attachment.feature","astNodeIds":["5","2"],"name":"Attaching images in an examples table","language":"en","steps":[{"id":"6","text":"a JPEG image is attached","type":"Action","astNodeIds":["0","2"]}],"tags":[]}}
diff --git a/testdata/src/examples-tables-undefined.ndjson b/testdata/src/examples-tables-undefined.ndjson
new file mode 100644
index 0000000..df0e7b2
--- /dev/null
+++ b/testdata/src/examples-tables-undefined.ndjson
@@ -0,0 +1,41 @@
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Examples Tables - With Undefined Steps\n The replacement pattern used in scenario outlines does not influence how steps\n are matched. The replacement pattern is replaced, and step definitions are\n matched against that text. Because of that the following results in one\n undefined step for each example and a suggested snippet to implement it. \n\n Scenario Outline: Eating cucumbers\n Given there are cucumbers\n When I eat cucumbers\n Then I should have cucumbers\n\n @undefined\n Examples: These are undefined because the value is not an {int}\n | start | eat | left |\n | pear | 1 | 12 |\n | 12 | banana | 12 |\n | 0 | 1 | apple |\n","uri":"samples/examples-tables-undefined/examples-undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables - With Undefined Steps","description":" The replacement pattern used in scenario outlines does not influence how steps\n are matched. The replacement pattern is replaced, and step definitions are\n matched against that text. Because of that the following results in one\n undefined step for each example and a suggested snippet to implement it. ","children":[{"scenario":{"id":"9","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are cucumbers"},{"id":"1","location":{"line":9,"column":5},"keyword":"When ","keywordType":"Action","text":"I eat cucumbers"},{"id":"2","location":{"line":10,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"I should have cucumbers"}],"examples":[{"id":"8","tags":[{"location":{"line":12,"column":5},"name":"@undefined","id":"7"}],"location":{"line":13,"column":5},"keyword":"Examples","name":"These are undefined because the value is not an {int}","description":"","tableHeader":{"id":"3","location":{"line":14,"column":7},"cells":[{"location":{"line":14,"column":9},"value":"start"},{"location":{"line":14,"column":17},"value":"eat"},{"location":{"line":14,"column":26},"value":"left"}]},"tableBody":[{"id":"4","location":{"line":15,"column":7},"cells":[{"location":{"line":15,"column":9},"value":"pear"},{"location":{"line":15,"column":17},"value":"1"},{"location":{"line":15,"column":26},"value":"12"}]},{"id":"5","location":{"line":16,"column":7},"cells":[{"location":{"line":16,"column":9},"value":"12"},{"location":{"line":16,"column":17},"value":"banana"},{"location":{"line":16,"column":26},"value":"12"}]},{"id":"6","location":{"line":17,"column":7},"cells":[{"location":{"line":17,"column":9},"value":"0"},{"location":{"line":17,"column":17},"value":"1"},{"location":{"line":17,"column":26},"value":"apple"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables-undefined/examples-undefined.feature"}}
+{"pickle":{"id":"13","uri":"samples/examples-tables-undefined/examples-undefined.feature","astNodeIds":["9","4"],"name":"Eating cucumbers","language":"en","steps":[{"id":"10","text":"there are pear cucumbers","type":"Context","astNodeIds":["0","4"]},{"id":"11","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","4"]},{"id":"12","text":"I should have 12 cucumbers","type":"Outcome","astNodeIds":["2","4"]}],"tags":[{"name":"@undefined","astNodeId":"7"}]}}
+{"pickle":{"id":"17","uri":"samples/examples-tables-undefined/examples-undefined.feature","astNodeIds":["9","5"],"name":"Eating cucumbers","language":"en","steps":[{"id":"14","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","5"]},{"id":"15","text":"I eat banana cucumbers","type":"Action","astNodeIds":["1","5"]},{"id":"16","text":"I should have 12 cucumbers","type":"Outcome","astNodeIds":["2","5"]}],"tags":[{"name":"@undefined","astNodeId":"7"}]}}
+{"pickle":{"id":"21","uri":"samples/examples-tables-undefined/examples-undefined.feature","astNodeIds":["9","6"],"name":"Eating cucumbers","language":"en","steps":[{"id":"18","text":"there are 0 cucumbers","type":"Context","astNodeIds":["0","6"]},{"id":"19","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","6"]},{"id":"20","text":"I should have apple cucumbers","type":"Outcome","astNodeIds":["2","6"]}],"tags":[{"name":"@undefined","astNodeId":"7"}]}}
+{"stepDefinition":{"id":"22","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables-undefined/examples-tables-undefined.ts","location":{"line":4}}}}
+{"stepDefinition":{"id":"23","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables-undefined/examples-tables-undefined.ts","location":{"line":8}}}}
+{"stepDefinition":{"id":"24","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I should have {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables-undefined/examples-tables-undefined.ts","location":{"line":12}}}}
+{"testRunStarted":{"id":"25","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"26","pickleId":"13","testSteps":[{"id":"27","pickleStepId":"10","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"28","pickleStepId":"11","stepDefinitionIds":["23"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"29","pickleStepId":"12","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"12","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"25"}}
+{"testCase":{"id":"30","pickleId":"17","testSteps":[{"id":"31","pickleStepId":"14","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"32","pickleStepId":"15","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"33","pickleStepId":"16","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"12","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"25"}}
+{"testCase":{"id":"34","pickleId":"21","testSteps":[{"id":"35","pickleStepId":"18","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"36","pickleStepId":"19","stepDefinitionIds":["23"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"37","pickleStepId":"20","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"25"}}
+{"testCaseStarted":{"id":"38","testCaseId":"26","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"38","testStepId":"27","timestamp":{"seconds":0,"nanos":2000000}}}
+{"suggestion":{"id":"39","pickleStepId":"10","snippets":[{"language":"typescript","code":"Given(\"there are pear cucumbers\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"38","testStepId":"27","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testStepStarted":{"testCaseStartedId":"38","testStepId":"28","timestamp":{"seconds":0,"nanos":4000000}}}
+{"testStepFinished":{"testCaseStartedId":"38","testStepId":"28","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":5000000}}}
+{"testStepStarted":{"testCaseStartedId":"38","testStepId":"29","timestamp":{"seconds":0,"nanos":6000000}}}
+{"testStepFinished":{"testCaseStartedId":"38","testStepId":"29","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":7000000}}}
+{"testCaseFinished":{"testCaseStartedId":"38","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"40","testCaseId":"30","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"40","testStepId":"31","timestamp":{"seconds":0,"nanos":10000000}}}
+{"testStepFinished":{"testCaseStartedId":"40","testStepId":"31","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
+{"testStepStarted":{"testCaseStartedId":"40","testStepId":"32","timestamp":{"seconds":0,"nanos":12000000}}}
+{"suggestion":{"id":"41","pickleStepId":"15","snippets":[{"language":"typescript","code":"When(\"I eat banana cucumbers\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"40","testStepId":"32","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":13000000}}}
+{"testStepStarted":{"testCaseStartedId":"40","testStepId":"33","timestamp":{"seconds":0,"nanos":14000000}}}
+{"testStepFinished":{"testCaseStartedId":"40","testStepId":"33","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}}
+{"testCaseFinished":{"testCaseStartedId":"40","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"42","testCaseId":"34","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"42","testStepId":"35","timestamp":{"seconds":0,"nanos":18000000}}}
+{"testStepFinished":{"testCaseStartedId":"42","testStepId":"35","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
+{"testStepStarted":{"testCaseStartedId":"42","testStepId":"36","timestamp":{"seconds":0,"nanos":20000000}}}
+{"testStepFinished":{"testCaseStartedId":"42","testStepId":"36","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}}
+{"testStepStarted":{"testCaseStartedId":"42","testStepId":"37","timestamp":{"seconds":0,"nanos":22000000}}}
+{"suggestion":{"id":"43","pickleStepId":"20","snippets":[{"language":"typescript","code":"Then(\"I should have apple cucumbers\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"42","testStepId":"37","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":23000000}}}
+{"testCaseFinished":{"testCaseStartedId":"42","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"25","timestamp":{"seconds":0,"nanos":25000000},"success":false}}
diff --git a/testdata/src/examples-tables-undefined.xml b/testdata/src/examples-tables-undefined.xml
new file mode 100644
index 0000000..e2d7dc3
--- /dev/null
+++ b/testdata/src/examples-tables-undefined.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/src/examples-tables.ndjson b/testdata/src/examples-tables.ndjson
index 7098e4c..3207ee0 100644
--- a/testdata/src/examples-tables.ndjson
+++ b/testdata/src/examples-tables.ndjson
@@ -1,100 +1,80 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
-{"source":{"data":"Feature: Examples Tables\n Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table\n underneath a Scenario, and use in the Scenario which match the\n table headers.\n\n The Scenario Outline name can also be parameterized. The name of the resulting\n pickle will have the replaced with the value from the examples\n table.\n\n Scenario Outline: Eating cucumbers\n Given there are cucumbers\n When I eat cucumbers\n Then I should have cucumbers\n\n @passing\n Examples: These are passing\n | start | eat | left |\n | 12 | 5 | 7 |\n | 20 | 5 | 15 |\n\n @failing\n Examples: These are failing\n | start | eat | left |\n | 12 | 20 | 0 |\n | 0 | 1 | 0 |\n\n @undefined\n Examples: These are undefined because the value is not an {int}\n | start | eat | left |\n | 12 | banana | 12 |\n | 0 | 1 | apple |\n\n Scenario Outline: Eating cucumbers with friends\n Given there are friends\n And there are cucumbers\n Then each person can eat cucumbers\n\n Examples:\n | friends | start | share |\n | 11 | 12 | 1 |\n | 1 | 4 | 2 |\n | 0 | 4 | 4 |\n","uri":"samples/examples-tables/examples-tables.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
-{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables","description":" Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table\n underneath a Scenario, and use in the Scenario which match the\n table headers.\n\n The Scenario Outline name can also be parameterized. The name of the resulting\n pickle will have the replaced with the value from the examples\n table.","children":[{"scenario":{"id":"18","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers","description":"","steps":[{"id":"0","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are cucumbers"},{"id":"1","location":{"line":13,"column":5},"keyword":"When ","keywordType":"Action","text":"I eat cucumbers"},{"id":"2","location":{"line":14,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"I should have cucumbers"}],"examples":[{"id":"7","tags":[{"location":{"line":16,"column":5},"name":"@passing","id":"6"}],"location":{"line":17,"column":5},"keyword":"Examples","name":"These are passing","description":"","tableHeader":{"id":"3","location":{"line":18,"column":7},"cells":[{"location":{"line":18,"column":9},"value":"start"},{"location":{"line":18,"column":17},"value":"eat"},{"location":{"line":18,"column":23},"value":"left"}]},"tableBody":[{"id":"4","location":{"line":19,"column":7},"cells":[{"location":{"line":19,"column":12},"value":"12"},{"location":{"line":19,"column":19},"value":"5"},{"location":{"line":19,"column":26},"value":"7"}]},{"id":"5","location":{"line":20,"column":7},"cells":[{"location":{"line":20,"column":12},"value":"20"},{"location":{"line":20,"column":19},"value":"5"},{"location":{"line":20,"column":25},"value":"15"}]}]},{"id":"12","tags":[{"location":{"line":22,"column":5},"name":"@failing","id":"11"}],"location":{"line":23,"column":5},"keyword":"Examples","name":"These are failing","description":"","tableHeader":{"id":"8","location":{"line":24,"column":7},"cells":[{"location":{"line":24,"column":9},"value":"start"},{"location":{"line":24,"column":17},"value":"eat"},{"location":{"line":24,"column":23},"value":"left"}]},"tableBody":[{"id":"9","location":{"line":25,"column":7},"cells":[{"location":{"line":25,"column":12},"value":"12"},{"location":{"line":25,"column":18},"value":"20"},{"location":{"line":25,"column":26},"value":"0"}]},{"id":"10","location":{"line":26,"column":7},"cells":[{"location":{"line":26,"column":13},"value":"0"},{"location":{"line":26,"column":19},"value":"1"},{"location":{"line":26,"column":26},"value":"0"}]}]},{"id":"17","tags":[{"location":{"line":28,"column":5},"name":"@undefined","id":"16"}],"location":{"line":29,"column":5},"keyword":"Examples","name":"These are undefined because the value is not an {int}","description":"","tableHeader":{"id":"13","location":{"line":30,"column":7},"cells":[{"location":{"line":30,"column":9},"value":"start"},{"location":{"line":30,"column":17},"value":"eat"},{"location":{"line":30,"column":26},"value":"left"}]},"tableBody":[{"id":"14","location":{"line":31,"column":7},"cells":[{"location":{"line":31,"column":12},"value":"12"},{"location":{"line":31,"column":17},"value":"banana"},{"location":{"line":31,"column":29},"value":"12"}]},{"id":"15","location":{"line":32,"column":7},"cells":[{"location":{"line":32,"column":13},"value":"0"},{"location":{"line":32,"column":22},"value":"1"},{"location":{"line":32,"column":26},"value":"apple"}]}]}]}},{"scenario":{"id":"27","tags":[],"location":{"line":34,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers with friends","description":"","steps":[{"id":"19","location":{"line":35,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are friends"},{"id":"20","location":{"line":36,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"there are cucumbers"},{"id":"21","location":{"line":37,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"each person can eat cucumbers"}],"examples":[{"id":"26","tags":[],"location":{"line":39,"column":5},"keyword":"Examples","name":"","description":"","tableHeader":{"id":"22","location":{"line":40,"column":7},"cells":[{"location":{"line":40,"column":9},"value":"friends"},{"location":{"line":40,"column":19},"value":"start"},{"location":{"line":40,"column":27},"value":"share"}]},"tableBody":[{"id":"23","location":{"line":41,"column":7},"cells":[{"location":{"line":41,"column":14},"value":"11"},{"location":{"line":41,"column":22},"value":"12"},{"location":{"line":41,"column":31},"value":"1"}]},{"id":"24","location":{"line":42,"column":7},"cells":[{"location":{"line":42,"column":15},"value":"1"},{"location":{"line":42,"column":23},"value":"4"},{"location":{"line":42,"column":31},"value":"2"}]},{"id":"25","location":{"line":43,"column":7},"cells":[{"location":{"line":43,"column":15},"value":"0"},{"location":{"line":43,"column":23},"value":"4"},{"location":{"line":43,"column":31},"value":"4"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables/examples-tables.feature"}}
-{"pickle":{"id":"31","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["18","4"],"name":"Eating cucumbers","language":"en","steps":[{"id":"28","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","4"]},{"id":"29","text":"I eat 5 cucumbers","type":"Action","astNodeIds":["1","4"]},{"id":"30","text":"I should have 7 cucumbers","type":"Outcome","astNodeIds":["2","4"]}],"tags":[{"name":"@passing","astNodeId":"6"}]}}
-{"pickle":{"id":"35","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["18","5"],"name":"Eating cucumbers","language":"en","steps":[{"id":"32","text":"there are 20 cucumbers","type":"Context","astNodeIds":["0","5"]},{"id":"33","text":"I eat 5 cucumbers","type":"Action","astNodeIds":["1","5"]},{"id":"34","text":"I should have 15 cucumbers","type":"Outcome","astNodeIds":["2","5"]}],"tags":[{"name":"@passing","astNodeId":"6"}]}}
-{"pickle":{"id":"39","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["18","9"],"name":"Eating cucumbers","language":"en","steps":[{"id":"36","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","9"]},{"id":"37","text":"I eat 20 cucumbers","type":"Action","astNodeIds":["1","9"]},{"id":"38","text":"I should have 0 cucumbers","type":"Outcome","astNodeIds":["2","9"]}],"tags":[{"name":"@failing","astNodeId":"11"}]}}
-{"pickle":{"id":"43","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["18","10"],"name":"Eating cucumbers","language":"en","steps":[{"id":"40","text":"there are 0 cucumbers","type":"Context","astNodeIds":["0","10"]},{"id":"41","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","10"]},{"id":"42","text":"I should have 0 cucumbers","type":"Outcome","astNodeIds":["2","10"]}],"tags":[{"name":"@failing","astNodeId":"11"}]}}
-{"pickle":{"id":"47","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["18","14"],"name":"Eating cucumbers","language":"en","steps":[{"id":"44","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","14"]},{"id":"45","text":"I eat banana cucumbers","type":"Action","astNodeIds":["1","14"]},{"id":"46","text":"I should have 12 cucumbers","type":"Outcome","astNodeIds":["2","14"]}],"tags":[{"name":"@undefined","astNodeId":"16"}]}}
-{"pickle":{"id":"51","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["18","15"],"name":"Eating cucumbers","language":"en","steps":[{"id":"48","text":"there are 0 cucumbers","type":"Context","astNodeIds":["0","15"]},{"id":"49","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","15"]},{"id":"50","text":"I should have apple cucumbers","type":"Outcome","astNodeIds":["2","15"]}],"tags":[{"name":"@undefined","astNodeId":"16"}]}}
-{"pickle":{"id":"55","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["27","23"],"name":"Eating cucumbers with 11 friends","language":"en","steps":[{"id":"52","text":"there are 11 friends","type":"Context","astNodeIds":["19","23"]},{"id":"53","text":"there are 12 cucumbers","type":"Context","astNodeIds":["20","23"]},{"id":"54","text":"each person can eat 1 cucumbers","type":"Outcome","astNodeIds":["21","23"]}],"tags":[]}}
-{"pickle":{"id":"59","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["27","24"],"name":"Eating cucumbers with 1 friends","language":"en","steps":[{"id":"56","text":"there are 1 friends","type":"Context","astNodeIds":["19","24"]},{"id":"57","text":"there are 4 cucumbers","type":"Context","astNodeIds":["20","24"]},{"id":"58","text":"each person can eat 2 cucumbers","type":"Outcome","astNodeIds":["21","24"]}],"tags":[]}}
-{"pickle":{"id":"63","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["27","25"],"name":"Eating cucumbers with 0 friends","language":"en","steps":[{"id":"60","text":"there are 0 friends","type":"Context","astNodeIds":["19","25"]},{"id":"61","text":"there are 4 cucumbers","type":"Context","astNodeIds":["20","25"]},{"id":"62","text":"each person can eat 4 cucumbers","type":"Outcome","astNodeIds":["21","25"]}],"tags":[]}}
-{"stepDefinition":{"id":"64","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":4}}}}
-{"stepDefinition":{"id":"65","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} friends"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":8}}}}
-{"stepDefinition":{"id":"66","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":12}}}}
-{"stepDefinition":{"id":"67","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I should have {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":16}}}}
-{"stepDefinition":{"id":"68","pattern":{"type":"CUCUMBER_EXPRESSION","source":"each person can eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":20}}}}
-{"testRunStarted":{"id":"69","timestamp":{"seconds":0,"nanos":0}}}
-{"testCase":{"id":"70","pickleId":"31","testSteps":[{"id":"71","pickleStepId":"28","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"72","pickleStepId":"29","stepDefinitionIds":["66"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"5","children":[]},"parameterTypeName":"int"}]}]},{"id":"73","pickleStepId":"30","stepDefinitionIds":["67"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"7","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"74","pickleId":"35","testSteps":[{"id":"75","pickleStepId":"32","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"20","children":[]},"parameterTypeName":"int"}]}]},{"id":"76","pickleStepId":"33","stepDefinitionIds":["66"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"5","children":[]},"parameterTypeName":"int"}]}]},{"id":"77","pickleStepId":"34","stepDefinitionIds":["67"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"15","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"78","pickleId":"39","testSteps":[{"id":"79","pickleStepId":"36","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"80","pickleStepId":"37","stepDefinitionIds":["66"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"20","children":[]},"parameterTypeName":"int"}]}]},{"id":"81","pickleStepId":"38","stepDefinitionIds":["67"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"0","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"82","pickleId":"43","testSteps":[{"id":"83","pickleStepId":"40","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"84","pickleStepId":"41","stepDefinitionIds":["66"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"85","pickleStepId":"42","stepDefinitionIds":["67"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"0","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"86","pickleId":"47","testSteps":[{"id":"87","pickleStepId":"44","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"88","pickleStepId":"45","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"89","pickleStepId":"46","stepDefinitionIds":["67"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"12","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"90","pickleId":"51","testSteps":[{"id":"91","pickleStepId":"48","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"92","pickleStepId":"49","stepDefinitionIds":["66"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"93","pickleStepId":"50","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"94","pickleId":"55","testSteps":[{"id":"95","pickleStepId":"52","stepDefinitionIds":["65"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"11","children":[]},"parameterTypeName":"int"}]}]},{"id":"96","pickleStepId":"53","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"97","pickleStepId":"54","stepDefinitionIds":["68"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"1","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"98","pickleId":"59","testSteps":[{"id":"99","pickleStepId":"56","stepDefinitionIds":["65"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"100","pickleStepId":"57","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"4","children":[]},"parameterTypeName":"int"}]}]},{"id":"101","pickleStepId":"58","stepDefinitionIds":["68"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"2","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCase":{"id":"102","pickleId":"63","testSteps":[{"id":"103","pickleStepId":"60","stepDefinitionIds":["65"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"104","pickleStepId":"61","stepDefinitionIds":["64"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"4","children":[]},"parameterTypeName":"int"}]}]},{"id":"105","pickleStepId":"62","stepDefinitionIds":["68"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"4","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"69"}}
-{"testCaseStarted":{"id":"106","testCaseId":"70","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"106","testStepId":"71","timestamp":{"seconds":0,"nanos":2000000}}}
-{"testStepFinished":{"testCaseStartedId":"106","testStepId":"71","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
-{"testStepStarted":{"testCaseStartedId":"106","testStepId":"72","timestamp":{"seconds":0,"nanos":4000000}}}
-{"testStepFinished":{"testCaseStartedId":"106","testStepId":"72","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}}
-{"testStepStarted":{"testCaseStartedId":"106","testStepId":"73","timestamp":{"seconds":0,"nanos":6000000}}}
-{"testStepFinished":{"testCaseStartedId":"106","testStepId":"73","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
-{"testCaseFinished":{"testCaseStartedId":"106","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"107","testCaseId":"74","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"107","testStepId":"75","timestamp":{"seconds":0,"nanos":10000000}}}
-{"testStepFinished":{"testCaseStartedId":"107","testStepId":"75","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
-{"testStepStarted":{"testCaseStartedId":"107","testStepId":"76","timestamp":{"seconds":0,"nanos":12000000}}}
-{"testStepFinished":{"testCaseStartedId":"107","testStepId":"76","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}}
-{"testStepStarted":{"testCaseStartedId":"107","testStepId":"77","timestamp":{"seconds":0,"nanos":14000000}}}
-{"testStepFinished":{"testCaseStartedId":"107","testStepId":"77","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}}
-{"testCaseFinished":{"testCaseStartedId":"107","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"108","testCaseId":"78","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"108","testStepId":"79","timestamp":{"seconds":0,"nanos":18000000}}}
-{"testStepFinished":{"testCaseStartedId":"108","testStepId":"79","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
-{"testStepStarted":{"testCaseStartedId":"108","testStepId":"80","timestamp":{"seconds":0,"nanos":20000000}}}
-{"testStepFinished":{"testCaseStartedId":"108","testStepId":"80","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}}
-{"testStepStarted":{"testCaseStartedId":"108","testStepId":"81","timestamp":{"seconds":0,"nanos":22000000}}}
-{"testStepFinished":{"testCaseStartedId":"108","testStepId":"81","testStepResult":{"message":"Expected values to be strictly equal:\n\n-8 !== 0\n","exception":{"type":"AssertionError","message":"Expected values to be strictly equal:\n\n-8 !== 0\n","stackTrace":"AssertionError: Expected values to be strictly equal:\n\n-8 !== 0\n\nsamples/examples-tables/examples-tables.feature:14"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}}
-{"testCaseFinished":{"testCaseStartedId":"108","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"109","testCaseId":"82","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"109","testStepId":"83","timestamp":{"seconds":0,"nanos":26000000}}}
-{"testStepFinished":{"testCaseStartedId":"109","testStepId":"83","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}}
-{"testStepStarted":{"testCaseStartedId":"109","testStepId":"84","timestamp":{"seconds":0,"nanos":28000000}}}
-{"testStepFinished":{"testCaseStartedId":"109","testStepId":"84","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}}
-{"testStepStarted":{"testCaseStartedId":"109","testStepId":"85","timestamp":{"seconds":0,"nanos":30000000}}}
-{"testStepFinished":{"testCaseStartedId":"109","testStepId":"85","testStepResult":{"message":"Expected values to be strictly equal:\n\n-1 !== 0\n","exception":{"type":"AssertionError","message":"Expected values to be strictly equal:\n\n-1 !== 0\n","stackTrace":"AssertionError: Expected values to be strictly equal:\n\n-1 !== 0\n\nsamples/examples-tables/examples-tables.feature:14"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}}
-{"testCaseFinished":{"testCaseStartedId":"109","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"110","testCaseId":"86","timestamp":{"seconds":0,"nanos":33000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"110","testStepId":"87","timestamp":{"seconds":0,"nanos":34000000}}}
-{"testStepFinished":{"testCaseStartedId":"110","testStepId":"87","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}}
-{"testStepStarted":{"testCaseStartedId":"110","testStepId":"88","timestamp":{"seconds":0,"nanos":36000000}}}
-{"testStepFinished":{"testCaseStartedId":"110","testStepId":"88","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":37000000}}}
-{"testStepStarted":{"testCaseStartedId":"110","testStepId":"89","timestamp":{"seconds":0,"nanos":38000000}}}
-{"testStepFinished":{"testCaseStartedId":"110","testStepId":"89","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":39000000}}}
-{"testCaseFinished":{"testCaseStartedId":"110","timestamp":{"seconds":0,"nanos":40000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"111","testCaseId":"90","timestamp":{"seconds":0,"nanos":41000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"111","testStepId":"91","timestamp":{"seconds":0,"nanos":42000000}}}
-{"testStepFinished":{"testCaseStartedId":"111","testStepId":"91","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":43000000}}}
-{"testStepStarted":{"testCaseStartedId":"111","testStepId":"92","timestamp":{"seconds":0,"nanos":44000000}}}
-{"testStepFinished":{"testCaseStartedId":"111","testStepId":"92","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":45000000}}}
-{"testStepStarted":{"testCaseStartedId":"111","testStepId":"93","timestamp":{"seconds":0,"nanos":46000000}}}
-{"testStepFinished":{"testCaseStartedId":"111","testStepId":"93","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":47000000}}}
-{"testCaseFinished":{"testCaseStartedId":"111","timestamp":{"seconds":0,"nanos":48000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"112","testCaseId":"94","timestamp":{"seconds":0,"nanos":49000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"112","testStepId":"95","timestamp":{"seconds":0,"nanos":50000000}}}
-{"testStepFinished":{"testCaseStartedId":"112","testStepId":"95","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":51000000}}}
-{"testStepStarted":{"testCaseStartedId":"112","testStepId":"96","timestamp":{"seconds":0,"nanos":52000000}}}
-{"testStepFinished":{"testCaseStartedId":"112","testStepId":"96","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":53000000}}}
-{"testStepStarted":{"testCaseStartedId":"112","testStepId":"97","timestamp":{"seconds":0,"nanos":54000000}}}
-{"testStepFinished":{"testCaseStartedId":"112","testStepId":"97","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":55000000}}}
-{"testCaseFinished":{"testCaseStartedId":"112","timestamp":{"seconds":0,"nanos":56000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"113","testCaseId":"98","timestamp":{"seconds":0,"nanos":57000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"113","testStepId":"99","timestamp":{"seconds":0,"nanos":58000000}}}
-{"testStepFinished":{"testCaseStartedId":"113","testStepId":"99","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":59000000}}}
-{"testStepStarted":{"testCaseStartedId":"113","testStepId":"100","timestamp":{"seconds":0,"nanos":60000000}}}
-{"testStepFinished":{"testCaseStartedId":"113","testStepId":"100","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":61000000}}}
-{"testStepStarted":{"testCaseStartedId":"113","testStepId":"101","timestamp":{"seconds":0,"nanos":62000000}}}
-{"testStepFinished":{"testCaseStartedId":"113","testStepId":"101","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":63000000}}}
-{"testCaseFinished":{"testCaseStartedId":"113","timestamp":{"seconds":0,"nanos":64000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"114","testCaseId":"102","timestamp":{"seconds":0,"nanos":65000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"114","testStepId":"103","timestamp":{"seconds":0,"nanos":66000000}}}
-{"testStepFinished":{"testCaseStartedId":"114","testStepId":"103","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":67000000}}}
-{"testStepStarted":{"testCaseStartedId":"114","testStepId":"104","timestamp":{"seconds":0,"nanos":68000000}}}
-{"testStepFinished":{"testCaseStartedId":"114","testStepId":"104","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":69000000}}}
-{"testStepStarted":{"testCaseStartedId":"114","testStepId":"105","timestamp":{"seconds":0,"nanos":70000000}}}
-{"testStepFinished":{"testCaseStartedId":"114","testStepId":"105","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":71000000}}}
-{"testCaseFinished":{"testCaseStartedId":"114","timestamp":{"seconds":0,"nanos":72000000},"willBeRetried":false}}
-{"testRunFinished":{"testRunStartedId":"69","timestamp":{"seconds":0,"nanos":73000000},"success":false}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Examples Tables\n Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table\n underneath a Scenario, and use in the Scenario which match the\n table headers.\n\n The Scenario Outline name can also be parameterized. The name of the resulting\n pickle will have the replaced with the value from the examples\n table.\n\n Scenario Outline: Eating cucumbers\n Given there are cucumbers\n When I eat cucumbers\n Then I should have cucumbers\n\n @passing\n Examples: These are passing\n | start | eat | left |\n | 12 | 5 | 7 |\n | 20 | 5 | 15 |\n\n @failing\n Examples: These are failing\n | start | eat | left |\n | 12 | 20 | 0 |\n | 0 | 1 | 0 |\n\n Scenario Outline: Eating cucumbers with friends\n Given there are friends\n And there are cucumbers\n Then each person can eat cucumbers\n\n Examples:\n | friends | start | share |\n | 11 | 12 | 1 |\n | 1 | 4 | 2 |\n | 0 | 4 | 4 |\n","uri":"samples/examples-tables/examples-tables.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables","description":" Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table\n underneath a Scenario, and use in the Scenario which match the\n table headers.\n\n The Scenario Outline name can also be parameterized. The name of the resulting\n pickle will have the replaced with the value from the examples\n table.","children":[{"scenario":{"id":"13","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers","description":"","steps":[{"id":"0","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are cucumbers"},{"id":"1","location":{"line":13,"column":5},"keyword":"When ","keywordType":"Action","text":"I eat cucumbers"},{"id":"2","location":{"line":14,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"I should have cucumbers"}],"examples":[{"id":"7","tags":[{"location":{"line":16,"column":5},"name":"@passing","id":"6"}],"location":{"line":17,"column":5},"keyword":"Examples","name":"These are passing","description":"","tableHeader":{"id":"3","location":{"line":18,"column":7},"cells":[{"location":{"line":18,"column":9},"value":"start"},{"location":{"line":18,"column":17},"value":"eat"},{"location":{"line":18,"column":23},"value":"left"}]},"tableBody":[{"id":"4","location":{"line":19,"column":7},"cells":[{"location":{"line":19,"column":12},"value":"12"},{"location":{"line":19,"column":19},"value":"5"},{"location":{"line":19,"column":26},"value":"7"}]},{"id":"5","location":{"line":20,"column":7},"cells":[{"location":{"line":20,"column":12},"value":"20"},{"location":{"line":20,"column":19},"value":"5"},{"location":{"line":20,"column":25},"value":"15"}]}]},{"id":"12","tags":[{"location":{"line":22,"column":5},"name":"@failing","id":"11"}],"location":{"line":23,"column":5},"keyword":"Examples","name":"These are failing","description":"","tableHeader":{"id":"8","location":{"line":24,"column":7},"cells":[{"location":{"line":24,"column":9},"value":"start"},{"location":{"line":24,"column":17},"value":"eat"},{"location":{"line":24,"column":23},"value":"left"}]},"tableBody":[{"id":"9","location":{"line":25,"column":7},"cells":[{"location":{"line":25,"column":12},"value":"12"},{"location":{"line":25,"column":18},"value":"20"},{"location":{"line":25,"column":26},"value":"0"}]},{"id":"10","location":{"line":26,"column":7},"cells":[{"location":{"line":26,"column":13},"value":"0"},{"location":{"line":26,"column":19},"value":"1"},{"location":{"line":26,"column":26},"value":"0"}]}]}]}},{"scenario":{"id":"22","tags":[],"location":{"line":28,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers with friends","description":"","steps":[{"id":"14","location":{"line":29,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are friends"},{"id":"15","location":{"line":30,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"there are cucumbers"},{"id":"16","location":{"line":31,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"each person can eat cucumbers"}],"examples":[{"id":"21","tags":[],"location":{"line":33,"column":5},"keyword":"Examples","name":"","description":"","tableHeader":{"id":"17","location":{"line":34,"column":7},"cells":[{"location":{"line":34,"column":9},"value":"friends"},{"location":{"line":34,"column":19},"value":"start"},{"location":{"line":34,"column":27},"value":"share"}]},"tableBody":[{"id":"18","location":{"line":35,"column":7},"cells":[{"location":{"line":35,"column":14},"value":"11"},{"location":{"line":35,"column":22},"value":"12"},{"location":{"line":35,"column":31},"value":"1"}]},{"id":"19","location":{"line":36,"column":7},"cells":[{"location":{"line":36,"column":15},"value":"1"},{"location":{"line":36,"column":23},"value":"4"},{"location":{"line":36,"column":31},"value":"2"}]},{"id":"20","location":{"line":37,"column":7},"cells":[{"location":{"line":37,"column":15},"value":"0"},{"location":{"line":37,"column":23},"value":"4"},{"location":{"line":37,"column":31},"value":"4"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables/examples-tables.feature"}}
+{"pickle":{"id":"26","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["13","4"],"name":"Eating cucumbers","language":"en","steps":[{"id":"23","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","4"]},{"id":"24","text":"I eat 5 cucumbers","type":"Action","astNodeIds":["1","4"]},{"id":"25","text":"I should have 7 cucumbers","type":"Outcome","astNodeIds":["2","4"]}],"tags":[{"name":"@passing","astNodeId":"6"}]}}
+{"pickle":{"id":"30","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["13","5"],"name":"Eating cucumbers","language":"en","steps":[{"id":"27","text":"there are 20 cucumbers","type":"Context","astNodeIds":["0","5"]},{"id":"28","text":"I eat 5 cucumbers","type":"Action","astNodeIds":["1","5"]},{"id":"29","text":"I should have 15 cucumbers","type":"Outcome","astNodeIds":["2","5"]}],"tags":[{"name":"@passing","astNodeId":"6"}]}}
+{"pickle":{"id":"34","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["13","9"],"name":"Eating cucumbers","language":"en","steps":[{"id":"31","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","9"]},{"id":"32","text":"I eat 20 cucumbers","type":"Action","astNodeIds":["1","9"]},{"id":"33","text":"I should have 0 cucumbers","type":"Outcome","astNodeIds":["2","9"]}],"tags":[{"name":"@failing","astNodeId":"11"}]}}
+{"pickle":{"id":"38","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["13","10"],"name":"Eating cucumbers","language":"en","steps":[{"id":"35","text":"there are 0 cucumbers","type":"Context","astNodeIds":["0","10"]},{"id":"36","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","10"]},{"id":"37","text":"I should have 0 cucumbers","type":"Outcome","astNodeIds":["2","10"]}],"tags":[{"name":"@failing","astNodeId":"11"}]}}
+{"pickle":{"id":"42","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["22","18"],"name":"Eating cucumbers with 11 friends","language":"en","steps":[{"id":"39","text":"there are 11 friends","type":"Context","astNodeIds":["14","18"]},{"id":"40","text":"there are 12 cucumbers","type":"Context","astNodeIds":["15","18"]},{"id":"41","text":"each person can eat 1 cucumbers","type":"Outcome","astNodeIds":["16","18"]}],"tags":[]}}
+{"pickle":{"id":"46","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["22","19"],"name":"Eating cucumbers with 1 friends","language":"en","steps":[{"id":"43","text":"there are 1 friends","type":"Context","astNodeIds":["14","19"]},{"id":"44","text":"there are 4 cucumbers","type":"Context","astNodeIds":["15","19"]},{"id":"45","text":"each person can eat 2 cucumbers","type":"Outcome","astNodeIds":["16","19"]}],"tags":[]}}
+{"pickle":{"id":"50","uri":"samples/examples-tables/examples-tables.feature","astNodeIds":["22","20"],"name":"Eating cucumbers with 0 friends","language":"en","steps":[{"id":"47","text":"there are 0 friends","type":"Context","astNodeIds":["14","20"]},{"id":"48","text":"there are 4 cucumbers","type":"Context","astNodeIds":["15","20"]},{"id":"49","text":"each person can eat 4 cucumbers","type":"Outcome","astNodeIds":["16","20"]}],"tags":[]}}
+{"stepDefinition":{"id":"51","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":4}}}}
+{"stepDefinition":{"id":"52","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} friends"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":8}}}}
+{"stepDefinition":{"id":"53","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":12}}}}
+{"stepDefinition":{"id":"54","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I should have {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":16}}}}
+{"stepDefinition":{"id":"55","pattern":{"type":"CUCUMBER_EXPRESSION","source":"each person can eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":20}}}}
+{"testRunStarted":{"id":"56","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"57","pickleId":"26","testSteps":[{"id":"58","pickleStepId":"23","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"59","pickleStepId":"24","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"5","children":[]},"parameterTypeName":"int"}]}]},{"id":"60","pickleStepId":"25","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"7","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCase":{"id":"61","pickleId":"30","testSteps":[{"id":"62","pickleStepId":"27","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"20","children":[]},"parameterTypeName":"int"}]}]},{"id":"63","pickleStepId":"28","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"5","children":[]},"parameterTypeName":"int"}]}]},{"id":"64","pickleStepId":"29","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"15","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCase":{"id":"65","pickleId":"34","testSteps":[{"id":"66","pickleStepId":"31","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"67","pickleStepId":"32","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"20","children":[]},"parameterTypeName":"int"}]}]},{"id":"68","pickleStepId":"33","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"0","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCase":{"id":"69","pickleId":"38","testSteps":[{"id":"70","pickleStepId":"35","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"71","pickleStepId":"36","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"72","pickleStepId":"37","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"0","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCase":{"id":"73","pickleId":"42","testSteps":[{"id":"74","pickleStepId":"39","stepDefinitionIds":["52"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"11","children":[]},"parameterTypeName":"int"}]}]},{"id":"75","pickleStepId":"40","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"76","pickleStepId":"41","stepDefinitionIds":["55"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"1","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCase":{"id":"77","pickleId":"46","testSteps":[{"id":"78","pickleStepId":"43","stepDefinitionIds":["52"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"79","pickleStepId":"44","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"4","children":[]},"parameterTypeName":"int"}]}]},{"id":"80","pickleStepId":"45","stepDefinitionIds":["55"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"2","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCase":{"id":"81","pickleId":"50","testSteps":[{"id":"82","pickleStepId":"47","stepDefinitionIds":["52"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"83","pickleStepId":"48","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"4","children":[]},"parameterTypeName":"int"}]}]},{"id":"84","pickleStepId":"49","stepDefinitionIds":["55"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"4","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}}
+{"testCaseStarted":{"id":"85","testCaseId":"57","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"85","testStepId":"58","timestamp":{"seconds":0,"nanos":2000000}}}
+{"testStepFinished":{"testCaseStartedId":"85","testStepId":"58","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testStepStarted":{"testCaseStartedId":"85","testStepId":"59","timestamp":{"seconds":0,"nanos":4000000}}}
+{"testStepFinished":{"testCaseStartedId":"85","testStepId":"59","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}}
+{"testStepStarted":{"testCaseStartedId":"85","testStepId":"60","timestamp":{"seconds":0,"nanos":6000000}}}
+{"testStepFinished":{"testCaseStartedId":"85","testStepId":"60","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
+{"testCaseFinished":{"testCaseStartedId":"85","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"86","testCaseId":"61","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"86","testStepId":"62","timestamp":{"seconds":0,"nanos":10000000}}}
+{"testStepFinished":{"testCaseStartedId":"86","testStepId":"62","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
+{"testStepStarted":{"testCaseStartedId":"86","testStepId":"63","timestamp":{"seconds":0,"nanos":12000000}}}
+{"testStepFinished":{"testCaseStartedId":"86","testStepId":"63","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}}
+{"testStepStarted":{"testCaseStartedId":"86","testStepId":"64","timestamp":{"seconds":0,"nanos":14000000}}}
+{"testStepFinished":{"testCaseStartedId":"86","testStepId":"64","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}}
+{"testCaseFinished":{"testCaseStartedId":"86","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"87","testCaseId":"65","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"87","testStepId":"66","timestamp":{"seconds":0,"nanos":18000000}}}
+{"testStepFinished":{"testCaseStartedId":"87","testStepId":"66","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
+{"testStepStarted":{"testCaseStartedId":"87","testStepId":"67","timestamp":{"seconds":0,"nanos":20000000}}}
+{"testStepFinished":{"testCaseStartedId":"87","testStepId":"67","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}}
+{"testStepStarted":{"testCaseStartedId":"87","testStepId":"68","timestamp":{"seconds":0,"nanos":22000000}}}
+{"testStepFinished":{"testCaseStartedId":"87","testStepId":"68","testStepResult":{"message":"Expected values to be strictly equal:\n\n-8 !== 0\n","exception":{"type":"AssertionError","message":"Expected values to be strictly equal:\n\n-8 !== 0\n","stackTrace":"AssertionError: Expected values to be strictly equal:\n\n-8 !== 0\n\nsamples/examples-tables/examples-tables.feature:14"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}}
+{"testCaseFinished":{"testCaseStartedId":"87","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"88","testCaseId":"69","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"88","testStepId":"70","timestamp":{"seconds":0,"nanos":26000000}}}
+{"testStepFinished":{"testCaseStartedId":"88","testStepId":"70","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}}
+{"testStepStarted":{"testCaseStartedId":"88","testStepId":"71","timestamp":{"seconds":0,"nanos":28000000}}}
+{"testStepFinished":{"testCaseStartedId":"88","testStepId":"71","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}}
+{"testStepStarted":{"testCaseStartedId":"88","testStepId":"72","timestamp":{"seconds":0,"nanos":30000000}}}
+{"testStepFinished":{"testCaseStartedId":"88","testStepId":"72","testStepResult":{"message":"Expected values to be strictly equal:\n\n-1 !== 0\n","exception":{"type":"AssertionError","message":"Expected values to be strictly equal:\n\n-1 !== 0\n","stackTrace":"AssertionError: Expected values to be strictly equal:\n\n-1 !== 0\n\nsamples/examples-tables/examples-tables.feature:14"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}}
+{"testCaseFinished":{"testCaseStartedId":"88","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"89","testCaseId":"73","timestamp":{"seconds":0,"nanos":33000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"89","testStepId":"74","timestamp":{"seconds":0,"nanos":34000000}}}
+{"testStepFinished":{"testCaseStartedId":"89","testStepId":"74","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}}
+{"testStepStarted":{"testCaseStartedId":"89","testStepId":"75","timestamp":{"seconds":0,"nanos":36000000}}}
+{"testStepFinished":{"testCaseStartedId":"89","testStepId":"75","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":37000000}}}
+{"testStepStarted":{"testCaseStartedId":"89","testStepId":"76","timestamp":{"seconds":0,"nanos":38000000}}}
+{"testStepFinished":{"testCaseStartedId":"89","testStepId":"76","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":39000000}}}
+{"testCaseFinished":{"testCaseStartedId":"89","timestamp":{"seconds":0,"nanos":40000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"90","testCaseId":"77","timestamp":{"seconds":0,"nanos":41000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"90","testStepId":"78","timestamp":{"seconds":0,"nanos":42000000}}}
+{"testStepFinished":{"testCaseStartedId":"90","testStepId":"78","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":43000000}}}
+{"testStepStarted":{"testCaseStartedId":"90","testStepId":"79","timestamp":{"seconds":0,"nanos":44000000}}}
+{"testStepFinished":{"testCaseStartedId":"90","testStepId":"79","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":45000000}}}
+{"testStepStarted":{"testCaseStartedId":"90","testStepId":"80","timestamp":{"seconds":0,"nanos":46000000}}}
+{"testStepFinished":{"testCaseStartedId":"90","testStepId":"80","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":47000000}}}
+{"testCaseFinished":{"testCaseStartedId":"90","timestamp":{"seconds":0,"nanos":48000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"91","testCaseId":"81","timestamp":{"seconds":0,"nanos":49000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"91","testStepId":"82","timestamp":{"seconds":0,"nanos":50000000}}}
+{"testStepFinished":{"testCaseStartedId":"91","testStepId":"82","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":51000000}}}
+{"testStepStarted":{"testCaseStartedId":"91","testStepId":"83","timestamp":{"seconds":0,"nanos":52000000}}}
+{"testStepFinished":{"testCaseStartedId":"91","testStepId":"83","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":53000000}}}
+{"testStepStarted":{"testCaseStartedId":"91","testStepId":"84","timestamp":{"seconds":0,"nanos":54000000}}}
+{"testStepFinished":{"testCaseStartedId":"91","testStepId":"84","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":55000000}}}
+{"testCaseFinished":{"testCaseStartedId":"91","timestamp":{"seconds":0,"nanos":56000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"56","timestamp":{"seconds":0,"nanos":57000000},"success":false}}
diff --git a/testdata/src/examples-tables.xml b/testdata/src/examples-tables.xml
index 018834e..29e9fd7 100644
--- a/testdata/src/examples-tables.xml
+++ b/testdata/src/examples-tables.xml
@@ -1,7 +1,7 @@
-
-
-
+
+
+
@@ -37,33 +37,9 @@ samples/examples-tables/examples-tables.feature:14]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/testdata/src/global-hooks-afterall-error.ndjson b/testdata/src/global-hooks-afterall-error.ndjson
index fab3025..f3fc04d 100644
--- a/testdata/src/global-hooks-afterall-error.ndjson
+++ b/testdata/src/global-hooks-afterall-error.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Global hooks - AfterAll error\n Errors in AfterAll hooks cause the whole test run to fail. The remaining AfterAll hooks will still run, in an\n effort to clean up resources as well as possible.\n\n Scenario: A passing scenario\n When a step passes\n","uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks - AfterAll error","description":" Errors in AfterAll hooks cause the whole test run to fail. The remaining AfterAll hooks will still run, in an\n effort to clean up resources as well as possible.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"A passing scenario","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.feature"}}
{"pickle":{"id":"3","uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.feature","astNodeIds":["1"],"tags":[],"name":"A passing scenario","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/global-hooks-attachments.ndjson b/testdata/src/global-hooks-attachments.ndjson
index ea1b2e0..dfc8e17 100644
--- a/testdata/src/global-hooks-attachments.ndjson
+++ b/testdata/src/global-hooks-attachments.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Global hooks with attachments\n Attachments can be captured in BeforeAll and AfterAll hooks.\n\n Scenario: A scenario\n When a step passes\n","uri":"samples/global-hooks-attachments/global-hooks-attachments.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks with attachments","description":" Attachments can be captured in BeforeAll and AfterAll hooks.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"A scenario","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks-attachments/global-hooks-attachments.feature"}}
{"pickle":{"id":"3","uri":"samples/global-hooks-attachments/global-hooks-attachments.feature","astNodeIds":["1"],"tags":[],"name":"A scenario","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/global-hooks-beforeall-error.ndjson b/testdata/src/global-hooks-beforeall-error.ndjson
index ace9adc..0785d79 100644
--- a/testdata/src/global-hooks-beforeall-error.ndjson
+++ b/testdata/src/global-hooks-beforeall-error.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Global hooks - BeforeAll error\n Errors in BeforeAll hooks cause the whole test run to fail. Test cases will not be executed. The remaining BeforeAll\n hooks will still run, along with all AfterAll hooks, in an effort to clean up resources as well as possible.\n\n Scenario: A passing scenario\n When a step passes\n","uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks - BeforeAll error","description":" Errors in BeforeAll hooks cause the whole test run to fail. Test cases will not be executed. The remaining BeforeAll\n hooks will still run, along with all AfterAll hooks, in an effort to clean up resources as well as possible.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"A passing scenario","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.feature"}}
{"pickle":{"id":"3","uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.feature","astNodeIds":["1"],"tags":[],"name":"A passing scenario","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/global-hooks.ndjson b/testdata/src/global-hooks.ndjson
index 9e9dfab..1af9114 100644
--- a/testdata/src/global-hooks.ndjson
+++ b/testdata/src/global-hooks.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Global hooks\n Hooks can be at the test run level, so they run once before or after all test cases.\n\n AfterAll hooks are executed in reverse order of definition.\n\n Scenario: A passing scenario\n When a step passes\n\n Scenario: A failing scenario\n When a step fails\n","uri":"samples/global-hooks/global-hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks","description":" Hooks can be at the test run level, so they run once before or after all test cases.\n\n AfterAll hooks are executed in reverse order of definition.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"A passing scenario","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"A failing scenario","description":"","steps":[{"id":"2","location":{"line":10,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks/global-hooks.feature"}}
{"pickle":{"id":"5","uri":"samples/global-hooks/global-hooks.feature","astNodeIds":["1"],"tags":[],"name":"A passing scenario","language":"en","steps":[{"id":"4","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/hooks-attachment.ndjson b/testdata/src/hooks-attachment.ndjson
index 870d0f7..2d7c087 100644
--- a/testdata/src/hooks-attachment.ndjson
+++ b/testdata/src/hooks-attachment.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Hooks - Attachments\n Hooks are special steps that run before or after each scenario's steps.\n\n Like regular steps, it is possible to attach a file to the output.\n\n Scenario: With an valid attachment in the hook and a passed step\n When a step passes\n","uri":"samples/hooks-attachment/hooks-attachment.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - Attachments","description":" Hooks are special steps that run before or after each scenario's steps.\n\n Like regular steps, it is possible to attach a file to the output.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"With an valid attachment in the hook and a passed step","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-attachment/hooks-attachment.feature"}}
{"pickle":{"id":"3","uri":"samples/hooks-attachment/hooks-attachment.feature","astNodeIds":["1"],"tags":[],"name":"With an valid attachment in the hook and a passed step","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/hooks-conditional.ndjson b/testdata/src/hooks-conditional.ndjson
index fd5f0ea..54723f5 100644
--- a/testdata/src/hooks-conditional.ndjson
+++ b/testdata/src/hooks-conditional.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Hooks - Conditional execution\n Hooks are special steps that run before or after each scenario's steps.\n\n They can also conditionally target specific scenarios, using tag expressions.\n\n @fail-before\n Scenario: A failure in the before hook and a skipped step\n When a step passes\n\n @fail-after\n Scenario: A failure in the after hook and a passed step\n When a step passes\n\n @passing-hook\n Scenario: With an tag, a passed step and hook\n When a step passes\n","uri":"samples/hooks-conditional/hooks-conditional.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - Conditional execution","description":" Hooks are special steps that run before or after each scenario's steps.\n\n They can also conditionally target specific scenarios, using tag expressions.","children":[{"scenario":{"id":"2","tags":[{"location":{"line":6,"column":3},"name":"@fail-before","id":"1"}],"location":{"line":7,"column":3},"keyword":"Scenario","name":"A failure in the before hook and a skipped step","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"5","tags":[{"location":{"line":10,"column":3},"name":"@fail-after","id":"4"}],"location":{"line":11,"column":3},"keyword":"Scenario","name":"A failure in the after hook and a passed step","description":"","steps":[{"id":"3","location":{"line":12,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"8","tags":[{"location":{"line":14,"column":3},"name":"@passing-hook","id":"7"}],"location":{"line":15,"column":3},"keyword":"Scenario","name":"With an tag, a passed step and hook","description":"","steps":[{"id":"6","location":{"line":16,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-conditional/hooks-conditional.feature"}}
{"pickle":{"id":"10","uri":"samples/hooks-conditional/hooks-conditional.feature","astNodeIds":["2"],"tags":[{"name":"@fail-before","astNodeId":"1"}],"name":"A failure in the before hook and a skipped step","language":"en","steps":[{"id":"9","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/hooks-named.ndjson b/testdata/src/hooks-named.ndjson
index 10bfe70..84fff79 100644
--- a/testdata/src/hooks-named.ndjson
+++ b/testdata/src/hooks-named.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Hooks - Named\n Hooks are special steps that run before or after each scenario's steps.\n\n Hooks can be given a name. Which is nice for reporting. Otherwise they work\n exactly the same as regular hooks.\n\n Scenario: With a named before and after hook\n When a step passes\n","uri":"samples/hooks-named/hooks-named.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - Named","description":" Hooks are special steps that run before or after each scenario's steps.\n\n Hooks can be given a name. Which is nice for reporting. Otherwise they work\n exactly the same as regular hooks.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"With a named before and after hook","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-named/hooks-named.feature"}}
{"pickle":{"id":"3","uri":"samples/hooks-named/hooks-named.feature","astNodeIds":["1"],"tags":[],"name":"With a named before and after hook","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/hooks-undefined.ndjson b/testdata/src/hooks-undefined.ndjson
new file mode 100644
index 0000000..4d90c89
--- /dev/null
+++ b/testdata/src/hooks-undefined.ndjson
@@ -0,0 +1,18 @@
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Hooks - With Undefined Steps\n Hooks also run before and after undefined steps\n\n Scenario: No tags and a undefined step\n When a step does not exist\n","uri":"samples/hooks-undefined/hooks-undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - With Undefined Steps","description":" Hooks also run before and after undefined steps","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a undefined step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step does not exist"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-undefined/hooks-undefined.feature"}}
+{"pickle":{"id":"3","uri":"samples/hooks-undefined/hooks-undefined.feature","astNodeIds":["1"],"tags":[],"name":"No tags and a undefined step","language":"en","steps":[{"id":"2","text":"a step does not exist","type":"Action","astNodeIds":["0"]}]}}
+{"hook":{"id":"4","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks-undefined/hooks-undefined.ts","location":{"line":3}}}}
+{"hook":{"id":"5","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks-undefined/hooks-undefined.ts","location":{"line":7}}}}
+{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","hookId":"4"},{"id":"9","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"10","hookId":"5"}],"testRunStartedId":"6"}}
+{"testCaseStarted":{"id":"11","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"11","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}}
+{"testStepFinished":{"testCaseStartedId":"11","testStepId":"8","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testStepStarted":{"testCaseStartedId":"11","testStepId":"9","timestamp":{"seconds":0,"nanos":4000000}}}
+{"suggestion":{"id":"12","pickleStepId":"2","snippets":[{"language":"typescript","code":"When(\"a step does not exist\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"11","testStepId":"9","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":5000000}}}
+{"testStepStarted":{"testCaseStartedId":"11","testStepId":"10","timestamp":{"seconds":0,"nanos":6000000}}}
+{"testStepFinished":{"testCaseStartedId":"11","testStepId":"10","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
+{"testCaseFinished":{"testCaseStartedId":"11","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":9000000},"success":false}}
diff --git a/testdata/src/hooks-undefined.xml b/testdata/src/hooks-undefined.xml
new file mode 100644
index 0000000..f4f19c5
--- /dev/null
+++ b/testdata/src/hooks-undefined.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/src/hooks.ndjson b/testdata/src/hooks.ndjson
index c327aaa..c99fa75 100644
--- a/testdata/src/hooks.ndjson
+++ b/testdata/src/hooks.ndjson
@@ -1,39 +1,29 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
-{"source":{"data":"Feature: Hooks\n Hooks are special steps that run before or after each scenario's steps.\n\n Scenario: No tags and a passed step\n When a step passes\n\n Scenario: No tags and a failed step\n When a step fails\n\n Scenario: No tags and a undefined step\n When a step does not exist\n","uri":"samples/hooks/hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
-{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks","description":" Hooks are special steps that run before or after each scenario's steps.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a passed step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"No tags and a failed step","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":10,"column":3},"keyword":"Scenario","name":"No tags and a undefined step","description":"","steps":[{"id":"4","location":{"line":11,"column":5},"keyword":"When ","keywordType":"Action","text":"a step does not exist"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks/hooks.feature"}}
-{"pickle":{"id":"7","uri":"samples/hooks/hooks.feature","astNodeIds":["1"],"tags":[],"name":"No tags and a passed step","language":"en","steps":[{"id":"6","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
-{"pickle":{"id":"9","uri":"samples/hooks/hooks.feature","astNodeIds":["3"],"tags":[],"name":"No tags and a failed step","language":"en","steps":[{"id":"8","text":"a step fails","type":"Action","astNodeIds":["2"]}]}}
-{"pickle":{"id":"11","uri":"samples/hooks/hooks.feature","astNodeIds":["5"],"tags":[],"name":"No tags and a undefined step","language":"en","steps":[{"id":"10","text":"a step does not exist","type":"Action","astNodeIds":["4"]}]}}
-{"stepDefinition":{"id":"13","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":7}}}}
-{"stepDefinition":{"id":"14","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step fails"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":11}}}}
-{"hook":{"id":"12","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":3}}}}
-{"hook":{"id":"15","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":15}}}}
-{"testRunStarted":{"id":"16","timestamp":{"seconds":0,"nanos":0}}}
-{"testCase":{"id":"17","pickleId":"7","testSteps":[{"id":"18","hookId":"12"},{"id":"19","pickleStepId":"6","stepDefinitionIds":["13"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"20","hookId":"15"}],"testRunStartedId":"16"}}
-{"testCase":{"id":"21","pickleId":"9","testSteps":[{"id":"22","hookId":"12"},{"id":"23","pickleStepId":"8","stepDefinitionIds":["14"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"24","hookId":"15"}],"testRunStartedId":"16"}}
-{"testCase":{"id":"25","pickleId":"11","testSteps":[{"id":"26","hookId":"12"},{"id":"27","pickleStepId":"10","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"28","hookId":"15"}],"testRunStartedId":"16"}}
-{"testCaseStarted":{"id":"29","testCaseId":"17","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"29","testStepId":"18","timestamp":{"seconds":0,"nanos":2000000}}}
-{"testStepFinished":{"testCaseStartedId":"29","testStepId":"18","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
-{"testStepStarted":{"testCaseStartedId":"29","testStepId":"19","timestamp":{"seconds":0,"nanos":4000000}}}
-{"testStepFinished":{"testCaseStartedId":"29","testStepId":"19","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}}
-{"testStepStarted":{"testCaseStartedId":"29","testStepId":"20","timestamp":{"seconds":0,"nanos":6000000}}}
-{"testStepFinished":{"testCaseStartedId":"29","testStepId":"20","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
-{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"30","testCaseId":"21","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"30","testStepId":"22","timestamp":{"seconds":0,"nanos":10000000}}}
-{"testStepFinished":{"testCaseStartedId":"30","testStepId":"22","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
-{"testStepStarted":{"testCaseStartedId":"30","testStepId":"23","timestamp":{"seconds":0,"nanos":12000000}}}
-{"testStepFinished":{"testCaseStartedId":"30","testStepId":"23","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/hooks/hooks.feature:8"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}}
-{"testStepStarted":{"testCaseStartedId":"30","testStepId":"24","timestamp":{"seconds":0,"nanos":14000000}}}
-{"testStepFinished":{"testCaseStartedId":"30","testStepId":"24","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}}
-{"testCaseFinished":{"testCaseStartedId":"30","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"31","testCaseId":"25","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"31","testStepId":"26","timestamp":{"seconds":0,"nanos":18000000}}}
-{"testStepFinished":{"testCaseStartedId":"31","testStepId":"26","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
-{"testStepStarted":{"testCaseStartedId":"31","testStepId":"27","timestamp":{"seconds":0,"nanos":20000000}}}
-{"testStepFinished":{"testCaseStartedId":"31","testStepId":"27","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":21000000}}}
-{"testStepStarted":{"testCaseStartedId":"31","testStepId":"28","timestamp":{"seconds":0,"nanos":22000000}}}
-{"testStepFinished":{"testCaseStartedId":"31","testStepId":"28","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}}
-{"testCaseFinished":{"testCaseStartedId":"31","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
-{"testRunFinished":{"testRunStartedId":"16","timestamp":{"seconds":0,"nanos":25000000},"success":false}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Hooks\n Hooks are special steps that run before or after each scenario's steps.\n\n Scenario: No tags and a passed step\n When a step passes\n\n Scenario: No tags and a failed step\n When a step fails\n","uri":"samples/hooks/hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks","description":" Hooks are special steps that run before or after each scenario's steps.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a passed step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"No tags and a failed step","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks/hooks.feature"}}
+{"pickle":{"id":"5","uri":"samples/hooks/hooks.feature","astNodeIds":["1"],"tags":[],"name":"No tags and a passed step","language":"en","steps":[{"id":"4","text":"a step passes","type":"Action","astNodeIds":["0"]}]}}
+{"pickle":{"id":"7","uri":"samples/hooks/hooks.feature","astNodeIds":["3"],"tags":[],"name":"No tags and a failed step","language":"en","steps":[{"id":"6","text":"a step fails","type":"Action","astNodeIds":["2"]}]}}
+{"stepDefinition":{"id":"9","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":7}}}}
+{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step fails"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":11}}}}
+{"hook":{"id":"8","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":3}}}}
+{"hook":{"id":"11","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":15}}}}
+{"testRunStarted":{"id":"12","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"13","pickleId":"5","testSteps":[{"id":"14","hookId":"8"},{"id":"15","pickleStepId":"4","stepDefinitionIds":["9"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"16","hookId":"11"}],"testRunStartedId":"12"}}
+{"testCase":{"id":"17","pickleId":"7","testSteps":[{"id":"18","hookId":"8"},{"id":"19","pickleStepId":"6","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"20","hookId":"11"}],"testRunStartedId":"12"}}
+{"testCaseStarted":{"id":"21","testCaseId":"13","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"21","testStepId":"14","timestamp":{"seconds":0,"nanos":2000000}}}
+{"testStepFinished":{"testCaseStartedId":"21","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testStepStarted":{"testCaseStartedId":"21","testStepId":"15","timestamp":{"seconds":0,"nanos":4000000}}}
+{"testStepFinished":{"testCaseStartedId":"21","testStepId":"15","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}}
+{"testStepStarted":{"testCaseStartedId":"21","testStepId":"16","timestamp":{"seconds":0,"nanos":6000000}}}
+{"testStepFinished":{"testCaseStartedId":"21","testStepId":"16","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
+{"testCaseFinished":{"testCaseStartedId":"21","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"22","testCaseId":"17","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"22","testStepId":"18","timestamp":{"seconds":0,"nanos":10000000}}}
+{"testStepFinished":{"testCaseStartedId":"22","testStepId":"18","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
+{"testStepStarted":{"testCaseStartedId":"22","testStepId":"19","timestamp":{"seconds":0,"nanos":12000000}}}
+{"testStepFinished":{"testCaseStartedId":"22","testStepId":"19","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/hooks/hooks.feature:8"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}}
+{"testStepStarted":{"testCaseStartedId":"22","testStepId":"20","timestamp":{"seconds":0,"nanos":14000000}}}
+{"testStepFinished":{"testCaseStartedId":"22","testStepId":"20","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}}
+{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"12","timestamp":{"seconds":0,"nanos":17000000},"success":false}}
diff --git a/testdata/src/hooks.xml b/testdata/src/hooks.xml
index b86a805..4929a2a 100644
--- a/testdata/src/hooks.xml
+++ b/testdata/src/hooks.xml
@@ -1,7 +1,7 @@
-
-
-
+
+
+
@@ -15,16 +15,6 @@ samples/hooks/hooks.feature:8]]>
-
-
-
-
-
-
-
-
diff --git a/testdata/src/markdown.ndjson b/testdata/src/markdown.ndjson
index 95a98b8..78a92b1 100644
--- a/testdata/src/markdown.ndjson
+++ b/testdata/src/markdown.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"# Feature: Cheese\n\nThis table is not picked up by Gherkin (not indented 2+ spaces)\n\n| foo | bar |\n| --- | --- |\n| boz | boo |\n\n\n## Rule: Nom nom nom\n\nI love cheese, especially fromage macaroni cheese. Rubber cheese ricotta caerphilly blue castello who moved my cheese queso bavarian bergkase melted cheese.\n\n### Scenario Outline: Ylajali!\n\n* Given some TypeScript code:\n ```typescript\n type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'\n ```\n* And some classic Gherkin:\n ```gherkin\n Given there are 24 apples in Mary's basket\n ```\n* When we use a data table and attach something and then \n | name | age |\n | ---- | --: |\n | Bill | 3 |\n | Jane | 6 |\n | Isla | 5 |\n* Then this might or might not run\n\n#### Examples: because we need more tables\n\nThis table is indented 2 spaces, so Gherkin will pick it up\n\n | what |\n | ---- |\n | fail |\n | pass |\n\nAnd oh by the way, this table is also ignored by Gherkin because it doesn't have 2+ space indent:\n\n| cheese |\n| -------- |\n| gouda |\n| gamalost |\n","uri":"samples/markdown/markdown.feature.md","mediaType":"text/x.cucumber.gherkin+markdown"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":3},"language":"en","keyword":"Feature","name":"Cheese","description":"| boz | boo |","children":[{"rule":{"id":"13","location":{"line":10,"column":4},"keyword":"Rule","name":"Nom nom nom","description":"","children":[{"scenario":{"id":"12","tags":[],"location":{"line":14,"column":5},"keyword":"Scenario Outline","name":"Ylajali!","description":"","steps":[{"id":"0","location":{"line":16,"column":3},"keyword":"Given ","keywordType":"Context","text":"some TypeScript code:","docString":{"location":{"line":17,"column":3},"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","delimiter":"```","mediaType":"typescript"}},{"id":"1","location":{"line":20,"column":3},"keyword":"And ","keywordType":"Conjunction","text":"some classic Gherkin:","docString":{"location":{"line":21,"column":3},"content":"Given there are 24 apples in Mary's basket","delimiter":"```","mediaType":"gherkin"}},{"id":"6","location":{"line":24,"column":3},"keyword":"When ","keywordType":"Action","text":"we use a data table and attach something and then ","dataTable":{"location":{"line":25,"column":3},"rows":[{"id":"2","location":{"line":25,"column":3},"cells":[{"location":{"line":25,"column":5},"value":"name"},{"location":{"line":25,"column":12},"value":"age"}]},{"id":"3","location":{"line":27,"column":3},"cells":[{"location":{"line":27,"column":5},"value":"Bill"},{"location":{"line":27,"column":14},"value":"3"}]},{"id":"4","location":{"line":28,"column":3},"cells":[{"location":{"line":28,"column":5},"value":"Jane"},{"location":{"line":28,"column":14},"value":"6"}]},{"id":"5","location":{"line":29,"column":3},"cells":[{"location":{"line":29,"column":5},"value":"Isla"},{"location":{"line":29,"column":14},"value":"5"}]}]}},{"id":"7","location":{"line":30,"column":3},"keyword":"Then ","keywordType":"Outcome","text":"this might or might not run"}],"examples":[{"id":"11","tags":[],"location":{"line":32,"column":6},"keyword":"Examples","name":"because we need more tables","description":"","tableHeader":{"id":"8","location":{"line":36,"column":3},"cells":[{"location":{"line":36,"column":5},"value":"what"}]},"tableBody":[{"id":"9","location":{"line":38,"column":3},"cells":[{"location":{"line":38,"column":5},"value":"fail"}]},{"id":"10","location":{"line":39,"column":3},"cells":[{"location":{"line":39,"column":5},"value":"pass"}]}]}]}}],"tags":[]}}]},"comments":[],"uri":"samples/markdown/markdown.feature.md"}}
{"pickle":{"id":"18","uri":"samples/markdown/markdown.feature.md","astNodeIds":["12","9"],"name":"Ylajali!","language":"en","steps":[{"id":"14","text":"some TypeScript code:","type":"Context","argument":{"docString":{"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","mediaType":"typescript"}},"astNodeIds":["0","9"]},{"id":"15","text":"some classic Gherkin:","type":"Context","argument":{"docString":{"content":"Given there are 24 apples in Mary's basket","mediaType":"gherkin"}},"astNodeIds":["1","9"]},{"id":"16","text":"we use a data table and attach something and then fail","type":"Action","argument":{"dataTable":{"rows":[{"cells":[{"value":"name"},{"value":"age"}]},{"cells":[{"value":"Bill"},{"value":"3"}]},{"cells":[{"value":"Jane"},{"value":"6"}]},{"cells":[{"value":"Isla"},{"value":"5"}]}]}},"astNodeIds":["6","9"]},{"id":"17","text":"this might or might not run","type":"Outcome","astNodeIds":["7","9"]}],"tags":[]}}
diff --git a/testdata/src/minimal.ndjson b/testdata/src/minimal.ndjson
index f35958f..dc5f170 100644
--- a/testdata/src/minimal.ndjson
+++ b/testdata/src/minimal.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","uri":"samples/minimal/minimal.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"minimal","description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"cukes","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"I have 42 cukes in my belly"}],"examples":[]}}]},"comments":[],"uri":"samples/minimal/minimal.feature"}}
{"pickle":{"id":"3","uri":"samples/minimal/minimal.feature","astNodeIds":["1"],"tags":[],"name":"cukes","language":"en","steps":[{"id":"2","text":"I have 42 cukes in my belly","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/multiple-features-reversed.ndjson b/testdata/src/multiple-features-reversed.ndjson
index de09fc6..6abdaea 100644
--- a/testdata/src/multiple-features-reversed.ndjson
+++ b/testdata/src/multiple-features-reversed.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: First feature\n\n Scenario: First scenario\n Given an order for \"eggs\"\n\n Scenario: Second scenario\n Given an order for \"milk\"\n\n Scenario: Third scenario\n Given an order for \"bread\"","uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"First feature","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"0","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"2","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"milk\""}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"4","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"bread\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature"}}
{"pickle":{"id":"7","uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature","astNodeIds":["1"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"6","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/multiple-features.ndjson b/testdata/src/multiple-features.ndjson
index 48ab90d..065a17c 100644
--- a/testdata/src/multiple-features.ndjson
+++ b/testdata/src/multiple-features.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: First feature\n\n Scenario: First scenario\n Given an order for \"eggs\"\n\n Scenario: Second scenario\n Given an order for \"milk\"\n\n Scenario: Third scenario\n Given an order for \"bread\"","uri":"samples/multiple-features/multiple-features-1.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"First feature","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"0","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"2","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"milk\""}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"4","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"bread\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features/multiple-features-1.feature"}}
{"pickle":{"id":"7","uri":"samples/multiple-features/multiple-features-1.feature","astNodeIds":["1"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"6","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/parameter-types.ndjson b/testdata/src/parameter-types.ndjson
index 380f641..0a7b63c 100644
--- a/testdata/src/parameter-types.ndjson
+++ b/testdata/src/parameter-types.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Parameter Types\n Cucumber lets you define your own parameter types, which can be used\n in Cucumber Expressions.\n\n This lets you define a precise domain-specific vocabulary which can be used to\n generate a glossary with examples taken from your scenarios.\n\n Parameter types also enable you to transform strings and tables into different types.\n\n Scenario: Flight transformer\n Given LHR-CDG has been delayed\n","uri":"samples/parameter-types/parameter-types.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Parameter Types","description":" Cucumber lets you define your own parameter types, which can be used\n in Cucumber Expressions.\n\n This lets you define a precise domain-specific vocabulary which can be used to\n generate a glossary with examples taken from your scenarios.\n\n Parameter types also enable you to transform strings and tables into different types.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":10,"column":3},"keyword":"Scenario","name":"Flight transformer","description":"","steps":[{"id":"0","location":{"line":11,"column":5},"keyword":"Given ","keywordType":"Context","text":"LHR-CDG has been delayed"}],"examples":[]}}]},"comments":[],"uri":"samples/parameter-types/parameter-types.feature"}}
{"pickle":{"id":"3","uri":"samples/parameter-types/parameter-types.feature","astNodeIds":["1"],"tags":[],"name":"Flight transformer","language":"en","steps":[{"id":"2","text":"LHR-CDG has been delayed","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/pending.ndjson b/testdata/src/pending.ndjson
index 3f95f6f..259b5e1 100644
--- a/testdata/src/pending.ndjson
+++ b/testdata/src/pending.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Pending steps\n During development, step definitions can signal at runtime that they are\n not yet implemented (or \"pending\") by returning or throwing a particular\n value.\n\n This causes subsequent steps in the scenario to be skipped, and the overall\n result to be treated as a failure.\n\n Scenario: Unimplemented step signals pending status\n Given an unimplemented pending step\n\n Scenario: Steps before unimplemented steps are executed\n Given an implemented non-pending step\n And an unimplemented pending step\n\n Scenario: Steps after unimplemented steps are skipped\n Given an unimplemented pending step\n And an implemented step that is skipped\n","uri":"samples/pending/pending.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Pending steps","description":" During development, step definitions can signal at runtime that they are\n not yet implemented (or \"pending\") by returning or throwing a particular\n value.\n\n This causes subsequent steps in the scenario to be skipped, and the overall\n result to be treated as a failure.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Unimplemented step signals pending status","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an unimplemented pending step"}],"examples":[]}},{"scenario":{"id":"4","tags":[],"location":{"line":12,"column":3},"keyword":"Scenario","name":"Steps before unimplemented steps are executed","description":"","steps":[{"id":"2","location":{"line":13,"column":5},"keyword":"Given ","keywordType":"Context","text":"an implemented non-pending step"},{"id":"3","location":{"line":14,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an unimplemented pending step"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":16,"column":3},"keyword":"Scenario","name":"Steps after unimplemented steps are skipped","description":"","steps":[{"id":"5","location":{"line":17,"column":5},"keyword":"Given ","keywordType":"Context","text":"an unimplemented pending step"},{"id":"6","location":{"line":18,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an implemented step that is skipped"}],"examples":[]}}]},"comments":[],"uri":"samples/pending/pending.feature"}}
{"pickle":{"id":"9","uri":"samples/pending/pending.feature","astNodeIds":["1"],"tags":[],"name":"Unimplemented step signals pending status","language":"en","steps":[{"id":"8","text":"an unimplemented pending step","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/regular-expression.ndjson b/testdata/src/regular-expression.ndjson
index 1b892ae..de09405 100644
--- a/testdata/src/regular-expression.ndjson
+++ b/testdata/src/regular-expression.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: regular expression\n \n Cucumber supports both Cucumber and regular expressions in step\n definitions. This shows a sample that uses a regular expression.\n \n Scenario: regular expression\n Given a cucumber\n Given a cucumber and a zucchini\n Given a cucumber and a zucchini and a gourd\n","uri":"samples/regular-expression/regular-expression.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"regular expression","description":" Cucumber supports both Cucumber and regular expressions in step\n definitions. This shows a sample that uses a regular expression.","children":[{"scenario":{"id":"3","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"regular expression","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"a cucumber"},{"id":"1","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"a cucumber and a zucchini"},{"id":"2","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a cucumber and a zucchini and a gourd"}],"examples":[]}}]},"comments":[],"uri":"samples/regular-expression/regular-expression.feature"}}
{"pickle":{"id":"7","uri":"samples/regular-expression/regular-expression.feature","astNodeIds":["3"],"tags":[],"name":"regular expression","language":"en","steps":[{"id":"4","text":"a cucumber","type":"Context","astNodeIds":["0"]},{"id":"5","text":"a cucumber and a zucchini","type":"Context","astNodeIds":["1"]},{"id":"6","text":"a cucumber and a zucchini and a gourd","type":"Context","astNodeIds":["2"]}]}}
diff --git a/testdata/src/retry-ambiguous.ndjson b/testdata/src/retry-ambiguous.ndjson
new file mode 100644
index 0000000..5d3e0d1
--- /dev/null
+++ b/testdata/src/retry-ambiguous.ndjson
@@ -0,0 +1,13 @@
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Retry - With Ambiguous Steps\n Scenario: Test cases won't retry when the status is AMBIGUOUS\n Given an ambiguous step\n","uri":"samples/retry-ambiguous/retry-ambiguous.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry - With Ambiguous Steps","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is AMBIGUOUS","description":"","steps":[{"id":"0","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"an ambiguous step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry-ambiguous/retry-ambiguous.feature"}}
+{"pickle":{"id":"3","uri":"samples/retry-ambiguous/retry-ambiguous.feature","astNodeIds":["1"],"tags":[],"name":"Test cases won't retry when the status is AMBIGUOUS","language":"en","steps":[{"id":"2","text":"an ambiguous step","type":"Context","astNodeIds":["0"]}]}}
+{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an ambiguous step"},"sourceReference":{"uri":"samples/retry-ambiguous/retry-ambiguous.ts","location":{"line":3}}}}
+{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an ambiguous step"},"sourceReference":{"uri":"samples/retry-ambiguous/retry-ambiguous.ts","location":{"line":7}}}}
+{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]},{"stepMatchArguments":[]}]}],"testRunStartedId":"6"}}
+{"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}}
+{"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"AMBIGUOUS","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testCaseFinished":{"testCaseStartedId":"9","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":5000000},"success":false}}
diff --git a/testdata/src/retry-ambiguous.xml b/testdata/src/retry-ambiguous.xml
new file mode 100644
index 0000000..86cec26
--- /dev/null
+++ b/testdata/src/retry-ambiguous.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/src/retry-pending.ndjson b/testdata/src/retry-pending.ndjson
new file mode 100644
index 0000000..21f00d7
--- /dev/null
+++ b/testdata/src/retry-pending.ndjson
@@ -0,0 +1,12 @@
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Retry - With Pending Steps\n Scenario: Test cases won't retry when the status is PENDING\n Given a pending step\n","uri":"samples/retry-pending/retry-pending.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry - With Pending Steps","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is PENDING","description":"","steps":[{"id":"0","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a pending step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry-pending/retry-pending.feature"}}
+{"pickle":{"id":"3","uri":"samples/retry-pending/retry-pending.feature","astNodeIds":["1"],"tags":[],"name":"Test cases won't retry when the status is PENDING","language":"en","steps":[{"id":"2","text":"a pending step","type":"Context","astNodeIds":["0"]}]}}
+{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a pending step"},"sourceReference":{"uri":"samples/retry-pending/retry-pending.ts","location":{"line":3}}}}
+{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"5"}}
+{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}}
+{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}}
diff --git a/testdata/src/retry-pending.xml b/testdata/src/retry-pending.xml
new file mode 100644
index 0000000..c5bd88c
--- /dev/null
+++ b/testdata/src/retry-pending.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/src/retry-undefined.ndjson b/testdata/src/retry-undefined.ndjson
new file mode 100644
index 0000000..1c0e31d
--- /dev/null
+++ b/testdata/src/retry-undefined.ndjson
@@ -0,0 +1,12 @@
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Retry - With Undefined Steps\n Scenario: Test cases won't retry when the status is UNDEFINED\n Given a non-existent step\n","uri":"samples/retry-undefined/retry-undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry - With Undefined Steps","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is UNDEFINED","description":"","steps":[{"id":"0","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a non-existent step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry-undefined/retry-undefined.feature"}}
+{"pickle":{"id":"3","uri":"samples/retry-undefined/retry-undefined.feature","astNodeIds":["1"],"tags":[],"name":"Test cases won't retry when the status is UNDEFINED","language":"en","steps":[{"id":"2","text":"a non-existent step","type":"Context","astNodeIds":["0"]}]}}
+{"testRunStarted":{"id":"4","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"5","pickleId":"3","testSteps":[{"id":"6","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"4"}}
+{"testCaseStarted":{"id":"7","testCaseId":"5","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"7","testStepId":"6","timestamp":{"seconds":0,"nanos":2000000}}}
+{"suggestion":{"id":"8","pickleStepId":"2","snippets":[{"language":"typescript","code":"Given(\"a non-existent step\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"7","testStepId":"6","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testCaseFinished":{"testCaseStartedId":"7","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"4","timestamp":{"seconds":0,"nanos":5000000},"success":false}}
diff --git a/testdata/src/retry-undefined.xml b/testdata/src/retry-undefined.xml
new file mode 100644
index 0000000..91a0b48
--- /dev/null
+++ b/testdata/src/retry-undefined.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/src/retry.ndjson b/testdata/src/retry.ndjson
index df7ae23..1bbc345 100644
--- a/testdata/src/retry.ndjson
+++ b/testdata/src/retry.ndjson
@@ -1,74 +1,53 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
-{"source":{"data":"Feature: Retry\n Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.\n\n Scenario: Test cases that pass aren't retried\n Given a step that always passes\n\n Scenario: Test cases that fail are retried if within the --retry limit\n Given a step that passes the second time\n\n Scenario: Test cases that fail will continue to retry up to the --retry limit\n Given a step that passes the third time\n\n Scenario: Test cases won't retry after failing more than the --retry limit\n Given a step that always fails\n\n Scenario: Test cases won't retry when the status is AMBIGUOUS\n Given an ambiguous step\n\n Scenario: Test cases won't retry when the status is PENDING\n Given a pending step\n\n Scenario: Test cases won't retry when the status is UNDEFINED\n Given a non-existent step\n","uri":"samples/retry/retry.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
-{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry","description":" Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":8,"column":3},"keyword":"Scenario","name":"Test cases that pass aren't retried","description":"","steps":[{"id":"0","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that always passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Test cases that fail are retried if within the --retry limit","description":"","steps":[{"id":"2","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that passes the second time"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":14,"column":3},"keyword":"Scenario","name":"Test cases that fail will continue to retry up to the --retry limit","description":"","steps":[{"id":"4","location":{"line":15,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that passes the third time"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Test cases won't retry after failing more than the --retry limit","description":"","steps":[{"id":"6","location":{"line":18,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that always fails"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":20,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is AMBIGUOUS","description":"","steps":[{"id":"8","location":{"line":21,"column":5},"keyword":"Given ","keywordType":"Context","text":"an ambiguous step"}],"examples":[]}},{"scenario":{"id":"11","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is PENDING","description":"","steps":[{"id":"10","location":{"line":24,"column":5},"keyword":"Given ","keywordType":"Context","text":"a pending step"}],"examples":[]}},{"scenario":{"id":"13","tags":[],"location":{"line":26,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is UNDEFINED","description":"","steps":[{"id":"12","location":{"line":27,"column":5},"keyword":"Given ","keywordType":"Context","text":"a non-existent step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry/retry.feature"}}
-{"pickle":{"id":"15","uri":"samples/retry/retry.feature","astNodeIds":["1"],"tags":[],"name":"Test cases that pass aren't retried","language":"en","steps":[{"id":"14","text":"a step that always passes","type":"Context","astNodeIds":["0"]}]}}
-{"pickle":{"id":"17","uri":"samples/retry/retry.feature","astNodeIds":["3"],"tags":[],"name":"Test cases that fail are retried if within the --retry limit","language":"en","steps":[{"id":"16","text":"a step that passes the second time","type":"Context","astNodeIds":["2"]}]}}
-{"pickle":{"id":"19","uri":"samples/retry/retry.feature","astNodeIds":["5"],"tags":[],"name":"Test cases that fail will continue to retry up to the --retry limit","language":"en","steps":[{"id":"18","text":"a step that passes the third time","type":"Context","astNodeIds":["4"]}]}}
-{"pickle":{"id":"21","uri":"samples/retry/retry.feature","astNodeIds":["7"],"tags":[],"name":"Test cases won't retry after failing more than the --retry limit","language":"en","steps":[{"id":"20","text":"a step that always fails","type":"Context","astNodeIds":["6"]}]}}
-{"pickle":{"id":"23","uri":"samples/retry/retry.feature","astNodeIds":["9"],"tags":[],"name":"Test cases won't retry when the status is AMBIGUOUS","language":"en","steps":[{"id":"22","text":"an ambiguous step","type":"Context","astNodeIds":["8"]}]}}
-{"pickle":{"id":"25","uri":"samples/retry/retry.feature","astNodeIds":["11"],"tags":[],"name":"Test cases won't retry when the status is PENDING","language":"en","steps":[{"id":"24","text":"a pending step","type":"Context","astNodeIds":["10"]}]}}
-{"pickle":{"id":"27","uri":"samples/retry/retry.feature","astNodeIds":["13"],"tags":[],"name":"Test cases won't retry when the status is UNDEFINED","language":"en","steps":[{"id":"26","text":"a non-existent step","type":"Context","astNodeIds":["12"]}]}}
-{"stepDefinition":{"id":"28","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that always passes"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":3}}}}
-{"stepDefinition":{"id":"29","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that passes the second time"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":8}}}}
-{"stepDefinition":{"id":"30","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that passes the third time"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":16}}}}
-{"stepDefinition":{"id":"31","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that always fails"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":23}}}}
-{"stepDefinition":{"id":"32","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an ambiguous step"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":27}}}}
-{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an ambiguous step"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":31}}}}
-{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a pending step"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":35}}}}
-{"testRunStarted":{"id":"35","timestamp":{"seconds":0,"nanos":0}}}
-{"testCase":{"id":"36","pickleId":"15","testSteps":[{"id":"37","pickleStepId":"14","stepDefinitionIds":["28"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}}
-{"testCase":{"id":"38","pickleId":"17","testSteps":[{"id":"39","pickleStepId":"16","stepDefinitionIds":["29"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}}
-{"testCase":{"id":"40","pickleId":"19","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}}
-{"testCase":{"id":"42","pickleId":"21","testSteps":[{"id":"43","pickleStepId":"20","stepDefinitionIds":["31"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}}
-{"testCase":{"id":"44","pickleId":"23","testSteps":[{"id":"45","pickleStepId":"22","stepDefinitionIds":["32","33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]},{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}}
-{"testCase":{"id":"46","pickleId":"25","testSteps":[{"id":"47","pickleStepId":"24","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}}
-{"testCase":{"id":"48","pickleId":"27","testSteps":[{"id":"49","pickleStepId":"26","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"35"}}
-{"testCaseStarted":{"id":"50","testCaseId":"36","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"50","testStepId":"37","timestamp":{"seconds":0,"nanos":2000000}}}
-{"testStepFinished":{"testCaseStartedId":"50","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
-{"testCaseFinished":{"testCaseStartedId":"50","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"51","testCaseId":"38","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"51","testStepId":"39","timestamp":{"seconds":0,"nanos":6000000}}}
-{"testStepFinished":{"testCaseStartedId":"51","testStepId":"39","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:12"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
-{"testCaseFinished":{"testCaseStartedId":"51","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":true}}
-{"testCaseStarted":{"id":"52","testCaseId":"38","timestamp":{"seconds":0,"nanos":9000000},"attempt":1}}
-{"testStepStarted":{"testCaseStartedId":"52","testStepId":"39","timestamp":{"seconds":0,"nanos":10000000}}}
-{"testStepFinished":{"testCaseStartedId":"52","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
-{"testCaseFinished":{"testCaseStartedId":"52","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"53","testCaseId":"40","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"53","testStepId":"41","timestamp":{"seconds":0,"nanos":14000000}}}
-{"testStepFinished":{"testCaseStartedId":"53","testStepId":"41","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:15"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}}
-{"testCaseFinished":{"testCaseStartedId":"53","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":true}}
-{"testCaseStarted":{"id":"54","testCaseId":"40","timestamp":{"seconds":0,"nanos":17000000},"attempt":1}}
-{"testStepStarted":{"testCaseStartedId":"54","testStepId":"41","timestamp":{"seconds":0,"nanos":18000000}}}
-{"testStepFinished":{"testCaseStartedId":"54","testStepId":"41","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:15"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
-{"testCaseFinished":{"testCaseStartedId":"54","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":true}}
-{"testCaseStarted":{"id":"55","testCaseId":"40","timestamp":{"seconds":0,"nanos":21000000},"attempt":2}}
-{"testStepStarted":{"testCaseStartedId":"55","testStepId":"41","timestamp":{"seconds":0,"nanos":22000000}}}
-{"testStepFinished":{"testCaseStartedId":"55","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}}
-{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"56","testCaseId":"42","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"56","testStepId":"43","timestamp":{"seconds":0,"nanos":26000000}}}
-{"testStepFinished":{"testCaseStartedId":"56","testStepId":"43","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}}
-{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":true}}
-{"testCaseStarted":{"id":"57","testCaseId":"42","timestamp":{"seconds":0,"nanos":29000000},"attempt":1}}
-{"testStepStarted":{"testCaseStartedId":"57","testStepId":"43","timestamp":{"seconds":0,"nanos":30000000}}}
-{"testStepFinished":{"testCaseStartedId":"57","testStepId":"43","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}}
-{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":true}}
-{"testCaseStarted":{"id":"58","testCaseId":"42","timestamp":{"seconds":0,"nanos":33000000},"attempt":2}}
-{"testStepStarted":{"testCaseStartedId":"58","testStepId":"43","timestamp":{"seconds":0,"nanos":34000000}}}
-{"testStepFinished":{"testCaseStartedId":"58","testStepId":"43","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}}
-{"testCaseFinished":{"testCaseStartedId":"58","timestamp":{"seconds":0,"nanos":36000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"59","testCaseId":"44","timestamp":{"seconds":0,"nanos":37000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"59","testStepId":"45","timestamp":{"seconds":0,"nanos":38000000}}}
-{"testStepFinished":{"testCaseStartedId":"59","testStepId":"45","testStepResult":{"status":"AMBIGUOUS","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":39000000}}}
-{"testCaseFinished":{"testCaseStartedId":"59","timestamp":{"seconds":0,"nanos":40000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"60","testCaseId":"46","timestamp":{"seconds":0,"nanos":41000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"60","testStepId":"47","timestamp":{"seconds":0,"nanos":42000000}}}
-{"testStepFinished":{"testCaseStartedId":"60","testStepId":"47","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":43000000}}}
-{"testCaseFinished":{"testCaseStartedId":"60","timestamp":{"seconds":0,"nanos":44000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"61","testCaseId":"48","timestamp":{"seconds":0,"nanos":45000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"61","testStepId":"49","timestamp":{"seconds":0,"nanos":46000000}}}
-{"testStepFinished":{"testCaseStartedId":"61","testStepId":"49","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":47000000}}}
-{"testCaseFinished":{"testCaseStartedId":"61","timestamp":{"seconds":0,"nanos":48000000},"willBeRetried":false}}
-{"testRunFinished":{"testRunStartedId":"35","timestamp":{"seconds":0,"nanos":49000000},"success":false}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Retry\n Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.\n\n Scenario: Test cases that pass aren't retried\n Given a step that always passes\n\n Scenario: Test cases that fail are retried if within the --retry limit\n Given a step that passes the second time\n\n Scenario: Test cases that fail will continue to retry up to the --retry limit\n Given a step that passes the third time\n\n Scenario: Test cases won't retry after failing more than the --retry limit\n Given a step that always fails\n","uri":"samples/retry/retry.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry","description":" Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":8,"column":3},"keyword":"Scenario","name":"Test cases that pass aren't retried","description":"","steps":[{"id":"0","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that always passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Test cases that fail are retried if within the --retry limit","description":"","steps":[{"id":"2","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that passes the second time"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":14,"column":3},"keyword":"Scenario","name":"Test cases that fail will continue to retry up to the --retry limit","description":"","steps":[{"id":"4","location":{"line":15,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that passes the third time"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Test cases won't retry after failing more than the --retry limit","description":"","steps":[{"id":"6","location":{"line":18,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that always fails"}],"examples":[]}}]},"comments":[],"uri":"samples/retry/retry.feature"}}
+{"pickle":{"id":"9","uri":"samples/retry/retry.feature","astNodeIds":["1"],"tags":[],"name":"Test cases that pass aren't retried","language":"en","steps":[{"id":"8","text":"a step that always passes","type":"Context","astNodeIds":["0"]}]}}
+{"pickle":{"id":"11","uri":"samples/retry/retry.feature","astNodeIds":["3"],"tags":[],"name":"Test cases that fail are retried if within the --retry limit","language":"en","steps":[{"id":"10","text":"a step that passes the second time","type":"Context","astNodeIds":["2"]}]}}
+{"pickle":{"id":"13","uri":"samples/retry/retry.feature","astNodeIds":["5"],"tags":[],"name":"Test cases that fail will continue to retry up to the --retry limit","language":"en","steps":[{"id":"12","text":"a step that passes the third time","type":"Context","astNodeIds":["4"]}]}}
+{"pickle":{"id":"15","uri":"samples/retry/retry.feature","astNodeIds":["7"],"tags":[],"name":"Test cases won't retry after failing more than the --retry limit","language":"en","steps":[{"id":"14","text":"a step that always fails","type":"Context","astNodeIds":["6"]}]}}
+{"stepDefinition":{"id":"16","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that always passes"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":3}}}}
+{"stepDefinition":{"id":"17","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that passes the second time"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":8}}}}
+{"stepDefinition":{"id":"18","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that passes the third time"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":16}}}}
+{"stepDefinition":{"id":"19","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that always fails"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":23}}}}
+{"testRunStarted":{"id":"20","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"21","pickleId":"9","testSteps":[{"id":"22","pickleStepId":"8","stepDefinitionIds":["16"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}}
+{"testCase":{"id":"23","pickleId":"11","testSteps":[{"id":"24","pickleStepId":"10","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}}
+{"testCase":{"id":"25","pickleId":"13","testSteps":[{"id":"26","pickleStepId":"12","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}}
+{"testCase":{"id":"27","pickleId":"15","testSteps":[{"id":"28","pickleStepId":"14","stepDefinitionIds":["19"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}}
+{"testCaseStarted":{"id":"29","testCaseId":"21","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"29","testStepId":"22","timestamp":{"seconds":0,"nanos":2000000}}}
+{"testStepFinished":{"testCaseStartedId":"29","testStepId":"22","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"30","testCaseId":"23","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"30","testStepId":"24","timestamp":{"seconds":0,"nanos":6000000}}}
+{"testStepFinished":{"testCaseStartedId":"30","testStepId":"24","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:12"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
+{"testCaseFinished":{"testCaseStartedId":"30","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":true}}
+{"testCaseStarted":{"id":"31","testCaseId":"23","timestamp":{"seconds":0,"nanos":9000000},"attempt":1}}
+{"testStepStarted":{"testCaseStartedId":"31","testStepId":"24","timestamp":{"seconds":0,"nanos":10000000}}}
+{"testStepFinished":{"testCaseStartedId":"31","testStepId":"24","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}}
+{"testCaseFinished":{"testCaseStartedId":"31","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"32","testCaseId":"25","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"32","testStepId":"26","timestamp":{"seconds":0,"nanos":14000000}}}
+{"testStepFinished":{"testCaseStartedId":"32","testStepId":"26","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:15"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}}
+{"testCaseFinished":{"testCaseStartedId":"32","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":true}}
+{"testCaseStarted":{"id":"33","testCaseId":"25","timestamp":{"seconds":0,"nanos":17000000},"attempt":1}}
+{"testStepStarted":{"testCaseStartedId":"33","testStepId":"26","timestamp":{"seconds":0,"nanos":18000000}}}
+{"testStepFinished":{"testCaseStartedId":"33","testStepId":"26","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:15"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
+{"testCaseFinished":{"testCaseStartedId":"33","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":true}}
+{"testCaseStarted":{"id":"34","testCaseId":"25","timestamp":{"seconds":0,"nanos":21000000},"attempt":2}}
+{"testStepStarted":{"testCaseStartedId":"34","testStepId":"26","timestamp":{"seconds":0,"nanos":22000000}}}
+{"testStepFinished":{"testCaseStartedId":"34","testStepId":"26","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}}
+{"testCaseFinished":{"testCaseStartedId":"34","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"35","testCaseId":"27","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"35","testStepId":"28","timestamp":{"seconds":0,"nanos":26000000}}}
+{"testStepFinished":{"testCaseStartedId":"35","testStepId":"28","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}}
+{"testCaseFinished":{"testCaseStartedId":"35","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":true}}
+{"testCaseStarted":{"id":"36","testCaseId":"27","timestamp":{"seconds":0,"nanos":29000000},"attempt":1}}
+{"testStepStarted":{"testCaseStartedId":"36","testStepId":"28","timestamp":{"seconds":0,"nanos":30000000}}}
+{"testStepFinished":{"testCaseStartedId":"36","testStepId":"28","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}}
+{"testCaseFinished":{"testCaseStartedId":"36","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":true}}
+{"testCaseStarted":{"id":"37","testCaseId":"27","timestamp":{"seconds":0,"nanos":33000000},"attempt":2}}
+{"testStepStarted":{"testCaseStartedId":"37","testStepId":"28","timestamp":{"seconds":0,"nanos":34000000}}}
+{"testStepFinished":{"testCaseStartedId":"37","testStepId":"28","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}}
+{"testCaseFinished":{"testCaseStartedId":"37","timestamp":{"seconds":0,"nanos":36000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"20","timestamp":{"seconds":0,"nanos":37000000},"success":false}}
diff --git a/testdata/src/retry.xml b/testdata/src/retry.xml
index 7f34eb8..55a11de 100644
--- a/testdata/src/retry.xml
+++ b/testdata/src/retry.xml
@@ -1,7 +1,7 @@
-
-
-
+
+
+
@@ -17,36 +17,6 @@ samples/retry/retry.feature:18]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/testdata/src/rules-backgrounds.ndjson b/testdata/src/rules-backgrounds.ndjson
index d256b6e..96eaa69 100644
--- a/testdata/src/rules-backgrounds.ndjson
+++ b/testdata/src/rules-backgrounds.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Rules with Backgrounds\n Like Features, Rules can also have Backgrounds, whose steps are prepended to those of each child Scenario. Only\n one Background at the Rule level is supported.\n\n It's even possible to have a Background at both Feature and Rule level, in which case they are concatenated.\n\n Background:\n Given an order for \"eggs\"\n And an order for \"milk\"\n And an order for \"bread\"\n\n Rule:\n Background:\n Given an order for \"batteries\"\n And an order for \"light bulbs\"\n\n Example: one scenario\n When an action\n Then an outcome\n\n Example: another scenario\n When an action\n Then an outcome","uri":"samples/rules-backgrounds/rules-backgrounds.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Rules with Backgrounds","description":" Like Features, Rules can also have Backgrounds, whose steps are prepended to those of each child Scenario. Only\n one Background at the Rule level is supported.\n\n It's even possible to have a Background at both Feature and Rule level, in which case they are concatenated.","children":[{"background":{"id":"3","location":{"line":7,"column":3},"keyword":"Background","name":"","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""},{"id":"1","location":{"line":9,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"milk\""},{"id":"2","location":{"line":10,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"bread\""}]}},{"rule":{"id":"13","location":{"line":12,"column":3},"keyword":"Rule","name":"","description":"","children":[{"background":{"id":"6","location":{"line":13,"column":5},"keyword":"Background","name":"","description":"","steps":[{"id":"4","location":{"line":14,"column":7},"keyword":"Given ","keywordType":"Context","text":"an order for \"batteries\""},{"id":"5","location":{"line":15,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"light bulbs\""}]}},{"scenario":{"id":"9","tags":[],"location":{"line":17,"column":5},"keyword":"Example","name":"one scenario","description":"","steps":[{"id":"7","location":{"line":18,"column":7},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"8","location":{"line":19,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}},{"scenario":{"id":"12","tags":[],"location":{"line":21,"column":5},"keyword":"Example","name":"another scenario","description":"","steps":[{"id":"10","location":{"line":22,"column":7},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"11","location":{"line":23,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}}],"tags":[]}}]},"comments":[],"uri":"samples/rules-backgrounds/rules-backgrounds.feature"}}
{"pickle":{"id":"21","uri":"samples/rules-backgrounds/rules-backgrounds.feature","astNodeIds":["9"],"tags":[],"name":"one scenario","language":"en","steps":[{"id":"14","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]},{"id":"15","text":"an order for \"milk\"","type":"Context","astNodeIds":["1"]},{"id":"16","text":"an order for \"bread\"","type":"Context","astNodeIds":["2"]},{"id":"17","text":"an order for \"batteries\"","type":"Context","astNodeIds":["4"]},{"id":"18","text":"an order for \"light bulbs\"","type":"Context","astNodeIds":["5"]},{"id":"19","text":"an action","type":"Action","astNodeIds":["7"]},{"id":"20","text":"an outcome","type":"Outcome","astNodeIds":["8"]}]}}
diff --git a/testdata/src/rules.ndjson b/testdata/src/rules.ndjson
index 81bb984..6431aba 100644
--- a/testdata/src/rules.ndjson
+++ b/testdata/src/rules.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Usage of a `Rule`\n You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.\n\n Rule: A sale cannot happen if the customer does not have enough money\n # Unhappy path\n Example: Not enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 125 cent chocolate bar\n Then the sale should not happen\n\n # Happy path\n Example: Enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 75 cent chocolate bar\n Then the sale should happen\n\n @some-tag\n Rule: a sale cannot happen if there is no stock\n # Unhappy path\n Example: No chocolates left\n Given the customer has 100 cents\n And there are no chocolate bars in stock\n When the customer tries to buy a 1 cent chocolate bar\n Then the sale should not happen\n","uri":"samples/rules/rules.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Usage of a `Rule`","description":" You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.","children":[{"rule":{"id":"10","location":{"line":7,"column":3},"keyword":"Rule","name":"A sale cannot happen if the customer does not have enough money","description":"","children":[{"scenario":{"id":"4","tags":[],"location":{"line":9,"column":5},"keyword":"Example","name":"Not enough money","description":"","steps":[{"id":"0","location":{"line":10,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"1","location":{"line":11,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"2","location":{"line":12,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 125 cent chocolate bar"},{"id":"3","location":{"line":13,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":16,"column":5},"keyword":"Example","name":"Enough money","description":"","steps":[{"id":"5","location":{"line":17,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"6","location":{"line":18,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"7","location":{"line":19,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 75 cent chocolate bar"},{"id":"8","location":{"line":20,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should happen"}],"examples":[]}}],"tags":[]}},{"rule":{"id":"17","location":{"line":23,"column":3},"keyword":"Rule","name":"a sale cannot happen if there is no stock","description":"","children":[{"scenario":{"id":"15","tags":[],"location":{"line":25,"column":5},"keyword":"Example","name":"No chocolates left","description":"","steps":[{"id":"11","location":{"line":26,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"12","location":{"line":27,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are no chocolate bars in stock"},{"id":"13","location":{"line":28,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 1 cent chocolate bar"},{"id":"14","location":{"line":29,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}}],"tags":[{"location":{"line":22,"column":3},"name":"@some-tag","id":"16"}]}}]},"comments":[{"location":{"line":8,"column":1},"text":" # Unhappy path"},{"location":{"line":15,"column":1},"text":" # Happy path"},{"location":{"line":24,"column":1},"text":" # Unhappy path"}],"uri":"samples/rules/rules.feature"}}
{"pickle":{"id":"22","uri":"samples/rules/rules.feature","astNodeIds":["4"],"tags":[],"name":"Not enough money","language":"en","steps":[{"id":"18","text":"the customer has 100 cents","type":"Context","astNodeIds":["0"]},{"id":"19","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["1"]},{"id":"20","text":"the customer tries to buy a 125 cent chocolate bar","type":"Action","astNodeIds":["2"]},{"id":"21","text":"the sale should not happen","type":"Outcome","astNodeIds":["3"]}]}}
diff --git a/testdata/src/skipped.ndjson b/testdata/src/skipped.ndjson
index 4c14170..a6832c2 100644
--- a/testdata/src/skipped.ndjson
+++ b/testdata/src/skipped.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Skipping scenarios\n\n Hooks and step definitions are able to signal at runtime that the scenario should\n be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED).\n\n This can be useful in certain situations e.g. the current environment doesn't have\n the right conditions for running a particular scenario.\n\n @skip\n Scenario: Skipping from a Before hook\n Given a step that is skipped\n\n Scenario: Skipping from a step doesn't affect the previous steps\n Given a step that does not skip\n And I skip a step\n\n Scenario: Skipping from a step causes the rest of the scenario to be skipped\n Given I skip a step\n And a step that is skipped\n","uri":"samples/skipped/skipped.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Skipping scenarios","description":" Hooks and step definitions are able to signal at runtime that the scenario should\n be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED).\n\n This can be useful in certain situations e.g. the current environment doesn't have\n the right conditions for running a particular scenario.","children":[{"scenario":{"id":"2","tags":[{"location":{"line":9,"column":3},"name":"@skip","id":"1"}],"location":{"line":10,"column":3},"keyword":"Scenario","name":"Skipping from a Before hook","description":"","steps":[{"id":"0","location":{"line":11,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is skipped"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":13,"column":3},"keyword":"Scenario","name":"Skipping from a step doesn't affect the previous steps","description":"","steps":[{"id":"3","location":{"line":14,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that does not skip"},{"id":"4","location":{"line":15,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"I skip a step"}],"examples":[]}},{"scenario":{"id":"8","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Skipping from a step causes the rest of the scenario to be skipped","description":"","steps":[{"id":"6","location":{"line":18,"column":5},"keyword":"Given ","keywordType":"Context","text":"I skip a step"},{"id":"7","location":{"line":19,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that is skipped"}],"examples":[]}}]},"comments":[],"uri":"samples/skipped/skipped.feature"}}
{"pickle":{"id":"10","uri":"samples/skipped/skipped.feature","astNodeIds":["2"],"tags":[{"name":"@skip","astNodeId":"1"}],"name":"Skipping from a Before hook","language":"en","steps":[{"id":"9","text":"a step that is skipped","type":"Context","astNodeIds":["0"]}]}}
diff --git a/testdata/src/stack-traces.ndjson b/testdata/src/stack-traces.ndjson
index e316f13..c646fee 100644
--- a/testdata/src/stack-traces.ndjson
+++ b/testdata/src/stack-traces.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Stack traces\n Stack traces can help you diagnose the source of a bug.\n\n Cucumber provides helpful stack traces that includes the stack frames from the\n Gherkin document and remove uninteresting frames by default.\n\n The first line of the stack trace will contain a reference to the feature file.\n\n Scenario: A failing step\n When a step throws an exception\n","uri":"samples/stack-traces/stack-traces.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Stack traces","description":" Stack traces can help you diagnose the source of a bug.\n\n Cucumber provides helpful stack traces that includes the stack frames from the\n Gherkin document and remove uninteresting frames by default.\n\n The first line of the stack trace will contain a reference to the feature file.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"A failing step","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"When ","keywordType":"Action","text":"a step throws an exception"}],"examples":[]}}]},"comments":[],"uri":"samples/stack-traces/stack-traces.feature"}}
{"pickle":{"id":"3","uri":"samples/stack-traces/stack-traces.feature","astNodeIds":["1"],"tags":[],"name":"A failing step","language":"en","steps":[{"id":"2","text":"a step throws an exception","type":"Action","astNodeIds":["0"]}]}}
diff --git a/testdata/src/undefined.ndjson b/testdata/src/undefined.ndjson
index 2e0d15e..59e8c46 100644
--- a/testdata/src/undefined.ndjson
+++ b/testdata/src/undefined.ndjson
@@ -1,29 +1,39 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
-{"source":{"data":"Feature: Undefined steps\n\n At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition.\n\n In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with\n subsequent steps being SKIPPED and the overall result will be FAILURE.\n\n Scenario: An undefined step causes a failure\n Given a step that is yet to be defined\n\n Scenario: Steps before undefined steps are executed\n Given an implemented step\n And a step that is yet to be defined\n\n Scenario: Steps after undefined steps are skipped\n Given a step that is yet to be defined\n And a step that will be skipped\n","uri":"samples/undefined/undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
-{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Undefined steps","description":" At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition.\n\n In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with\n subsequent steps being SKIPPED and the overall result will be FAILURE.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":8,"column":3},"keyword":"Scenario","name":"An undefined step causes a failure","description":"","steps":[{"id":"0","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is yet to be defined"}],"examples":[]}},{"scenario":{"id":"4","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Steps before undefined steps are executed","description":"","steps":[{"id":"2","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"an implemented step"},{"id":"3","location":{"line":13,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that is yet to be defined"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":15,"column":3},"keyword":"Scenario","name":"Steps after undefined steps are skipped","description":"","steps":[{"id":"5","location":{"line":16,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is yet to be defined"},{"id":"6","location":{"line":17,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that will be skipped"}],"examples":[]}}]},"comments":[],"uri":"samples/undefined/undefined.feature"}}
-{"pickle":{"id":"9","uri":"samples/undefined/undefined.feature","astNodeIds":["1"],"tags":[],"name":"An undefined step causes a failure","language":"en","steps":[{"id":"8","text":"a step that is yet to be defined","type":"Context","astNodeIds":["0"]}]}}
-{"pickle":{"id":"12","uri":"samples/undefined/undefined.feature","astNodeIds":["4"],"tags":[],"name":"Steps before undefined steps are executed","language":"en","steps":[{"id":"10","text":"an implemented step","type":"Context","astNodeIds":["2"]},{"id":"11","text":"a step that is yet to be defined","type":"Context","astNodeIds":["3"]}]}}
-{"pickle":{"id":"15","uri":"samples/undefined/undefined.feature","astNodeIds":["7"],"tags":[],"name":"Steps after undefined steps are skipped","language":"en","steps":[{"id":"13","text":"a step that is yet to be defined","type":"Context","astNodeIds":["5"]},{"id":"14","text":"a step that will be skipped","type":"Context","astNodeIds":["6"]}]}}
-{"stepDefinition":{"id":"16","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an implemented step"},"sourceReference":{"uri":"samples/undefined/undefined.ts","location":{"line":3}}}}
-{"stepDefinition":{"id":"17","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that will be skipped"},"sourceReference":{"uri":"samples/undefined/undefined.ts","location":{"line":7}}}}
-{"testRunStarted":{"id":"18","timestamp":{"seconds":0,"nanos":0}}}
-{"testCase":{"id":"19","pickleId":"9","testSteps":[{"id":"20","pickleStepId":"8","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"18"}}
-{"testCase":{"id":"21","pickleId":"12","testSteps":[{"id":"22","pickleStepId":"10","stepDefinitionIds":["16"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"23","pickleStepId":"11","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"18"}}
-{"testCase":{"id":"24","pickleId":"15","testSteps":[{"id":"25","pickleStepId":"13","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"26","pickleStepId":"14","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"18"}}
-{"testCaseStarted":{"id":"27","testCaseId":"19","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"27","testStepId":"20","timestamp":{"seconds":0,"nanos":2000000}}}
-{"testStepFinished":{"testCaseStartedId":"27","testStepId":"20","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
-{"testCaseFinished":{"testCaseStartedId":"27","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"28","testCaseId":"21","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"28","testStepId":"22","timestamp":{"seconds":0,"nanos":6000000}}}
-{"testStepFinished":{"testCaseStartedId":"28","testStepId":"22","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
-{"testStepStarted":{"testCaseStartedId":"28","testStepId":"23","timestamp":{"seconds":0,"nanos":8000000}}}
-{"testStepFinished":{"testCaseStartedId":"28","testStepId":"23","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":9000000}}}
-{"testCaseFinished":{"testCaseStartedId":"28","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}}
-{"testCaseStarted":{"id":"29","testCaseId":"24","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}}
-{"testStepStarted":{"testCaseStartedId":"29","testStepId":"25","timestamp":{"seconds":0,"nanos":12000000}}}
-{"testStepFinished":{"testCaseStartedId":"29","testStepId":"25","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":13000000}}}
-{"testStepStarted":{"testCaseStartedId":"29","testStepId":"26","timestamp":{"seconds":0,"nanos":14000000}}}
-{"testStepFinished":{"testCaseStartedId":"29","testStepId":"26","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}}
-{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
-{"testRunFinished":{"testRunStartedId":"18","timestamp":{"seconds":0,"nanos":17000000},"success":false}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Undefined steps\n\n At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition.\n\n In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with\n subsequent steps being SKIPPED and the overall result will be FAILURE.\n\n Scenario: An undefined step causes a failure\n Given a step that is yet to be defined\n\n Scenario: Steps before undefined steps are executed\n Given an implemented step\n And a step that is yet to be defined\n\n Scenario: Steps after undefined steps are skipped\n Given a step that is yet to be defined\n And a step that will be skipped\n\n Scenario: Snippets reflect parameter types\n Given a list of 8 things\n","uri":"samples/undefined/undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Undefined steps","description":" At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition.\n\n In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with\n subsequent steps being SKIPPED and the overall result will be FAILURE.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":8,"column":3},"keyword":"Scenario","name":"An undefined step causes a failure","description":"","steps":[{"id":"0","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is yet to be defined"}],"examples":[]}},{"scenario":{"id":"4","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Steps before undefined steps are executed","description":"","steps":[{"id":"2","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"an implemented step"},{"id":"3","location":{"line":13,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that is yet to be defined"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":15,"column":3},"keyword":"Scenario","name":"Steps after undefined steps are skipped","description":"","steps":[{"id":"5","location":{"line":16,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is yet to be defined"},{"id":"6","location":{"line":17,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that will be skipped"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":19,"column":3},"keyword":"Scenario","name":"Snippets reflect parameter types","description":"","steps":[{"id":"8","location":{"line":20,"column":5},"keyword":"Given ","keywordType":"Context","text":"a list of 8 things"}],"examples":[]}}]},"comments":[],"uri":"samples/undefined/undefined.feature"}}
+{"pickle":{"id":"11","uri":"samples/undefined/undefined.feature","astNodeIds":["1"],"tags":[],"name":"An undefined step causes a failure","language":"en","steps":[{"id":"10","text":"a step that is yet to be defined","type":"Context","astNodeIds":["0"]}]}}
+{"pickle":{"id":"14","uri":"samples/undefined/undefined.feature","astNodeIds":["4"],"tags":[],"name":"Steps before undefined steps are executed","language":"en","steps":[{"id":"12","text":"an implemented step","type":"Context","astNodeIds":["2"]},{"id":"13","text":"a step that is yet to be defined","type":"Context","astNodeIds":["3"]}]}}
+{"pickle":{"id":"17","uri":"samples/undefined/undefined.feature","astNodeIds":["7"],"tags":[],"name":"Steps after undefined steps are skipped","language":"en","steps":[{"id":"15","text":"a step that is yet to be defined","type":"Context","astNodeIds":["5"]},{"id":"16","text":"a step that will be skipped","type":"Context","astNodeIds":["6"]}]}}
+{"pickle":{"id":"19","uri":"samples/undefined/undefined.feature","astNodeIds":["9"],"tags":[],"name":"Snippets reflect parameter types","language":"en","steps":[{"id":"18","text":"a list of 8 things","type":"Context","astNodeIds":["8"]}]}}
+{"stepDefinition":{"id":"20","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an implemented step"},"sourceReference":{"uri":"samples/undefined/undefined.ts","location":{"line":3}}}}
+{"stepDefinition":{"id":"21","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that will be skipped"},"sourceReference":{"uri":"samples/undefined/undefined.ts","location":{"line":7}}}}
+{"testRunStarted":{"id":"22","timestamp":{"seconds":0,"nanos":0}}}
+{"testCase":{"id":"23","pickleId":"11","testSteps":[{"id":"24","pickleStepId":"10","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"22"}}
+{"testCase":{"id":"25","pickleId":"14","testSteps":[{"id":"26","pickleStepId":"12","stepDefinitionIds":["20"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"27","pickleStepId":"13","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"22"}}
+{"testCase":{"id":"28","pickleId":"17","testSteps":[{"id":"29","pickleStepId":"15","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"30","pickleStepId":"16","stepDefinitionIds":["21"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"22"}}
+{"testCase":{"id":"31","pickleId":"19","testSteps":[{"id":"32","pickleStepId":"18","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"22"}}
+{"testCaseStarted":{"id":"33","testCaseId":"23","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"33","testStepId":"24","timestamp":{"seconds":0,"nanos":2000000}}}
+{"suggestion":{"id":"34","pickleStepId":"10","snippets":[{"language":"typescript","code":"Given(\"a step that is yet to be defined\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"33","testStepId":"24","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
+{"testCaseFinished":{"testCaseStartedId":"33","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"35","testCaseId":"25","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"35","testStepId":"26","timestamp":{"seconds":0,"nanos":6000000}}}
+{"testStepFinished":{"testCaseStartedId":"35","testStepId":"26","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
+{"testStepStarted":{"testCaseStartedId":"35","testStepId":"27","timestamp":{"seconds":0,"nanos":8000000}}}
+{"suggestion":{"id":"36","pickleStepId":"13","snippets":[{"language":"typescript","code":"Given(\"a step that is yet to be defined\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"35","testStepId":"27","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":9000000}}}
+{"testCaseFinished":{"testCaseStartedId":"35","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"37","testCaseId":"28","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"37","testStepId":"29","timestamp":{"seconds":0,"nanos":12000000}}}
+{"suggestion":{"id":"38","pickleStepId":"15","snippets":[{"language":"typescript","code":"Given(\"a step that is yet to be defined\", () => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"37","testStepId":"29","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":13000000}}}
+{"testStepStarted":{"testCaseStartedId":"37","testStepId":"30","timestamp":{"seconds":0,"nanos":14000000}}}
+{"testStepFinished":{"testCaseStartedId":"37","testStepId":"30","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}}
+{"testCaseFinished":{"testCaseStartedId":"37","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
+{"testCaseStarted":{"id":"39","testCaseId":"31","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}}
+{"testStepStarted":{"testCaseStartedId":"39","testStepId":"32","timestamp":{"seconds":0,"nanos":18000000}}}
+{"suggestion":{"id":"40","pickleStepId":"18","snippets":[{"language":"typescript","code":"Given(\"a list of {int} things\", (int: number) => {\n return \"pending\"\n})"},{"language":"typescript","code":"Given(\"a list of {float} things\", (float: number) => {\n return \"pending\"\n})"}]}}
+{"testStepFinished":{"testCaseStartedId":"39","testStepId":"32","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":19000000}}}
+{"testCaseFinished":{"testCaseStartedId":"39","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}}
+{"testRunFinished":{"testRunStartedId":"22","timestamp":{"seconds":0,"nanos":21000000},"success":false}}
diff --git a/testdata/src/undefined.xml b/testdata/src/undefined.xml
index be79bd8..7800827 100644
--- a/testdata/src/undefined.xml
+++ b/testdata/src/undefined.xml
@@ -1,7 +1,7 @@
-
-
-
+
+
+
@@ -35,6 +35,16 @@ And a step that will be skipped.............................................skip
+
+
+
+
+
+
+
+
diff --git a/testdata/src/unknown-parameter-type.ndjson b/testdata/src/unknown-parameter-type.ndjson
index 6510b74..0bd3002 100644
--- a/testdata/src/unknown-parameter-type.ndjson
+++ b/testdata/src/unknown-parameter-type.ndjson
@@ -1,12 +1,13 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
-{"source":{"data":"Feature: Parameter Types\n Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run.\n\n Scenario: undefined parameter type\n Given CDG is closed because of a strike\n","uri":"samples/unknown-parameter-type/unknown-parameter-type.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
-{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Parameter Types","description":" Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"undefined parameter type","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"CDG is closed because of a strike"}],"examples":[]}}]},"comments":[],"uri":"samples/unknown-parameter-type/unknown-parameter-type.feature"}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"source":{"data":"Feature: Parameter Types\n Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run. Additionally, because\n the step is effectively undefined, a suggestion will also be created.\n\n Scenario: undefined parameter type\n Given CDG is closed because of a strike\n","uri":"samples/unknown-parameter-type/unknown-parameter-type.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
+{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Parameter Types","description":" Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run. Additionally, because\n the step is effectively undefined, a suggestion will also be created.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"undefined parameter type","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"CDG is closed because of a strike"}],"examples":[]}}]},"comments":[],"uri":"samples/unknown-parameter-type/unknown-parameter-type.feature"}}
{"pickle":{"id":"3","uri":"samples/unknown-parameter-type/unknown-parameter-type.feature","astNodeIds":["1"],"tags":[],"name":"undefined parameter type","language":"en","steps":[{"id":"2","text":"CDG is closed because of a strike","type":"Context","astNodeIds":["0"]}]}}
{"undefinedParameterType":{"name":"airport","expression":"{airport} is closed because of a strike"}}
{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}}
{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"5"}}
{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}}
+{"suggestion":{"id":"9","pickleStepId":"2","snippets":[{"language":"typescript","code":"Given(\"CDG is closed because of a strike\", () => {\n return \"pending\"\n})"}]}}
{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}}
diff --git a/testdata/src/unused-steps.ndjson b/testdata/src/unused-steps.ndjson
index d1fcfdf..39182bd 100644
--- a/testdata/src/unused-steps.ndjson
+++ b/testdata/src/unused-steps.ndjson
@@ -1,4 +1,4 @@
-{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
+{"meta":{"protocolVersion":"29.0.1","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Unused steps\n Depending on the run, some step definitions may not be used. This is valid, and the step definitions are still\n includes in the stream of messages, which allows formatters to report on step usage if desired.\n\n Scenario: a scenario\n Given a step that is used\n","uri":"samples/unused-steps/unused-steps.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Unused steps","description":" Depending on the run, some step definitions may not be used. This is valid, and the step definitions are still\n includes in the stream of messages, which allows formatters to report on step usage if desired.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is used"}],"examples":[]}}]},"comments":[],"uri":"samples/unused-steps/unused-steps.feature"}}
{"pickle":{"id":"3","uri":"samples/unused-steps/unused-steps.feature","astNodeIds":["1"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"2","text":"a step that is used","type":"Context","astNodeIds":["0"]}]}}