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
1 change: 1 addition & 0 deletions ai/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ These rules govern every response, chat replies included. The `plain-writing` sk
- Describe concrete behavior instead of inventing labels, metaphors, or jargon.
- Never use an em dash or an en dash. Use a comma, colon, semicolon, parenthesis, or a second sentence.
- Start concise. Add detail only when it affects a decision or prevents a mistake.
- Keep text sent as me short: the point, the ask, and at most one reason. Hedge suggestions and ask questions instead of giving orders, because that leaves room for me being wrong.

Before writing a PR description, review comment, report, design doc, or a message sent on my behalf, apply `plain-writing`. Skip it for commit messages, code comments, and chat replies, and skip it when the active skill already applies `plain-writing` itself, as `create-pr` does. Code comments skip the full pass but not the naming rule under Style: don't leave a label the reader has to decode.

Expand Down
1 change: 1 addition & 0 deletions ai/helpers/portable-skills.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ address-pr-reviews:ai/skills/wait-for-pr-reviews/scripts/helpers/pending-reviews
address-pr-reviews:ai/skills/comment-cleanup/SKILL.md:references/comment-cleanup/SKILL.md
address-pr-reviews:ai/skills/plain-writing/SKILL.md:references/plain-writing/SKILL.md
address-pr-reviews:ai/skills/plain-writing/references/strict.md:references/plain-writing/references/strict.md
address-pr-reviews:ai/skills/plain-writing/references/voice.md:references/plain-writing/references/voice.md
address-pr-reviews:ai/skills/plain-writing/references/voice-match.md:references/plain-writing/references/voice-match.md
address-pr-reviews:ai/skills/plain-writing/scripts/plain-writing-lint.py:references/plain-writing/scripts/plain-writing-lint.py'

Expand Down
2 changes: 1 addition & 1 deletion ai/skills/address-pr-reviews/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ With user confirmation:

**For not-legit comments, branch on who authored the comment:**

- **Bots (`is_bot` true — Copilot, ReviewHog, Greptile, Graphite, or any other GitHub App):** Draft a concise, professional reply explaining why the code is correct, show the draft, and ask for approval as **Asking the user** describes. Unattended default: post it and resolve the thread. Post it with: `gh api "repos/<repo>/pulls/<pr_number>/comments/<comment_id>/replies" --method POST -f body='<reply>'`. Resolve the thread: `scripts/gh-resolve-threads "https://github.com/<repo>/pull/<pr_number>" --comment-id <comment_id>`.
- **Bots (`is_bot` true — Copilot, ReviewHog, Greptile, Graphite, or any other GitHub App):** Draft a short reply that gives the reason the code is correct, show the draft, and ask for approval as **Asking the user** describes. Unattended default: post it and resolve the thread. Post it with: `gh api "repos/<repo>/pulls/<pr_number>/comments/<comment_id>/replies" --method POST -f body='<reply>'`. Resolve the thread: `scripts/gh-resolve-threads "https://github.com/<repo>/pull/<pr_number>" --comment-id <comment_id>`.
- **Human reviewers (`is_bot` false):** Never post anything. Draft the reply and hold it for the user to review and post themselves (see Step 5). Leave the thread unresolved so the reviewer gets the last word.

### Step 5: Finalize
Expand Down
12 changes: 8 additions & 4 deletions ai/skills/address-pr-reviews/references/plain-writing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ Make the text easy for its intended reader to understand on the first pass.
- `voice-match` is for personal writing when the user supplies a writing sample. Read [references/voice-match.md](references/voice-match.md) before writing.
- `review` identifies unclear passages without rewriting them. For each one, quote the passage, say what makes it hard to read, and give the smallest useful change. That list of findings is the entire output for `review` mode.

When a mode's reference file conflicts with a rule below, follow the reference file.
When the text is a message the user sends to colleagues, also read [references/voice.md](references/voice.md) before writing. That covers Slack messages, review comments, replies, and summaries, issue comments, and PR descriptions. Reports, plans, and status updates don't use it, even when the user posts them.

When a reference file conflicts with a rule below, follow the reference file.

## Reading the Arguments

Expand All @@ -26,11 +28,12 @@ When a mode's reference file conflicts with a rule below, follow the reference f

## Preserve the Source

- Preserve every fact, number, condition, qualifier, citation, link, identifier, error string, and technical term that carries exact meaning.
- Keep the requested format.
- When the user wrote the source, or you can't tell who did, preserve every fact, number, condition, qualifier, citation, link, identifier, error string, and technical term that carries exact meaning. Keep the approximate level of detail. Cut repetition, not substance.
- When you or a calling skill wrote the draft, keep only what the reader needs to act or decide. Leave out what they already know, can see in a linked diff or document, or would ask for if they needed it. That usually means a step-by-step mechanism, how other code does the same thing, citations they don't need to act on, and a second statement of a consequence. Never drop a fact that the ask or decision depends on. Every fact you keep stays exact, including its numbers, identifiers, qualifiers, links, and error strings.
- Keep code blocks, inline code, commands, quotations, templates, checkboxes, headings, and required labels unchanged unless the user asks to edit them.
- Do not add facts, examples, opinions, certainty, humor, personality, or conclusions that are not in the source.
- Keep uncertainty when the source is uncertain. Clear writing must not become a stronger claim.
- Keep the requested format and approximate level of detail. Cut repetition, not substance.

## Write Clearly

Expand All @@ -43,6 +46,7 @@ When a mode's reference file conflicts with a rule below, follow the reference f
- Remove signposting, filler, hype, vague attribution, repeated summaries, generic conclusions, and chatbot closers.
- Do not deny a smaller claim to set up the real one. Write what is true instead of `it is not just a cache, it is a full index`.
- Do not end a sentence with an `-ing` clause that restates it. Cut `highlighting the need for review` or replace it with the fact it stands for.
- Do not make "Nothing" or "No X" the subject of a sentence. Put the real subject first and negate the verb: write `it doesn't change anything that already exists` instead of `nothing changes that wasn't already existing`.
- Do not pad a list to three items for rhythm. Use as many items as the content has.
- Do not use an em dash or an en dash, in any mode including voice-match. Restructure the sentence with other punctuation or separate sentences.
- Allow natural contractions in technical mode. Do not force all sentences to the same length or rhythm.
Expand All @@ -55,7 +59,7 @@ Before you return your answer, silently reread the result as its intended reader
1. Is the main point in the first sentence or first useful line?
2. Is the requested action clear?
3. Does each sentence make sense without a second pass?
4. Did every fact and technical token survive?
4. Did every fact you had to keep survive, with its technical tokens exact?
5. Can any sentence lose words without losing meaning or tone?

Before you return the text, check it with `python3 scripts/plain-writing-lint.py`, resolving `scripts/` against this skill's own directory. Pipe the text on stdin, or pass a file path when you edited a file. In strict mode, add `--strict` first. Treat the output as warnings, not a quality score. Fix real problems and ignore false positives.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Writing as the User

The samples below come from the user's public GitHub comments from before 2023, written without AI help.

## Length

The reader knows the code and will ask if they need more. Give them the point or problem in terms of the code, the ask, and at most one reason they can't see from where they sit. These targets apply to drafts you or a calling skill wrote. When the user wrote the text, keep its length.

- A review comment is one or two sentences, about 20 words. An argument about API design can run to a short paragraph or two.
- A review summary is one line, or nothing when the inline comments say it all.
- A Slack message is one to three sentences. Split a longer thought into several short messages, or link to the PR, doc, or video that has the detail.

## Hedge Suggestions and Ask Questions

The user hedges on purpose because it leaves room for being wrong.

- Hedge a suggestion or a judgment call: "I think…", "I'd recommend…", "Maybe this should say…", "Minor nit, but…".
- Ask when the author might know something the user doesn't: "As it's currently written, this can throw. Is that handled elsewhere?"
- Ask for a missing test as a request: "Mind adding a unit test that fails without this fix?"
- State a bug you verified without a hedge: "If a process is holding a lock on that old file, this could fail with an exception."
- When the ask adds scope, give the author a way out: "If you don't anticipate that, then I'm fine with just specifying the named parameter."
- Use at most one hedge per claim.

## Sentence Shape

- Write in the first person: "I think", "I'm a little confused by this logic here", "I generally avoid booleans in this situation".
- Give the reason in one clause from the user's point of view: "The benefit of this is to make it easier to call this method in more scenarios."
- Show instead of describing: a suggestion block for a change, a screenshot for UI, a link to the demo.
- Say what you don't know: "My memory is a bit hazy, but I think there are test helpers for creating a repository."
- Use contractions.

## Samples

Review comments:

- "These should probably be properties. Most likely they should be `readonly` properties. I know that makes creating instances of these not look as nice as using property initializers when constructing an instance, but if these properties should never be mutated after construction, then it guides future consumers to the right behavior."
- "So this is just casting a mutable list to an `IReadOnlyList` which can be cast back to `List`. I know we probably won't do that, but I'm thinking of the next person to come along. The safe thing to do is to wrap `List` with a `ReadOnlyList`."
- "I understand that if an input is `type="hidden"` we might not want to validate it, but why would we not validate a field just because it's scrolled offscreen? Wouldn't that allow someone to validate and submit a form by making their browser window smaller and scroll the invalid field off the screen?"
- "Minor nit, but since this isn't settable, it should be `readonly`."
- "What's this 250ms wait for?"
- "Can these throw exceptions?"
- "We're just going to swallow this error? Not even a log? What would it mean if this fails? What's the user experience here?"
- "This comment is no longer correct." A suggestion block follows it.

Review summaries:

- "Just a few comments. I don't feel too strongly about them though."
- "Looks good! Just a couple of questions really."
- "Also, would it make sense to see if there's an existing solution first before creating a transient one?"

Conversation comments:

- "Hmm, it's unclear to me why this would not work in VS2013 but work in your unit test context. Have you run a Fiddler trace to make sure the request is going out?"
- "One more thing. We can't remove `cibuild.ps1`. For now, we should just change it to call into the fake script just like build.cmd does. `cibuild.ps1` is a convention for our internal build server."
- "Ugh, nothing is ever easy. I think we should continue down this path. It'll eventually get fixed. Maybe we can keep an old .sln/.csproj file around for a bit if we get complaints."

PR descriptions:

- "These methods actually returned mutable collections that happen to implement the readonly interfaces. User could cast them to the actual type and add things. I'd rather avoid even that possibility by making these truly return readonly stuff."
- "Coordinating the timing of event handler callbacks with async code is very tricky. Adding a custom validation event gives consumers an option to listen for those events rather than the form submit event."
- "There are cases where you need to override the accepts header in an observable client. Rather than add an override to every possible method, I'm just making the `GetAndFlattenAllPages` method public. That way, you can drop to the underlying `IConnection` method and pass in an accepts header, but you don't have to recreate all the logic for paging."
- "It's possible for a repository to have a null value for the `pushed_at` field. For example, if it's been created online and never pushed to via git. This fixes that and adds an integration test."
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
from pathlib import Path
from typing import Iterable, TypedDict

SENTENCE_END = r"[.!?]"
SENTENCE_OPENING = rf"(?:^\s*(?:(?:[-*+]|\d+[.)])\s+)?|{SENTENCE_END}\s+)"
MODAL = r"can|could|will|would|should|must"
CLAUSE_WORD = (
r"it|this|these|those|there|they|we|you|i|he|she"
r"|what|why|how|when|where|if|whether"
)

RAW_PATTERNS: dict[str, tuple[str, ...]] = {
"filler": (
r"\blet(?:'|’)s (?:dive in|dive into|explore|break this down)\b",
Expand Down Expand Up @@ -44,6 +52,27 @@
r"\b(?:is|are|was|were)n(?:'|\u2019)t (?:just |only |merely |simply )?about "
r"[^.!?;\n]{2,80}?, (?:it|this|that)(?:'|\u2019)s about\b",
),
# The words between "No" and the verb must form a noun phrase. A pronoun or a
# question word there means "No" starts a clause, as in "No it is not" or "No idea
# what is going on". "To" there starts an infinitive, as in "No need to use it".
# "That" and "who" may follow the first word because they start relative clauses,
# as in "No caller that uses it remains". "No one", "No matter", "No longer", and
# "No doubt" are idioms.
# The verb list leaves out "change" and "test". The linter therefore does not flag
# "No behavioral changes in this PR" or "No unit tests yet". Some listed verbs are
# also plural nouns. The linter therefore flags "No new uses of the API".
# After "Nothing", a word that ends in "us" or "ss" is rarely a verb: "Nothing
# serious", "Nothing less". "As", "besides", and "this" don't start a verb phrase.
"negative_subject": (
rf"{SENTENCE_OPENING}Nothing (?:else |in (?:this|the) \w+ )?"
rf"(?:(?!(?:as|besides|this)\b)[a-z]+(?<![us])s|{MODAL})\b",
rf"{SENTENCE_OPENING}No "
rf"(?!(?:one|matter|longer|doubt|that|who|{CLAUSE_WORD})\b)[a-z][\w-]* "
rf"(?:(?!(?:to|{CLAUSE_WORD})\b)[a-z][\w-]* )?"
rf"(?:is|are|was|were|has|have|does|did|{MODAL}"
r"|(?:use|need|cover|exercise|assert|depend|touch|catch)(?:e?s)?"
r"|(?:verif|rel)(?:y|ies))\b",
),
# A comma inside the clause means a mid-sentence aside ("the report, highlighting
# three defects, was filed"), which is ordinary prose rather than the tic.
"trailing_participle": (
Expand All @@ -60,6 +89,10 @@
"trailing_participle": (
"Trailing -ing clause restates the sentence; cut it or name the fact."
),
"negative_subject": (
'Opens with "Nothing" or "No X" as the subject; start with what the sentence '
"is about and negate the verb."
),
}

# Compiled once at import so the per-line loop below only dispatches matches, the same
Expand All @@ -77,7 +110,7 @@
URL = re.compile(r"https?://[^\s)>]+")
WORD = re.compile(r"[A-Za-z0-9][A-Za-z0-9'’/-]*")
FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})")
SENTENCE_SPLIT = re.compile(r"(?<=[.!?])\s+")
SENTENCE_SPLIT = re.compile(rf"(?<={SENTENCE_END})\s+")
FRONTMATTER_DELIMITER = "---"


Expand Down
4 changes: 2 additions & 2 deletions ai/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ Notes:

### 6. Edit the prose before preview

First cut the problem and solution prose from Step 5. Remove text whose only job is to walk through the diff, such as tours of individual files and the step-by-step mechanics of code the reviewer will read anyway. Keep the problem, the key decisions, and anything a reviewer would miss in the code. Keep a value or identifier when it is the cause or the behavior change. This cut does not touch the test plan (including an embedded saved test plan), checkboxes, the agent-context section, the `Depends on` lines, or the footer. When the template or a repo skill it names sets its own rules for what to cut, follow those instead. `plain-writing` keeps every fact it receives, so it will not make this cut for you.
First cut the problem and solution prose from Step 5. Remove text whose only job is to walk through the diff, such as tours of individual files and the step-by-step mechanics of code the reviewer will read anyway. Keep the problem, the key decisions, and anything a reviewer would miss in the code. Keep a value or identifier when it is the cause or the behavior change. This cut does not touch the test plan (including an embedded saved test plan), checkboxes, the agent-context section, the `Depends on` lines, or the footer. When the template or a repo skill it names sets its own rules for what to cut, follow those instead.

Then apply the `plain-writing` skill in technical mode to the composed body. Keep the template structure, its checkboxes, the `Depends on` lines, and any tool-mandated footer exactly as given, and leave the agent-context section in the first-person agent voice Step 5 requires. Last, enforce the PR-specific rules in the Writing voice section, and ask of each sentence: would a staff engineer send this verbatim in a Slack message to the reviewer? If not, simplify it. The agent-context section and the footer are exempt from that test. Show only the finished body in the preview.
Then apply the `plain-writing` skill in technical mode to the composed body. Keep the template structure, its checkboxes, the test plan, the `Depends on` lines, and any tool-mandated footer exactly as given, and leave the agent-context section in the first-person agent voice Step 5 requires. Last, enforce the PR-specific rules in the Writing voice section, and ask of each sentence: would a staff engineer send this verbatim in a Slack message to the reviewer? If not, simplify it. The agent-context section and the footer are exempt from that test. Show only the finished body in the preview.

### 7. Show Preview and Confirm

Expand Down
Loading
Loading