From 9d8cbb1fe2d07a71889fe78810732c02c139d66c Mon Sep 17 00:00:00 2001 From: "melodic-standards-sync[bot]" <300666570+melodic-standards-sync[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 22:28:40 +0000 Subject: [PATCH] chore: sync standards components (1af4bb247c890ec874d0c5986eb7c280441b4502) --- .../pr-check-managed-files-hosted.yml | 6 + docs/contracts/issue-body/contract.json | 235 ++++++++++++++++++ docs/standards/issue-tracker.md | 6 +- 3 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 docs/contracts/issue-body/contract.json diff --git a/.github/workflows/pr-check-managed-files-hosted.yml b/.github/workflows/pr-check-managed-files-hosted.yml index 38cd1e0ff76..c620851039c 100644 --- a/.github/workflows/pr-check-managed-files-hosted.yml +++ b/.github/workflows/pr-check-managed-files-hosted.yml @@ -41,10 +41,15 @@ on: - .editorconfig - .editorconfig-checker.json - .gitattributes + - .github/ISSUE_TEMPLATE/agent-ready.yml + - .github/ISSUE_TEMPLATE/bug-report.yml + - .github/ISSUE_TEMPLATE/feature-request.yml + - .github/ISSUE_TEMPLATE/task.yml - .github/actionlint.yaml - .github/standards/dependabot-policy/policy.json - .github/standards/github-actions-conventions/vocabulary.json - .github/standards/github-actions-conventions/vocabulary.schema.json + - .github/standards/issue-body-contract/contract.json - .github/standards/pyright/pyrightconfig.json - .github/standards/runner-policy/package-lock.json - .github/standards/runner-policy/package.json @@ -69,6 +74,7 @@ on: - .shellcheckrc - REVIEW.md - _typos.toml + - docs/contracts/issue-body/contract.json - docs/conventions/source-control/commit-convention.yml - docs/standards/README.md - docs/standards/bulk-mutation.md diff --git a/docs/contracts/issue-body/contract.json b/docs/contracts/issue-body/contract.json new file mode 100644 index 00000000000..93a7c625dea --- /dev/null +++ b/docs/contracts/issue-body/contract.json @@ -0,0 +1,235 @@ +{ + "schemaVersion": 1, + "headingLevels": [2, 3], + "emptyAnswer": "_No response_", + "floorLabels": ["needs-triage"], + "types": { + "bug": { + "form": "bug-report.yml", + "name": "Bug Report", + "description": "Report an unexpected problem or behavior.", + "nativeType": "Bug", + "labels": ["needs-triage"], + "fields": [ + { + "id": "steps", + "heading": "Steps to reproduce", + "kind": "textarea", + "required": true, + "description": "Minimal steps that reproduce the behavior.", + "placeholder": "1. ...\n2. ...\n3. ..." + }, + { + "id": "expected", + "heading": "Expected behavior", + "kind": "textarea", + "required": true, + "description": "What should happen?" + }, + { + "id": "actual", + "heading": "Actual behavior", + "kind": "textarea", + "required": true, + "description": "What actually happens? Include error messages, stack traces, or screenshots." + }, + { + "id": "severity", + "heading": "Severity", + "kind": "dropdown", + "required": true, + "description": "How bad is it? Say why under Severity justification.", + "options": ["low", "medium", "high", "critical"] + }, + { + "id": "severity-justification", + "heading": "Severity justification", + "kind": "textarea", + "required": true, + "description": "One sentence: the blast radius, what it blocks, or the data at risk." + }, + { + "id": "fix-location", + "heading": "Suggested fix location", + "kind": "input", + "required": false, + "description": "The file and function or class where the fix likely belongs, if known. No patch." + }, + { + "id": "environment", + "heading": "Environment", + "kind": "input", + "required": false, + "description": "OS, runtime and tool versions, and anything else relevant." + } + ] + }, + "feature": { + "form": "feature-request.yml", + "name": "Feature Request", + "description": "Propose a new feature or enhancement.", + "nativeType": "Feature", + "labels": ["needs-triage"], + "fields": [ + { + "id": "user-story", + "heading": "User story", + "kind": "textarea", + "required": true, + "description": "Who benefits and why?", + "placeholder": "As a [role], I want [capability] so that [benefit]." + }, + { + "id": "acceptance-criteria", + "heading": "Acceptance criteria", + "kind": "textarea", + "required": true, + "description": "How do we know this is done? One verifiable check per line.", + "placeholder": "- [ ] Criterion 1\n- [ ] Criterion 2" + }, + { + "id": "area", + "heading": "Affected area / component", + "kind": "input", + "required": false, + "description": "Which part of the project would this affect?" + } + ] + }, + "task": { + "form": "task.yml", + "name": "Task", + "description": "A chore, maintenance task, audit, or other housekeeping item.", + "nativeType": "Task", + "labels": ["needs-triage"], + "fields": [ + { + "id": "context", + "heading": "Context", + "kind": "textarea", + "required": true, + "description": "What surfaced this, and what leaving it costs." + }, + { + "id": "proposed-work", + "heading": "Proposed work", + "kind": "textarea", + "required": true, + "description": "The concrete next actions.", + "placeholder": "- ..." + }, + { + "id": "acceptance-criteria", + "heading": "Acceptance criteria", + "kind": "textarea", + "required": true, + "description": "One verifiable check per line.", + "placeholder": "- [ ] ..." + }, + { + "id": "references", + "heading": "References", + "kind": "textarea", + "required": false, + "description": "Rules, files, pull requests, or docs this relates to." + } + ] + }, + "agent-ready": { + "form": "agent-ready.yml", + "name": "Agent-Ready Work Item", + "description": "Work written as an agent brief. Triage decides its type and whether it is agent-ready.", + "nativeType": null, + "labels": ["needs-triage"], + "fields": [ + { + "id": "summary", + "heading": "Summary", + "kind": "input", + "required": true, + "description": "The change, in one line." + }, + { + "id": "root-cause", + "heading": "Root cause", + "kind": "input", + "required": false, + "description": "Bugs only: unknown, or known: the cause in one line." + }, + { + "id": "current-behavior", + "heading": "Current behavior", + "kind": "textarea", + "required": true, + "description": "What the code does today." + }, + { + "id": "desired-behavior", + "heading": "Desired behavior", + "kind": "textarea", + "required": true, + "description": "What is true once this lands, edge cases and failure handling included. The end state, not edit instructions." + }, + { + "id": "key-interfaces", + "heading": "Key interfaces", + "kind": "textarea", + "required": false, + "description": "The types, signatures, config keys, or observable behaviors that change, named by contract rather than file path or line." + }, + { + "id": "acceptance-criteria", + "heading": "Acceptance criteria", + "kind": "textarea", + "required": true, + "description": "Checks that each give a yes or no.", + "placeholder": "- [ ] ..." + }, + { + "id": "out-of-scope", + "heading": "Out of scope", + "kind": "textarea", + "required": true, + "description": "The neighboring code and features this change leaves as they are." + } + ] + }, + "decomposed-item": { + "form": null, + "name": "Decomposed work item", + "description": "A vertical slice /work-items:decompose publishes, born triaged; no public form.", + "nativeType": null, + "labels": [], + "fields": [ + { + "id": "outcome", + "heading": "Outcome", + "kind": "textarea", + "required": true, + "description": "The one observable change a user gets when this slice merges, end to end." + }, + { + "id": "key-interfaces", + "heading": "Key interfaces", + "kind": "textarea", + "required": true, + "description": "The design this slice touches, named by contract, or None." + }, + { + "id": "done-when", + "heading": "Done when", + "kind": "textarea", + "required": true, + "description": "The slice's acceptance criteria, each a check with a pass or fail answer." + }, + { + "id": "out-of-scope", + "heading": "Out of scope", + "kind": "textarea", + "required": true, + "description": "Adjacent work this slice must not change." + } + ] + } + } +} diff --git a/docs/standards/issue-tracker.md b/docs/standards/issue-tracker.md index ae57fed97a5..027fb4c8b79 100644 --- a/docs/standards/issue-tracker.md +++ b/docs/standards/issue-tracker.md @@ -29,7 +29,7 @@ The naming grammar above governs labels only. Issue titles are free text: there **Structured metadata, not title prefixes.** Set the type with `--type` or the issue form's Types field; set area and other axes with labels. PR titles are unchanged, remaining Conventional Commits enforced by the `pr-contract` composite inside the required `ci-status` check, because squash-merge makes the PR title the default-branch subject line. Issue titles and PR titles serve different layers and follow different rules. -**Issue forms set no title prefix.** GitHub's issue-form examples ship a `title: "[Bug]: "` default; forms here leave `title` empty and set the type through the form's `type:` key instead, so every issue reads the same. +**Issue forms set no title prefix.** GitHub's issue-form examples ship a `title: "[Bug]: "` default; forms here leave `title` empty and set the type through the form's `type:` key instead, so every issue reads the same. The one exception is the agent-ready work-item form, which sets no type: its work can be a bug, a feature or a task, so triage sets the type (the [issue-body contract](https://github.com/melodic-software/standards/blob/main/components/issue-body-contract/README.md#rules-the-contract-keeps) records why). **Open issues conform; closed ones stay.** Retitle an open issue that breaks this rule, unless automation finds that issue by its exact title. Closed issues are not backfilled. @@ -43,13 +43,13 @@ Every issue meets one rule, whether a person, an agent session or a bot workflow - a parent issue when the work belongs to a larger one; - a blocked-by edge when it waits on other work or a decision (see "The status axis" below). -The title and Issue Type are set at filing, always. The filer sets the other values it knows at filing; triage sets the rest before the issue is worked, and no value is ever a placeholder. A bot that files the same kind of record repeatedly (a post-merge alert, a monitoring incident) knows these values and sets them at filing. An issue tracks work with an owner, so a bot's current status belongs in its run's job summary; it files an issue only for an incident that needs action, one per incident. +The title and Issue Type are set at filing, always, except on an issue filed through the agent-ready work-item form, whose type triage sets. The filer sets the other values it knows at filing; triage sets the rest before the issue is worked, and no value is ever a placeholder. A bot that files the same kind of record repeatedly (a post-merge alert, a monitoring incident) knows these values and sets them at filing. An issue tracks work with an owner, so a bot's current status belongs in its run's job summary; it files an issue only for an incident that needs action, one per incident. ## The type axis On organization repositories the type axis is **native GitHub Issue Types**, not labels. Issue Types are an organization-level, single-select field: an issue carries **at most one** type, never two. GitHub positions them as the successor to a type-prefixed label. They are the governance mechanism for this axis; there is no parallel type label alongside them. GitHub does not require every issue to carry a type: search exposes a positive `type:"Bug"`-style qualifier per enabled type but no `no:type` negation to find the untyped remainder directly (querying `no:type` silently returns the unfiltered total, not a filtered one), so finding what's missing means comparing per-type counts against the total or reading an issue's `type` field, which is `null` when unset. An issue created via a form or API call that skips the field can silently fall outside the taxonomy this way. -**Issue creation always sets the type.** CLI and agent-driven issue creation passes `--type` with one of the org's enabled native types, for example `gh issue create --type Task` (or `Bug`/`Feature`, whichever applies). The web UI's issue form surfaces the same Types field at creation time, but GitHub does not force it to be filled in there either; setting it is this policy's requirement, not something the platform enforces on either path. github-iac does not assign a type on a schedule; [ADR 0003](https://github.com/melodic-software/github-iac/blob/main/docs/adr/0003-issue-type-axis-native-issue-types.md) deleted that sweep. An issue that skips the field stays untyped until a person or the triage lane sets one of the live organization types. +**Issue creation always sets the type**, apart from the agent-ready work-item form above. CLI and agent-driven issue creation passes `--type` with one of the org's enabled native types, for example `gh issue create --type Task` (or `Bug`/`Feature`, whichever applies). The web UI's issue form surfaces the same Types field at creation time, but GitHub does not force it to be filled in there either; setting it is this policy's requirement, not something the platform enforces on either path. github-iac does not assign a type on a schedule; [ADR 0003](https://github.com/melodic-software/github-iac/blob/main/docs/adr/0003-issue-type-axis-native-issue-types.md) deleted that sweep. An issue that skips the field stays untyped until a person or the triage lane sets one of the live organization types. Personal accounts have no native Issue Types, so their repositories retain a labeled type axis under the same naming grammar. This is a deliberate per-account divergence, not drift; each account's own infrastructure-as-code program is the record of which mechanism its repositories use (see "Definitions live in infrastructure-as-code" above).