mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-13 20:59:12 +00:00
Add Bug Receipt evidence-gated debugging skill (#2628)
* Add Bug Receipt agent skill * Refine Bug Receipt proof boundaries
This commit is contained in:
@@ -92,6 +92,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
|
||||
| [breakdown-feature-prd](../skills/breakdown-feature-prd/SKILL.md)<br />`gh skills install github/awesome-copilot breakdown-feature-prd` | Prompt for creating Product Requirements Documents (PRDs) for new features, based on an Epic. | None |
|
||||
| [breakdown-plan](../skills/breakdown-plan/SKILL.md)<br />`gh skills install github/awesome-copilot breakdown-plan` | Issue Planning and Automation prompt that generates comprehensive project plans with Epic > Feature > Story/Enabler > Test hierarchy, dependencies, priorities, and automated tracking. | None |
|
||||
| [breakdown-test](../skills/breakdown-test/SKILL.md)<br />`gh skills install github/awesome-copilot breakdown-test` | Test Planning and Quality Assurance prompt that generates comprehensive test strategies, task breakdowns, and quality validation plans for GitHub projects. | None |
|
||||
| [bug-receipt](../skills/bug-receipt/SKILL.md)<br />`gh skills install github/awesome-copilot bug-receipt` | Fix software defects with an auditable proof receipt: reproduce, trace root cause, repair, verify, and report VERIFIED, PARTIAL, or BLOCKED. Use for bug fixes and regressions. | `assets/receipt.template.json`<br />`references/receipt-contract.md`<br />`references/receipt.schema.json`<br />`scripts/validate-receipt.mjs` |
|
||||
| [bug-reproduction-brief](../skills/bug-reproduction-brief/SKILL.md)<br />`gh skills install github/awesome-copilot bug-reproduction-brief` | Turn a vague, intermittent, or environment-specific bug report into a minimal evidence-backed reproduction before proposing a fix. | None |
|
||||
| [build-evidence-map](../skills/build-evidence-map/SKILL.md)<br />`gh skills install github/awesome-copilot build-evidence-map` | Build an auditable evidence map for a contested technical choice, research synthesis, proposal review, or consequential decision. Use when Copilot must preserve supporting, contradicting, qualifying, and missing evidence with exact source regions instead of collapsing disagreement into prose. | `references/evidence-ladder.md`<br />`references/map-schema.md`<br />`scripts/contract.mjs`<br />`scripts/validate.mjs` |
|
||||
| [centos-linux-triage](../skills/centos-linux-triage/SKILL.md)<br />`gh skills install github/awesome-copilot centos-linux-triage` | Triage and resolve CentOS issues using RHEL-compatible tooling, SELinux-aware practices, and firewalld. | None |
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: bug-receipt
|
||||
description: 'Fix software defects with an auditable proof receipt: reproduce, trace root cause, repair, verify, and report VERIFIED, PARTIAL, or BLOCKED. Use for bug fixes and regressions.'
|
||||
---
|
||||
|
||||
# Bug Receipt
|
||||
|
||||
Treat the receipt as the completion gate, not as decoration added after a conclusion.
|
||||
|
||||
## Define proof before editing
|
||||
|
||||
Write a compact working ledger with the observed problem, intended behavior, strongest direct acceptance check, and proof layers required by the affected surface. Keep it current while investigating.
|
||||
|
||||
Choose proof that can falsify the fix. A green build is not a substitute for a browser interaction, API round trip, persistence reload, or concurrency sequence when one of those is the user-visible contract.
|
||||
|
||||
## Establish the baseline
|
||||
|
||||
1. Restate the observed defect and the intended behavior in one sentence each.
|
||||
2. Run the narrowest safe reproduction before editing whenever the environment permits it.
|
||||
3. Record the exact command or interaction and the decisive failing observation.
|
||||
4. If reproduction is unavailable, state why and cap the final status at `PARTIAL` or `BLOCKED`.
|
||||
|
||||
Do not convert an assumption, stale log, source read, or passing build into a reproduced baseline.
|
||||
|
||||
## Trace the cause
|
||||
|
||||
Follow the live owner path far enough to distinguish the responsible cause from a nearby symptom. Cite concrete evidence such as a file and line, stack frame, request/response, state transition, or runtime observation.
|
||||
|
||||
Separate:
|
||||
|
||||
- facts directly observed;
|
||||
- bounded inferences supported by those facts;
|
||||
- remaining gaps.
|
||||
|
||||
Do not claim root cause from plausibility alone.
|
||||
|
||||
## Repair the responsible layer
|
||||
|
||||
Make the smallest change that fixes the responsible behavior and preserves adjacent contracts. Avoid unrelated cleanup, silent fallbacks, fixture-specific exceptions, retries, or post-processing unless the product contract requires them.
|
||||
|
||||
Record every changed file or artifact and its role in the repair.
|
||||
|
||||
## Close the proof loop
|
||||
|
||||
Run, in proportion to the defect:
|
||||
|
||||
1. the original reproduction or direct acceptance check;
|
||||
2. the nearest relevant negative or regression check;
|
||||
3. the affected build, type, lint, or integration gate when applicable;
|
||||
4. the live UI, network, backend, or runtime path when the user-visible claim depends on it.
|
||||
|
||||
Record exact commands and observed results. Never invent a test, command, count, file location, or runtime observation.
|
||||
|
||||
Use these minimum direct checks when applicable:
|
||||
|
||||
| Defect surface | Direct proof |
|
||||
| --- | --- |
|
||||
| Logic or failing test | Original failing input or focused test now passes |
|
||||
| UI behavior | Real interaction plus relevant console and network observation |
|
||||
| API or integration | Request, response, and responsible service behavior |
|
||||
| Persistence | Write/read or reload round trip through the real owner path |
|
||||
| Race or lifecycle | Repeated triggering sequence and the violated invariant |
|
||||
| Build or configuration | Affected build, startup, or deployment path |
|
||||
|
||||
## Assign status
|
||||
|
||||
- Use `VERIFIED` only when the baseline failure was observed, root-cause evidence is concrete, the responsible change is identified, every declared verification passed, and no material gap remains.
|
||||
- Use `PARTIAL` when useful evidence exists but at least one required proof layer is missing or inconclusive.
|
||||
- Use `BLOCKED` when the fix or its proof cannot proceed because of a specific external condition.
|
||||
|
||||
For `BLOCKED`, name the single next evidence package or experiment that closes the causal chain. When the failure spans systems, require correlated evidence from every relevant owner rather than an isolated capture.
|
||||
|
||||
Passing syntax, compilation, one narrow unit test, or source inspection alone does not prove downstream behavior unless it is the complete acceptance contract.
|
||||
|
||||
## Return the receipt
|
||||
|
||||
Finish with this compact structure:
|
||||
|
||||
```text
|
||||
BUG RECEIPT · VERIFIED | PARTIAL | BLOCKED
|
||||
|
||||
Problem <observed defect and intended behavior>
|
||||
Baseline <exact command or interaction>
|
||||
<decisive observed result>
|
||||
Root cause <location and evidence-backed mechanism>
|
||||
Change <file or artifact — responsible repair>
|
||||
Proof <check: result · check: result>
|
||||
Gaps <none, or the exact missing proof>
|
||||
```
|
||||
|
||||
Use `not run` explicitly where applicable. Do not omit a row to make the receipt look complete.
|
||||
|
||||
For a machine-readable receipt or CI integration, read [references/receipt-contract.md](references/receipt-contract.md) and conform to its JSON fields and status invariants.
|
||||
|
||||
When a JSON artifact is requested, start from [assets/receipt.template.json](assets/receipt.template.json), write it to a task-owned path, and validate it with `node scripts/validate-receipt.mjs <receipt.json>` from this skill directory. Do not commit the generated receipt unless the user requests it.
|
||||
|
||||
## Source and license
|
||||
|
||||
Originally published at https://github.com/lMysticl/bug-receipt under the MIT License.
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": 1,
|
||||
"status": "partial",
|
||||
"problem": "Describe the observed defect and intended behavior.",
|
||||
"baseline": {
|
||||
"command": "Record the exact reproduction command or interaction.",
|
||||
"result": "not-run",
|
||||
"evidence": "State the decisive observation, or why it could not be obtained."
|
||||
},
|
||||
"rootCause": {
|
||||
"summary": "State the evidence-backed mechanism, or mark it unresolved.",
|
||||
"evidence": []
|
||||
},
|
||||
"changes": [],
|
||||
"verification": [],
|
||||
"gaps": [
|
||||
"Replace this with the exact missing proof layer."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Machine-readable receipt contract
|
||||
|
||||
Use JSON only when the user, CI, or another tool needs a structured artifact. Keep the normal final answer human-readable.
|
||||
|
||||
## Required fields
|
||||
|
||||
- `version`: integer `1`.
|
||||
- `status`: `verified`, `partial`, or `blocked`.
|
||||
- `problem`: concise defect and intended behavior.
|
||||
- `baseline`: object with `command`, `result`, and `evidence`.
|
||||
- `rootCause`: object with `summary` and at least one evidence item for `verified`.
|
||||
- `changes`: array of `{ "file", "summary" }` objects.
|
||||
- `verification`: array of `{ "command", "result", "evidence" }` objects.
|
||||
- `gaps`: array of explicit missing proof statements.
|
||||
|
||||
Baseline results are `failed`, `observed`, or `not-run`. Verification results are `passed`, `failed`, or `not-run`.
|
||||
|
||||
## Status invariants
|
||||
|
||||
For `verified`:
|
||||
|
||||
- Require an observed baseline: `failed` or `observed`, never `not-run`.
|
||||
- Require at least one concrete root-cause evidence item with `location` and `observation`.
|
||||
- Require at least one changed file or artifact.
|
||||
- Require at least one verification item.
|
||||
- Require every verification result to be `passed`.
|
||||
- Require `gaps` to be empty.
|
||||
|
||||
For `partial`:
|
||||
|
||||
- Preserve all evidence obtained.
|
||||
- Put every missing or inconclusive proof layer in `gaps`.
|
||||
- Never convert an unrun check into `passed`.
|
||||
|
||||
For `blocked`:
|
||||
|
||||
- Require at least one gap naming the external blocking condition.
|
||||
- Leave unperformed work empty or mark it `not-run`; do not speculate about the result.
|
||||
|
||||
Validate against [receipt.schema.json](receipt.schema.json), run `node scripts/validate-receipt.mjs <file>` from the skill directory, or pipe JSON to `node scripts/validate-receipt.mjs - --json`.
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://lmysticl.github.io/bug-receipt/receipt.schema.json",
|
||||
"title": "Bug Receipt",
|
||||
"description": "A machine-readable evidence receipt for a software bug fix.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["version", "status", "problem", "baseline", "rootCause", "changes", "verification", "gaps"],
|
||||
"properties": {
|
||||
"version": { "const": 1 },
|
||||
"status": { "enum": ["verified", "partial", "blocked"] },
|
||||
"problem": { "$ref": "#/$defs/nonEmptyString" },
|
||||
"baseline": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["command", "result", "evidence"],
|
||||
"properties": {
|
||||
"command": { "$ref": "#/$defs/nonEmptyString" },
|
||||
"result": { "enum": ["failed", "observed", "not-run"] },
|
||||
"evidence": { "$ref": "#/$defs/nonEmptyString" }
|
||||
}
|
||||
},
|
||||
"rootCause": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["summary", "evidence"],
|
||||
"properties": {
|
||||
"summary": { "$ref": "#/$defs/nonEmptyString" },
|
||||
"evidence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["location", "observation"],
|
||||
"properties": {
|
||||
"location": { "$ref": "#/$defs/nonEmptyString" },
|
||||
"observation": { "$ref": "#/$defs/nonEmptyString" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"changes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["file", "summary"],
|
||||
"properties": {
|
||||
"file": { "$ref": "#/$defs/nonEmptyString" },
|
||||
"summary": { "$ref": "#/$defs/nonEmptyString" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["command", "result", "evidence"],
|
||||
"properties": {
|
||||
"command": { "$ref": "#/$defs/nonEmptyString" },
|
||||
"result": { "enum": ["passed", "failed", "not-run"] },
|
||||
"evidence": { "$ref": "#/$defs/nonEmptyString" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"gaps": { "type": "array", "items": { "$ref": "#/$defs/nonEmptyString" } }
|
||||
},
|
||||
"$defs": {
|
||||
"nonEmptyString": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "status": { "const": "verified" } }, "required": ["status"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"baseline": { "type": "object", "properties": { "result": { "enum": ["failed", "observed"] } } },
|
||||
"rootCause": { "type": "object", "properties": { "evidence": { "type": "array", "minItems": 1 } } },
|
||||
"changes": { "type": "array", "minItems": 1 },
|
||||
"verification": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "result": { "const": "passed" } } } },
|
||||
"gaps": { "type": "array", "maxItems": 0 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "status": { "enum": ["partial", "blocked"] } }, "required": ["status"] },
|
||||
"then": { "properties": { "gaps": { "type": "array", "minItems": 1 } } }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
const statuses = new Set(['verified', 'partial', 'blocked'])
|
||||
const baselineResults = new Set(['failed', 'observed', 'not-run'])
|
||||
const verificationResults = new Set(['passed', 'failed', 'not-run'])
|
||||
|
||||
const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0
|
||||
|
||||
export const sampleReceipt = {
|
||||
version: 1,
|
||||
status: 'verified',
|
||||
problem: 'A 10% checkout discount returns 100 instead of 90 after currency rounding.',
|
||||
baseline: {
|
||||
command: 'npm test -- discount.test.ts',
|
||||
result: 'failed',
|
||||
evidence: 'Expected 90, received 100.',
|
||||
},
|
||||
rootCause: {
|
||||
summary: 'The subtotal was rounded before the percentage discount was applied.',
|
||||
evidence: [{ location: 'src/pricing.ts:42', observation: 'roundCurrency(subtotal) was passed into applyDiscount().' }],
|
||||
},
|
||||
changes: [{ file: 'src/pricing.ts', summary: 'Apply the discount to the subtotal before currency rounding.' }],
|
||||
verification: [
|
||||
{ command: 'npm test -- discount.test.ts', result: 'passed', evidence: '1 test passed.' },
|
||||
{ command: 'npm test', result: 'passed', evidence: '42 tests passed.' },
|
||||
],
|
||||
gaps: [],
|
||||
}
|
||||
|
||||
export function validateReceipt(receipt) {
|
||||
const issues = []
|
||||
const add = (path, message) => issues.push({ path, message })
|
||||
const rejectUnknown = (value, allowed, path) => {
|
||||
if (!isObject(value)) return
|
||||
for (const key of Object.keys(value)) {
|
||||
if (!allowed.has(key)) add(path ? `${path}.${key}` : key, 'Unknown field.')
|
||||
}
|
||||
}
|
||||
|
||||
if (!isObject(receipt)) return { valid: false, issues: [{ path: '$', message: 'Receipt must be a JSON object.' }] }
|
||||
rejectUnknown(receipt, new Set(['version', 'status', 'problem', 'baseline', 'rootCause', 'changes', 'verification', 'gaps']), '')
|
||||
|
||||
if (receipt.version !== 1) add('version', 'Must equal 1.')
|
||||
if (!statuses.has(receipt.status)) add('status', 'Must be verified, partial, or blocked.')
|
||||
if (!nonEmpty(receipt.problem)) add('problem', 'Must be a non-empty string.')
|
||||
|
||||
if (!isObject(receipt.baseline)) {
|
||||
add('baseline', 'Must be an object.')
|
||||
} else {
|
||||
rejectUnknown(receipt.baseline, new Set(['command', 'result', 'evidence']), 'baseline')
|
||||
if (!nonEmpty(receipt.baseline.command)) add('baseline.command', 'Must be a non-empty string.')
|
||||
if (!baselineResults.has(receipt.baseline.result)) add('baseline.result', 'Must be failed, observed, or not-run.')
|
||||
if (!nonEmpty(receipt.baseline.evidence)) add('baseline.evidence', 'Must be a non-empty string.')
|
||||
}
|
||||
|
||||
if (!isObject(receipt.rootCause)) {
|
||||
add('rootCause', 'Must be an object.')
|
||||
} else {
|
||||
rejectUnknown(receipt.rootCause, new Set(['summary', 'evidence']), 'rootCause')
|
||||
if (!nonEmpty(receipt.rootCause.summary)) add('rootCause.summary', 'Must be a non-empty string.')
|
||||
if (!Array.isArray(receipt.rootCause.evidence)) {
|
||||
add('rootCause.evidence', 'Must be an array.')
|
||||
} else {
|
||||
receipt.rootCause.evidence.forEach((entry, index) => {
|
||||
if (!isObject(entry)) return add(`rootCause.evidence[${index}]`, 'Must be an object.')
|
||||
rejectUnknown(entry, new Set(['location', 'observation']), `rootCause.evidence[${index}]`)
|
||||
if (!nonEmpty(entry.location)) add(`rootCause.evidence[${index}].location`, 'Must be a non-empty string.')
|
||||
if (!nonEmpty(entry.observation)) add(`rootCause.evidence[${index}].observation`, 'Must be a non-empty string.')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(receipt.changes)) {
|
||||
add('changes', 'Must be an array.')
|
||||
} else {
|
||||
receipt.changes.forEach((entry, index) => {
|
||||
if (!isObject(entry)) return add(`changes[${index}]`, 'Must be an object.')
|
||||
rejectUnknown(entry, new Set(['file', 'summary']), `changes[${index}]`)
|
||||
if (!nonEmpty(entry.file)) add(`changes[${index}].file`, 'Must be a non-empty string.')
|
||||
if (!nonEmpty(entry.summary)) add(`changes[${index}].summary`, 'Must be a non-empty string.')
|
||||
})
|
||||
}
|
||||
|
||||
if (!Array.isArray(receipt.verification)) {
|
||||
add('verification', 'Must be an array.')
|
||||
} else {
|
||||
receipt.verification.forEach((entry, index) => {
|
||||
if (!isObject(entry)) return add(`verification[${index}]`, 'Must be an object.')
|
||||
rejectUnknown(entry, new Set(['command', 'result', 'evidence']), `verification[${index}]`)
|
||||
if (!nonEmpty(entry.command)) add(`verification[${index}].command`, 'Must be a non-empty string.')
|
||||
if (!verificationResults.has(entry.result)) add(`verification[${index}].result`, 'Must be passed, failed, or not-run.')
|
||||
if (!nonEmpty(entry.evidence)) add(`verification[${index}].evidence`, 'Must be a non-empty string.')
|
||||
})
|
||||
}
|
||||
|
||||
if (!Array.isArray(receipt.gaps) || receipt.gaps.some((gap) => !nonEmpty(gap))) add('gaps', 'Must be an array of non-empty strings.')
|
||||
|
||||
if (receipt.status === 'verified') {
|
||||
if (receipt.baseline?.result === 'not-run') add('baseline.result', 'Verified requires an observed baseline.')
|
||||
if (!Array.isArray(receipt.rootCause?.evidence) || receipt.rootCause.evidence.length === 0) add('rootCause.evidence', 'Verified requires concrete root-cause evidence.')
|
||||
if (!Array.isArray(receipt.changes) || receipt.changes.length === 0) add('changes', 'Verified requires at least one changed file or artifact.')
|
||||
if (!Array.isArray(receipt.verification) || receipt.verification.length === 0) add('verification', 'Verified requires at least one verification check.')
|
||||
if (receipt.verification?.some((entry) => entry?.result !== 'passed')) add('verification', 'Every verification check must pass for verified status.')
|
||||
if (Array.isArray(receipt.gaps) && receipt.gaps.length > 0) add('gaps', 'Verified status cannot contain proof gaps.')
|
||||
}
|
||||
|
||||
if (receipt.status === 'partial' && Array.isArray(receipt.gaps) && receipt.gaps.length === 0) add('gaps', 'Partial status must name at least one missing proof layer.')
|
||||
if (receipt.status === 'blocked' && Array.isArray(receipt.gaps) && receipt.gaps.length === 0) add('gaps', 'Blocked status must name the external blocking condition.')
|
||||
|
||||
return { valid: issues.length === 0, issues }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const path = process.argv[2]
|
||||
if (!path) throw new Error('Usage: node scripts/validate-receipt.mjs <receipt.json> [--json]')
|
||||
|
||||
let input = ''
|
||||
if (path === '-') {
|
||||
process.stdin.setEncoding('utf8')
|
||||
for await (const chunk of process.stdin) input += chunk
|
||||
} else {
|
||||
input = await readFile(resolve(path), 'utf8')
|
||||
}
|
||||
const receipt = JSON.parse(input)
|
||||
const result = validateReceipt(receipt)
|
||||
|
||||
if (process.argv.includes('--json')) {
|
||||
process.stdout.write(`${JSON.stringify(result)}\n`)
|
||||
} else if (result.valid) {
|
||||
process.stdout.write(`✓ ${path} is a valid ${receipt.status.toUpperCase()} bug receipt.\n`)
|
||||
} else {
|
||||
process.stderr.write(`✗ ${path} is not a valid bug receipt:\n`)
|
||||
for (const issue of result.issues) process.stderr.write(` ${issue.path}: ${issue.message}\n`)
|
||||
}
|
||||
|
||||
process.exitCode = result.valid ? 0 : 1
|
||||
}
|
||||
|
||||
const invokedUrl = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : ''
|
||||
if (import.meta.url === invokedUrl) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`bug-receipt: ${error.message}\n`)
|
||||
process.exitCode = 2
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user