Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:** <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`)
Expand All @@ -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.

---
Expand Down
62 changes: 56 additions & 6 deletions docs/issue-template.md
Original file line number Diff line number Diff line change
@@ -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
````

---

Expand All @@ -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.
Expand All @@ -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.
4 changes: 3 additions & 1 deletion scripts/__tests__/issue-description.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
16 changes: 11 additions & 5 deletions scripts/lib/issue-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
Expand Down
Loading