From 0d28b0b7d74baa908a3145dfbcb20d35800ba8fc Mon Sep 17 00:00:00 2001 From: Ryan Smith Date: Sun, 19 Apr 2026 13:32:52 -0700 Subject: [PATCH] docs(skill): default to full six-section template on issue creation (SMI-4344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AC validator enforces a floor (120-char body, 2+ AC items) but not a target depth, so issues often land near that floor. Shift the default depth to the full template (Context → Problem → Proposal → Acceptance Criteria → Verification → Out of Scope); brevity is opt-in via explicit user phrasing ("quick issue", "one-liner", "just the AC", etc.). Changes are instruction-layer only — no validator, exit-code, or API changes: - SKILL.md: depth-rule paragraph in bullet #1; inline template expanded to six sections; MCP callout notes depth ≠ validation - docs/issue-template.md: six-section skeleton + populated Full example - scripts/lib/issue-description.ts: buildIssueTemplate() returns six sections (drops Notes, adds Problem / Proposal / Verification) - scripts/__tests__/issue-description.test.ts: assertions updated for new shape Full-example body dogfooded through `validate-description --stdin` → exit 0. All 49 tests pass. Co-Authored-By: claude-flow Co-Authored-By: Claude --- SKILL.md | 21 +++++-- docs/issue-template.md | 62 +++++++++++++++++++-- scripts/__tests__/issue-description.test.ts | 4 +- scripts/lib/issue-description.ts | 16 ++++-- 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/SKILL.md b/SKILL.md index 595730f..7bed638 100644 --- a/SKILL.md +++ b/SKILL.md @@ -177,23 +177,34 @@ See [Project Management Commands](#project-management-commands) for full referen **When creating a Linear issue, always complete these three steps — even if the user doesn't mention them.** -1. **Detailed description with Acceptance Criteria.** Every issue description MUST include an `## Acceptance Criteria` section with at least 2 concrete, testable checklist items. See [docs/issue-template.md](docs/issue-template.md) for the canonical template. The CLI `create-issue` / `create-sub-issue` will reject descriptions missing this structure; for MCP `save_issue` callers, validate the draft first with `npm run ops -- validate-description --stdin` (see below). If the user provides only a title, draft the description yourself using the template below. +1. **Detailed description with Acceptance Criteria.** Every issue description MUST include an `## Acceptance Criteria` section with at least 2 concrete, testable checklist items. See [docs/issue-template.md](docs/issue-template.md) for the canonical template plus a populated full example. The CLI `create-issue` / `create-sub-issue` will reject descriptions missing this structure; for MCP `save_issue` callers, validate the draft first with `npm run ops -- validate-description --stdin` (see below). If the user provides only a title, draft the description yourself using the template below. + + **Depth — default to the full six-section template.** Unless the user's phrasing clearly signals brevity (*"quick issue"*, *"one-liner"*, *"just the AC"*, *"brief"*, *"terse"*, *"minimum"*, *"short"*), structure the body as **Context → Problem → Proposal → Acceptance Criteria → Verification → Out of scope**. The 120-char / 2-item floor is what the validator *rejects*, not what reviewers *want*. If the user gives you only a title, draft a verbose body from the full template — ask follow-up questions rather than shipping the floor. For trivial changes (typo fix, one-line config tweak), collapsing `Problem` into `Context` and dropping `Verification` is fine when the AC is self-evidently testable — collapse deliberately, not by default. ```markdown ## Context **Title:** - <What is changing and why. 1-3 sentences.> + <What is changing and why. 2-4 sentences. Link prior issues, docs, or incidents that motivate this.> + + ## Problem + <What specifically is broken, missing, or insufficient today. Name the file, flow, or behavior.> + + ## Proposal + <What you intend to do about it. High-level approach, not implementation line-by-line.> ## Acceptance Criteria - [ ] <Concrete, testable outcome> - [ ] <Concrete, testable outcome> + ## Verification + <How the AC will actually be checked. Manual steps, test command, or review instruction.> + ## Out of Scope - - <Optional: what this issue does NOT cover> + - <What this issue does NOT cover — redirect to the follow-up or explain why it's deferred> ``` - Print the template on demand with: `npm run ops -- create-issue --template`. + Print the template on demand with: `npm run ops -- create-issue --template`. See [docs/issue-template.md](docs/issue-template.md) for a fully populated example. 2. **Labels.** Apply from the [label taxonomy](docs/labels.md): - Exactly ONE type label (`feature`, `bug`, `refactor`, `chore`, `spike`) @@ -214,6 +225,8 @@ See [Project Management Commands](#project-management-commands) for full referen > > The CLI already gates this for `create-issue` / `create-sub-issue`. MCP has no server-side gate — this pre-flight + the retroactive `npm run lint-issues` audit are the only enforcement for the MCP path. For longer drafts in a file, use `--file <path>` instead of `--stdin`. > +> **Depth ≠ validation.** Validation passing (exit 0) only means the 120-char / 2-AC floor is met. Structure the body as the full six-section template (Context → Problem → Proposal → AC → Verification → Out of Scope) unless the user explicitly asked for brevity — see bullet #1 above. +> > **Enforcement model.** CLI + SDK paths are hard-gated; the MCP path is instruction + audit. A PreToolUse hook that intercepts `save_issue` was considered and rejected: it only fires when Claude Code is the runtime, install is per-user, and the payload shape is harness-version-dependent. Run `npm run lint-issues -- --since 24h` locally or in CI to catch instruction-layer drift retroactively. --- diff --git a/docs/issue-template.md b/docs/issue-template.md index 237c1ff..f292b3f 100644 --- a/docs/issue-template.md +++ b/docs/issue-template.md @@ -1,23 +1,71 @@ # Issue Description Template -> **How to use:** Copy this entire file into the `description` argument of `create-issue` / `create-sub-issue`, or run `npm run ops -- create-issue --template` to get the same text on stdout. Fill in every section before submitting; empty checkboxes fail validation. +> **How to use:** Copy the skeleton below into the `description` argument of `create-issue` / `create-sub-issue`, or run `npm run ops -- create-issue --template` to get the same text on stdout. Fill in every section before submitting; empty checkboxes fail validation. +> +> **Default to detail.** The skeleton has six sections. Use all six unless the user asked for brevity ("quick issue", "one-liner", "just the AC", "brief", "terse", "minimum", "short"). The validator enforces a floor (120-char body, 2+ AC items); reviewers want the full shape. When in doubt, write more — see the [Full example](#full-example) below for what "earned depth" looks like. --- ## Context **Title:** <to fill> -<What is changing and why. 1-3 sentences.> +<What is changing and why. 2-4 sentences. Link prior issues, docs, or incidents that motivate this.> + +## Problem +<What specifically is broken, missing, or insufficient today. Name the file, flow, or behavior.> + +## Proposal +<What you intend to do about it. High-level approach, not implementation line-by-line.> ## Acceptance Criteria - [ ] <Concrete, testable outcome> - [ ] <Concrete, testable outcome> +## Verification +<How the AC will actually be checked. Manual steps, test command, or review instruction.> + ## Out of Scope -- <Optional: what this issue does NOT cover> +- <What this issue does NOT cover — redirect to the follow-up or explain why it's deferred> + +--- + +## Full example + +A real-shaped issue body, every section populated, nothing ceremonial. This is the depth bar — match it when drafting. The block below is what the `description` argument should look like verbatim (minus the code-fence wrapper). + +**Title:** Cache Linear label list in memory to avoid re-fetching on every `labels validate` call -## Notes -<Optional: links, references, screenshots> +````markdown +## Context +`labels validate` is invoked from `create-issue` (`scripts/linear-ops.ts:135`) on every issue creation. It re-fetches the full label list from Linear on each call — ~400ms round-trip. For batch scripts creating 10+ issues in a loop, that's 4+ seconds of avoidable latency, and it's the dominant cost now that the `lin` CLI fast-path handles the cheap cases. + +## Problem +`scripts/lib/labels.ts:fetchAllLabels()` has no caching. Each caller gets a fresh network fetch even when the label set hasn't changed within the process lifetime. No in-memory map, no module-level singleton, no short-lived cache. + +## Proposal +Add an in-memory cache to `fetchAllLabels()` keyed by workspace ID (derived from the SDK client). Cache TTL is the process lifetime — no invalidation needed, because new labels appearing mid-batch isn't a realistic case for CLI scripts. Fall through to the network on cache miss; populate the cache on success only (do not cache failures). + +## Acceptance Criteria +- [ ] `fetchAllLabels()` makes at most one network call per process for a given workspace +- [ ] A new test in `scripts/__tests__/labels.test.ts` spies on the fetcher and asserts call count ≤ 1 across 3 consecutive `validate` invocations +- [ ] No change to `fetchAllLabels()` signature — all call sites remain identical +- [ ] `LINEAR_DISABLE_LABEL_CACHE=1` env var escape hatch restores fetch-every-call behavior + +## Verification +```bash +npm run build && npm test +# then, with a valid LINEAR_API_KEY: +time npm run ops -- labels validate "feature,backend" # run 3 times back-to-back +# First run: ~400ms (network). Runs 2-3: <50ms each (cached). +LINEAR_DISABLE_LABEL_CACHE=1 npm run ops -- labels validate "feature,backend" +# Should re-fetch, ~400ms again. +``` + +## Out of Scope +- Persisting the cache across process invocations (filesystem or Redis) — follow-up if batch latency is still problematic +- Invalidation on label CRUD mutations within the same process — assumed rare; if it bites someone, add a clear-on-mutate hook then +- Caching the project or team lists — same pattern applies but track separately +```` --- @@ -35,6 +83,8 @@ Warnings (don't block): - No `## Context` / `## Why` / `## Background` heading. - All acceptance-criteria items under 10 characters. +The validator is a **floor**, not a target. `Problem`, `Proposal`, and `Verification` are not mechanically enforced — they're strongly recommended by the depth rule in `SKILL.md`. Reviewers read for them; skipping them means the issue reads as "just passed validation" rather than "ready to build against." + ## Escape hatches - **Per-invocation**: `--strict=false` flag downgrades validation to a warning for that single run. @@ -43,4 +93,4 @@ Warnings (don't block): ## Bug reports and "Steps to Reproduce" -If your issue is a bug report and you prefer `## Steps to Reproduce` over `## Acceptance Criteria`, include both — the AC section can list the fixed-behavior assertions (e.g. `- [ ] Login succeeds for valid credentials`). The validator only checks for the presence of `Acceptance Criteria`; other sections are free-form. +If your issue is a bug report and you prefer `## Steps to Reproduce` over `## Proposal`, include both — `Steps to Reproduce` under `## Context` or `## Problem`, and the AC section lists the fixed-behavior assertions (e.g. `- [ ] Login succeeds for valid credentials`). The validator only checks for the presence of `Acceptance Criteria`; other sections are free-form. diff --git a/scripts/__tests__/issue-description.test.ts b/scripts/__tests__/issue-description.test.ts index 9656ab3..62eb385 100644 --- a/scripts/__tests__/issue-description.test.ts +++ b/scripts/__tests__/issue-description.test.ts @@ -138,9 +138,11 @@ describe('buildIssueTemplate', () => { it('returns a string containing required sections', () => { const t = buildIssueTemplate(); assert.ok(t.includes('## Context')); + assert.ok(t.includes('## Problem')); + assert.ok(t.includes('## Proposal')); assert.ok(t.includes('## Acceptance Criteria')); + assert.ok(t.includes('## Verification')); assert.ok(t.includes('## Out of Scope')); - assert.ok(t.includes('## Notes')); }); it('does NOT include an H1', () => { diff --git a/scripts/lib/issue-description.ts b/scripts/lib/issue-description.ts index 8e83aba..6a06485 100644 --- a/scripts/lib/issue-description.ts +++ b/scripts/lib/issue-description.ts @@ -115,17 +115,23 @@ export function buildIssueTemplate(title?: string): string { '## Context', titleLine, '', - '<What is changing and why. 1-3 sentences.>', + '<What is changing and why. 2-4 sentences. Link prior issues, docs, or incidents that motivate this.>', + '', + '## Problem', + '<What specifically is broken, missing, or insufficient today. Name the file, flow, or behavior.>', + '', + '## Proposal', + '<What you intend to do about it. High-level approach, not implementation line-by-line.>', '', '## Acceptance Criteria', '- [ ] <Concrete, testable outcome>', '- [ ] <Concrete, testable outcome>', '', - '## Out of Scope', - '- <Optional: what this issue does NOT cover>', + '## Verification', + '<How the AC will actually be checked. Manual steps, test command, or review instruction.>', '', - '## Notes', - '<Optional: links, references, screenshots>', + '## Out of Scope', + '- <What this issue does NOT cover — redirect to the follow-up or explain why it is deferred>', '' ].join('\n') }