doesitfire 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. doesitfire-0.1.2/.gitignore +13 -0
  2. doesitfire-0.1.2/CHANGELOG.md +48 -0
  3. doesitfire-0.1.2/LICENSE +21 -0
  4. doesitfire-0.1.2/PKG-INFO +306 -0
  5. doesitfire-0.1.2/README.md +278 -0
  6. doesitfire-0.1.2/examples/doesitfire.mistral.yaml +51 -0
  7. doesitfire-0.1.2/examples/doesitfire.yaml +48 -0
  8. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-fire-r0-6d69a44f.jsonl +3898 -0
  9. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-fire-r0-e409dfa5.jsonl +3388 -0
  10. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-fire-r0-edbcb326.jsonl +593 -0
  11. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-fire-r0-f2fb5411.jsonl +3761 -0
  12. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-no_fire-r0-5212ab0a.jsonl +758 -0
  13. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-no_fire-r0-9080e467.jsonl +648 -0
  14. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-no_fire-r0-d98b2514.jsonl +1023 -0
  15. doesitfire-0.1.2/examples/recorded/logs/claude-code-commit-message-no_fire-r0-fa668f55.jsonl +721 -0
  16. doesitfire-0.1.2/examples/recorded/logs/claude-code-regex-explainer-fire-r0-acdcfc02.jsonl +1533 -0
  17. doesitfire-0.1.2/examples/recorded/logs/claude-code-regex-explainer-fire-r0-b575a6ef.jsonl +5605 -0
  18. doesitfire-0.1.2/examples/recorded/logs/claude-code-regex-explainer-fire-r0-d87944e8.jsonl +4564 -0
  19. doesitfire-0.1.2/examples/recorded/logs/claude-code-regex-explainer-fire-r0-ec2fff5e.jsonl +1957 -0
  20. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r0-6d69a44f.jsonl +4 -0
  21. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r0-e409dfa5.jsonl +4 -0
  22. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r0-edbcb326.jsonl +4 -0
  23. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r0-f2fb5411.jsonl +4 -0
  24. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r1-6d69a44f.jsonl +4 -0
  25. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r1-e409dfa5.jsonl +4 -0
  26. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r1-edbcb326.jsonl +4 -0
  27. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r1-f2fb5411.jsonl +4 -0
  28. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r2-6d69a44f.jsonl +4 -0
  29. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r2-e409dfa5.jsonl +4 -0
  30. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r2-edbcb326.jsonl +4 -0
  31. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-fire-r2-f2fb5411.jsonl +4 -0
  32. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r0-5212ab0a.jsonl +4 -0
  33. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r0-9080e467.jsonl +3 -0
  34. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r0-d98b2514.jsonl +3 -0
  35. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r0-fa668f55.jsonl +3 -0
  36. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r1-5212ab0a.jsonl +4 -0
  37. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r1-9080e467.jsonl +3 -0
  38. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r1-d98b2514.jsonl +3 -0
  39. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r1-fa668f55.jsonl +3 -0
  40. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r2-5212ab0a.jsonl +4 -0
  41. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r2-9080e467.jsonl +3 -0
  42. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r2-d98b2514.jsonl +3 -0
  43. doesitfire-0.1.2/examples/recorded/logs/opencode-commit-message-no_fire-r2-fa668f55.jsonl +3 -0
  44. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r0-00fe9152.jsonl +4 -0
  45. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r0-39ed1761.jsonl +4 -0
  46. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r0-4420d371.jsonl +4 -0
  47. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r0-e38f97a6.jsonl +4 -0
  48. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r1-00fe9152.jsonl +4 -0
  49. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r1-39ed1761.jsonl +4 -0
  50. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r1-4420d371.jsonl +4 -0
  51. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r1-e38f97a6.jsonl +4 -0
  52. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r2-00fe9152.jsonl +4 -0
  53. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r2-39ed1761.jsonl +4 -0
  54. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r2-4420d371.jsonl +4 -0
  55. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-fire-r2-e38f97a6.jsonl +4 -0
  56. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r0-1f198403.jsonl +4 -0
  57. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r0-3284c6af.jsonl +4 -0
  58. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r0-db1d220c.jsonl +3 -0
  59. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r0-fd36fd0d.jsonl +3 -0
  60. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r1-1f198403.jsonl +4 -0
  61. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r1-3284c6af.jsonl +4 -0
  62. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r1-db1d220c.jsonl +3 -0
  63. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r1-fd36fd0d.jsonl +3 -0
  64. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r2-1f198403.jsonl +4 -0
  65. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r2-3284c6af.jsonl +3 -0
  66. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r2-db1d220c.jsonl +3 -0
  67. doesitfire-0.1.2/examples/recorded/logs/opencode-dockerfile-review-no_fire-r2-fd36fd0d.jsonl +3 -0
  68. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r0-acdcfc02.jsonl +4 -0
  69. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r0-b575a6ef.jsonl +4 -0
  70. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r0-d87944e8.jsonl +4 -0
  71. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r0-ec2fff5e.jsonl +4 -0
  72. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r1-acdcfc02.jsonl +4 -0
  73. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r1-b575a6ef.jsonl +4 -0
  74. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r1-d87944e8.jsonl +4 -0
  75. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r1-ec2fff5e.jsonl +4 -0
  76. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r2-acdcfc02.jsonl +4 -0
  77. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r2-b575a6ef.jsonl +4 -0
  78. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r2-d87944e8.jsonl +4 -0
  79. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-fire-r2-ec2fff5e.jsonl +4 -0
  80. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r0-7071a2c0.jsonl +3 -0
  81. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r0-95c9e10d.jsonl +3 -0
  82. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r0-b75ba907.jsonl +3 -0
  83. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r0-be76a2dc.jsonl +3 -0
  84. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r1-7071a2c0.jsonl +3 -0
  85. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r1-95c9e10d.jsonl +3 -0
  86. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r1-b75ba907.jsonl +3 -0
  87. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r1-be76a2dc.jsonl +3 -0
  88. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r2-7071a2c0.jsonl +3 -0
  89. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r2-95c9e10d.jsonl +3 -0
  90. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r2-b75ba907.jsonl +3 -0
  91. doesitfire-0.1.2/examples/recorded/logs/opencode-regex-explainer-no_fire-r2-be76a2dc.jsonl +3 -0
  92. doesitfire-0.1.2/examples/recorded/results.json +1393 -0
  93. doesitfire-0.1.2/examples/skills/commit-message/SKILL.md +16 -0
  94. doesitfire-0.1.2/examples/skills/dockerfile-review/SKILL.md +15 -0
  95. doesitfire-0.1.2/examples/skills/regex-explainer/SKILL.md +14 -0
  96. doesitfire-0.1.2/examples/workflows/doesitfire.yml +38 -0
  97. doesitfire-0.1.2/plugin/README.md +23 -0
  98. doesitfire-0.1.2/pyproject.toml +65 -0
  99. doesitfire-0.1.2/src/doesitfire/__init__.py +3 -0
  100. doesitfire-0.1.2/src/doesitfire/agents.py +310 -0
  101. doesitfire-0.1.2/src/doesitfire/cli.py +263 -0
  102. doesitfire-0.1.2/src/doesitfire/config.py +251 -0
  103. doesitfire-0.1.2/src/doesitfire/data/demo-results.json +1393 -0
  104. doesitfire-0.1.2/src/doesitfire/docker/Dockerfile +24 -0
  105. doesitfire-0.1.2/src/doesitfire/docker/egress-proxy.mjs +113 -0
  106. doesitfire-0.1.2/src/doesitfire/docker/entrypoint.sh +14 -0
  107. doesitfire-0.1.2/src/doesitfire/errors.py +17 -0
  108. doesitfire-0.1.2/src/doesitfire/html.py +26 -0
  109. doesitfire-0.1.2/src/doesitfire/metrics.py +117 -0
  110. doesitfire-0.1.2/src/doesitfire/providers.py +121 -0
  111. doesitfire-0.1.2/src/doesitfire/report.py +175 -0
  112. doesitfire-0.1.2/src/doesitfire/runner.py +283 -0
  113. doesitfire-0.1.2/src/doesitfire/sandbox.py +276 -0
  114. doesitfire-0.1.2/src/doesitfire/templates/report.html +341 -0
  115. doesitfire-0.1.2/tests/conftest.py +75 -0
  116. doesitfire-0.1.2/tests/fixtures/events/claude-code-fire.jsonl +6 -0
  117. doesitfire-0.1.2/tests/fixtures/events/claude-code-nofire.jsonl +3 -0
  118. doesitfire-0.1.2/tests/fixtures/events/codex-fire.jsonl +7 -0
  119. doesitfire-0.1.2/tests/fixtures/events/codex-nofire.jsonl +5 -0
  120. doesitfire-0.1.2/tests/fixtures/events/opencode-fire.jsonl +6 -0
  121. doesitfire-0.1.2/tests/fixtures/events/opencode-nofire.jsonl +3 -0
  122. doesitfire-0.1.2/tests/fixtures/mock-suite.yaml +15 -0
  123. doesitfire-0.1.2/tests/mockmodel.py +345 -0
  124. doesitfire-0.1.2/tests/test_agents.py +141 -0
  125. doesitfire-0.1.2/tests/test_config.py +121 -0
  126. doesitfire-0.1.2/tests/test_integration_docker.py +37 -0
  127. doesitfire-0.1.2/tests/test_metrics_reports.py +68 -0
  128. doesitfire-0.1.2/tests/test_providers.py +32 -0
  129. doesitfire-0.1.2/tests/test_runner_cli.py +252 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .env
10
+ doesitfire-results/
11
+ demo-out/
12
+ site/
13
+ .DS_Store
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.2] - 2026-11-04
8
+
9
+ ### Added
10
+ - Claude Code plugin (`/plugin install doesitfire --marketplace diegoperezg7/doesitfire`) with the `test-skill-activation` skill.
11
+
12
+ ## [0.1.1] - 2026-11-04
13
+
14
+ ### Changed
15
+ - HTML report: numbers, percentages and money are formatted with `Intl.NumberFormat` for the active language
16
+ (EN `$0.05`, ES `0,05 US$`).
17
+ - The report lists the price used for each paid agent, with its source and the date it was checked.
18
+ - `results.json` records the configured price per agent.
19
+
20
+ ### Fixed
21
+ - Recorded demo cost. v0.1.0 used an unverified price of 0.10 USD per million tokens for Mistral
22
+ `ministral-8b-latest`. The verified price is 0.15 USD per million input and output tokens
23
+ (`ministral-8b-latest` is `ministral-8b-2512`; [docs.mistral.ai](https://docs.mistral.ai/models/ministral-3-8b-25-12),
24
+ checked 2026-11-04). Recomputed from the recorded token usage without re-running: 361,490 tokens, **0.054 USD**
25
+ (v0.1.0 said 0.036 USD). The activation results do not change.
26
+ - `examples/doesitfire.mistral.yaml` uses the verified price.
27
+
28
+ ## [0.1.0] - 2026-11-03
29
+
30
+ ### Added
31
+ - `doesitfire run`: runs Claude Code 2.1.317, Codex 0.166.0 and OpenCode 1.19.5 headless in Docker,
32
+ N repeats per prompt (default 3), and reports activation per agent and skill: recall, false-fire
33
+ rate and precision.
34
+ - Activation detection: Claude Code `Skill` tool call, OpenCode `skill` tool call, Codex reading the
35
+ skill's `SKILL.md` (experimental: Codex has no activation event).
36
+ - Sandbox: internal Docker network with an egress proxy that only lets the model API through,
37
+ side-effect tools denied, turn cap, CPU, memory, pids and time limits, read-only root filesystem.
38
+ - Providers: Ollama (free, local), Anthropic, OpenAI, OpenRouter and Mistral, keys from env vars only.
39
+ - `--max-cost` budget guard (default 1 USD), checked before every run.
40
+ - Outputs: terminal matrix, `results.json`, JUnit XML, static HTML report (EN/ES, light/dark),
41
+ badge SVG and shields.io endpoint JSON.
42
+ - `doesitfire init` scaffolds `doesitfire.yaml` from the skills found in a repo.
43
+ - `doesitfire demo` shows a recorded real run of three example skills.
44
+ - Composite GitHub Action and an example workflow.
45
+
46
+ [0.1.2]: https://github.com/diegoperezg7/doesitfire/releases/tag/v0.1.2
47
+ [0.1.1]: https://github.com/diegoperezg7/doesitfire/releases/tag/v0.1.1
48
+ [0.1.0]: https://github.com/diegoperezg7/doesitfire/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Diego Pérez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.5
2
+ Name: doesitfire
3
+ Version: 0.1.2
4
+ Summary: Does your SKILL.md actually fire? Test skill activation in Claude Code, Codex and OpenCode with one command.
5
+ Project-URL: Homepage, https://github.com/diegoperezg7/doesitfire
6
+ Project-URL: Repository, https://github.com/diegoperezg7/doesitfire
7
+ Project-URL: Issues, https://github.com/diegoperezg7/doesitfire/issues
8
+ Project-URL: Changelog, https://github.com/diegoperezg7/doesitfire/blob/main/CHANGELOG.md
9
+ Author: Diego Pérez
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent-skills,claude-code,codex,evaluation,llm,opencode,skill-md,testing
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: openai==3.42.1
24
+ Requires-Dist: pyyaml==6.0.3
25
+ Requires-Dist: rich==15.0.0
26
+ Requires-Dist: typer==0.27.3
27
+ Description-Content-Type: text/markdown
28
+
29
+ <p align="center">
30
+ <img src="docs/assets/banner.svg" alt="doesitfire" width="560">
31
+ </p>
32
+
33
+ <p align="center">
34
+ <a href="https://github.com/diegoperezg7/doesitfire/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/diegoperezg7/doesitfire/actions/workflows/ci.yml/badge.svg"></a>
35
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-a1319b"></a>
36
+ <a href="https://github.com/diegoperezg7/doesitfire/releases"><img alt="Release" src="https://img.shields.io/github/v/release/diegoperezg7/doesitfire?color=a1319b"></a>
37
+ <a href="https://github.com/diegoperezg7/doesitfire/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/diegoperezg7/doesitfire?style=flat&color=a1319b"></a>
38
+ </p>
39
+
40
+ <p align="center"><b>Does your SKILL.md actually fire? Test skill activation in Claude Code, Codex and OpenCode with one command.</b></p>
41
+
42
+ <p align="center"><a href="README.es.md">Leer en español</a> · <a href="https://diegoperezg7.github.io/doesitfire/">Example report</a></p>
43
+
44
+ <p align="center"><img src="docs/assets/demo.gif" alt="doesitfire demo in a terminal" width="820"></p>
45
+
46
+ You wrote a skill. Its `description` is the only thing the agent sees when it decides whether to load it.
47
+ Does Claude Code load it when a user asks for it? Does Codex? Does OpenCode? Does it also load when nobody asked?
48
+ doesitfire runs the real agent CLIs on prompts that should and should not trigger each skill, a few times each,
49
+ and gives you one activation matrix per agent.
50
+
51
+ ## Why
52
+
53
+ Other tools test part of this:
54
+
55
+ | Tool | What it measures | Agents |
56
+ |---|---|---|
57
+ | [microsoft/waza](https://github.com/microsoft/waza) (1,499★) | Has trigger tests, run through its GitHub Copilot executor | GitHub Copilot |
58
+ | `claude plugin eval` (built into the Claude Code CLI) | Eval cases with graders against a plugin or skills dir | Claude Code only |
59
+ | [darkrishabh/agent-skills-eval](https://github.com/darkrishabh/agent-skills-eval) (809★) | Output quality with vs without the skill injected | Not activation |
60
+ | [NVIDIA/SkillEvaluator](https://github.com/NVIDIA/SkillEvaluator) (626★) | Output quality with vs without the skill injected | Not activation |
61
+ | **doesitfire** | **Whether the real agent loads the skill on its own**, and how often it loads it when it should not | **Claude Code, Codex, OpenCode** |
62
+
63
+ Star counts from the GitHub API on 3 Nov 2026.
64
+
65
+ ## Quickstart
66
+
67
+ The demo needs no API key, no Docker and no model. It shows a **recorded** real run of the three
68
+ example skills in [`examples/`](examples):
69
+
70
+ ```bash
71
+ uvx --from git+https://github.com/diegoperezg7/doesitfire@v0.1.2 doesitfire demo
72
+ ```
73
+
74
+ or from a clone:
75
+
76
+ ```bash
77
+ git clone https://github.com/diegoperezg7/doesitfire && cd doesitfire && uv run doesitfire demo
78
+ ```
79
+
80
+ `uvx doesitfire` is coming to PyPI.
81
+
82
+ ## Example output
83
+
84
+ <!-- results:start -->
85
+ Recorded on 2026-11-03 on an Apple M4, 16 GB with doesitfire 0.1.0. Each agent ran **its own CLI** with the model shown; read each column on its own, not as a ranking of agents.
86
+
87
+ | Agent | CLI | Model | Skills | Prompts | Repeats | Runs | Errored (excluded) |
88
+ |---|---|---|---|---|---|---|---|
89
+ | claude-code | 2.1.317 | ollama/qwen3:4b-16k | 2 | 12 of 24 | 1 | 12 | 1 |
90
+ | opencode | 1.19.5 | mistral/ministral-8b-latest | 3 | 24 | 3 | 72 | 11 |
91
+
92
+ Errors: claude-code: 1 x `timeout after 900s`; opencode: 11 x `Bad Request`
93
+
94
+ Fired runs / valid runs. *should* = prompts that should load the skill; *should not* = near misses.
95
+
96
+ | Skill | claude-code · should / should not | opencode · should / should not |
97
+ |---|---|---|
98
+ | `commit-message` | 1/4 / 0/4 | 12/12 / 3/12 |
99
+ | `regex-explainer` | 0/3 / 0/0 | 6/6 / 0/12 |
100
+ | `dockerfile-review` | 0/0 / 0/0 | 12/12 / 0/7 |
101
+ | **all** | 1/7 / 0/4 | 30/30 / 3/31 |
102
+
103
+ Recall per agent: claude-code 14%, opencode 100%. False-fire rate: claude-code 0%, opencode 10%.
104
+
105
+ The raw JSON events of every run are in [`examples/recorded/logs/`](examples/recorded/logs).
106
+
107
+ <p align="center"><img src="docs/assets/report-light.png" alt="HTML report" width="820"></p>
108
+ <!-- results:end -->
109
+
110
+ **claude-code: 11 valid runs of 24 prompts (12 run, 1 timeout excluded as infrastructure), 1 repeat. opencode: 61 valid runs of 72 (24 prompts x 3 repeats, 11 Mistral HTTP 400 excluded). codex: not in the recorded demo.**
111
+
112
+ The suite is [`examples/doesitfire.yaml`](examples/doesitfire.yaml) (all agents on Ollama) and
113
+ [`examples/doesitfire.mistral.yaml`](examples/doesitfire.mistral.yaml) (OpenCode on Mistral). OpenCode's errored runs are
114
+ HTTP 400s from Mistral: when the model calls a tool that doesitfire denies (for example `WebFetch`), OpenCode's next
115
+ request ends with an assistant message, which Mistral rejects. They are excluded from the rates, not counted as "did not fire".
116
+
117
+ ## Test your own skills
118
+
119
+ You need Docker and either Ollama (free, local) or an API key.
120
+
121
+ ```bash
122
+ cd your-repo # with skills in .claude/skills, .agents/skills, skills/, ...
123
+ uvx --from git+https://github.com/diegoperezg7/doesitfire@v0.1.2 doesitfire init
124
+ # edit doesitfire.yaml: 4+ prompts that should load each skill, 4+ near misses that should not
125
+ uvx --from git+https://github.com/diegoperezg7/doesitfire@v0.1.2 doesitfire run
126
+ ```
127
+
128
+ `run` builds the agent image on first use (about 1.9 GB, a few minutes), then prints the matrix and writes
129
+ `doesitfire-results/` with `results.json`, `report.html`, `junit.xml`, `badge.svg` and `badge.json`.
130
+
131
+ ### Local and free with Ollama
132
+
133
+ The agent CLIs send 6k to 11k token prompts, and Ollama loads models with a 4k context by default, so create a
134
+ 16k variant once (same weights, no extra download):
135
+
136
+ ```bash
137
+ ollama pull qwen3:4b
138
+ ollama create qwen3:4b-16k -f ollama/Modelfile # FROM qwen3:4b + PARAMETER num_ctx 16384
139
+ ```
140
+
141
+ On Linux, start Ollama with `OLLAMA_HOST=0.0.0.0` so containers can reach it.
142
+
143
+ ### Paid models
144
+
145
+ Keys are read from environment variables only (see [`.env.example`](.env.example)). Paid providers need a
146
+ price per million tokens so `--max-cost` (default 1 USD) can stop the run before it overspends:
147
+
148
+ ```yaml
149
+ agents:
150
+ claude-code: {provider: anthropic, model: claude-haiku-5-5, price: {input: 1.0, output: 5.0}}
151
+ codex: {provider: openai, model: gpt-5.6-luna, price: {input: 0.20, output: 1.20}}
152
+ opencode: {provider: openrouter, model: deepseek/deepseek-v4.1-flash, price: {input: 0.045, output: 0.50}}
153
+ ```
154
+
155
+ | Agent | Providers | API it needs |
156
+ |---|---|---|
157
+ | Claude Code | `anthropic` (`ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`), `openrouter`, `ollama` | Anthropic Messages |
158
+ | Codex | `openai`, `openrouter`, `ollama` | OpenAI Responses |
159
+ | OpenCode | `ollama`, `openrouter`, `openai`, `mistral`, `anthropic` | Chat Completions or Anthropic |
160
+
161
+ ## How it works
162
+
163
+ ```mermaid
164
+ flowchart LR
165
+ Y[doesitfire.yaml<br/>skills + prompts] --> R[runner<br/>skill x prompt x agent x repeat]
166
+ R --> C1
167
+ subgraph net [internal Docker network, no route out]
168
+ C1[agent container<br/>Claude Code / Codex / OpenCode<br/>skills in its discovery dir]
169
+ P[egress proxy<br/>model API only]
170
+ C1 --> P
171
+ end
172
+ P --> M[(model API<br/>or Ollama)]
173
+ C1 -- JSON events --> D[activation detector]
174
+ D --> O[matrix · results.json · JUnit · HTML · badge]
175
+ ```
176
+
177
+ For every skill, prompt, agent and repeat, doesitfire starts a fresh container, copies the skills into that
178
+ agent's discovery directory (`.claude/skills`, `.agents/skills`, `.opencode/skills`) and runs the CLI headless.
179
+ It reads the CLI's JSON event stream as it arrives:
180
+
181
+ | Agent | Command | A run counts as fired when |
182
+ |---|---|---|
183
+ | Claude Code | `claude -p --output-format stream-json --verbose` | a `Skill` tool call names the skill |
184
+ | OpenCode | `opencode run --format json` | its native `skill` tool is called with the skill |
185
+ | Codex (experimental) | `codex exec --json` | a command reads `<skill>/SKILL.md`. Codex documents no activation event, so this is inferred |
186
+
187
+ With a local model, a run stops as soon as the outcome is known (a tested skill fired, or the turn cap was hit).
188
+ With a paid model, it runs to the end so the CLI reports token usage for the budget.
189
+
190
+ **Metrics**, per agent and skill: **recall** (should-fire runs that loaded the skill), **false-fire rate**
191
+ (should-not runs that loaded it) and **precision** (loads that were wanted). Runs that error out (timeout,
192
+ API error) are left out of every rate and shown apart.
193
+
194
+ **Isolation.** Each container is non-root, with a read-only root filesystem, all capabilities dropped,
195
+ `no-new-privileges`, CPU, memory, pids and time limits. Its only network peer is an egress proxy that allows the
196
+ model API host and port and returns 403 for everything else (blocked attempts are listed in the report).
197
+ Bash, Write, Edit and web tools are denied in Claude Code, edit, bash and webfetch in OpenCode, and turns are
198
+ capped at 2. Codex has no read-only file tool and its own sandbox cannot start inside an unprivileged container,
199
+ so its shell runs inside the container limits. doesitfire refuses to run without Docker. Details in
200
+ [SECURITY.md](SECURITY.md).
201
+
202
+ ## Configuration
203
+
204
+ ```yaml
205
+ repeats: 3 # runs per prompt and agent; activation is not deterministic
206
+ timeout: 300 # seconds per agent run
207
+ max_turns: 2 # agent turns per run; activation happens in the first one
208
+ cpus: 1 # per container
209
+ memory: 1g # per container
210
+
211
+ agents:
212
+ claude-code: {provider: ollama, model: qwen3:4b-16k}
213
+ codex: {provider: ollama, model: qwen3:4b-16k}
214
+ opencode: false # skip an agent
215
+
216
+ skills:
217
+ - path: .claude/skills/commit-message # a directory with a SKILL.md
218
+ should_fire:
219
+ - "Write a commit message for my change: I added a login form."
220
+ should_not_fire:
221
+ - "How do I undo my last git commit but keep the changes?"
222
+ ```
223
+
224
+ | Command | What it does |
225
+ |---|---|
226
+ | `doesitfire init [DIR]` | Writes `doesitfire.yaml` with every skill found under `DIR` |
227
+ | `doesitfire run` | Runs the matrix. `--agents`, `--skills`, `--repeats`, `--max-cost`, `--min-recall`, `--max-false-fire`, `--out` |
228
+ | `doesitfire report results.json` | Re-renders the matrix, HTML, JUnit and badge |
229
+ | `doesitfire demo` | Shows the recorded example run |
230
+
231
+ `--min-recall 0.75 --max-false-fire 0.25` makes `run` exit 1 when any agent x skill cell misses them; that is
232
+ what you want in CI. Budget stops exit 3 and keep the partial results.
233
+
234
+ ## GitHub Action
235
+
236
+ Linux runners only (the agents run in Docker; hosted macOS runners have no Docker):
237
+
238
+ ```yaml
239
+ - uses: diegoperezg7/doesitfire@v0.1.2
240
+ with:
241
+ config: doesitfire.yaml
242
+ max-cost: "1.0"
243
+ min-recall: "0.75"
244
+ max-false-fire: "0.25"
245
+ env:
246
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
247
+ ```
248
+
249
+ A complete workflow that also uploads the report and the JUnit results is in
250
+ [`examples/workflows/doesitfire.yml`](examples/workflows/doesitfire.yml).
251
+
252
+ **Badge.** `run` writes `badge.svg` and `badge.json` (a [shields.io endpoint](https://shields.io/badges/endpoint-badge)).
253
+ Commit `badge.svg` and link it, or publish `badge.json` (for example to GitHub Pages) and use
254
+ `https://img.shields.io/endpoint?url=<url-of-badge.json>`.
255
+
256
+ ## What was live-tested
257
+
258
+ | Agent | Live-tested with | Notes |
259
+ |---|---|---|
260
+ | Claude Code 2.1.317 | Ollama `qwen3:4b` (16k context) through Ollama's Anthropic-compatible `/v1/messages`. The recorded demo has 12 of the 24 prompts (1 repeat): at 4-14 minutes per run on this laptop the rest did not finish in time | No Claude model: this release was built without an Anthropic key. `provider: anthropic` (API key or `CLAUDE_CODE_OAUTH_TOKEN` from `claude setup-token`) is unit-tested, not live-tested. |
261
+ | Codex 0.166.0 | Supported; activation detection validated in the Docker integration test (real Codex CLI against the mock model), not in the recorded demo | Detection is **experimental** (reads of `SKILL.md`). A live Ollama run was started but did not finish in time for v0.1.0. |
262
+ | OpenCode 1.19.5 | Mistral `ministral-8b-latest` (paid, OpenAI-compatible) | The recorded demo uses it. Also checked against Ollama during development. |
263
+
264
+ Mistral is the only paid provider called live (OpenCode, 72 runs, cost measured from usage). Anthropic, OpenAI and
265
+ OpenRouter are wired and unit-tested, including the `--max-cost` guard, but were not called. CI runs the three real CLIs in Docker against a scripted mock
266
+ model ([`tests/mockmodel.py`](tests/mockmodel.py)) on every push, so a CLI output change breaks the build, not your results.
267
+
268
+ Every agent runs with its CLI defaults, thinking included. Turning qwen3's thinking off in Ollama looks like a speed-up,
269
+ but the model then writes its reasoning as plain text and stops before emitting the tool call, so skills look like
270
+ they never fire. doesitfire does not do that.
271
+
272
+ One Ollama quirk is handled for you: Ollama's Responses API drops input messages with role `developer`, which is where
273
+ Codex lists the available skills. For Codex on Ollama only, the egress proxy rewrites them to `system` (plain HTTP
274
+ to your local Ollama; HTTPS traffic to paid APIs is tunnelled untouched).
275
+
276
+ ## Use it from Claude Code
277
+
278
+ Install the plugin and ask Claude in plain words; its `test-skill-activation` skill finds your skills, writes prompts that should and should not trigger each one, agrees the budget with you, runs doesitfire and rewrites the descriptions of skills that miss, then re-runs them to check.
279
+
280
+ ```text
281
+ /plugin install doesitfire --marketplace diegoperezg7/doesitfire
282
+ ```
283
+
284
+ The skill tells Claude to ask before any paid run, and `--max-cost` still caps the spend.
285
+
286
+ ## Roadmap
287
+
288
+ - Cursor, Gemini CLI and GitHub Copilot CLI.
289
+ - Suggesting prompts and better descriptions with an LLM.
290
+ - Multi-turn conversations.
291
+ - Conflicts between skills (two skills competing for the same prompt).
292
+ - GitHub Marketplace listing.
293
+
294
+ ## Part of a series
295
+
296
+ Open source tools that evaluate agents and LLMs on your own data:
297
+
298
+ - [memarena](https://github.com/diegoperezg7/memarena): Benchmark memory layers on your own conversations.
299
+ - [parsearena](https://github.com/diegoperezg7/parsearena): Compare PDF parsers side by side and their effect on retrieval.
300
+ - [commitarena](https://github.com/diegoperezg7/commitarena): Replay real bug fixes from your git history against any model.
301
+ - [toolbloat](https://github.com/diegoperezg7/toolbloat): Measure the token cost of your MCP stack and tool-choice errors.
302
+ - [rulelab](https://github.com/diegoperezg7/rulelab): Find which AGENTS.md rules earn their tokens.
303
+
304
+ ## License
305
+
306
+ [MIT](LICENSE). The three example skills in `examples/skills` are MIT too and were written for this project.
@@ -0,0 +1,278 @@
1
+ <p align="center">
2
+ <img src="docs/assets/banner.svg" alt="doesitfire" width="560">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/diegoperezg7/doesitfire/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/diegoperezg7/doesitfire/actions/workflows/ci.yml/badge.svg"></a>
7
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-a1319b"></a>
8
+ <a href="https://github.com/diegoperezg7/doesitfire/releases"><img alt="Release" src="https://img.shields.io/github/v/release/diegoperezg7/doesitfire?color=a1319b"></a>
9
+ <a href="https://github.com/diegoperezg7/doesitfire/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/diegoperezg7/doesitfire?style=flat&color=a1319b"></a>
10
+ </p>
11
+
12
+ <p align="center"><b>Does your SKILL.md actually fire? Test skill activation in Claude Code, Codex and OpenCode with one command.</b></p>
13
+
14
+ <p align="center"><a href="README.es.md">Leer en español</a> · <a href="https://diegoperezg7.github.io/doesitfire/">Example report</a></p>
15
+
16
+ <p align="center"><img src="docs/assets/demo.gif" alt="doesitfire demo in a terminal" width="820"></p>
17
+
18
+ You wrote a skill. Its `description` is the only thing the agent sees when it decides whether to load it.
19
+ Does Claude Code load it when a user asks for it? Does Codex? Does OpenCode? Does it also load when nobody asked?
20
+ doesitfire runs the real agent CLIs on prompts that should and should not trigger each skill, a few times each,
21
+ and gives you one activation matrix per agent.
22
+
23
+ ## Why
24
+
25
+ Other tools test part of this:
26
+
27
+ | Tool | What it measures | Agents |
28
+ |---|---|---|
29
+ | [microsoft/waza](https://github.com/microsoft/waza) (1,499★) | Has trigger tests, run through its GitHub Copilot executor | GitHub Copilot |
30
+ | `claude plugin eval` (built into the Claude Code CLI) | Eval cases with graders against a plugin or skills dir | Claude Code only |
31
+ | [darkrishabh/agent-skills-eval](https://github.com/darkrishabh/agent-skills-eval) (809★) | Output quality with vs without the skill injected | Not activation |
32
+ | [NVIDIA/SkillEvaluator](https://github.com/NVIDIA/SkillEvaluator) (626★) | Output quality with vs without the skill injected | Not activation |
33
+ | **doesitfire** | **Whether the real agent loads the skill on its own**, and how often it loads it when it should not | **Claude Code, Codex, OpenCode** |
34
+
35
+ Star counts from the GitHub API on 3 Nov 2026.
36
+
37
+ ## Quickstart
38
+
39
+ The demo needs no API key, no Docker and no model. It shows a **recorded** real run of the three
40
+ example skills in [`examples/`](examples):
41
+
42
+ ```bash
43
+ uvx --from git+https://github.com/diegoperezg7/doesitfire@v0.1.2 doesitfire demo
44
+ ```
45
+
46
+ or from a clone:
47
+
48
+ ```bash
49
+ git clone https://github.com/diegoperezg7/doesitfire && cd doesitfire && uv run doesitfire demo
50
+ ```
51
+
52
+ `uvx doesitfire` is coming to PyPI.
53
+
54
+ ## Example output
55
+
56
+ <!-- results:start -->
57
+ Recorded on 2026-11-03 on an Apple M4, 16 GB with doesitfire 0.1.0. Each agent ran **its own CLI** with the model shown; read each column on its own, not as a ranking of agents.
58
+
59
+ | Agent | CLI | Model | Skills | Prompts | Repeats | Runs | Errored (excluded) |
60
+ |---|---|---|---|---|---|---|---|
61
+ | claude-code | 2.1.317 | ollama/qwen3:4b-16k | 2 | 12 of 24 | 1 | 12 | 1 |
62
+ | opencode | 1.19.5 | mistral/ministral-8b-latest | 3 | 24 | 3 | 72 | 11 |
63
+
64
+ Errors: claude-code: 1 x `timeout after 900s`; opencode: 11 x `Bad Request`
65
+
66
+ Fired runs / valid runs. *should* = prompts that should load the skill; *should not* = near misses.
67
+
68
+ | Skill | claude-code · should / should not | opencode · should / should not |
69
+ |---|---|---|
70
+ | `commit-message` | 1/4 / 0/4 | 12/12 / 3/12 |
71
+ | `regex-explainer` | 0/3 / 0/0 | 6/6 / 0/12 |
72
+ | `dockerfile-review` | 0/0 / 0/0 | 12/12 / 0/7 |
73
+ | **all** | 1/7 / 0/4 | 30/30 / 3/31 |
74
+
75
+ Recall per agent: claude-code 14%, opencode 100%. False-fire rate: claude-code 0%, opencode 10%.
76
+
77
+ The raw JSON events of every run are in [`examples/recorded/logs/`](examples/recorded/logs).
78
+
79
+ <p align="center"><img src="docs/assets/report-light.png" alt="HTML report" width="820"></p>
80
+ <!-- results:end -->
81
+
82
+ **claude-code: 11 valid runs of 24 prompts (12 run, 1 timeout excluded as infrastructure), 1 repeat. opencode: 61 valid runs of 72 (24 prompts x 3 repeats, 11 Mistral HTTP 400 excluded). codex: not in the recorded demo.**
83
+
84
+ The suite is [`examples/doesitfire.yaml`](examples/doesitfire.yaml) (all agents on Ollama) and
85
+ [`examples/doesitfire.mistral.yaml`](examples/doesitfire.mistral.yaml) (OpenCode on Mistral). OpenCode's errored runs are
86
+ HTTP 400s from Mistral: when the model calls a tool that doesitfire denies (for example `WebFetch`), OpenCode's next
87
+ request ends with an assistant message, which Mistral rejects. They are excluded from the rates, not counted as "did not fire".
88
+
89
+ ## Test your own skills
90
+
91
+ You need Docker and either Ollama (free, local) or an API key.
92
+
93
+ ```bash
94
+ cd your-repo # with skills in .claude/skills, .agents/skills, skills/, ...
95
+ uvx --from git+https://github.com/diegoperezg7/doesitfire@v0.1.2 doesitfire init
96
+ # edit doesitfire.yaml: 4+ prompts that should load each skill, 4+ near misses that should not
97
+ uvx --from git+https://github.com/diegoperezg7/doesitfire@v0.1.2 doesitfire run
98
+ ```
99
+
100
+ `run` builds the agent image on first use (about 1.9 GB, a few minutes), then prints the matrix and writes
101
+ `doesitfire-results/` with `results.json`, `report.html`, `junit.xml`, `badge.svg` and `badge.json`.
102
+
103
+ ### Local and free with Ollama
104
+
105
+ The agent CLIs send 6k to 11k token prompts, and Ollama loads models with a 4k context by default, so create a
106
+ 16k variant once (same weights, no extra download):
107
+
108
+ ```bash
109
+ ollama pull qwen3:4b
110
+ ollama create qwen3:4b-16k -f ollama/Modelfile # FROM qwen3:4b + PARAMETER num_ctx 16384
111
+ ```
112
+
113
+ On Linux, start Ollama with `OLLAMA_HOST=0.0.0.0` so containers can reach it.
114
+
115
+ ### Paid models
116
+
117
+ Keys are read from environment variables only (see [`.env.example`](.env.example)). Paid providers need a
118
+ price per million tokens so `--max-cost` (default 1 USD) can stop the run before it overspends:
119
+
120
+ ```yaml
121
+ agents:
122
+ claude-code: {provider: anthropic, model: claude-haiku-5-5, price: {input: 1.0, output: 5.0}}
123
+ codex: {provider: openai, model: gpt-5.6-luna, price: {input: 0.20, output: 1.20}}
124
+ opencode: {provider: openrouter, model: deepseek/deepseek-v4.1-flash, price: {input: 0.045, output: 0.50}}
125
+ ```
126
+
127
+ | Agent | Providers | API it needs |
128
+ |---|---|---|
129
+ | Claude Code | `anthropic` (`ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`), `openrouter`, `ollama` | Anthropic Messages |
130
+ | Codex | `openai`, `openrouter`, `ollama` | OpenAI Responses |
131
+ | OpenCode | `ollama`, `openrouter`, `openai`, `mistral`, `anthropic` | Chat Completions or Anthropic |
132
+
133
+ ## How it works
134
+
135
+ ```mermaid
136
+ flowchart LR
137
+ Y[doesitfire.yaml<br/>skills + prompts] --> R[runner<br/>skill x prompt x agent x repeat]
138
+ R --> C1
139
+ subgraph net [internal Docker network, no route out]
140
+ C1[agent container<br/>Claude Code / Codex / OpenCode<br/>skills in its discovery dir]
141
+ P[egress proxy<br/>model API only]
142
+ C1 --> P
143
+ end
144
+ P --> M[(model API<br/>or Ollama)]
145
+ C1 -- JSON events --> D[activation detector]
146
+ D --> O[matrix · results.json · JUnit · HTML · badge]
147
+ ```
148
+
149
+ For every skill, prompt, agent and repeat, doesitfire starts a fresh container, copies the skills into that
150
+ agent's discovery directory (`.claude/skills`, `.agents/skills`, `.opencode/skills`) and runs the CLI headless.
151
+ It reads the CLI's JSON event stream as it arrives:
152
+
153
+ | Agent | Command | A run counts as fired when |
154
+ |---|---|---|
155
+ | Claude Code | `claude -p --output-format stream-json --verbose` | a `Skill` tool call names the skill |
156
+ | OpenCode | `opencode run --format json` | its native `skill` tool is called with the skill |
157
+ | Codex (experimental) | `codex exec --json` | a command reads `<skill>/SKILL.md`. Codex documents no activation event, so this is inferred |
158
+
159
+ With a local model, a run stops as soon as the outcome is known (a tested skill fired, or the turn cap was hit).
160
+ With a paid model, it runs to the end so the CLI reports token usage for the budget.
161
+
162
+ **Metrics**, per agent and skill: **recall** (should-fire runs that loaded the skill), **false-fire rate**
163
+ (should-not runs that loaded it) and **precision** (loads that were wanted). Runs that error out (timeout,
164
+ API error) are left out of every rate and shown apart.
165
+
166
+ **Isolation.** Each container is non-root, with a read-only root filesystem, all capabilities dropped,
167
+ `no-new-privileges`, CPU, memory, pids and time limits. Its only network peer is an egress proxy that allows the
168
+ model API host and port and returns 403 for everything else (blocked attempts are listed in the report).
169
+ Bash, Write, Edit and web tools are denied in Claude Code, edit, bash and webfetch in OpenCode, and turns are
170
+ capped at 2. Codex has no read-only file tool and its own sandbox cannot start inside an unprivileged container,
171
+ so its shell runs inside the container limits. doesitfire refuses to run without Docker. Details in
172
+ [SECURITY.md](SECURITY.md).
173
+
174
+ ## Configuration
175
+
176
+ ```yaml
177
+ repeats: 3 # runs per prompt and agent; activation is not deterministic
178
+ timeout: 300 # seconds per agent run
179
+ max_turns: 2 # agent turns per run; activation happens in the first one
180
+ cpus: 1 # per container
181
+ memory: 1g # per container
182
+
183
+ agents:
184
+ claude-code: {provider: ollama, model: qwen3:4b-16k}
185
+ codex: {provider: ollama, model: qwen3:4b-16k}
186
+ opencode: false # skip an agent
187
+
188
+ skills:
189
+ - path: .claude/skills/commit-message # a directory with a SKILL.md
190
+ should_fire:
191
+ - "Write a commit message for my change: I added a login form."
192
+ should_not_fire:
193
+ - "How do I undo my last git commit but keep the changes?"
194
+ ```
195
+
196
+ | Command | What it does |
197
+ |---|---|
198
+ | `doesitfire init [DIR]` | Writes `doesitfire.yaml` with every skill found under `DIR` |
199
+ | `doesitfire run` | Runs the matrix. `--agents`, `--skills`, `--repeats`, `--max-cost`, `--min-recall`, `--max-false-fire`, `--out` |
200
+ | `doesitfire report results.json` | Re-renders the matrix, HTML, JUnit and badge |
201
+ | `doesitfire demo` | Shows the recorded example run |
202
+
203
+ `--min-recall 0.75 --max-false-fire 0.25` makes `run` exit 1 when any agent x skill cell misses them; that is
204
+ what you want in CI. Budget stops exit 3 and keep the partial results.
205
+
206
+ ## GitHub Action
207
+
208
+ Linux runners only (the agents run in Docker; hosted macOS runners have no Docker):
209
+
210
+ ```yaml
211
+ - uses: diegoperezg7/doesitfire@v0.1.2
212
+ with:
213
+ config: doesitfire.yaml
214
+ max-cost: "1.0"
215
+ min-recall: "0.75"
216
+ max-false-fire: "0.25"
217
+ env:
218
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
219
+ ```
220
+
221
+ A complete workflow that also uploads the report and the JUnit results is in
222
+ [`examples/workflows/doesitfire.yml`](examples/workflows/doesitfire.yml).
223
+
224
+ **Badge.** `run` writes `badge.svg` and `badge.json` (a [shields.io endpoint](https://shields.io/badges/endpoint-badge)).
225
+ Commit `badge.svg` and link it, or publish `badge.json` (for example to GitHub Pages) and use
226
+ `https://img.shields.io/endpoint?url=<url-of-badge.json>`.
227
+
228
+ ## What was live-tested
229
+
230
+ | Agent | Live-tested with | Notes |
231
+ |---|---|---|
232
+ | Claude Code 2.1.317 | Ollama `qwen3:4b` (16k context) through Ollama's Anthropic-compatible `/v1/messages`. The recorded demo has 12 of the 24 prompts (1 repeat): at 4-14 minutes per run on this laptop the rest did not finish in time | No Claude model: this release was built without an Anthropic key. `provider: anthropic` (API key or `CLAUDE_CODE_OAUTH_TOKEN` from `claude setup-token`) is unit-tested, not live-tested. |
233
+ | Codex 0.166.0 | Supported; activation detection validated in the Docker integration test (real Codex CLI against the mock model), not in the recorded demo | Detection is **experimental** (reads of `SKILL.md`). A live Ollama run was started but did not finish in time for v0.1.0. |
234
+ | OpenCode 1.19.5 | Mistral `ministral-8b-latest` (paid, OpenAI-compatible) | The recorded demo uses it. Also checked against Ollama during development. |
235
+
236
+ Mistral is the only paid provider called live (OpenCode, 72 runs, cost measured from usage). Anthropic, OpenAI and
237
+ OpenRouter are wired and unit-tested, including the `--max-cost` guard, but were not called. CI runs the three real CLIs in Docker against a scripted mock
238
+ model ([`tests/mockmodel.py`](tests/mockmodel.py)) on every push, so a CLI output change breaks the build, not your results.
239
+
240
+ Every agent runs with its CLI defaults, thinking included. Turning qwen3's thinking off in Ollama looks like a speed-up,
241
+ but the model then writes its reasoning as plain text and stops before emitting the tool call, so skills look like
242
+ they never fire. doesitfire does not do that.
243
+
244
+ One Ollama quirk is handled for you: Ollama's Responses API drops input messages with role `developer`, which is where
245
+ Codex lists the available skills. For Codex on Ollama only, the egress proxy rewrites them to `system` (plain HTTP
246
+ to your local Ollama; HTTPS traffic to paid APIs is tunnelled untouched).
247
+
248
+ ## Use it from Claude Code
249
+
250
+ Install the plugin and ask Claude in plain words; its `test-skill-activation` skill finds your skills, writes prompts that should and should not trigger each one, agrees the budget with you, runs doesitfire and rewrites the descriptions of skills that miss, then re-runs them to check.
251
+
252
+ ```text
253
+ /plugin install doesitfire --marketplace diegoperezg7/doesitfire
254
+ ```
255
+
256
+ The skill tells Claude to ask before any paid run, and `--max-cost` still caps the spend.
257
+
258
+ ## Roadmap
259
+
260
+ - Cursor, Gemini CLI and GitHub Copilot CLI.
261
+ - Suggesting prompts and better descriptions with an LLM.
262
+ - Multi-turn conversations.
263
+ - Conflicts between skills (two skills competing for the same prompt).
264
+ - GitHub Marketplace listing.
265
+
266
+ ## Part of a series
267
+
268
+ Open source tools that evaluate agents and LLMs on your own data:
269
+
270
+ - [memarena](https://github.com/diegoperezg7/memarena): Benchmark memory layers on your own conversations.
271
+ - [parsearena](https://github.com/diegoperezg7/parsearena): Compare PDF parsers side by side and their effect on retrieval.
272
+ - [commitarena](https://github.com/diegoperezg7/commitarena): Replay real bug fixes from your git history against any model.
273
+ - [toolbloat](https://github.com/diegoperezg7/toolbloat): Measure the token cost of your MCP stack and tool-choice errors.
274
+ - [rulelab](https://github.com/diegoperezg7/rulelab): Find which AGENTS.md rules earn their tokens.
275
+
276
+ ## License
277
+
278
+ [MIT](LICENSE). The three example skills in `examples/skills` are MIT too and were written for this project.