diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 0000000..611db70 --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1,6 @@ +# Per-machine permission overrides (each contributor curates their own) +settings.local.json + +# Scratch space written by skills (sources.md, slack.md, report.html, etc.) +tmp/* +!tmp/.gitkeep diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 0000000..ba3f2fe --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,158 @@ +# .claude — Agents, Skills, and Drafts + +Claude Code configuration for the Trunk docs (Mintlify) repo. Skills are documentation-focused: scaffolding new pages, processing notes into PRs, reviewing changes, verifying PRs against production. + +## Structure + +``` +.claude/ +├── agents/ # Subagent definitions (spawned via the Agent tool) +│ └── doc-researcher.md # Gathers Linear/PR/Slite/docs context before writing +├── skills/ # User-invoked workflows (triggered via /skill-name) +│ ├── docs-research/ # Audit existing docs to find gaps and placement +│ ├── outline-docs/ # Scaffold a new docs page from scratch +│ ├── write-docs/ # Trunk2 context → PR pipeline (9 phases) +│ └── verify-docs-pr/ # Verify a docs PR's feature is live in prod +├── drafts/ # Optional input notes files for write-docs +│ └── TEMPLATE.md # Scaffold for new draft notes +├── tmp/ # Scratch outputs (gitignored) +├── settings.json # Shared permissions (committed) +└── settings.local.json # Per-machine overrides (gitignored) +``` + +## Mental model + +The flow is **research → write → verify**: + +| Phase | Skill | +|---|---| +| Research | `/docs-research` (existing-coverage audit) + `doc-researcher` agent (Linear/PR/Slite context) | +| Write | `/outline-docs` (blank-page scaffold) or `/write-docs` (full PR pipeline) | +| Verify | `/verify-docs-pr` (is the feature actually live in prod?) | + +## Agents vs. Skills + +**Agents** (`agents/`) are autonomous subprocesses spawned by the `Agent` tool. They run with a specific model and limited toolset, do their work, and return results to the parent conversation. Use agents for parallelizable research tasks. + +**Skills** (`skills/`) are user-invoked workflows triggered with `/skill-name`. They run in the main conversation with full tool access and follow a structured multi-phase pipeline. Use skills for end-to-end tasks that produce artifacts (PRs, tickets, reports). + +| Type | How to invoke | Runs where | Best for | +|---|---|---|---| +| Agent | Spawned via the `Agent` tool | Background subprocess | Research, context gathering, parallel work | +| Skill | `/skill-name` | Main conversation | Multi-step workflows with user checkpoints | + +## Skill reference + +### `/docs-research` + +**When:** Before writing a new doc (or right after a deploy) to audit existing coverage, find gaps, and decide where new content should live. + +**What it does:** Five-phase audit: +1. Maps the relevant product-area group in `docs.json` and lists candidate `.mdx` files +2. Searches existing docs (hosted search + local grep) for the topic and synonyms +3. Classifies each hit as `covered`, `partial`, or `adjacent` +4. Recommends placement for new content — defaulting to extending an existing page over creating a new one +5. Generates a structured report with existing coverage, gaps, suggested placement, and cross-links to add + +**Inputs:** A topic / feature name / product area. Optional: a feature description, PR body, or Linear ticket. `full` for a site-wide audit. + +**Outputs:** A research report that feeds directly into `/outline-docs` or `/write-docs`. + +--- + +### `/outline-docs` + +**When:** Starting a brand-new docs page from scratch with no prior spec. + +**What it does:** Asks page type (Overview / Reference / Guide), title, and save path. Generates a scaffolded `.mdx` file with sections pre-filled with 1-2 sentences plus focused `` markers. Adds the page to `docs.json` navigation. Runs `trunk fmt`. + +**Inputs:** Conversation context (title, page type, topic) — asks only for what's missing. + +**Outputs:** A new `.mdx` file ready for content, plus a post-checklist. + +--- + +### `/write-docs` + +**When:** Given a draft notes file, trunk2 PR numbers, Linear ticket IDs, a deploy tag, or Slite links — anything that says "document this feature." + +**What it does:** Full 9-phase pipeline: +1. Overlap detection (Phase 0) — checks GitHub for existing or conflicting docs PRs **and** Linear for existing planning tickets before starting work; asks the user if anything matches +2. Research across Linear, Slite, Slack, trunk2 PR diffs, and existing docs +3. Drafts new content or in-place edits, updating `docs.json` if adding pages +4. Creates a branch, commits, opens a **draft** PR with author tags +5. Updates Linear (links the existing ticket from Phase 0 or creates a new one) and writes a Slack post draft +6. Invokes `/verify-docs-pr` to check whether the feature is actually live in prod + +**Inputs:** Trunk2 PR refs, Linear ticket IDs, a deploy tag, Slite links, or an optional `.claude/drafts/.md` file for batch workflows. + +**Outputs:** Branch, draft PR, Linear update, Slack post in `tmp//slack.md`. + +**Discipline:** One feature = one PR. Always opens as draft for human review. + +--- + +### `/verify-docs-pr` + +**When:** A docs PR is open and you need to confirm the feature it documents is actually shipped to customers before publishing. + +**What it does:** Classifies a PR as `live`, `staged`, `pending`, `blocked`, or `unknown` using indirect signals: linked eng PR merge state, follow-up PRs in trunk2, Slack rollout chatter, e2e flag defaults, and legacy code presence. Posts the verdict as a comment on the docs PR and the linked Linear ticket. Updates the PR title with a verdict prefix (`[ready to merge]`, `[blocked]`, etc.) and flips non-live PRs to draft. + +**Inputs:** A PR number (single mode), or no arg for a sweep across all open PRs. + +**Outputs:** PR comment, Linear comment, title prefix update, draft state. + +**Auto-invoked:** by `/write-docs` Phase 4 after PR creation. + +--- + +## Agent reference + +### `doc-researcher` + +Subagent (not a slash command). Spawned via the `Agent` tool with `subagent_type: doc-researcher`. + +**Model:** Sonnet 4.6 (chosen for speed). + +**Tools:** `Read`, `Grep`, `Glob`, `Bash` — read-only. + +**When:** Before `/write-docs` when the scope is unclear or multiple tickets need surveying. + +**What it does:** Reads Linear tickets, linked PRs, and existing docs pages. Returns a structured research brief: feature summary, source PRs (with GitHub author handles), current docs coverage, key technical details pulled from code, and a suggested doc structure. + +**Use it in parallel** with other research (Slack, Slite searches) to save time. + +## Drafts + +`drafts/` is optional scratch space for batch workflows. `/write-docs` accepts trunk2 PR / Linear / deploy-tag refs directly, so most invocations skip drafts entirely. Use a draft file when you want to curate notes by hand before processing — for example, post-deploy when several features ship and you want to triage which ones need docs first. + +- **`TEMPLATE.md`** — scaffold for new drafts. Don't edit; copy it. +- **`.md`** — one per feature. Author manually, then run `/write-docs `. + +If you do use a draft file, treat it as input: never modify or delete it mid-pipeline. + +## Tmp + +`tmp/` is scratch space written by skills during execution: + +- `tmp//sources.md` — research audit trail for the reviewer +- `tmp//slack.md` — Slack post draft (mrkdwn format, ready to paste) +- `tmp/report.html` — cumulative HTML report from `/write-docs` runs + +Everything under `tmp/` is gitignored except `.gitkeep`. + +## Settings + +- **`settings.json`** — shared permissions baseline. Committed. Curate carefully; anything in here is auto-approved for every contributor on this repo. +- **`settings.local.json`** — per-machine overrides. Gitignored. Each contributor curates their own. + +## Source of truth + +Generic versions of these skills live in `~/Developer/gutils/claude-code/skills/trunk/` (the canonical personal-use copy). The versions in this directory are **project-tuned for Mintlify**: + +- `docs-research` — audits `docs.json` groups, reads `.mdx` files, defaults to extending existing pages over creating new ones +- `outline-docs` — uses `.mdx`, updates `docs.json` nav, generates Mintlify callouts +- `write-docs` — updates `docs.json` instead of `summary.md` +- `verify-docs-pr` — hardcoded to `trunk-io/docs2` + +When updating one of these skills, decide whether the change is generic (also update gutils) or project-specific (only update here). Intentional drift between the two is fine and expected. diff --git a/.claude/agents/doc-researcher.md b/.claude/agents/doc-researcher.md new file mode 100644 index 0000000..d28f391 --- /dev/null +++ b/.claude/agents/doc-researcher.md @@ -0,0 +1,49 @@ +--- +name: doc-researcher +description: "Researches Linear tickets, PRs, and existing docs to compile context for documentation work. Use PROACTIVELY before write-docs when the scope is unclear or when multiple tickets need to be surveyed." +model: claude-sonnet-4-6 +tools: Read, Grep, Glob, Bash +--- + +You are a documentation researcher for Trunk.io. Your job is to gather +and organize all available context about a feature before docs are written. + +You are working inside the local Mintlify-powered Trunk docs repository (`trunk-io/docs2`). Pages are `.mdx` files organized by product area, with site navigation defined in `docs.json`. + +## Process +1. Look up all provided Linear ticket IDs — extract descriptions, + comments, linked PRs, related tickets +2. Explore the local filesystem to find any current docs coverage of the topic +3. For each linked trunk2 PR, note: what changed, who authored it, + any inline comments or review discussion that explains behavior +4. Check if the feature has any existing changelog entries or roadmap mentions + +## Output Format + +Produce a structured research brief: + +### Feature Summary +2-3 sentence plain-English summary of what shipped and why. + +### Source Tickets +List of Linear ticket IDs with their status and one-line description. + +### Source PRs +| PR | Author (GitHub handle) | Status | Key changes | +|----|------------------------|--------|-------------| + +### Current Docs Coverage +- What exists already (file paths + one-line summary of content) +- What's missing or stale + +### Key Technical Details +Bullet list of specific behaviors, defaults, flag names, API shapes, +edge cases — pulled directly from PR code/comments, not inferred. + +### Suggested Doc Structure +Where new/updated content should live, and what sections it needs. + +### Handoff Note for doc-writer +Any context the writer needs that isn't obvious from the tickets — +e.g., "the UI was redesigned; old screenshots in existing docs are wrong" +or "this feature is gated behind a settings toggle, document that clearly." diff --git a/.claude/drafts/TEMPLATE.md b/.claude/drafts/TEMPLATE.md new file mode 100644 index 0000000..57852d0 --- /dev/null +++ b/.claude/drafts/TEMPLATE.md @@ -0,0 +1,44 @@ +# [Feature/Change Title] + +## Type + + + +## Priority + + + +## Linear Tickets + + + +- TRUNK-XXXXX + +## What Changed + + + +## GitHub PRs + + + +## Context Links + + + +## Target Docs + + + +## Context + + diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..b087a85 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,95 @@ +{ + "permissions": { + "allow": [ + "Bash(claude mcp list:*)", + "Bash(echo:*)", + "Bash(date:*)", + "Bash(ls:*)", + "Bash(chmod:*)", + "Bash(open:*)", + "Bash(code:*)", + + "Bash(git add:*)", + "Bash(git branch:*)", + "Bash(git checkout:*)", + "Bash(git cherry-pick:*)", + "Bash(git check-ignore:*)", + "Bash(git clean:*)", + "Bash(git commit:*)", + "Bash(git config:*)", + "Bash(git diff:*)", + "Bash(git fetch:*)", + "Bash(git log:*)", + "Bash(git pull:*)", + "Bash(git push:*)", + "Bash(git rebase:*)", + "Bash(git reset:*)", + "Bash(git revert:*)", + "Bash(git rm:*)", + "Bash(git stash:*)", + "Bash(git status:*)", + "Bash(git worktree:*)", + + "Bash(gh api:*)", + "Bash(gh auth:*)", + "Bash(gh issue:*)", + "Bash(gh pr:*)", + "Bash(gh pr comment:*)", + "Bash(gh pr create:*)", + "Bash(gh pr edit:*)", + "Bash(gh pr ready:*)", + "Bash(gh pr view:*)", + "Bash(gh release:*)", + "Bash(gh repo:*)", + "Bash(gh run:*)", + "Bash(gh search:*)", + + "Bash(npm:*)", + "Bash(npx:*)", + "Bash(mint:*)", + "Bash(trunk:*)", + "Bash(curl:*)", + "Bash(jq:*)", + "Bash(xargs:*)", + "Bash(find:*)", + "Bash(grep:*)", + "Bash(awk:*)", + "Bash(python3:*)", + + "WebFetch(domain:trunk.io)", + "WebFetch(domain:docs.trunk.io)", + "WebFetch(domain:mintlify.com)", + "WebSearch", + + "mcp__claude_ai_trunk_docs__searchDocumentation", + + "mcp__claude_ai_Linear__list_teams", + "mcp__claude_ai_Linear__list_issues", + "mcp__claude_ai_Linear__list_issue_labels", + "mcp__claude_ai_Linear__list_comments", + "mcp__claude_ai_Linear__get_issue", + "mcp__claude_ai_Linear__get_project", + "mcp__claude_ai_Linear__save_issue", + "mcp__claude_ai_Linear__save_comment", + "mcp__claude_ai_Linear__create_comment", + + "mcp__claude_ai_Trunk_Slite__search-notes", + "mcp__claude_ai_Trunk_Slite__list-channels", + "mcp__claude_ai_Trunk_Slite__get-note", + "mcp__claude_ai_Trunk_Slite__get-note-children", + "mcp__claude_ai_Trunk_Slite__create-note", + "mcp__claude_ai_Trunk_Slite__append-blocks", + + "mcp__claude_ai_Slack__slack_search_public_and_private", + "mcp__claude_ai_Slack__slack_search_channels", + "mcp__claude_ai_Slack__slack_read_thread", + "mcp__claude_ai_Slack__slack_send_message", + + "Skill(write-docs)", + "Skill(outline-docs)", + "Skill(docs-research)", + "Skill(verify-docs-pr)", + "Skill(schedule)" + ] + } +} diff --git a/.claude/skills/docs-research/SKILL.md b/.claude/skills/docs-research/SKILL.md new file mode 100644 index 0000000..613fda6 --- /dev/null +++ b/.claude/skills/docs-research/SKILL.md @@ -0,0 +1,119 @@ +--- +name: docs-research +description: >- + Audit the existing Mintlify docs site to inform new documentation work. + Run before /outline-docs or /write-docs to (1) identify gaps in coverage, + (2) recommend placement for new content, and (3) prevent duplicated effort + by surfacing existing pages that already touch the topic. +allowed-tools: Read, Glob, Grep, Bash(rg *), Bash(jq *), mcp__claude_ai_trunk_docs__searchDocumentation +--- + +# Docs Research + +Survey the existing Trunk docs site before writing new content. Produces a structured report covering existing coverage, gaps, recommended placement, and cross-link opportunities. + +## Inputs + +The user provides: + +- **A topic, feature name, or product area** — required scope (e.g., "auto-quarantine override", "Merge Queue health page", "flaky test history timeline") +- **Optional context** — a feature description, PR body, Linear ticket, or notes file that describes what's about to be documented +- **`full`** — site-wide audit mode (slower; reads every page in every product area) + +If no scope is provided, ask what to research before proceeding. + +## Workflow + +Follow these phases in order. + +### Phase 1: Map the relevant area + +1. Read `docs.json`. Identify which top-level group(s) the topic belongs to (e.g., `flaky-tests`, `merge-queue`, `setup-and-administration`). +2. List all `.mdx` files in the relevant group directories via `Glob`. +3. For each candidate page, read its frontmatter (`title`, `description`) and section headers (lines starting with `##` or `###`) to build a lightweight coverage map. Don't read full bodies yet. + +In `full` mode, skip the topic-narrowing and walk every group. + +### Phase 2: Search for topical overlap + +For the given topic and its likely synonyms: + +1. **Hosted docs search** — `mcp__claude_ai_trunk_docs__searchDocumentation` for the topic, the feature name, and 2-3 likely synonyms. +2. **Local grep** — `Grep` across the relevant `.mdx` files for the same terms. +3. **Hit list** — for each match, capture: + - File path + - Section header containing the match + - One-line summary of what that section currently says about the topic (read just enough of the section body to summarize) + +### Phase 3: Classify each hit + +For every existing page that touches the topic, label it: + +| Label | Meaning | +|---|---| +| `covered` | Topic is fully documented here; new content would duplicate | +| `partial` | Topic is mentioned briefly; could be expanded in-place rather than creating a new page | +| `adjacent` | Related but distinct topic; new content should cross-link, not merge | + +Anything mentioned in the input scope but absent from the hit list is a **gap**. + +### Phase 4: Recommend placement + +For each gap or partial-coverage finding, propose where the new content should live: + +1. Identify the most relevant product-area group in `docs.json`. +2. Decide between **extending an existing page** (default) and **creating a new page** (only if the topic clearly doesn't fit any existing page's scope). +3. If creating new, suggest 2-3 specific placement options with rationale (e.g., "new file under `flaky-tests/configuration/auto-quarantine-overrides.mdx`, between `auto-quarantine.mdx` and `quarantine-history.mdx` to maintain the configuration → behavior → audit ordering"). +4. Note natural cross-links — pages that should reference the new content once it exists. + +**Bias:** extending an existing page beats creating a new one. Only recommend a new page when the topic warrants its own scope and the existing pages would feel bloated if extended. + +### Phase 5: Generate report + +Print a structured report. Format: + +``` +Docs Research — +======================================== + +## Existing coverage + +| Page | Coverage | What it says | +|---|---|---| +| .mdx | covered | | +| .mdx | partial | | +| .mdx | adjacent | | + +## Gaps +- — not covered anywhere +- — not covered anywhere + +## Recommended placement +1. **Extend** `.mdx` — +2. **New page** at `.mdx` — + +## Cross-links to add +- `` → `` +- `` → `` + +## Suggested next step +- /outline-docs to scaffold at +- /write-docs targeting +- (no action — topic is already well covered) +``` + +In `full` mode, replace the per-topic structure with a per-group coverage matrix. + +## When to use + +- **Before `/outline-docs`** — confirm a new page is needed and identify the right path +- **Before `/write-docs`** — surface existing content the new docs should reference or replace +- **After a deploy** — spot gaps where shipped features are undocumented +- **For periodic audits** — run in `full` mode to find stale, duplicated, or thin coverage across product areas + +## Guidelines + +- **Code is law.** Prefer canonical sources (trunk2 PRs, code, official Mintlify docs) over Slack speculation when classifying coverage. +- **Be specific.** "Auto-quarantine is mentioned" is useless. "Auto-quarantine is mentioned in `flaky-tests/configuration.mdx` Phase 3 as a one-liner; behavior details and override flow are missing" is what we want. +- **Default to extending, not creating.** Three thin pages on adjacent topics is worse than one well-organized page. Only recommend a new page when the topic deserves its own scope. +- **Surface dependents.** When a page changes location or scope (in `full` mode audits), note any cross-links from other pages that would need updating. diff --git a/.claude/skills/outline-docs/SKILL.md b/.claude/skills/outline-docs/SKILL.md new file mode 100644 index 0000000..6adcd8d --- /dev/null +++ b/.claude/skills/outline-docs/SKILL.md @@ -0,0 +1,345 @@ +--- +name: outline-docs +description: >- + Use when starting a new docs page to scaffold a structured outline. + Extracts page type, title, and context from your message, asks only for + missing information, then generates a file with sections pre-filled with + initial content (1-2 sentences) and focused TODO comments for expansion. + Prefer this to the write-docs skill when there is no prior specification. +allowed-tools: Write, Bash(trunk fmt) +--- + +# Outline Docs + +Generate a scaffolded outline for a new docs page in the Trunk docs repo. The skill prompts for page type, title, and save path, then creates a file with proper headers and `` placeholders that the engineer fills in with content. Includes a post-checklist with formatting and validation steps. + +## Contents + +- [Inputs](#inputs) +- [Workflow](#workflow) + - [Phase 1: Gather inputs](#phase-1-gather-inputs) + - [Phase 2: Generate preview](#phase-2-generate-preview) + - [Phase 3: Write file](#phase-3-write-file) + - [Phase 4: Docs organization](#phase-4-docs-organization) + - [Phase 5: Post-checklist](#phase-5-post-checklist) +- [Templates](#templates) + +## Inputs + +The user may provide upfront context: +- **Page type** — one of: Overview, Reference, Guide +- **Page title** — the H1 heading (e.g., "Installing the CLI") +- **Save path** — where to write the file (optional; defaults to `./untitled.mdx`) +- **Topic description** — what the page should cover (e.g., "walkthrough of investigating and fixing flaky tests") +- **Key prerequisites** — required setup, permissions, or tools mentioned + +## Workflow + +Follow these phases in order. + +### Phase 0: Extract upfront context + +Before asking any questions, scan the user's message for: +- **Page type hints** — words like "guide", "overview", "reference", "how-to", "walkthrough" +- **Page title hints** — capitalized phrases, quoted strings, or suggested topics +- **Path hints** — directory structures, URLs, or path suggestions +- **Topic details** — what the page should document or teach +- **Prerequisites** — requirements, access levels, tools, or setup mentioned + +Store any detected values. Only ask for information that's missing or ambiguous. + +### Phase 1: Gather missing inputs + +Ask only for inputs not extracted from upfront context (ask one question per message): + +**If page type is unknown:** + +#### Question: Page type + +Prompt the user to choose a page type. Include definitions and examples for each: + +``` +What type of page are you creating? + +(1) Overview + High-level introduction to a product, feature, or concept. + Examples: product README, "What is X?" page, feature landing page. + +(2) Reference + Lookup documentation for config options, CLI flags, API fields, + or other structured data. + Examples: configuration reference, command listing, options table. + +(3) Guide + Step-by-step how-to for completing a specific task. + Examples: "Getting started", setup walkthrough, "How to configure X". + +Enter 1, 2, or 3: +``` + +Store the user's response as `page_type`. + +**If page title is unknown:** + +#### Question: Page title + +``` +What is the page title? (This becomes the H1 header) + +Examples: "Installing the CLI", "Configuring Linters", "Understanding Test Reports" + +Title: +``` + +Store the user's response as `page_title`. + +**If save path is unknown:** + +#### Question: Save path + +``` +Where should I save this file? +(Default: ./untitled.mdx in the current directory) + +Path (or press Enter for default): +``` + +Store the user's response as `save_path`. If blank, use `./untitled.mdx`. + +**After gathering missing inputs:** + +Confirm extracted + provided values to the user: +``` +Using: +- Page type: [page_type] +- Title: [page_title] +- Path: [save_path] +- Topic: [topic_description if provided] + +Ready to generate outline. +``` + +### Phase 2: Generate preview + +Generate the outline based on the page type, title, and any topic context provided. Use the appropriate template from the [Templates](#templates) section below, replacing `` with `page_title`. + +**Pre-fill strategy:** +- Replace obvious `<!-- TODO -->` comments with 1-2 sentences of initial content from Claude based on topic/context +- Keep `<!-- TODO: ... -->` comments for sections that need user expansion or details Claude can't infer +- Example: Instead of `<!-- TODO: Explain prerequisites -->`, write: "This guide requires beta access and the Investigate Flaky Tests setting enabled. <!-- TODO: Add specific account requirements or version constraints -->" + +Display the outline in the terminal: + +``` +Generated outline: +======================================== +[Show full outline markdown here, with Claude pre-fill + remaining TODOs] +======================================== + +Does this outline look right? +(y = write it / n = start over) +``` + +- If user enters `y`, proceed to Phase 3 +- If user enters `n`, return to Phase 1 to gather new inputs + +### Phase 3: Write file + +Call `Write` tool to create the file at `save_path` with the generated outline. + +After writing, output: + +``` +✅ Outline written to: <save_path> + +Next steps: +1. Fill in all <!-- TODO --> sections +2. Add a frontmatter description if this is a top-level overview page +3. Verify all links to related pages are correct +4. Run the post-checklist (see below) +``` + +Then proceed to Phase 4. + +### Phase 4: Docs Organization + +Add the new page to `docs.json` at the root of the repo. Insert its path (without the `.mdx` extension) into the appropriate `groups[].pages` array based on the product area. Match the position of nearby pages for sort order. + +Then proceed to Phase 5. + +### Phase 5: Post-checklist + +Print the post-checklist for the user to complete: + +``` +Post-Checklist: +[ ] Fill in all <!-- TODO --> sections +[ ] Add frontmatter description (if page is a top-level overview) +[ ] Verify all links to related pages are correct +[ ] Run: trunk check +[ ] Fill in TODOs, run `trunk check` for linting, and request review on the PR +``` + +Then, on the user's behalf, automatically run: + +```bash +trunk fmt +``` + +to format the skeleton markdown. + +Report the results. If either command reports errors, display them and suggest fixes. + +## Templates + +Each template uses `<title>` as a placeholder for the page title. Replace it with the actual title from Phase 1, Question 2. + +### Overview template + +Use this template when the user chooses page type **(1) Overview**. + +```markdown +--- +description: [Claude generates 1-2 sentences summarizing the feature/product for SEO. TODO: Refine if needed.] +--- + +# <title> + +[Claude writes 1-2 sentences explaining what this feature is and the primary user benefit. TODO: Add specific use cases or scenarios if needed.] + +### How it works + +[Claude provides a high-level explanation of the mechanism or value proposition based on topic context. TODO: Add diagrams, ASCII art, or deeper technical detail if appropriate.] + +### Key features + +[Claude lists 3-5 key features with brief descriptions. TODO: Expand with additional features or rearrange by priority.] + +### Get started + +[Claude provides a link or brief guidance pointing to the getting-started page(s) for this feature. TODO: Add alternatives or decision tree if multiple paths exist.] +``` + +**When to use this template:** +- Product/feature homepage pages +- "What is X?" pages +- Top-level overview pages that introduce a concept +- README files that lead to more detailed pages + +--- + +### Reference template + +Use this template when the user chooses page type **(2) Reference**. + +```markdown +# <title> + +[Claude writes 1-2 sentences explaining what this reference documents and when to use it. TODO: Add details about scope or version constraints if needed.] + +### Quick reference + +[Claude creates a table of the most common options/fields/commands with appropriate columns. TODO: Add or remove rows based on actual options to document.] + +| Option | Type | Description | +|--------|------|-------------| +| [Claude populates 1-2 examples] | | [Brief description] | +| <!-- TODO: Add additional rows --> | | | + +### [Option or section name from topic] + +[Claude provides a high-level description of this option. TODO: Add detailed explanation, code example, and caveats specific to your use case.] + +```yaml +# [Claude provides a basic example structure. TODO: Expand with complete configuration or command usage.] +``` + +### Examples + +[Claude provides 1-2 real-world examples of how to use this reference. TODO: Add domain-specific examples or alternative approaches if applicable.] +``` + +**When to use this template:** +- Configuration option references +- CLI command listing pages +- API field documentation +- Setting/option reference pages +- Structured lookup documentation + +--- + +### Guide template + +Use this template when the user chooses page type **(3) Guide**. + +```markdown +# <title> + +[Claude writes a direct, feature-focused intro (1-2 sentences). Example: "You can configure Trunk to automatically analyze your flaky tests and raise fix PRs." TODO: Adjust the capability description if needed.] + +### Prerequisites + +[Claude lists essential requirements based on workflow context (access, tools, setup, knowledge). TODO: Add or remove prerequisites specific to your implementation.] + +- Beta access via waitlist (request at https://slack.trunk.io) +- The "Investigate Flaky Tests" setting enabled +- <!-- TODO: Add other prerequisites if applicable --> + +### Step 1: [Claude names step based on workflow] + +[Claude provides a brief description of what this step accomplishes and what the user does. TODO: Add more detail or clarifications.] + +```bash +# [Claude provides example command or action. TODO: Add actual command with parameters for your use case.] +``` + +<Note> +[Claude adds a relevant tip or caveat if appropriate. Delete this Note block if not needed. TODO: Customize guidance if needed. Available Mintlify callouts: `<Note>`, `<Tip>`, `<Info>`, `<Warning>`, `<Check>`.] +</Note> + +**✅ Success:** [Claude describes expected outcome or confirmation. TODO: Specify success criteria if different.] + +### Step 2: [Claude names next step] + +[Claude provides description. TODO: Fill in step details.] + +<!-- Repeat Step N blocks as needed --> + +### What's next? + +[Claude suggests next logical steps or links to related documentation. TODO: Add links to follow-on guides or references.] +``` + +**When to use this template:** +- Step-by-step setup guides +- "Getting started" pages +- "How to configure X" pages +- Task-focused how-to articles +- Walkthrough guides with sequential steps + +--- + +## Post-Checklist Guidance + +After writing the file, the user should complete these steps: + +1. **Fill in all TODO sections** — Replace every `<!-- TODO -->` comment with actual content. Comments explain what goes in each section. + +2. **Add frontmatter description** — Add a `description:` field in the frontmatter for SEO and AI discoverability. Example: + ```yaml + --- + title: Page title + description: High-level intro to Trunk Code Quality and how it works. + --- + ``` + +3. **Verify links** — Ensure all cross-references to related pages use correct relative paths and match the actual file locations. Mintlify uses standard MDX links, without the file extension: + ```mdx + [Related page](/path/to/page) + ``` + +4. **Run validation** — Execute `trunk check` to check for linting issues. + +5. **Review content** — Fill in every `<!-- TODO -->` block, then rely on `trunk check` for linting and the normal PR review flow for editorial feedback. Run `/docs-research` if you discover the topic overlaps an existing page and you need to reconsider placement. + +If any tool reports errors or review identifies issues, fix them before merging. diff --git a/.claude/skills/verify-docs-pr/SKILL.md b/.claude/skills/verify-docs-pr/SKILL.md new file mode 100644 index 0000000..bded2cb --- /dev/null +++ b/.claude/skills/verify-docs-pr/SKILL.md @@ -0,0 +1,258 @@ +--- +name: verify-docs-pr +description: Verify that features documented in open docs PRs are actually live in production before publishing the docs. Classifies each PR as live, staged, pending, blocked, or unknown using indirect signals (eng PR merge state, follow-up PRs in trunk2, Slack rollout chatter, e2e flag defaults, legacy code presence). Posts the verdict on the docs PR and the linked Linear ticket. Use when given a docs PR number, when running a sweep across all open docs PRs, or as the post-creation step inside write-docs. +allowed-tools: Bash(gh *), Bash(git *), Bash(grep *), Bash(jq *), Read, Grep, mcp__claude_ai_Linear__get_issue, mcp__claude_ai_Linear__list_comments, mcp__claude_ai_Linear__save_comment, mcp__claude_ai_Slack__slack_search_public_and_private +--- + +# Verify Docs PR + +Verify whether the feature described in a docs PR is live in production before the docs get published. + +## Inputs + +- **PR number** (single mode): `/verify-docs-pr 589` +- **No arg** (sweep mode): runs across all open PRs in `trunk-io/docs2` +- **Auto-invoked from `write-docs`** Phase 4 with the freshly-created PR number + +## Verdicts + +| Verdict | Meaning | +|---|---| +| `live` | Customers can use the feature. Ready to publish. | +| `staged` | Feature on in staging, off in prod. Re-run after rollout. | +| `pending` | Eng work merged but feature flag still off in prod. Hold. | +| `blocked` | Eng PR is not merged or has been reverted. Hold. | +| `unknown` | Could not determine state from available signals. Manual check needed. | + +## Workflow + +### Phase 0: Resolve scope + +1. If a single PR number is provided, scope = that one PR. +2. Otherwise, list all open PRs: + ``` + gh pr list --repo trunk-io/docs2 --state open --limit 100 --json number,title,isDraft,headRefName + ``` +3. If scope > 13 PRs, dispatch parallel sub-agents in chunks of ~13 PRs each. See "Sweep parallelization" below. + +### Phase 1: Per-PR check + +For each docs PR in scope, run all five steps (A-E). + +#### Step A: Parse PR body + +1. Fetch PR data: + ``` + gh pr view <NUM> --repo trunk-io/docs2 --json body,headRefName,isDraft,state,comments + ``` +2. If `state` is `MERGED` or `CLOSED`, print `PR #<NUM> already <state>; skipping` and move to the next PR. +3. From the body, extract: + - **Eng PR refs**: match `trunk-io/(\w+)#(\d+)` and `https://github\.com/trunk-io/(\w+)/pull/(\d+)` + - **Linear ticket IDs**: match `TRUNK-\d+` +4. If no eng PR refs were found: + - Use `mcp__claude_ai_Linear__get_issue` for each Linear ticket ID + - Read its `relations` for related engineering tickets + - For each related eng ticket, look up its linked PRs via the same Linear MCP call + +#### Step B: Check eng PR state + +For each engineering PR reference: + +5. Fetch state: + ``` + gh pr view <num> --repo trunk-io/<repo> --json state,mergedAt,mergeCommit,files,body + ``` +6. If `state` is `OPEN` or `CLOSED` (not merged): tag this ref `blocked`. Skip to Step E. +7. If `state` is `MERGED`: continue. Verify the merge commit is still part of `main`: + ``` + gh api repos/trunk-io/<repo>/compare/main...<mergeCommit.oid> --jq '.status' + ``` + If the status is `identical` or `behind`, the merge commit is on main and the merge is intact. If `diverged`, the merge has been reverted. Tag the ref `blocked` with note "merged then reverted". + +#### Step C: Find feature flags + +For each merged eng PR: + +8. Read the PR body for explicit flag mentions. Patterns: + - Backticks adjacent to "flag" (e.g., `` gated behind the `enableFilteredUploadsPage` LaunchDarkly flag ``) + - camelCase identifiers starting with `enable`, `show`, `use` +9. Pull the diff: + ``` + gh pr diff <num> --repo trunk-io/<repo> + ``` + Grep for: + - `flags.ts` references + - Strings inside `useFeatureFlag(...)` and `useFlag(...)` calls + - LaunchDarkly URLs (e.g., `app.launchdarkly.com/projects/.../flags/<name>/`) +10. Collect unique flag names. If none found, set `flag=none` and proceed to Step E. + +#### Step D: Gather rollout signals (per flag) + +For each detected flag `<flag>`: + +11. **Follow-up PRs in trunk2:** + ``` + gh search prs --repo trunk-io/trunk2 "\"<flag>\"" --limit 30 --json number,title,state,createdAt,closedAt,url + ``` + Note: the literal quotes around the flag name force exact-phrase matching. Without them, common substrings like `enable` or `show` would return unrelated PRs and bias the rollout signal count. + + Filter to PRs whose `createdAt` is after the original eng PR's `mergedAt`. Look in titles for keywords: "rollout", "100%", "delete legacy", "remove flag", "ramp up". + +12. **Slack search:** + Use `mcp__claude_ai_Slack__slack_search_public_and_private` with: + - Query: `<flag>` + - Sort: `timestamp` (newest first) + - Look for messages from the LaunchDarkly bot, eng confirmations of flag state, rollout dates + - Recommended channels to scan in results: `#eng`, `#team-flaky-tests`, `#team-merge-queue`, `#production-notifications`, `#staging-notifications` + + A 0-result Slack search is itself a signal. It counts toward `pending`. + +13. **e2e flag default:** + ``` + gh api repos/trunk-io/trunk2/contents/ts/apps/e2e/flags.json --jq '.content' | base64 -d | jq '.flagValues["<flag>"]' + ``` + A `true` here only confirms it works in tests, not prod. + +14. **Legacy code presence:** + Eng PR bodies often mention a legacy component being preserved (e.g., "When the flag is off, the legacy `<UploadsClient>` renders unchanged"). Extract the legacy name from the eng PR body (regex: `legacy \x60(\w+)\x60` and similar) and search trunk2: + ``` + gh api 'search/code?q=<legacy-name>+repo:trunk-io/trunk2' --jq '.items[].path' + ``` + Presence of the legacy code path in `main` = flag not yet 100% rolled out. + +#### Step E: Classify + +Apply rules in order. First match wins. + +| Condition | Verdict | +|---|---| +| Any referenced eng PR is unmerged or reverted | `blocked` | +| Eng PR state itself was unavailable after retry (state unknown) | `unknown` | +| All eng PRs merged AND no flag found | `live` | +| Slack message confirms flag at 100% prod, OR a follow-up "delete legacy" PR is merged | `live` | +| Slack message dated AFTER the eng PR's `mergedAt` confirms flag on in staging, off in prod | `staged` | +| Eng PR merged, flag exists, no Slack rollout signals, legacy code still present | `pending` | +| Eng PR merged, flag exists, mixed or insufficient signals | `unknown` | + +**Recency rule.** Slack messages from before the eng PR's `mergedAt` describe a state the eng PR may have changed. Treat pre-merge messages as background context only, not as current-state signals. A Slack message must be timestamped after `mergedAt` to count as a positive `live` or `staged` signal. + +### Phase 2: Output + +For each PR with a verdict: + +15. **Console output.** + - Single mode: print full reasoning (eng work, flag, signals checked, suggested next action). + - Sweep mode: one line per PR, sorted by severity (`blocked` → `pending` → `staged` → `unknown` → `live`). End with a summary line. + +16. **PR comment.** + Body template (replace `<...>` placeholders): + ``` + <!-- verify-docs-pr --> + **Verification status (<DATE>): `<verdict>`** + + <verdict-opening-line, see "Verdict messages" below> + + - Eng PR: <links> + - Flag: `<flag-name>` (or "none" if ungated) + - Signals: <bulleted list of rollout signals checked> + + <suggested next action> + ``` + Check for an existing `<!-- verify-docs-pr -->` comment in the PR's `comments` array. If present, edit it via `gh api` (`PATCH /repos/.../issues/comments/<id>` with the new body). Otherwise post a new comment via `gh pr comment <num> --body "<body>" --repo trunk-io/docs2`. + + Do not use em dashes (U+2014) in the comment body. Use periods, commas, or parentheses instead. + +17. **Linear comment.** + Find the linked Linear ticket from the docs PR body. Use `mcp__claude_ai_Linear__list_comments` with the issue ID to find any existing `<!-- verify-docs-pr -->` comment. + - If present, update via `mcp__claude_ai_Linear__save_comment` with the comment ID. + - Otherwise create a new comment with `mcp__claude_ai_Linear__save_comment`. + + Body template: + ``` + <!-- verify-docs-pr --> + Verification status (<DATE>): `<verdict>` + + Docs PR: https://github.com/trunk-io/docs2/pull/<NUM> + + <same body as PR comment, minus the marker> + ``` + + Do not use em dashes (U+2014) in the comment body. Use periods, commas, or parentheses instead. + +18. **PR state action.** + + **Draft flag.** + - `live`: no draft change. + - Anything else: if `isDraft` is `false`, flip to draft via `gh pr ready <num> --undo --repo trunk-io/docs2`. If already draft, no-op. + + **Title prefix.** Adds a visible queue signal so anyone scanning open PRs can see the verdict without opening the PR. `live` PRs get a positive cue, non-`live` PRs get a hold cue. + + Known prefixes managed by this skill: `[ready to merge]`, `[staged]`, `[feature not live]`, `[blocked]`. Treat them as case-sensitive and bracket-anchored. + + Per verdict: + + | Verdict | Title prefix | + |---|---| + | `live` | `[ready to merge]` | + | `staged` | `[staged]` | + | `pending` | `[feature not live]` | + | `blocked` | `[blocked]` | + | `unknown` | none (the verdict is already non-actionable; an extra prefix would be noise) | + + Algorithm: + 1. Read the current title from the PR data fetched in Step A. + 2. Strip any leading known prefix to derive the base title. Match the regex `^\[(ready to merge|staged|feature not live|blocked)\] ` (anchored, single trailing space). + 3. Compose the new title: if the verdict has a prefix, `<prefix> <base>`; otherwise just `<base>`. + 4. If the new title differs from the current title, update via: + ``` + gh pr edit <num> --repo trunk-io/docs2 --title "<new title>" + ``` + If they match, no-op. + + This keeps the title in sync with the verdict on every run. The prefix lifecycle is fully automatic in both directions: a PR that was `pending` and flips to `live` swaps `[feature not live]` for `[ready to merge]`; a PR whose eng work gets reverted swaps `[ready to merge]` for `[blocked]`. + + Do not stack prefixes. If you ever see a title with multiple known prefixes (e.g., a manual edit that added `[blocked][staged]`), treat the leftmost match as the only one to strip and let the next verification settle the rest. + +## Verdict messages + +Per verdict, use this opening line in the comment body: + +| Verdict | Opening line | +|---|---| +| `live` | "Verified: customers can use this. Ready to publish." | +| `staged` | "On in staging only. Re-run after prod rollout." | +| `pending` | "Eng merged but flag off in prod. Hold off." | +| `blocked` | "Eng PR not merged. Hold." | +| `unknown` | "Could not determine state from available signals. Manual check needed." | + +## Sweep parallelization + +When scope > 13 PRs: + +1. Split the PR list into chunks of up to 13 each (4 chunks for 41 PRs). +2. For each chunk, dispatch a sub-agent. Brief the agent to run Phase 1 and Phase 2 for each PR in its chunk and return a structured result: `{pr, verdict, summary_line, comment_posted, linear_updated}`. +3. Run all agents concurrently. +4. Collect results. If an agent fails, list its PRs as "unverified" and suggest re-running them individually with `/verify-docs-pr <num>`. +5. Sort the combined results by severity and print the summary table. + +## Edge cases + +- **PR has no eng refs and no Linear ticket:** verdict = `unknown`. Comment lists what's missing. +- **Eng PR is in a non-trunk2 repo (e.g., analytics-cli, flake-farm):** treat the merge state check the same way. Skip flag detection (only trunk2 has the LD flag patterns). +- **Multiple flags from the same eng PR:** gather signals for all; classify on the most conservative result. +- **Multiple eng PRs with mixed states:** `blocked` wins if any is unmerged. +- **Stacked merges:** If the merged eng PR's body mentions "stacked PRs", "Trunk Merge Queue", or lists multiple `trunk-io/<repo>#NNN` references in its body, recursively run Step C (find feature flags) on each child PR. The merge commit's flat diff may not surface flag definitions added in earlier child PRs of the stack. Real-world example: trunk2#3583 (Test Collections) merged children #3545-#3550 and the docs PR for it (docs#554) was incorrectly classified as `live` because the child PR contents weren't inspected. +- **API timeouts (`gh` or Slack):** retry once. On second failure, set verdict = `unknown` with a note about the unavailable signal. +- **PR already merged:** print "PR #N already merged; skipping" and skip entirely. Do not comment. +- **Docs PR body has no `TRUNK-XXX` reference:** The skill cannot find the linked Linear ticket reliably. Print a warning ("No Linear ticket found in PR body; skipping Linear comment") and proceed without posting to Linear. Do NOT guess at which ticket to post to. + +## Manual validation cases + +When changes ship to this skill, verify all six cases pass: + +1. `/verify-docs-pr 589` → verdict `pending`, references `enableFilteredUploadsPage`. +2. `/verify-docs-pr 534` → verdict `live` (no flag found). +3. `/verify-docs-pr 522` → verdict `live` (pure docs change, no eng PR found). +4. `/verify-docs-pr` → all open PRs classified, summary printed sorted by severity. +5. Run `/verify-docs-pr 589` twice → existing comment is edited, no duplicate posted. +6. Title prefix lifecycle: run on a `pending` PR with no prefix → title gets `[feature not live]` prepended. Re-run with the verdict still `pending` → no further change. Simulate the verdict flipping to `live` → `[feature not live]` is replaced with `[ready to merge]`. Simulate the eng PR being reverted (verdict flips to `blocked`) → `[ready to merge]` is replaced with `[blocked]`. diff --git a/.claude/skills/write-docs/OUTPUTS.md b/.claude/skills/write-docs/OUTPUTS.md new file mode 100644 index 0000000..33e42c1 --- /dev/null +++ b/.claude/skills/write-docs/OUTPUTS.md @@ -0,0 +1,59 @@ +# Output Formats + +Reference for all outputs produced by the write-docs skill. + +## Contents + +- [PR body format](#pr-body-format) +- [Slack post format](#slack-post-format) +- [Report card format](#report-card-format) + +## PR Body Format + +PR title: `[TRUNK-XXXXX] Short descriptive title` (prefix with Linear ticket ID if one exists). + +PR body sections: +- **Summary** — bullet list of changes +- **Linear tickets** — clickable links to all related tickets +- **Engineering authors** — GitHub handles of engineers who built the feature (from trunk2 PRs), for technical accuracy review +- **Context links** — all Slack, Slite, Loom links from the notes +- **Files changed** — list of files created/modified +- **Open questions** — things that could not be confirmed from available context +- **Test plan** — checklist for reviewer (e.g., "check GitBook preview", "verify code example works") + +## Slack Post Format + +Write to `.claude/tmp/<draft-name>/slack.md`. Must be directly copy-pasteable into Slack. + +**MUST use Slack mrkdwn syntax, NOT Markdown:** +- Bold: `*text*` (single asterisks, not double) +- Links: `<URL|display text>` +- Bullets: use the `*` character on a new line (Slack list style) +- No Markdown headers (`##`), links (`[text](url)`), or bold (`**text**`) + +Template: +``` +*[Feature Name] — docs update ready for review* + +[1-2 sentence summary of what changed in the docs.] + +* PR: <GitHub PR URL|#NNN> +* Linear: <Linear ticket URL|TRUNK-XXXXX> + +*Open questions for the team:* +* [list any items needing eng confirmation] +``` + +## Report Card Format + +Append an HTML card to `.claude/tmp/report.html`. If the file does not exist, create it with basic HTML styling. + +Each card includes: +- PR link +- Linear link +- Change type badge +- Changes summary +- Context links +- Related tickets +- Review focus areas +- Open questions diff --git a/.claude/skills/write-docs/OVERLAP-CHECK.md b/.claude/skills/write-docs/OVERLAP-CHECK.md new file mode 100644 index 0000000..a114cea --- /dev/null +++ b/.claude/skills/write-docs/OVERLAP-CHECK.md @@ -0,0 +1,73 @@ +# Duplicate & Overlap Check + +Run these checks before starting any work. Stop and ask the user if any match is found. + +## Step 1: Check for existing PRs/branches from this draft + +1. Derive the expected branch topic from the draft filename (e.g., `flag-as-flaky.md` -> `flag-as-flaky`). Get the username prefix from `git config user.name` (kebab-cased). + +2. Search for open PRs matching the branch: + ```bash + gh pr list --repo trunk-io/docs2 --state open --head "<username>/<topic>" --json number,title,url,headRefName + ``` + Also search by topic keyword: + ```bash + gh pr list --repo trunk-io/docs2 --state open --json number,title,url,headRefName | jq '.[] | select(.headRefName | contains("<topic>"))' + ``` + +3. Check local branches: + ```bash + git branch --list "*<topic>*" + ``` + +4. **If a match is found**: Show the user the existing PR/branch and ask: + - (a) Update the existing PR + - (b) Close it and start fresh + - (c) Skip this draft + + Do NOT proceed until the user responds. + +## Step 2: Check for overlapping PRs from other authors + +1. Read the draft to identify target docs files/product area. + +2. List all open PRs: + ```bash + gh pr list --repo trunk-io/docs2 --state open --json number,title,headRefName,url --limit 50 + ``` + +3. For any PR that looks related (by title or branch name matching the same product area), check file overlap: + ```bash + gh pr view <number> --repo trunk-io/docs2 --json files --jq '[.files[].path]' + ``` + +4. **If overlapping PRs are found**: Show the user the overlapping PR and affected files, then ask: + - (a) Proceed anyway (changes will likely conflict) + - (b) Wait for that PR to merge first + - (c) Skip this draft + + Do NOT proceed until the user responds. + +5. **If no overlaps found**: Continue to Step 3. + +## Step 3: Check for existing Linear tickets + +Automation (the daily DevRel scanner, the `/changelog` skill, others) may have already filed a planning ticket for this feature. Find it before creating a duplicate. + +1. Extract feature keywords from the draft — topic name, product area, and any `TRUNK-NNNNN` refs already in the notes file. + +2. **If the draft already references a `TRUNK-NNNNN` ticket**: fetch it directly via `mcp__claude_ai_Linear__get_issue` to confirm it's the right one and capture its current state. Proceed to Phase 1. + +3. **Otherwise, search Linear** for matching tickets: + - Tool: `mcp__claude_ai_Linear__list_issues` + - Team ID: `16f26d2e-3c38-4c56-869d-9fea8f33321e` (Trunk Engineering) + - Filter by feature keywords; look for ticket titles or descriptions mentioning the same feature name, product area, or carrying a `changelog` / `docs` label + +4. **If matching tickets are found**: show the user the ticket links and titles, then ask: + - (a) Link the new docs PR to this ticket and pull its context into the draft (no new ticket) + - (b) Create a new ticket anyway and document why (rare — usually only if the existing ticket is closed or scoped to something unrelated) + - (c) Skip this draft (someone else owns the planning) + + Do NOT proceed until the user responds. + +5. **If no matching tickets are found**: continue to Phase 1. A new Linear ticket will be created in Phase 5 of `/write-docs`. diff --git a/.claude/skills/write-docs/README.md b/.claude/skills/write-docs/README.md new file mode 100644 index 0000000..08e3aec --- /dev/null +++ b/.claude/skills/write-docs/README.md @@ -0,0 +1,278 @@ +# write-docs skill + +End-to-end documentation pipeline: raw notes -> reviewed docs PR with Linear tracking. + +Invoked as `/write-docs <input>`. + +## Directory Structure + +``` +write-docs/ +├── SKILL.md # Main instructions (~130 lines, loaded when skill triggers) +├── OVERLAP-CHECK.md # Detailed dupe/overlap check procedure (loaded in Phase 0) +├── OUTPUTS.md # PR body, Slack post, report format specs (loaded in Phases 4-6) +└── README.md # This file (human reference only) +``` + +## How It Works + +The skill uses progressive disclosure: Claude loads SKILL.md when triggered, then reads OVERLAP-CHECK.md and OUTPUTS.md only when it reaches the relevant phase. This keeps the context window lean. + +### Pipeline + +``` +Phase 0 Duplicate & overlap check Stops if a PR/branch already covers this draft +Phase 1 Parse input Read notes file, extract tickets, PRs, context links +Phase 2 Research Linear tickets, Slite PRDs, Slack channels, + published docs, trunk2 PR diffs, gap analysis +Phase 2.5 Sources Write audit trail to .claude/tmp/<draft>/sources.md +Phase 3 Draft Edit existing pages or create new docs files +Phase 4 Branch & PR Stash → branch → commit → push → gh pr create +Phase 5 Linear Create/update ticket, attach links, add relations +Phase 6 Stage Write slack.md, append to report.html +Phase 7 Clean up Restore original branch and stashed changes +``` + +### Research Sources (Phase 2) + +The skill cross-references five sources to build context before writing anything: + +| Source | What it finds | MCP Server | +| ------------------ | ---------------------------------------------------------------------------------------- | --------------------- | +| **Linear** | Ticket descriptions, status, assignees, related tickets | `claude.ai Linear` | +| **Slite** | PRDs, specs, roadmap items, knowledge base articles | `claude.ai Trunk Slite` | +| **Slack** | Team discussions, changelogs, feature context from `#team-flaky-tests`, `#team-merge-queue` | `claude.ai Slack` | +| **Published docs** | Existing documentation pages, hierarchy from `docs.json` | `claude.ai trunk docs` | +| **GitHub** | trunk2 PR diffs, code changes, implementation details | `gh` CLI | + +### Inputs + +| Input type | Example | What happens | +| ----------------- | --------------------------------- | --------------------------------------------------------------- | +| Notes file | `.claude/drafts/flag-as-flaky.md` | Primary mode. Reads file, extracts metadata, researches, writes | +| trunk2 PR numbers | `3187 3177` | Reads PR details via `gh`, extracts Linear IDs | +| Linear ticket IDs | `TRUNK-17633` | Looks up ticket, finds related PRs and context | +| Deploy tag | `v126` | Documents features shipped in a specific release | + +Notes files follow the template at `.claude/drafts/TEMPLATE.md`. + +### Outputs + +**Committed (the PR):** + +- Branch: `<git-username>/<kebab-case-topic>` +- PR title: `[TRUNK-XXXXX] Short descriptive title` +- PR body: summary, Linear links, context links, files changed, open questions, test plan + +**Staged (gitignored, under `.claude/tmp/<draft-name>/`):** + +- `sources.md` — audit trail for reviewers (every source consulted) +- `slack.md` — copy-pasteable Slack announcement (uses Slack mrkdwn, not Markdown) + +**Cumulative (`.claude/tmp/report.html`):** + +- HTML card appended per run — PR link, Linear link, changes summary, open questions + +**Linear:** + +- Ticket created/updated in Docs Maintenance project with `docs` label +- Context links attached (Slack, Slite, Loom, etc.) +- Related engineering tickets linked via `relatedTo` +- Status set to "In Review" + +### Prerequisites + +The skill requires these MCP servers to be connected. Verify with `claude mcp list`: + +- `claude.ai Slack` — for searching team channels +- `claude.ai Trunk Slite` — for PRDs and specs +- `claude.ai trunk docs` — for searching published docs +- `claude.ai Linear` — for ticket management +- GitHub via `gh` CLI (authenticated with access to `trunk-io/trunk2`) + +--- + +## Demo Script + +Walkthrough for demoing `/write-docs` at an all-hands or team meeting. Total runtime: ~5 minutes. + +### Pre-Demo Setup (10 min before) + +#### 1. Clean terminal state + +```bash +cd ~/TRUNK/docs +git checkout main +git pull origin main +git status # should be clean +``` + +#### 2. Verify no leftover demo artifacts + +```bash +git branch --list "*indefinite-monitor-muting*" +gh pr list --repo trunk-io/docs2 --state open --json number,title,headRefName | jq '.[] | select(.headRefName | contains("indefinite-monitor-muting"))' + +# If either returns results, clean up: +# gh pr close <number> --repo trunk-io/docs2 --delete-branch +# git branch -D sam-gutentag/indefinite-monitor-muting +``` + +#### 3. Verify draft and clear previous outputs + +```bash +cat .claude/drafts/indefinite-monitor-muting.md +rm -rf .claude/tmp/indefinite-monitor-muting/ +``` + +#### 4. Verify MCP servers + +```bash +claude mcp list +``` + +All five should show connected (see [Prerequisites](#prerequisites) above). + +#### 5. Start Claude Code + +```bash +claude +``` + +Wait for it to load. Verify `/write-docs` is recognized. + +#### 6. Terminal setup + +- Font size: 18-20pt for readability +- Terminal width: full screen, max 120 chars +- Dark theme for projector visibility + +### The Demo + +#### ACT 1: The Problem (30 seconds) + +**[Speaking to audience, terminal visible but idle]** + +> "Quick show of hands — who's had to write documentation for a feature someone else built? Yeah. It's not the writing that's painful, it's the context-gathering. You're reading Slack threads, digging through PRs, searching Linear, figuring out which docs pages need updating. Then you do the actual writing. Then you create a branch, open a PR, update the ticket, post in Slack. That's 30 minutes for something that should take 5." + +> "We built a Claude Code skill that does all of that from a single command. Let me show you." + +#### ACT 2: Show the Input (30 seconds) + +**[In Claude Code, type:]** + +``` +cat .claude/drafts/indefinite-monitor-muting.md +``` + +**[Let the file scroll. Point out key sections:]** + +> "This is a notes file — it's what an engineer or PM drops into our drafts folder. It has the feature name, links to the PRs that shipped it, and which docs pages need updating. It's rough and that's fine — it's input, not output." + +> "Notice there's no Linear ticket yet, no branch, no PR. Just notes." + +#### ACT 3: Run the Skill (2-3 minutes) + +**[Type the command:]** + +``` +/write-docs .claude/drafts/indefinite-monitor-muting.md +``` + +**[Narrate each phase as it runs. Don't rush — let the audience watch the tool calls.]** + +When you see `gh pr list` calls: +> "Phase zero — it's checking whether someone already opened a PR for this, or if another PR touches the same docs files." + +When you see `mcp__claude_ai_Linear` calls: +> "Now it's researching. Starts with Linear to get ticket context..." + +When you see Slite or Slack MCP calls: +> "It's searching our Slite knowledge base for PRDs and checking the #team-flaky-tests Slack channel for recent discussion. This is context that used to take 10 minutes of manual digging." + +When you see `gh pr view` or `gh pr diff` calls: +> "Reading the trunk2 PR to see what code actually shipped." + +When you see a file being written to `.claude/tmp/`: +> "That's the sources file — an audit trail so reviewers can trace any claim back to its source." + +When you see `Edit` tool calls on docs files: +> "Now it's writing the actual docs. It read the existing pages first to match our tone and structure." + +When you see `git` commands: +> "Creating a branch, committing, pushing..." + +When you see `gh pr create`: +> "And there's the PR." + +When you see Linear calls: +> "Creating a Linear ticket, attaching the PR link, context URLs, and linking related engineering tickets." + +#### ACT 4: Show the Outputs (1 minute) + +**[Claude will output a summary with links. Click through each one:]** + +**GitHub PR** — open in browser: +> "The title has the Linear ticket ID. The body has open questions — things it couldn't confirm from the available context. It flags what needs human verification instead of guessing." + +**Linear ticket** — open in browser: +> "Docs Maintenance project, assigned to me, status In Review. It attached the PR link, the Slack threads it found, and any Slite docs it consulted." + +**[Show staged outputs:]** + +``` +cat .claude/tmp/indefinite-monitor-muting/slack.md +cat .claude/tmp/indefinite-monitor-muting/sources.md +``` + +> "A copy-pasteable Slack post and a full sources audit trail." + +#### ACT 5: Wrap Up (30 seconds) + +> "From one command and a rough notes file: a docs PR, a Linear ticket, an audit trail, and a team notification. About 3 minutes." + +> "The skill searches five systems — Linear, Slite, Slack, our published docs, and GitHub PRs — to build context before writing a single line. Every PR still gets human review." + +### Q&A + +**"What if the docs it writes are wrong?"** +> "Every PR gets human review. The skill flags open questions explicitly. It's doing the 80%, a human does the last 20%." + +**"Can this work for other repos?"** +> "The skill is specific to our docs workflow, but the pattern is portable. You write a SKILL.md describing the pipeline and Claude Code follows it." + +**"How long did it take to build?"** +> "About 130 lines of instructions plus two reference files. The iteration was the slow part — maybe 2-3 sessions." + +**"What if it creates a PR that conflicts with someone else's work?"** +> "Phase 0 catches that. It checks every open PR for file-level overlap before doing any work." + +**"Does it handle new pages or just updates?"** +> "Both. New pages get created and added to docs.json. Updates edit existing files in place." + +**"How does it know what's in Slite and Slack?"** +> "We connected them as MCP servers — same protocol as Linear. The skill searches them during research, and knows to check #team-flaky-tests and #team-merge-queue for product context." + +### Post-Demo Cleanup + +```bash +gh pr close <PR_NUMBER> --repo trunk-io/docs2 --delete-branch +git checkout main +git branch -D sam-gutentag/indefinite-monitor-muting 2>/dev/null +rm -rf .claude/tmp/indefinite-monitor-muting/ +``` + +### Dry Run Checklist + +Do a full dry run the day before. Clean up everything so the live demo starts fresh. + +- [ ] Draft file exists at `.claude/drafts/indefinite-monitor-muting.md` +- [ ] No existing branch `sam-gutentag/indefinite-monitor-muting` +- [ ] No existing PR for indefinite-monitor-muting +- [ ] No existing `.claude/tmp/indefinite-monitor-muting/` directory +- [ ] Claude Code starts cleanly and recognizes the skill +- [ ] MCP servers are responding (Linear, GitBook, Slack, Slite) +- [ ] Terminal font size is readable from the back of the room +- [ ] You've timed the run — should be 2-4 minutes +- [ ] You know where to find the PR and Linear links in the output +- [ ] Linear "Docs Maintenance" project view is open in a browser tab diff --git a/.claude/skills/write-docs/SKILL.md b/.claude/skills/write-docs/SKILL.md new file mode 100644 index 0000000..7ac1018 --- /dev/null +++ b/.claude/skills/write-docs/SKILL.md @@ -0,0 +1,150 @@ +--- +name: write-docs +description: Process notes, Slack threads, Slite docs, or trunk2 deploy context into documentation changes. Creates a branch, edits docs, opens a PR, and updates Linear. Use when given a notes file from .claude/drafts/, trunk2 PR numbers, Linear ticket IDs, a deploy tag, Slite doc links, or when the user says "write docs", "document this", or "process drafts". +allowed-tools: Bash(git *), Bash(gh pr *), Bash(gh issue *), Bash(gh api *), Bash(trunk fmt *), Bash(trunk check *), Read, Write, Edit, Glob, Grep, mcp__claude_ai_Linear__get_issue, mcp__claude_ai_Linear__list_issues, mcp__claude_ai_Linear__list_projects, mcp__claude_ai_Linear__save_issue, mcp__claude_ai_Linear__save_comment, mcp__claude_ai_Linear__create_attachment, mcp__claude_ai_trunk_docs__searchDocumentation, mcp__claude_ai_Trunk_Slite__*, mcp__claude_ai_Slack__*, WebFetch +--- + +# Writing Docs + +Turn raw notes, Slack pastes, and PR references into reviewed docs PRs with full Linear tracking. + +## Contents + +- [Inputs](#inputs) +- [Products](#products) +- [Workflow](#workflow) +- [Guidelines](#guidelines) +- [Batch processing](#batch-processing) + +## Inputs + +The user provides any combination of: +- **Notes file** (e.g., `.claude/drafts/my-feature.md`) — primary input. Template: `.claude/drafts/TEMPLATE.md` +- **trunk2 PR numbers** — PRs from `trunk-io/trunk2` +- **Linear ticket IDs** — `TRUNK-NNNNN` references +- **Deploy tag** (e.g., `v126`) — features shipped in a release +- **Slite doc links** — PRDs, specs, or knowledge base articles from Slite +- **Context links** — Slack threads, Loom videos, Google Docs, etc. +- **Specific docs page** — if the user knows what needs changing + +## Products + +Docs are organized by product area: +- **Merge Queue** — `/merge-queue/` +- **Flaky Tests** — `/flaky-tests/` +- **CI Autopilot** — `/ci-autopilot/` +- **Code Quality** — `/code-quality/` +- **Setup & Administration** — `/setup-and-administration/` + +## Workflow + +Copy this checklist and track progress: + +``` +Progress: +- [ ] Phase 0: Duplicate & overlap check +- [ ] Phase 1: Parse input +- [ ] Phase 2: Research (Linear, Slite, Slack, docs, PRs) +- [ ] Phase 2.5: Write sources audit file +- [ ] Phase 3: Draft documentation +- [ ] Phase 4: Branch, commit, PR +- [ ] Phase 5: Update Linear +- [ ] Phase 6: Stage outputs (Slack post, report) +- [ ] Phase 7: Clean up and summarize +``` + +### Phase 0: Duplicate & Overlap Check + +Before doing any work, verify no existing PR already covers this draft. See [OVERLAP-CHECK.md](OVERLAP-CHECK.md) for the detailed check procedure. + +If a match or overlap is found, stop and ask the user how to proceed before continuing. + +### Phase 1: Parse and Understand + +1. If a notes file was provided, read it and extract: feature name, Linear ticket IDs, GitHub PR URLs, context links, product area, change type, and key details. +2. If trunk2 PR numbers or a deploy tag were provided instead, use `gh pr view <number> --repo trunk-io/trunk2` to get details and extract Linear ticket IDs. + +### Phase 2: Research + +**Code is law.** Actual source code and PR diffs are the authoritative source of truth. Slite specs, PRDs, Slack threads, and other planning docs provide context, intent, and examples — but when they conflict with what the code does, the code wins. Only document what is actually implemented. + +3. **Linear tickets**: Use `mcp__claude_ai_Linear__get_issue` for each ticket ID. Search by feature name for related engineering tickets. +4. **Slite docs**: Search Slite for PRDs, specs, roadmap items, and knowledge base articles related to the feature. Use feature name and product area as search terms. Retrieve relevant docs for product intent, requirements, and decisions. +5. **Slack channels**: Search relevant Slack channels for recent discussion, changelogs, and context: + - `#team-flaky-tests` — Flaky Tests product discussions + - `#team-merge-queue` — Merge Queue product discussions + - Search by feature name, ticket ID, or key terms from the notes +6. **Existing docs**: Use `mcp__claude_ai_trunk_docs__searchDocumentation`, Glob, and Grep. Read `docs.json` for the site navigation and group structure. +7. **trunk2 PR diffs** (if available): `gh pr diff <number> --repo trunk-io/trunk2 --name-only`, then read key files. When possible, also read the current source on `main` via `gh api` to confirm the latest state. +8. **Gap analysis**: Compare existing docs vs. what the code implements. Cross-reference planning docs (Slite, Slack) against code to identify discrepancies — features described in specs but not yet implemented should be flagged as open questions, not documented as existing functionality. + +### Phase 2.5: Generate Sources File + +9. Write `.claude/tmp/<draft-name>/sources.md` with all Linear tickets, GitHub PRs, Slite docs, Slack threads, existing docs, code references, and context links found during research. This is the reviewer audit trail. Must include: + - A **"Code-Confirmed Details"** section listing metric names, endpoint paths, auth mechanisms, etc. as they exist in the actual codebase + - A **"Differences: Code vs. Planning Docs"** table highlighting any discrepancies between what planning docs describe and what the code implements + - An **"Open Questions"** section for anything that could not be confirmed from code + +### Phase 3: Draft Documentation + +10. Write or edit documentation: + - **Match tone and structure** of existing Trunk docs — read nearby files first + - **New pages**: write full content; **Updates**: edit in place; **Explainers**: add to relevant existing page + - Update `docs.json` if adding new pages (insert the page path, without the `.mdx` extension, into the relevant `groups[].pages` array) + - Lead with user benefit, not implementation details + - Use present tense, include practical examples + - Don't mention internal systems (ClickHouse, Prisma, SST, Lambda) + +### Phase 4: Branch, Commit, and PR + +11. **Branch**: From `main`. Name: `<git-username>/<kebab-case-topic>` (username from `git config user.name`, kebab-cased). Stash unrelated changes first. +12. **Commit**: Stage changed files. Include `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`. +13. **Identify engineering authors**: Before creating the PR, look up who built the feature. For every `trunk-io/trunk2` PR linked in the Linear ticket or found during research: + - Fetch the PR via `gh pr view <number> --repo trunk-io/trunk2 --json author` + - Extract the author's GitHub handle + - Collect all unique authors across all linked PRs — list every one, do not filter by contribution size + - Include all authors in the PR body under an "Engineering authors" section so Sam can tag them for technical accuracy review + + **Known Trunk team GitHub handle reference** (fallback if API is unavailable): + + | Name | GitHub handle | + |------|--------------| + | Phil Vendola | @pvendola | + | Tyler Jang | @tyler-jang | + | Ben Cook | @bwcook | + | Alexander Graebe | @alexgraebe | + | Ventsi Tsachev | @ventsislavtsachev | + + If you cannot determine a handle confidently, write `[unknown — check trunk2 PR: <url>]`. + +14. **Push and PR**: `gh pr create --draft` with structured body. **Always create as a draft** — Sam reviews every PR manually before marking it ready. See [OUTPUTS.md](OUTPUTS.md) for PR body format. Include the engineering authors list in the PR body. +15. **Request reviewers**: After creating the PR, add engineering authors as reviewers using `gh pr edit <number> --add-reviewer <handle1>,<handle2>`. This works even on draft PRs — reviewers see the PR and can leave early feedback. If a handle lookup fails, note it in the PR body for Sam to add manually. + +### Phase 5: Update Linear + +16. Create or update the docs ticket — add PR link, context links, change summary. Set status to "In Review". If no ticket exists, create one in Trunk Engineering with `docs` label. +17. Attach all context links (Slack, Slite, Loom) as attachments with descriptive titles. +18. Add `relatedTo` relations for every related engineering ticket found during research. + +### Phase 6: Stage Outputs + +19. **Slack post**: Write to `.claude/tmp/<draft-name>/slack.md`. See [OUTPUTS.md](OUTPUTS.md) for Slack formatting rules. +20. **Report**: Append an HTML card to `.claude/tmp/report.html`. See [OUTPUTS.md](OUTPUTS.md) for report format. + +### Phase 7: Clean Up + +21. Return to original branch, restore stashed changes. +22. Show the user: branch name, PR link, Linear ticket link, files changed, open questions, staged output file paths. + +## Guidelines + +- **One notes file = one PR.** Flag multi-topic drafts and ask how to split. +- **Preserve the notes file** — never delete or modify it. +- **Ask before guessing** — list specific questions rather than making assumptions. +- **Always include full PR URLs** in Linear comments and descriptions. +- **Match existing style** — read adjacent docs before writing. +- **Prioritize accuracy** — flag inferred vs. confirmed items in open questions. + +## Batch Processing + +Process each notes file in `.claude/drafts/` one at a time. Each gets its own branch, PR, Linear ticket, and `.claude/tmp/<draft-name>/` directory. The skill handles git stash/restore between runs. diff --git a/.claude/tmp/.gitkeep b/.claude/tmp/.gitkeep new file mode 100644 index 0000000..e69de29