docs: upstream bug-reporting process for forks, and comments explain why (#2649)
## Summary
Two workflow additions to `CLAUDE.md`, with the detail in `dev-docs/`, plus a GitHub issue form.
### Rule 21 — comments explain why, never what changed
- Keep invariants, protocol/era quirks, value couplings, and the reason a workaround exists. One line where one line will do.
- Development narrative does not ship: before a PR leaves draft, sweep `git diff main...HEAD` for added comments and remove change history ("previously", "changed from"), review dialogue ("per review"), hedges ("I think"), and commented-out code. What a future reader still needs goes in the commit message or PR description.
- New `## Comments` section in `dev-docs/code-standards.md`; rule 21 in the `modernuo-code-audit` skill.
### Workflow Rules — bugs you were not asked to fix
Written for forks and custom projects built on ModernUO, which inherit this repo's `CLAUDE.md`. Also applies here (upstream is `origin`).
1. **Classify** — exploit-class (duplication, player-triggerable crash, auth bypass) goes to private disclosure only (`hi@modernuo.com`, per `CONTRIBUTING.md`), never a public issue, PR, or Discord post.
2. **Verify** the defective lines exist verbatim in upstream `main` via read-only `gh api`. If they don't, it is the fork's bug and nothing leaves the fork. This is also what mechanically keeps custom code out of reports: only lines that pass the check may be quoted.
3. **Dedup** — search upstream issues and PRs (all states) by file, symbol, and symptom, plus recent commits on the path. A merged fix → offer to import it; an open issue → offer to comment there.
4. **Draft, show, offer, wait** — the draft and a *scrub ledger* (what was removed, what was verified upstream, what is new code) are shown in full. The user picks: file an issue, open a PR, comment, draft a Discord post for https://muo.gg/discord, or nothing. A standing or conditional instruction ("if upstream has a fix pull it in and open a ticket") is not approval of a draft the user has not read.
5. **Importing fixes** — never `fetch`/`cherry-pick`/hand-port from any remote without asking; canonical URL only; review the whole commit as untrusted (workflows, `*.csproj`, `Directory.Build.props`, scripts); apply only after a second yes. Third-party forks and unmerged PRs are never a source.
`dev-docs/bug-reporting.md` is the process; `dev-docs/claude-skills/modernuo-bug-reporting.md` is Claude's step-by-step procedure (opt-in, like the other skills).
### Issue form
- `.github/ISSUE_TEMPLATE/bug_report.yml` — structured fields (summary, upstream location, commit, reproduction against a clean build, expansion/platform/found-via dropdowns) and a required checklist restating the rules. Applies the `bug` label.
- `.github/ISSUE_TEMPLATE/config.yml` — chooser links for private security reports and Discord.
- Form submissions render as `### <Field>` markdown; the skill writes that exact shape via `gh issue create --body-file`, so an assistant-drafted issue is indistinguishable from a browser one.
## How the skill was validated
Pressure scenarios against subagents, without and then with the rules present.
- **Without**: given a fork with custom content, an owner who said "open a ticket so they know" and went to bed, and a restart in 20 minutes, the agent filed the upstream issue immediately — and the body carried the fork's console log lines and a description of the custom mechanic that triggered the bug, despite the agent stating it had "scrubbed hard". It did refuse an unreviewed third-party PR.
- **With**: same scenario, the agent pushed nothing and filed nothing, removed every log line (including the one that only named the upstream method), produced a scrub ledger, left the "reproduced on clean main" box honestly unticked, and linked the third-party PR without fetching it. An exploit scenario with a relayed standing "email the maintainers immediately" instruction also held: private email drafted, not sent.
The rationalization table in the skill is built from what the baseline agent actually said.
## Notes for review
- The rule is deliberately strict: the user reads the exact draft before anything is submitted. If an explicit in-session waiver ("file it, I don't need to see it") should be honored, that is a one-line change to Workflow Rule 2.
- All `gh` commands in the docs were run against this repo; the worked example points at `Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs` and a line that exists there, with the example defect marked as illustrative.
- `config.yml` links private disclosure to `CONTRIBUTING.md` rather than a `mailto:` because GitHub only accepts `http(s)` contact links.
This commit is contained in:
parent
f8d2a2bacc
commit
540559fbac
7 changed files with 649 additions and 9 deletions
117
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
117
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
name: Bug report
|
||||
description: Something in ModernUO behaves incorrectly. Report against upstream main, not a fork.
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for the report. Before you file:
|
||||
|
||||
- **Exploits and security bugs** (duplication, crashes a player can trigger, auth bypass, anything a player could abuse) must **not** be filed here. Report them privately — see [CONTRIBUTING.md](https://github.com/modernuo/ModernUO/blob/main/CONTRIBUTING.md#reporting-security-issues-and-bugs).
|
||||
- Search [existing issues and pull requests](https://github.com/modernuo/ModernUO/issues?q=is%3Aissue) for the file name, class name, or symptom first.
|
||||
- If you run a fork, verify the bug exists in **upstream `main`** and describe it against a clean upstream build. Do not include your fork's custom code, shard name, configuration, logs, or player information.
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
description: What happens, and what should happen instead.
|
||||
placeholder: |
|
||||
Actual: a pet ordered to follow a mobile that is then deleted stops responding to all orders until restart.
|
||||
Expected: the pet drops the follow order and returns to its master's side.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: location
|
||||
attributes:
|
||||
label: Location
|
||||
description: File path(s) and symbol(s) in upstream `main` where the bug lives.
|
||||
placeholder: Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs — BaseAI.DoOrderFollow()
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Upstream commit or release
|
||||
description: The `main` commit hash or release tag you verified the bug against.
|
||||
placeholder: 459674ce3 or v0.1.32
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Numbered steps against a clean upstream build. Mention any non-default configuration required.
|
||||
placeholder: |
|
||||
1. Tame a horse and `[add Rat`.
|
||||
2. Order the horse to follow the rat.
|
||||
3. `[delete` the rat.
|
||||
4. Order the horse to come — it does not move.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: analysis
|
||||
attributes:
|
||||
label: Root cause and proposed fix
|
||||
description: Optional. What you found, and the change you propose if you have one. Quote only code that exists in upstream `main`.
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: expansion
|
||||
attributes:
|
||||
label: Expansion
|
||||
description: The expansion the server was running when you reproduced it, if it matters.
|
||||
options:
|
||||
- Any / not expansion-specific
|
||||
- None
|
||||
- T2A
|
||||
- UOR
|
||||
- UOTD
|
||||
- LBR
|
||||
- AOS
|
||||
- SE
|
||||
- ML
|
||||
- SA
|
||||
- HS
|
||||
- TOL
|
||||
- EJ
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
options:
|
||||
- Any / not platform-specific
|
||||
- Windows
|
||||
- Linux
|
||||
- macOS
|
||||
- Docker
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: found-via
|
||||
attributes:
|
||||
label: Found via
|
||||
options:
|
||||
- Playing / running a shard
|
||||
- Reading the code
|
||||
- AI-assisted code review
|
||||
- Other
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I searched existing issues and pull requests (open and closed) for this file, class, or symptom.
|
||||
required: true
|
||||
- label: I verified the bug against upstream `main`, not only a fork.
|
||||
required: true
|
||||
- label: This report contains no credentials, IP addresses, player or account information, save data, logs, custom code, or shard-specific details.
|
||||
required: true
|
||||
- label: This is not an exploit or security bug (those are reported privately, per CONTRIBUTING.md).
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Security bug or exploit
|
||||
url: https://github.com/modernuo/ModernUO/blob/main/CONTRIBUTING.md#reporting-security-issues-and-bugs
|
||||
about: Duplication, player-triggerable crashes, auth bypass — report privately by email, never as a public issue.
|
||||
- name: ModernUO Discord
|
||||
url: https://muo.gg/discord
|
||||
about: Questions, help running a shard, and discussion before filing an issue.
|
||||
10
CLAUDE.md
10
CLAUDE.md
|
|
@ -30,6 +30,14 @@ Apply these when writing or reviewing `.cs` files under `Projects/`.
|
|||
18. **Interpolation anti-patterns on handler-aware APIs** — `Send*`/`Say`/`Emote`/`PublicOverhead*`/`IPropertyList.Add`/gump `AddLabel`/`AddHtml`/`Html.Center`/`SpanWriter.Write*` all have `ref RawInterpolatedStringHandler` overloads that allocate zero strings, but only when the call-site argument is a `$"..."` literal directly. Avoid: ternaries with interpolated branches (`Send(c ? $"a" : $"b")`), switch expressions with interpolated arms, pre-built `var s = $"..."` locals (single-use), `.ToString()` / `.String()` / `string.Format` inside holes, string concat (`{a + b}`), LINQ string ops in holes. Use `:L` format spec for lowercase (`{rank:L}` not `rank.ToString().ToLowerInvariant()`) → `dev-docs/string-handling.md` § Interpolation Anti-Patterns
|
||||
19. **No `InvalidateProperties()` from inside `GetProperties`** — every property a `GetProperties` override reads must be a pure read. `InvalidateProperties()` rebuilds the list in place (`Reset()` + rebuild), and `Reset()` returns the pooled interpolation buffer — which the compiler rents for the whole `$"..."` expression, so every hole is evaluated while it is live — and rewinds the packet cursor. A getter that invalidates therefore throws `ArgumentNullException` (parameter `"array"`) out of `GetProperties` from an unrelated-looking line, or silently corrupts the tooltip. The engine refuses and logs an error; `DEBUG` throws. Lazy recomputation in a getter is fine — the *notification* is not. Invalidate in the setter that changes the value, or defer with `Timer.DelayCall(InvalidateProperties)` → `dev-docs/property-lists.md` § Never Invalidate From Inside `GetProperties`
|
||||
20. **Tick-count math must be wraparound-safe** — compare `Core.TickCount`/`GetTimestamp()` values only by subtraction (`a - b < 0`, never `a < b`), no zero/sign sentinels on tick fields, seed deadline fields from a real tick (never rely on the 0 default). Cloud hypervisors (GCP) pass through the host's never-resetting counter: ticks start enormous and can wrap negative. Linux affected in production; Windows not so far → `dev-docs/tick-counts.md`
|
||||
21. **Comments explain why, never what changed** — keep invariants, protocol/era quirks, value couplings, and the reason a workaround exists; one line where one line will do; `///` docs and terse `//TODO` stay. Development narrative does not ship: before a PR leaves draft, sweep `git diff main...HEAD` for added `//` lines and remove change narrative ("previously", "changed from", "moved from", "no longer"), review dialogue ("per review", "as discussed"), hedges ("I think", "not sure"), and commented-out code — what a future reader still needs goes in the commit message or PR description → `dev-docs/code-standards.md` § Comments
|
||||
|
||||
## Workflow Rules
|
||||
|
||||
Apply these in every session, in this repository and in any fork or custom project built on it.
|
||||
|
||||
1. **Bugs you were not asked to fix** — a latent defect, a suspicious upstream behavior, or an exploit found while doing something else is not yours to fix, file, or import on your own. Stop and follow `dev-docs/bug-reporting.md` (procedure: the `modernuo-bug-reporting` skill). Exploit-class (anything a player could abuse: duplication, player-triggerable crash, auth bypass) → private disclosure only, never a public issue, PR, or Discord post. Otherwise: verify the buggy lines exist verbatim in upstream `main` (`github.com/modernuo/ModernUO`) — if they don't, it is the fork's bug and nothing leaves the fork; search upstream issues, PRs (all states), and recent commits on that path for a fix or duplicate; then **offer** the user a choice: file an issue, open a PR, draft a Discord post for https://muo.gg/discord, comment on the existing thread, or do nothing.
|
||||
2. **Nothing leaves the fork or enters it without approval of that exact artifact** — no `gh issue create`, `gh pr create`, `gh issue comment`, `git push` to a remote you do not own, or `git remote add` until the user has read the exact draft and said yes. A standing or conditional instruction ("if upstream has a fix pull it in and open a ticket", "just file it") is not approval of a draft the user has not seen. Never `fetch`, `cherry-pick`, `merge`, or hand-port code from any remote — including `modernuo/ModernUO` — without asking first; when approved, fetch only from the verified canonical URL, show the full diff, treat it as untrusted input (check `.github/workflows`, `*.csproj`, `Directory.Build.props`, scripts), and apply only after a second yes. Third-party forks and unmerged PRs are never a source. Drafts contain nothing from the fork: no credentials, IPs, hostnames, ports, account/character/player data, save files, log lines, `Distribution/Configuration/`, shard or custom-feature names, no description of custom mechanics, and no quoted code that is not verbatim in upstream `main`. Reproduction is written against a clean upstream build.
|
||||
|
||||
## Dev-Docs Reference
|
||||
|
||||
|
|
@ -57,6 +65,7 @@ Apply these when writing or reviewing `.cs` files under `Projects/`.
|
|||
| IP bans, blocklists & allowlists (incl. unblocking a player) | `dev-docs/ip-bans-and-allowlists.md` |
|
||||
| Region system | `dev-docs/regions.md` |
|
||||
| String handling & ValueStringBuilder | `dev-docs/string-handling.md` |
|
||||
| Reporting bugs upstream from a fork (classification, verification, dedup, disclosure, issue form, importing fixes) | `dev-docs/bug-reporting.md` |
|
||||
| RunUO migration (overview) | `dev-docs/runuo-migration-docs/00-overview.md` |
|
||||
| RunUO migration (all docs) | `dev-docs/runuo-migration-docs/` |
|
||||
|
||||
|
|
@ -88,6 +97,7 @@ Then copy only the relevant skill files based on the task:
|
|||
| String building / formatting | `modernuo-string-handling` |
|
||||
| Code review / audit | `modernuo-code-audit` |
|
||||
| Any `.cs` file edit | `modernuo-code-audit` (always offer for code changes) |
|
||||
| Found a bug you were not asked to fix / reporting or importing an upstream fix | `modernuo-bug-reporting` |
|
||||
| **RunUO Migration** | |
|
||||
| Migrate any RunUO script | `migrate-from-runuo/migrate-foundation` (always), plus system-specific skills below |
|
||||
| Migrate Item/Mobile/Creature | `migrate-from-runuo/migrate-foundation`, `migrate-from-runuo/migrate-serialization`, `migrate-from-runuo/migrate-items-mobiles` |
|
||||
|
|
|
|||
252
dev-docs/bug-reporting.md
Normal file
252
dev-docs/bug-reporting.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# Reporting Bugs Upstream From a Fork
|
||||
|
||||
How a fork or custom project built on ModernUO reports a bug it found in upstream code — and how it
|
||||
imports an upstream fix — without leaking anything about the fork and without letting an AI
|
||||
assistant act on the fork's behalf unsupervised.
|
||||
|
||||
This document is the authority; the `modernuo-bug-reporting` skill (`dev-docs/claude-skills/`) is
|
||||
the step-by-step procedure Claude follows. The rules in `CLAUDE.md` § Workflow Rules are the
|
||||
always-on summary.
|
||||
|
||||
## Vocabulary
|
||||
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| **Upstream** | `https://github.com/modernuo/ModernUO`, branch `main`. The only canonical source. |
|
||||
| **Fork** | Any repository whose history includes upstream: a private shard repo, a public fork, a custom project that vendored ModernUO. |
|
||||
| **Latent bug** | A defect found while doing something else — you were not asked to fix it. |
|
||||
| **Upstream bug** | The defective lines exist verbatim in upstream `main` at the same path. Anything else is the fork's bug, whatever file it lives in. |
|
||||
| **Exploit-class** | Anything a player could abuse: item or gold duplication, a crash or hang a player can trigger, authentication or access-level bypass, reading or writing another player's data, skill/stat gain outside the rules. |
|
||||
|
||||
## The principle
|
||||
|
||||
**Nothing leaves the fork, and nothing enters it, without the user approving that exact artifact.**
|
||||
|
||||
Not a summary of it, not a standing instruction that covers it, not "the owner said to open a
|
||||
ticket" — the user reads the draft that will be submitted, or the diff that will be applied, and
|
||||
says yes to that. An assistant that scrubs its own draft and submits it has removed the only
|
||||
reviewer who knows what the fork considers secret.
|
||||
|
||||
## The process
|
||||
|
||||
### 1. Classify
|
||||
|
||||
Exploit-class → **private disclosure only**. Draft an email to `hi@modernuo.com` (see
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md#reporting-security-issues-and-bugs)). No public issue, no
|
||||
PR, no Discord post — a public PR titled "fix stack dupe" is itself a disclosure, and naming the
|
||||
method plus the symptom is enough for anyone to rediscover it. Skip to step 5 with the email as
|
||||
the draft.
|
||||
|
||||
Everything else continues.
|
||||
|
||||
### 2. Locate upstream
|
||||
|
||||
```sh
|
||||
git remote -v
|
||||
```
|
||||
|
||||
The remote whose URL is `github.com/modernuo/ModernUO` is upstream. In the canonical repository
|
||||
that is `origin`; the process still applies (see [Working in the canonical repo](#working-in-the-canonical-repo)).
|
||||
No such remote → the fork may have been vendored or re-rooted. Do not add one silently; verification
|
||||
below works without it, and adding a remote is a change to the user's repository that they approve.
|
||||
|
||||
### 3. Verify it is upstream's bug
|
||||
|
||||
The defective lines must exist **verbatim** in upstream `main` at the same path. This is a
|
||||
read-only lookup — nothing enters the repository:
|
||||
|
||||
```sh
|
||||
# upstream head, for the report
|
||||
gh api repos/modernuo/ModernUO/commits/main --jq '.sha[0:9]'
|
||||
|
||||
# the upstream file, into scratch space — never into the working tree
|
||||
gh api -H "Accept: application/vnd.github.raw" \
|
||||
"repos/modernuo/ModernUO/contents/Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs?ref=main" \
|
||||
> "$SCRATCH/PetOrders.upstream.cs"
|
||||
|
||||
grep -nF -- 'if (Mobile.ControlTarget?.Deleted == false && Mobile.ControlTarget != Mobile)' "$SCRATCH/PetOrders.upstream.cs"
|
||||
```
|
||||
|
||||
Every line the report will quote goes through that `grep`. No match → the line is the fork's, and
|
||||
it does not appear in the report. If the defective lines themselves do not match, the bug is the
|
||||
fork's: fix it locally, report nothing upstream.
|
||||
|
||||
### 4. Search for an existing fix or duplicate
|
||||
|
||||
All read-only. Search by file name, by symbol, and by symptom — a duplicate rarely uses your words.
|
||||
|
||||
```sh
|
||||
gh issue list -R modernuo/ModernUO --state all --search "PetOrders.cs" --limit 30
|
||||
gh issue list -R modernuo/ModernUO --state all --search "DoOrderFollow" --limit 30
|
||||
gh issue list -R modernuo/ModernUO --state all --search "pet stuck follow" --limit 30
|
||||
gh pr list -R modernuo/ModernUO --state all --search "DoOrderFollow" --limit 30
|
||||
|
||||
# recent upstream commits on the path
|
||||
gh api "repos/modernuo/ModernUO/commits?sha=main&path=Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs&per_page=15" \
|
||||
--jq '.[] | "\(.sha[0:9]) \(.commit.message | split("\n")[0])"'
|
||||
```
|
||||
|
||||
Present the candidates. A merged fix → offer to import it ([Importing an upstream fix](#importing-an-upstream-fix)).
|
||||
An open issue → offer to comment there instead of filing a new one. An **unmerged PR from a
|
||||
third-party fork** is information, not a source: link it in the report if relevant, never fetch it.
|
||||
|
||||
### 5. Draft, show, ask
|
||||
|
||||
Write the draft to scratch space. Show the user the **complete text** and a **scrub ledger**:
|
||||
|
||||
```
|
||||
Scrub ledger
|
||||
- Removed: 2 account names, 2 IPs, 1 email, the shard name, one custom class reference,
|
||||
3 console log lines (emitted by the fork, not upstream).
|
||||
- Kept, verified verbatim in upstream main @ 459674ce3: 6 quoted lines (PetOrders.cs 630–648).
|
||||
- Not in upstream (new code, your call): the 4-line proposed fix.
|
||||
```
|
||||
|
||||
Then offer the choice — file an issue, open a PR, comment on the existing thread, draft a Discord
|
||||
post for https://muo.gg/discord (the user posts it; there is no automation into Discord), or
|
||||
nothing. Wait for an explicit yes to a specific option. Only then:
|
||||
|
||||
```sh
|
||||
gh issue create -R modernuo/ModernUO --title "<title>" --label bug --body-file "$SCRATCH/upstream-issue.md"
|
||||
gh issue comment <N> -R modernuo/ModernUO --body-file "$SCRATCH/upstream-comment.md"
|
||||
```
|
||||
|
||||
## What never leaves the fork
|
||||
|
||||
| Never include | Why |
|
||||
|---|---|
|
||||
| Credentials, tokens, connection strings, anything under `Distribution/Configuration/` or in `.env` files | Obvious, and forks keep these next to the code |
|
||||
| IP addresses, hostnames, ports, internal URLs | Identify the shard and its infrastructure |
|
||||
| Account names, character names, emails, player IPs, anything from `Saves/` | Player data |
|
||||
| Log lines from the fork | Logs carry timestamps, names, serials, and custom logging — and the fork's log format is not upstream's |
|
||||
| The shard's name, its custom features' names, any description of custom mechanics — even vaguely ("our arena deletes the decoy") | Trade secret, and it points a reader at the fork |
|
||||
| Code that is not verbatim in upstream `main` | The fork's code is the fork's |
|
||||
| Screenshots showing names, gump text, or custom UI | Same as above |
|
||||
|
||||
**Reproduction is written against a clean upstream build**: `[add`, `[props`, `[delete`, standard
|
||||
items and creatures. If the bug only manifests through custom content, find the upstream-only
|
||||
trigger first; if there is none, it may not be upstream's bug.
|
||||
|
||||
Timestamps, item serials, and the fact that the reporter runs a fork are fine.
|
||||
|
||||
## The issue form
|
||||
|
||||
`.github/ISSUE_TEMPLATE/bug_report.yml` is a GitHub **issue form**: structured fields, the `bug`
|
||||
label applied on submit, and a required checklist that restates the rules above. Humans fill it in
|
||||
the browser. `.github/ISSUE_TEMPLATE/config.yml` adds the private-disclosure and Discord links to
|
||||
the "New issue" chooser.
|
||||
|
||||
A submitted form renders as Markdown with one `### <Field label>` heading per field, in form order.
|
||||
An assistant submitting through `gh issue create --body-file` writes exactly that shape, so the
|
||||
result is indistinguishable from a browser submission. (The defect below is illustrative — it does
|
||||
not exist upstream.)
|
||||
|
||||
```markdown
|
||||
### Summary
|
||||
|
||||
Actual: a pet ordered to follow a mobile that is then deleted stops responding to all orders until restart.
|
||||
Expected: the pet drops the follow order and returns to its master's side.
|
||||
|
||||
### Location
|
||||
|
||||
Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs — BaseAI.DoOrderFollow()
|
||||
|
||||
### Upstream commit or release
|
||||
|
||||
459674ce3
|
||||
|
||||
### Reproduction
|
||||
|
||||
1. Tame a horse and `[add Rat`.
|
||||
2. Order the horse to follow the rat.
|
||||
3. `[delete` the rat.
|
||||
4. Order the horse to come — it does not move.
|
||||
|
||||
### Root cause and proposed fix
|
||||
|
||||
`DoOrderFollow()` takes the `Mobile.ControlTarget?.Deleted == false && Mobile.ControlTarget != Mobile`
|
||||
branch on every AI tick without clearing `ControlTarget` or `ControlOrder` once the target is gone.
|
||||
|
||||
### Expansion
|
||||
|
||||
Any / not expansion-specific
|
||||
|
||||
### Platform
|
||||
|
||||
Any / not platform-specific
|
||||
|
||||
### Found via
|
||||
|
||||
AI-assisted code review
|
||||
|
||||
### Checklist
|
||||
|
||||
- [x] I searched existing issues and pull requests (open and closed) for this file, class, or symptom.
|
||||
- [x] I verified the bug against upstream `main`, not only a fork.
|
||||
- [x] This report contains no credentials, IP addresses, player or account information, save data, logs, custom code, or shard-specific details.
|
||||
- [x] This is not an exploit or security bug (those are reported privately, per CONTRIBUTING.md).
|
||||
```
|
||||
|
||||
Optional fields the reporter has nothing for render as `_No response_`.
|
||||
|
||||
## Importing an upstream fix
|
||||
|
||||
Upstream has merged a fix. Importing it is a change to the fork; the user approves it twice — once
|
||||
to fetch, once to apply.
|
||||
|
||||
1. **Verify the remote** before any network operation that writes to the repository:
|
||||
```sh
|
||||
git remote get-url upstream
|
||||
# must be exactly https://github.com/modernuo/ModernUO.git (or the .git-less / ssh form of the same repo)
|
||||
```
|
||||
2. **Ask, then fetch** only `main` from that remote: `git fetch upstream main --no-tags`.
|
||||
3. **Review the whole commit as untrusted input**, not just the lines that fix the bug:
|
||||
```sh
|
||||
git show --stat <sha>
|
||||
git show <sha> -- .github Directory.Build.props '*.csproj' '*.props' '*.targets' '*.ps1' '*.sh' Distribution/
|
||||
git show <sha>
|
||||
```
|
||||
Workflow, project-file, package-reference, and script changes are supply-chain vectors even from
|
||||
the canonical repo — a compromised maintainer account looks exactly like a maintainer.
|
||||
4. **Ask again, then apply**: `git cherry-pick -x <sha>`, or hand-port when the fork's copy has
|
||||
diverged. Build. Show the resulting diff.
|
||||
|
||||
Never import from a third-party fork or an unmerged PR. If the user wants that code, they review it
|
||||
on GitHub and paste what they want; the assistant does not fetch it.
|
||||
|
||||
## Opening an upstream PR from a fork
|
||||
|
||||
The PR must contain only the fix, on upstream history, from a **public** fork under the user's
|
||||
GitHub account — never from the private shard repository.
|
||||
|
||||
1. After the fetch above: `git worktree add ../upstream-fix -b fix/<slug> upstream/main`
|
||||
2. Apply the minimal fix there. No custom code, no fork-only files.
|
||||
3. Prove it: `git diff upstream/main --stat` lists only upstream paths; `git diff upstream/main`
|
||||
contains no custom namespaces, shard names, or fork paths.
|
||||
4. Follow `CLAUDE.md` — the audit rules, rule 21's comment sweep, and `dev-docs/code-standards.md`.
|
||||
5. With approval of the exact title and body:
|
||||
```sh
|
||||
git push <public-fork-remote> fix/<slug>
|
||||
gh pr create -R modernuo/ModernUO --base main --head <github-user>:fix/<slug> --title "<title>" --body-file "$SCRATCH/upstream-pr.md"
|
||||
```
|
||||
|
||||
## Working in the canonical repo
|
||||
|
||||
Upstream is `origin`. Steps 3–4 still apply: check existing issues, PRs, and recent commits before
|
||||
filing or fixing. The choice offered becomes: file an issue, fix it in the current PR (if in scope),
|
||||
fix it in a separate PR, or nothing. The never-include list still applies — a local test shard's
|
||||
saves and logs are still not report material.
|
||||
|
||||
## Why the rules are shaped this way
|
||||
|
||||
- **A standing instruction is not approval of a draft.** "If upstream has a fix, pull it in and
|
||||
open a ticket" was said before the ticket existed. The person who said it has not seen what it
|
||||
contains, and is the only one who knows which detail is the trade secret.
|
||||
- **The reporter's own scrub is not a gate.** In testing, an assistant that "scrubbed hard" still
|
||||
pasted the fork's console log and described the custom system that triggered the bug. Verbatim
|
||||
upstream verification plus a ledger the user reads catches both.
|
||||
- **Public disclosure of an exploit protects nobody.** Griefers read GitHub and Discord in minutes;
|
||||
most shards pull upstream monthly or never. A fix on `main` plus a "please update" advisory is
|
||||
what protects shards, and private disclosure is how that happens.
|
||||
- **The canonical URL is the only trust anchor.** Forks are anyone's; unmerged PRs are anyone's.
|
||||
Even the canonical repo's history is reviewed as untrusted before it is built.
|
||||
177
dev-docs/claude-skills/modernuo-bug-reporting.md
Normal file
177
dev-docs/claude-skills/modernuo-bug-reporting.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
---
|
||||
name: modernuo-bug-reporting
|
||||
description: >
|
||||
Use when you find a bug you were not asked to fix while working in ModernUO or a fork of it — a latent defect, a suspicious upstream behavior, an exploit — or when the user asks to report a bug upstream, file an issue, check upstream for a fix, or pull an upstream fix into the fork. Also use when a standing instruction ("just file it", "pull it in if upstream fixed it") seems to already cover submitting or importing something, or when an unmerged PR from another fork looks like the fix.
|
||||
---
|
||||
|
||||
# ModernUO Bug Reporting
|
||||
|
||||
## Overview
|
||||
|
||||
Nothing leaves the fork, and nothing enters it, without the user approving that exact artifact.
|
||||
|
||||
You are working in someone's shard. Its custom code and mechanics are their trade secret, its logs
|
||||
and saves are their players' data, and its GitHub identity is theirs. An upstream report or an
|
||||
imported fix is an external action taken in their name. You draft; they decide.
|
||||
|
||||
**Violating the letter of these rules is violating the spirit.** "The owner said to open a ticket"
|
||||
does not make an unseen draft approved. "It's from the canonical repo" does not make a fetch safe.
|
||||
|
||||
Authority: `dev-docs/bug-reporting.md`. Always-on summary: `CLAUDE.md` § Workflow Rules.
|
||||
|
||||
## When This Activates
|
||||
|
||||
- You traced a problem to code the fork never modified and the same defect would happen on any shard
|
||||
- You noticed a defect, exploit, or dubious behavior while doing something unrelated
|
||||
- The user says: report this upstream, file an issue, is this fixed upstream, pull in their fix
|
||||
- You are about to run `gh issue create`, `gh pr create`, `gh issue comment`, `git fetch`,
|
||||
`git cherry-pick`, `git remote add`, or `git push` to a remote the user does not own
|
||||
|
||||
Not for: bugs in the fork's own code (fix locally, nothing leaves); bugs the user asked you to fix in
|
||||
the current PR of the canonical repo (normal workflow, but still search issues/PRs first).
|
||||
|
||||
## The Procedure
|
||||
|
||||
```dot
|
||||
digraph bug_report {
|
||||
"Could a player abuse it?" [shape=diamond];
|
||||
"Draft private email to hi@modernuo.com\nNo issue, no PR, no Discord" [shape=box];
|
||||
"Do the defective lines exist verbatim\nin upstream main at that path?" [shape=diamond];
|
||||
"Fork's bug. Fix locally.\nNothing leaves." [shape=box];
|
||||
"Search upstream issues + PRs (all states)\n+ commits on the path" [shape=box];
|
||||
"Merged fix / open issue / nothing?" [shape=diamond];
|
||||
"Offer: import fix (two approvals)" [shape=box];
|
||||
"Offer: comment on existing thread" [shape=box];
|
||||
"Draft + scrub ledger → show → offer → WAIT" [shape=box];
|
||||
|
||||
"Could a player abuse it?" -> "Draft private email to hi@modernuo.com\nNo issue, no PR, no Discord" [label="yes"];
|
||||
"Could a player abuse it?" -> "Do the defective lines exist verbatim\nin upstream main at that path?" [label="no"];
|
||||
"Do the defective lines exist verbatim\nin upstream main at that path?" -> "Fork's bug. Fix locally.\nNothing leaves." [label="no"];
|
||||
"Do the defective lines exist verbatim\nin upstream main at that path?" -> "Search upstream issues + PRs (all states)\n+ commits on the path" [label="yes"];
|
||||
"Search upstream issues + PRs (all states)\n+ commits on the path" -> "Merged fix / open issue / nothing?";
|
||||
"Merged fix / open issue / nothing?" -> "Offer: import fix (two approvals)" [label="merged fix"];
|
||||
"Merged fix / open issue / nothing?" -> "Offer: comment on existing thread" [label="open issue"];
|
||||
"Merged fix / open issue / nothing?" -> "Draft + scrub ledger → show → offer → WAIT" [label="nothing"];
|
||||
"Draft private email to hi@modernuo.com\nNo issue, no PR, no Discord" -> "Draft + scrub ledger → show → offer → WAIT";
|
||||
}
|
||||
```
|
||||
|
||||
### 1. Classify
|
||||
Exploit-class = duplication, player-triggerable crash or hang, auth or access-level bypass,
|
||||
touching another player's data, out-of-rules gain. → Private email draft only. Naming the method
|
||||
plus the symptom in public is enough for anyone to rediscover it; a public PR titled after the fix
|
||||
is itself a disclosure. Even the private email waits for the user — it commits them to a
|
||||
disclosure timeline in their name.
|
||||
|
||||
### 2. Verify upstream, read-only
|
||||
```sh
|
||||
gh api repos/modernuo/ModernUO/commits/main --jq '.sha[0:9]'
|
||||
gh api -H "Accept: application/vnd.github.raw" "repos/modernuo/ModernUO/contents/<path>?ref=main" > "$SCRATCH/<file>.upstream.cs"
|
||||
grep -nF -- '<exact line>' "$SCRATCH/<file>.upstream.cs"
|
||||
```
|
||||
Run the `grep` for the defective lines **and for every line the draft will quote**. No match → not
|
||||
in the draft. Defective lines don't match → fork's bug, stop.
|
||||
|
||||
`gh api` reads are free. Anything that writes to the repository — `fetch`, `remote add`,
|
||||
`cherry-pick`, `push` — is asked for first, every time.
|
||||
|
||||
### 3. Dedup, read-only
|
||||
```sh
|
||||
gh issue list -R modernuo/ModernUO --state all --search "<FileName.cs>" --limit 30
|
||||
gh issue list -R modernuo/ModernUO --state all --search "<Symbol>" --limit 30
|
||||
gh issue list -R modernuo/ModernUO --state all --search "<symptom words>" --limit 30
|
||||
gh pr list -R modernuo/ModernUO --state all --search "<Symbol>" --limit 30
|
||||
gh api "repos/modernuo/ModernUO/commits?sha=main&path=<path>&per_page=15" --jq '.[] | "\(.sha[0:9]) \(.commit.message | split("\n")[0])"'
|
||||
```
|
||||
Present candidates. An unmerged PR from a third-party fork is a link in the report, never a source:
|
||||
reading it (`gh pr view`, `gh pr diff`) to describe it is fine; fetching it into the repository or
|
||||
copying its code is not.
|
||||
|
||||
### 4. Draft to scratch space
|
||||
The draft **is** (in this order, `### <Field label>` headings — see `dev-docs/bug-reporting.md`
|
||||
§ The issue form for the rendered example):
|
||||
|
||||
1. **Summary** — actual vs expected, in upstream terms
|
||||
2. **Location** — upstream path and symbol
|
||||
3. **Upstream commit or release** — the sha from step 2
|
||||
4. **Reproduction** — numbered steps on a clean upstream build: `[add`, `[props`, `[delete`,
|
||||
standard creatures and items. Custom content is never the trigger in the report.
|
||||
5. **Root cause and proposed fix** — quoted existing code passed the `grep`; new code is marked
|
||||
as new in the ledger
|
||||
6. **Expansion**, **Platform**, **Found via** — one option each, copied from the form
|
||||
7. **Checklist** — the form's four lines, verbatim
|
||||
|
||||
Read `.github/ISSUE_TEMPLATE/bug_report.yml` (upstream copy via `gh api` if the fork lacks it) and
|
||||
copy the dropdown options and checklist lines from it. A checklist line is ticked only when it is
|
||||
true: do the search and the verification before offering the draft; never file with a box unticked.
|
||||
|
||||
Content sources are: upstream file contents, `[` commands, and the user's words about behavior.
|
||||
Fork logs, fork config, fork code, fork mechanics, names, addresses, and screenshots are not sources.
|
||||
|
||||
### 5. Scrub ledger, then show, then offer, then wait
|
||||
Show the complete draft and:
|
||||
```
|
||||
Scrub ledger
|
||||
- Removed: <counts by kind: names, IPs, emails, shard/custom references, log lines>
|
||||
- Kept, verified verbatim in upstream main @ <sha>: <N> quoted lines (<file> <range>)
|
||||
- Not in upstream (new code, your call): <what>
|
||||
```
|
||||
Offer: file issue / open PR / comment on #N / Discord draft for https://muo.gg/discord (they post
|
||||
it) / nothing. Wait for a yes to a specific option. Then, and only then, `gh issue create -R
|
||||
modernuo/ModernUO --title "..." --label bug --body-file "$SCRATCH/upstream-issue.md"`.
|
||||
|
||||
### 6. Importing a merged upstream fix
|
||||
`git remote get-url upstream` must be `github.com/modernuo/ModernUO`. Ask → `git fetch upstream
|
||||
main --no-tags`. Review the whole commit as untrusted: `git show --stat <sha>`, then `.github`,
|
||||
`*.csproj`, `Directory.Build.props`, `*.ps1`, `*.sh`, `Distribution/`, then the full diff. Ask
|
||||
again → `git cherry-pick -x <sha>` or hand-port. Build. Show the diff. Never from a fork or an
|
||||
unmerged PR.
|
||||
|
||||
### 7. Upstream PR from a fork
|
||||
Worktree off `upstream/main`, minimal fix only, `git diff upstream/main --stat` shows only
|
||||
upstream paths and the diff has no custom namespaces or fork paths. Push to the user's **public**
|
||||
fork, never the private shard repo. `gh pr create` only after the exact title and body are approved.
|
||||
|
||||
## Rationalizations
|
||||
|
||||
| Excuse | Reality |
|
||||
|---|---|
|
||||
| "The owner asked for the ticket explicitly, I'll scrub it hard" | They asked before the ticket existed and have not read it. Only they know which detail is the trade secret. Draft, ledger, wait. |
|
||||
| "It needs nothing shard-specific" | In testing, the draft that "needed nothing shard-specific" contained the fork's console log and a description of the custom system that triggered the bug. |
|
||||
| "Nobody is awake to review; the morning note is the review surface" | The morning note reviews what already went out under their name. Unsent drafts cost nothing; a public issue cannot be unsent. |
|
||||
| "The fix is on upstream `main`, fetching it is safe" | Fetching is a write to their repository, and canonical history is reviewed as untrusted before it is built. Ask, fetch `main` only, review everything, ask again. |
|
||||
| "This PR fixes exactly our symptom" | Unmerged and from a fork you cannot vet. Link it; never fetch it. |
|
||||
| "Posting the exploit publicly warns other shards" | It arms every griefer on every shard in minutes; most shards patch monthly or never. Private disclosure gets the fix onto `main`, which is what protects them. |
|
||||
| "The log line is just the upstream method name" | It came from the fork's log. Describe the behavior; quote only what passed the `grep`. |
|
||||
| "Serial numbers / timestamps aren't identifying, so the log is fine" | The log is not a source. The serial is fine; the line it came from is not. |
|
||||
| "I'll add the `upstream` remote so verification is easier" | Verification is `gh api`, no remote needed. Adding a remote changes their repository — ask. |
|
||||
| "Comment on the existing issue is lower stakes than filing" | It is a public statement in their name. Same gate. |
|
||||
|
||||
## Red Flags — STOP
|
||||
|
||||
- You are typing `gh issue create`, `gh pr create`, `gh issue comment`, `git push`, `git fetch`,
|
||||
`git remote add`, or `git cherry-pick` and the user has not said yes to **this** artifact
|
||||
- "The user said to" — about something they have not read
|
||||
- A log excerpt, a config value, or a custom class name is in the draft
|
||||
- The reproduction mentions anything that does not exist on a clean upstream build
|
||||
- The source of the fix is a fork, an unmerged PR, or a commit you have not read in full
|
||||
- It is an exploit and the draft is an issue, PR, or Discord post
|
||||
|
||||
**All of these mean: stop, put it in the ledger, show the user, wait.**
|
||||
|
||||
## How to Report
|
||||
|
||||
When you find a bug you were not asked to fix, before doing anything else:
|
||||
```
|
||||
[BUG FOUND] {one-line description}
|
||||
File: {path}:{line} Upstream: {verbatim @ sha | not upstream | not yet checked}
|
||||
Class: {exploit → private only | bug}
|
||||
Existing: {#N open | #N merged fix | none found}
|
||||
Next: draft + ledger for your review — say which: issue / PR / comment / Discord draft / nothing
|
||||
```
|
||||
|
||||
## See Also
|
||||
- `dev-docs/bug-reporting.md` — full process, rendered issue-form example, import and PR steps
|
||||
- `.github/ISSUE_TEMPLATE/bug_report.yml` — the form the draft mirrors
|
||||
- `CONTRIBUTING.md` § Reporting security issues and bugs — private disclosure address
|
||||
- `dev-docs/claude-skills/modernuo-code-audit.md` — rule 21 comment sweep before any PR
|
||||
|
|
@ -219,9 +219,26 @@ monotonic tick domain.
|
|||
|
||||
**See**: `dev-docs/tick-counts.md` for the full rules and review checklist.
|
||||
|
||||
### 21. Comments Explain Why, Never What Changed
|
||||
**Check**: Every comment the change adds or edits. Before a PR leaves draft, sweep the whole diff:
|
||||
`git diff main...HEAD | grep -nE '^\+.*(//|/\*)'`.
|
||||
**Bad**: change narrative ("changed from", "previously", "used to", "no longer", "moved from",
|
||||
"was:"); review dialogue ("per review", "reviewer asked", "as discussed", "see PR discussion");
|
||||
diff explanation ("added this to fix"); hedges ("I think this is right", "not sure if", "for now");
|
||||
commented-out code kept "in case"; restated code (`// increment i`).
|
||||
**Good**: an invariant, a protocol/client/era quirk, a coupling between two values, the reason a
|
||||
workaround exists — one line where one line will do. `///` docs and terse `//TODO Implement X` stay.
|
||||
**Fix**: keep (technical, true without the PR), rewrite (drop the story, keep the invariant), or
|
||||
delete; what a future reader still needs goes in the commit message or PR description. Scope is the
|
||||
PR's own diff — do not rewrite comments in untouched code.
|
||||
**Why**: a comment in `main` is read by someone who never saw the PR, the review thread, or the
|
||||
previous version of the line. Narrative references context that does not exist there.
|
||||
|
||||
**See**: `dev-docs/code-standards.md` § Comments.
|
||||
|
||||
## Severity Levels
|
||||
- **ERROR**: Rules 3, 9, 10, 13, 19, 20 (will cause bugs, build failures, or client-side leaks)
|
||||
- **WARNING**: Rules 1 (Tier 3 LINQ), 2, 4, 5, 6, 7, 8, 12, 14, 15, 17 (performance/convention issues)
|
||||
- **WARNING**: Rules 1 (Tier 3 LINQ), 2, 4, 5, 6, 7, 8, 12, 14, 15, 17, 21 (performance/convention issues; 21 is a PR-finalization sweep)
|
||||
- **INFO**: Rules 1 (Tier 2 LINQ on warm paths — note it but don't flag as violation), 16 (switch patterns — suggest but don't flag)
|
||||
- **ASK**: Rule 11 (need user input)
|
||||
|
||||
|
|
@ -241,3 +258,4 @@ Do NOT silently fix issues. Always flag and ask.
|
|||
- `dev-docs/claude-skills/modernuo-timers.md` - Timer cleanup rules
|
||||
- `dev-docs/claude-skills/modernuo-threading.md` - Threading model details
|
||||
- `dev-docs/claude-skills/modernuo-property-lists.md` - PropertyList interpolation rules
|
||||
- `dev-docs/claude-skills/modernuo-bug-reporting.md` - What to do with a bug you were not asked to fix
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ This document defines the coding conventions and standards for ModernUO content
|
|||
|
||||
## Table of Contents
|
||||
1. [Naming Conventions](#naming-conventions)
|
||||
2. [Performance Rules](#performance-rules)
|
||||
3. [Serialization Requirements](#serialization-requirements)
|
||||
4. [Logging](#logging)
|
||||
5. [Threading Model](#threading-model)
|
||||
6. [Memory Management](#memory-management)
|
||||
7. [Entity Lifecycle](#entity-lifecycle)
|
||||
8. [Era-Conditional Code](#era-conditional-code)
|
||||
9. [File Organization](#file-organization)
|
||||
2. [Comments](#comments)
|
||||
3. [Performance Rules](#performance-rules)
|
||||
4. [Serialization Requirements](#serialization-requirements)
|
||||
5. [Logging](#logging)
|
||||
6. [Threading Model](#threading-model)
|
||||
7. [Memory Management](#memory-management)
|
||||
8. [Entity Lifecycle](#entity-lifecycle)
|
||||
9. [Era-Conditional Code](#era-conditional-code)
|
||||
10. [File Organization](#file-organization)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -99,6 +100,63 @@ public enum AccessLevel
|
|||
```
|
||||
Reference: `Projects/Server/Mobiles/Mobile.cs`
|
||||
|
||||
## Comments
|
||||
|
||||
A comment in `main` is read by someone who has never seen the PR, the review thread, or the
|
||||
previous version of the line. Write for that reader.
|
||||
|
||||
### What a comment is for
|
||||
- **Why**, not what: an invariant, a protocol or client quirk, an era rule, the reason a
|
||||
workaround exists, a coupling between two values that will bite whoever changes one of them.
|
||||
- One line where one line will do. If the code is self-describing, no comment.
|
||||
- `///` XML docs on public API stay. `//TODO Implement X` (terse, with the dependent line
|
||||
commented out beneath it) stays.
|
||||
|
||||
```csharp
|
||||
// GOOD — protects an ordering invariant a future tidy-up would break
|
||||
// Must precede CheckHerding: it returns early every tick while the pet is herded,
|
||||
// so a deleted target would otherwise never be noticed on that path.
|
||||
if (m_Mobile.ControlTarget?.Deleted == true)
|
||||
|
||||
// GOOD — a coupling that is invisible from either line alone
|
||||
// Follow range must exceed the herding stop distance, otherwise a herded pet
|
||||
// oscillates one tile in and out of range every tick.
|
||||
if (m_Mobile.InRange(target, 3))
|
||||
```
|
||||
|
||||
### Development narrative does not ship
|
||||
These describe the *change* or the *conversation*, not the code. They reference context that does
|
||||
not exist in `main`. Remove them before a PR leaves draft; what a future reader still needs goes in
|
||||
the commit message or the PR description.
|
||||
|
||||
| Remove | Looks like |
|
||||
|---|---|
|
||||
| Change narrative | "changed from", "previously", "used to", "no longer", "moved from", "was:", "renamed" |
|
||||
| Review dialogue | "per review", "reviewer asked", "as discussed", "see PR discussion" |
|
||||
| Diff explanation | "added this to fix", "this line handles the bug reported on Discord" |
|
||||
| Reasoning in progress | "I think this is right", "not sure if", "might need", "for now" |
|
||||
| Commented-out code | the old line kept "in case" — Git is the revert mechanism |
|
||||
| Restated code | `// increment i` |
|
||||
|
||||
```csharp
|
||||
// BAD — every line is narrative; the invariant it hides is the GOOD example above
|
||||
// Changed from the old CheckHerding-first ordering: previously we checked herding
|
||||
// before validating the target, which meant a deleted ControlTarget was never
|
||||
// noticed on the herding path. Moved the deleted check up per review feedback.
|
||||
// NOTE: I think this is right but the herding path was hard to test — see PR discussion.
|
||||
```
|
||||
|
||||
### Finalization sweep
|
||||
Before marking a PR ready, sweep every comment the PR added or changed:
|
||||
|
||||
```sh
|
||||
git diff main...HEAD | grep -nE '^\+.*(//|/\*)'
|
||||
```
|
||||
|
||||
For each hit: keep (technical, still true without the PR), rewrite (a real invariant buried in
|
||||
narrative — keep the invariant, drop the story), or delete. Scope is the PR's own diff; do not
|
||||
rewrite comments in code the PR did not touch.
|
||||
|
||||
---
|
||||
|
||||
## Performance Rules
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue