Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

kibble

kibble

Dogfood your docs.

ReleaseGo versionLicense

Eating your own dog food means using what you ship the way a stranger would. Nobody does it for documentation, because your machine already has everything installed and the instructions pass by inspection. kibble is the bowl: it runs your documented steps in a clean container from zero, as a reader with nothing would, so a broken install fails in CI instead of in their terminal.

Your README tells people to run go install ..., then some setup, then a quickstart. Every one of those rots the moment the code moves, and you are the last to know.

The stranger is not always a person now. Coding agents install tools by doing what the README says, and they fail differently than people do. Someone who follows a broken instruction knows they followed it correctly, reads the error, and works around the document. An agent cannot tell a stale command from its own mistake, so it retries, invents variants, and reports success it did not have. A line that has been wrong for six months gets run all day by something that will never complain about it.

Install

go install github.com/dcadolph/kibble@latest

Requires Docker on the host. A drop-in replacement that speaks docker's command line, such as Podman, works through KIBBLE_DOCKER=podman.

Usage

Point it at one or more repository directories, or none at all:

kibble
kibble ./myrepo
kibble ./repo-a ./repo-b

With no path it checks the directory you are standing in, so cd into a project and run it. There are no prompts. kibble's home is a CI job, and a tool that stops to ask a question there either hangs on a closed pipe or needs a flag to defeat it.

Example output:

REPO KIND STATUS TIME DETAIL
myrepo brew PASS 1s formula exists (install not attempted)
myrepo example PASS 22s 15 lines ran, 9 skipped
myrepo flag-check PASS 0s 9 cited flags ok, 4 subcommands cited
myrepo git-clone PASS 41s myrepo version 1.4.0
myrepo go-install PASS 28s myrepo version 1.4.0
5 pass, 0 fail, 0 other of 5 checks

Use it in CI

Add a workflow that fails a pull request when a documented install breaks:

name: docson: pull_requestjobs:
kibble:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dcadolph/kibble@v0.19.0with:
repo: .# args: -strict # fail on timeouts, smoke failures, drift, and gaps too

The runner already has Docker. A failed install or example is annotated on the exact README line that broke, so it shows up inline in the pull request the way a failing test does; doc drift becomes a warning annotation, and the job summary gets the full results table. The action downloads the released binary for the pinned version and verifies its checksum before running it. kibble runs on its own README this way on every commit.

Flags

FlagDefaultWhat
-imagegolang:1.26Fallback image when no toolchain is detected.
-timeout240sPer-step build timeout.
-workers3Max concurrent installs.
-jsonfalseEmit results as JSON to stdout.
-versionfalsePrint the version and exit.
-strictfalseAlso fail on timeouts, smoke failures, drift, and gaps.
-examplestrueReplay each document's example blocks in the container.
-planfalsePrint the example plans as JSON and exit.
-suggestfalsePropose a .kibble.yml using a model and exit.
-mcpfalseServe the Model Context Protocol over stdio.
-brew-installfalseRun documented brew installs for real instead of checking the formula exists.

One dash or two, either works: -strict and --strict name the same flag.

What the verdicts mean

kibble runs installs from zero, smoke-tests what lands, replays quickstarts in one session, and checks cited flags and subcommands against the binary's own help. Every result is one of seven verdicts, and the boundaries between them are the product:

  • PASS ran and worked. FAIL ran and did not; nothing kibble merely looked up can produce one.
  • SKIP means kibble could not judge the line, with the reason.
  • GAP means the document is incomplete: a file, directory, or setting nothing creates.
  • DRIFT means the docs cite a flag or subcommand the binary no longer has.
  • TIMEOUT and ERROR keep slow networks and kibble's own trouble out of your verdict.

A gap, a drift, or a timeout never fails a default run; -strict promotes them. The full reasoning, including why a check that cries wolf is worse than no check, is in docs/DESIGN.md.

Configuration

Most repositories need none. When a heuristic cannot settle a call, a .kibble.yml at the repository root does: fixtures, environment, substitutions, background services with readiness probes, and per-line run or skip rules, so the run stays reproducible and the engine stays the thing that decides pass or fail. -suggest has a model draft the file for you to review; -mcp serves the same engine to an agent. All of it is in docs/CONFIG.md.

Security

kibble executes commands it read out of documentation, which means a README is untrusted input: anyone who can change the docs can change what runs. Every command runs in a fresh, unprivileged, capability-dropped container with nothing mounted from the host, and the network stays open because verifying an install is fetching it. Treat a kibble run the way you treat a build script, and read docs/SECURITY.md before pointing it at a repository you do not trust.

Proof

corpus/repos.tsv pins real repositories to hand-verified verdict counts, and a scheduled run fails when kibble's judgment moves. corpus/mutations.tsv holds the other direction: corrupt one documented line of a pinned repository and kibble must catch it, in a finding that names the damage. Correct documentation passes and one edit of rot flips, which is the pair a verifier has to hold. Details in docs/DESIGN.md.

Roadmap

  • JUnit XML output for CI systems that are not GitHub.

Why "kibble"

Dogfooding means using your own product before you ship it. kibble is the bowl: it feeds your docs back to a fresh machine and tells you whether they still go down.

License

MIT. See LICENSE.

About

Test your README's install steps in a clean container, so a broken install fails in CI instead of in a new user's terminal. CLI and GitHub Action.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages