vinta-ai-workflows 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to `vinta-ai-workflows` are documented in this file.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
6
6
  the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] — 2026-08-05
9
+
10
+ ### Changed
11
+
12
+ - **PR context is now written in Simple English.** `integrate-phase` runs
13
+ `deslop-comments` over the prs-context file it just wrote, before `open-pr.sh`
14
+ publishes it, so the PR title, the description body, and every inline comment
15
+ read as Simple English — one idea per sentence, current behavior stated
16
+ directly, no AI-slop vocabulary. The project's PR-template structure survives
17
+ the pass untouched. `amend-plan` runs the same pass whenever it rewrites a
18
+ prs-context file after a force-push. New step 5 in
19
+ [`partials/pr-context.md`](skills/vinta-derive-skills/resources/plan-execution/partials/pr-context.md)
20
+ (the old steps 5–7 shift to 6–8); the writing rules are also stated up front in
21
+ [`prs-context-template.md`](skills/vinta-derive-skills/resources/prs-context-template.md)
22
+ so the first draft comes out clean. No config change — `deslop-comments`
23
+ already always ships.
24
+ - **`deslop-comments` now deletes comments, not just rewrites them.** The pass
25
+ used to reword whatever it found; deleting a comment is now a normal outcome.
26
+ The "What counts as slop" list splits accordingly — rules 1–5 are rewordings,
27
+ rules 6–12 are usually deletions — and seven new rules cover the comments that
28
+ should not exist at any length: **where-used lists** (call-site inventories go
29
+ stale the first time someone adds a caller), **another module's internals
30
+ explained from here**, **one decision explained in several places** (write the
31
+ reason once at the site that makes the decision), **restating the code**, **a
32
+ comment standing in for a name** (report the rename, don't edit the comment),
33
+ **plans and deferred work** ("temporary until Y"), and **paths not taken**
34
+ ("tried X, settled on Y"). A new **essay-voice** rule bans sayings, metaphor,
35
+ and "it looks like X but is really Y" framing, and the buzzword table grew by
36
+ roughly a dozen entries (`gloss`, `roll-call`, `signal`, `the tell`, `unpick`,
37
+ `papering over`, `strictly worse`, `earns its place`, …).
38
+
39
+ Process gained four steps: check comments next to changed code for claims that
40
+ are no longer true (nothing else catches a stale comment — no test, no type
41
+ error, no lint rule), decide delete-or-reword before editing, collect
42
+ duplicates across the whole file set before editing any of it, and grep the
43
+ result for the skill's own buzzword list, since those words get written and
44
+ then skimmed past. The summary now reports comment counts before and after,
45
+ plus any comment found to be factually wrong.
46
+
47
+ A new **"When the fix is not a comment edit"** section covers the case where
48
+ the real fix is a test, a rename, or a function split: leave the comment,
49
+ report it with the file and what must exist first. "What to leave alone" gains
50
+ a matching guard — keep the reason a non-obvious line exists, and shorten the
51
+ wording instead. This pass removes noise, not knowledge.
52
+
8
53
  ## [0.4.0] — 2026-07-31
9
54
 
10
55
  ### Added
package/README.md CHANGED
@@ -240,7 +240,7 @@ Land at `ai-tools/skills/<name>/SKILL.md`. Always-on unless flagged optional.
240
240
  | `integrate-phase` | always (unit) | Plan-execution sub-skill: push the reviewed phase along the project's commit strategy + open the PR via context file. Under `commit_strategy = ask`, ships as `integrate-phase-stacked` + `integrate-phase-modular`. |
241
241
  | `amend-plan` | always (generated) | History-rewriting companion to `implement-plan` — revises in-flight plans, amends prior-phase commits, force-pushes, rebases stacked downstream branches. Reuses `review-phase` + `implement-phase`'s shared prompt loop. |
242
242
  | [`handoff`](skills/vinta-derive-skills/resources/foundation-skills/handoff/SKILL.md) | always | Session-continuation handoff doc → `.vinta-ai-workflows/handoffs/`. **Write** captures goal, verified-vs-unverified state, decisions + rejected alternatives, landmines, and the single next step — gathered from the repo, never memory. **Resume** verifies a handoff's claims against the repo before continuing from its next step. |
243
- | [`deslop-comments`](skills/vinta-derive-skills/resources/foundation-skills/deslop-comments/SKILL.md) | always | Rewrite comments + doc blocks touched during a task into Simple English (strips AI-slop vocabulary + negative framing; comment-only — no renames, no behavior change). `review-phase` Layer 2 depends on it; also invokable standalone. |
243
+ | [`deslop-comments`](skills/vinta-derive-skills/resources/foundation-skills/deslop-comments/SKILL.md) | always | Rewrite comments + doc blocks touched during a task into Simple English, and delete the ones that shouldn't exist at all — where-used lists, another module's internals, restatements of the code, deferred-work notes, paths not taken (comment-only; no renames, no behavior change). `review-phase` Layer 2 depends on it, `integrate-phase` / `amend-plan` run it over the PR-context file, and it's invokable standalone. |
244
244
  | `write-unit-test` | default-on (generated) | Write durable unit tests for a unit of behavior, enforcing six framework-agnostic rules + this project's captured test conventions, with runner- and stack-specific best-practice packs. Ships automatically whenever a unit-test framework is detected (set `disabled` to opt out). Generated from a template; config under `skills.write-unit-test.*`. See below. |
245
245
  | `systematic-debugging` | **opt-in** | Root-cause-first debugging with project-specific repro commands + MCP evidence-gathering (error tracking, traces, logs, metrics, alerts). Renders from a catalogue of observability MCP servers the user declares. |
246
246
  | `add-e2e-test` | **opt-in** | Add an e2e test. Body covers e2e framework, page-object pattern, auth/storage-state, seed helpers, tenant scoping, screenshot conventions. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vinta-ai-workflows",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Bootstrap AI tooling (AGENTS.md, skills, sub-agents) across Claude Code, Codex, Cursor, and VS Code + GitHub Copilot.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -275,7 +275,7 @@ foundation_skills:
275
275
  add-one-off-script: <Optional foundation skills → add-one-off-script answer → enabled | disabled>
276
276
  prepare-worktree: <Optional foundation skills → prepare-worktree answer → enabled | disabled>
277
277
  thermo-nuclear-code-quality-review: <Optional foundation skills → thermo-nuclear-code-quality-review answer → enabled | disabled>
278
- deslop-comments: enabled # always ships — review-phase Layer 2 comment-hygiene + fix loop depend on it
278
+ deslop-comments: enabled # always ships — review-phase Layer 2 comment-hygiene + fix loop and the integrate-phase PR-context prose pass depend on it
279
279
  handoff: enabled # always ships — project-agnostic session-continuation handoff docs
280
280
  handoff-to-client: <Optional foundation skills → handoff-to-client answer → enabled | disabled; only asked for API-only repos>
281
281
  write-unit-test: <enabled when a unit-test framework was detected (Project conventions → Test framework(s)); disabled when none found — default-on, not an opt-in>
@@ -401,7 +401,7 @@ ai-tools/
401
401
  │ ├── plan-feature/SKILL.md ← always (copied verbatim from derive-skills resources)
402
402
  │ ├── create-spec/SKILL.md ← always (copied verbatim)
403
403
  │ ├── create-qa-use-cases/SKILL.md ← always (copied verbatim)
404
- │ ├── deslop-comments/SKILL.md ← always (copied verbatim; review-phase Layer 2 comment-hygiene + fix loop depend on it)
404
+ │ ├── deslop-comments/SKILL.md ← always (copied verbatim; review-phase Layer 2 comment-hygiene + fix loop and the integrate-phase PR-context prose pass depend on it)
405
405
  │ ├── handoff/SKILL.md ← always (copied verbatim; session-continuation handoff docs under .vinta-ai-workflows/handoffs/)
406
406
  │ ├── implement-plan/SKILL.md ← always (conductor; generated from template, project-specific)
407
407
  │ ├── implement-phase/SKILL.md ← always (plan-execution unit; co-shipped with implement-plan)
@@ -452,7 +452,7 @@ Plus the symlinks + per-vendor generated files, set up by the install step.
452
452
 
453
453
  Foundation skills break into three buckets — see [vinta-derive-skills](../vinta-derive-skills/SKILL.md) for the full mechanics:
454
454
 
455
- - **Always copy verbatim**: `plan-feature`, `create-spec`, `create-qa-use-cases`, `deslop-comments`, `handoff`. Bundled with the bootstrap skill set; project-agnostic enough to ship as-is (with light path scrubs). `deslop-comments` always ships because `review-phase`'s Layer 2 comment-hygiene check + fix loop dispatch it. `handoff` always ships because its session-continuation body is fully project-agnostic.
455
+ - **Always copy verbatim**: `plan-feature`, `create-spec`, `create-qa-use-cases`, `deslop-comments`, `handoff`. Bundled with the bootstrap skill set; project-agnostic enough to ship as-is (with light path scrubs). `deslop-comments` always ships because `review-phase`'s Layer 2 comment-hygiene check + fix loop dispatch it, and `integrate-phase` / `amend-plan` run it over the PR-context file. `handoff` always ships because its session-continuation body is fully project-agnostic.
456
456
  - **Always generate**: the plan-execution unit — `implement-plan` (conductor) + its co-shipped sub-skills `implement-phase` / `review-phase` / `integrate-phase`, plus `amend-plan` (conductor). Bodies have too much project-specific content (test commands, branch convention, PR + co-author policy, agent dispatch) — generated from parameterized templates + shared partials using interview answers + inventory. The sub-skills are not independently opt-in; they always ship with the conductors. `write-unit-test` is also template-rendered but **conditionally generated** — it ships whenever a unit-test framework was detected (not an opt-in question, not ask-first): the rendered body bakes in the project's test command + captured `skills.write-unit-test.*` conventions, and the verbatim packs land in its `resources/packs/` — one runner pack for the detected test framework plus a stack pack per matched stack (Django / FastAPI / Flask / Medplum / React / Next.js / TanStack Start / React Router / Prisma / pure-package), so framework-specific advice only ships when that framework is present. A few stack packs also apply a one-time project-setup step at derive time (e.g. Medplum wires the two FHIR-indexing Vitest setup files — `test.globalSetup.ts` + `test.setup.ts` — into the Vitest config so `MockClient` search filtering works) — a project mutation done once, not per test.
457
457
  - **Optional, ask first**: `add-e2e-test`, `add-env-var`, `systematic-debugging`, `add-one-off-script`, `prepare-worktree`, `thermo-nuclear-code-quality-review`, `handoff-to-client`. Skipped by default; orchestrator asks via `AskUserQuestion` whether the project has the relevant flow at all. `add-e2e-test` / `add-env-var`: if yes + user has a template → copy + adapt; if yes + no template → draft from scratch via interview; if no → don't ship. `systematic-debugging`: if yes → render the bundled template plus the per-tool MCP catalogue blocks for the observability tools selected in its follow-up; if no → don't ship. `add-one-off-script`: if yes → copy the bundled SKILL.md verbatim plus the language-specific `BaseOneOffScript` template (`one_off_script_base.py` / `one_off_script_base.ts`) chosen via its follow-up; if no → don't ship. `prepare-worktree`: if yes → copy the bundled SKILL.md verbatim, populate `skills.prepare-worktree.*` defaults from its follow-ups, and (when the user opted in via the worktree-default follow-up) flip `run_options.implement-plan.use_worktree` to `true` so `implement-plan`'s Step 0 question (c) defaults to yes; if no → don't ship. `thermo-nuclear-code-quality-review`: if yes → copy the bundled SKILL.md verbatim (no follow-up config); if no → don't ship. `handoff-to-client`: asked only for API-only repos; if yes → render the bundled template using `skills.handoff-to-client.*` (client platforms, API style, spec path, output dir) from its follow-ups; if no → don't ship.
458
458
 
@@ -47,7 +47,7 @@ These are project-agnostic enough to ship as-is. Copy from this skill's [resourc
47
47
  | `create-spec` | [resources/foundation-skills/create-spec/SKILL.md](resources/foundation-skills/create-spec/SKILL.md) | Turn a raw feature prompt into a structured spec doc. |
48
48
  | `create-qa-use-cases` | [resources/foundation-skills/create-qa-use-cases/SKILL.md](resources/foundation-skills/create-qa-use-cases/SKILL.md) | Bootstrap a project's `QA_USE_CASES.md` from the active spec/plan. **E2E-gated** — ship only when `foundation_skills.add-e2e-test` is `enabled`; QA use-cases exist to seed e2e specs, so a no-e2e project has no use for it. |
49
49
  | `open-pr-from-context` | [resources/foundation-skills/open-pr-from-context/](resources/foundation-skills/open-pr-from-context/) | Thin SKILL.md wrapper around [scripts/open-pr.sh](resources/foundation-skills/open-pr-from-context/scripts/open-pr.sh) — bash script that publishes one `.vinta-ai-workflows/prs-context/{feature}/{phase}.md` file as a real PR + inline comments via the project's PR CLI (`gh` / `glab`). Used by `implement-plan` (when CLI + `yq`/`jq` are available) and standalone (to publish a `pending` file later). |
50
- | `deslop-comments` | [resources/foundation-skills/deslop-comments/SKILL.md](resources/foundation-skills/deslop-comments/SKILL.md) | Rewrite comments + doc blocks touched during a task into Simple English — strip AI-slop vocabulary and negative framing; comment-only, no renames or behavior change. **Always ships** — the `review-phase` Layer 2 comment-hygiene check + fix loop dispatch it, so it must exist in the target. Also user-invokable standalone ("deslop these comments"). |
50
+ | `deslop-comments` | [resources/foundation-skills/deslop-comments/SKILL.md](resources/foundation-skills/deslop-comments/SKILL.md) | Rewrite comments + doc blocks touched during a task into Simple English — strip AI-slop vocabulary and negative framing; comment-only, no renames or behavior change. **Always ships** — the `review-phase` Layer 2 comment-hygiene check + fix loop dispatch it, and `integrate-phase` / `amend-plan` run it over the PR-context file, so it must exist in the target. Also user-invokable standalone ("deslop these comments"). |
51
51
  | `handoff` | [resources/foundation-skills/handoff/SKILL.md](resources/foundation-skills/handoff/SKILL.md) | Write or consume a session-continuation handoff doc under `.vinta-ai-workflows/handoffs/` — goal, current state, decisions + rationale, landmines, next step — so a fresh agent session (or a teammate) resumes in-flight work without re-deriving context. **Always ships** — fully project-agnostic; standalone (no cross-links into the always-on trio). |
52
52
  | `prepare-worktree` | [resources/foundation-skills/prepare-worktree/](resources/foundation-skills/prepare-worktree/) | Provision a runnable, isolated git worktree for parallel feature work — symlink dep dirs (or copy / reinstall when the plan churns deps), copy + mutate env files, fork dev + test DBs when migrations are in scope, namespace docker-compose project + network, OS-sandbox the worktree so main-checkout writes are blocked (bundled [scripts/sandbox-run.sh](resources/foundation-skills/prepare-worktree/scripts/sandbox-run.sh) — `sandbox-exec` / `bwrap`), write a teardown-ready summary YAML. Used by `implement-plan` when Step 0 question (c) opts in; standalone for ad-hoc isolation. Opt-in (bucket A + ask-first via the bootstrap's **Optional foundation skills** interview). |
53
53
  | `thermo-nuclear-code-quality-review` | [resources/foundation-skills/thermo-nuclear-code-quality-review/SKILL.md](resources/foundation-skills/thermo-nuclear-code-quality-review/SKILL.md) | Opt-in deep structural-maintainability audit of a diff — abstraction quality, giant files, spaghetti-condition growth — harsher than the standard per-phase review; hunts for "code-judo" reframes that collapse branches / helpers / layers rather than polishing them. Read-only; hands each fix to the `fixer` agent. Invoked deliberately by the user, or escalated from the `review-phase` Layer 3 structural-simplification lens. Opt-in (bucket A + ask-first via the bootstrap's **Optional foundation skills** interview). |
@@ -57,7 +57,7 @@ These reference each other:
57
57
  - `plan-feature` reads spec; dispatches `create-qa-use-cases` when the doc is missing (e2e-enabled projects only — see below).
58
58
  - `implement-plan` (bucket B below) writes per-phase PR-context files following [resources/prs-context-template.md](resources/prs-context-template.md), then invokes `open-pr-from-context` when a PR CLI is detected.
59
59
 
60
- The **always-on unit** is `plan-feature` + `create-spec` + `open-pr-from-context` — copy all three or none. `deslop-comments` also always ships (independently — it doesn't cross-reference the trio), because the `review-phase` Layer 2 comment-hygiene check + fix loop dispatch it; a target missing it would leave that review step dangling. `handoff` also always ships independently — its body is fully project-agnostic and nothing else depends on it. `create-qa-use-cases` joins the unit **only when `foundation_skills.add-e2e-test` is `enabled`**; when add-e2e-test is disabled (or absent), don't ship it and set `foundation_skills.create-qa-use-cases: disabled` in `.vinta-ai-workflows.yaml` to match.
60
+ The **always-on unit** is `plan-feature` + `create-spec` + `open-pr-from-context` — copy all three or none. `deslop-comments` also always ships (independently — it doesn't cross-reference the trio), because the `review-phase` Layer 2 comment-hygiene check + fix loop dispatch it and `integrate-phase` / `amend-plan` run it over the PR-context file; a target missing it would leave those steps dangling. `handoff` also always ships independently — its body is fully project-agnostic and nothing else depends on it. `create-qa-use-cases` joins the unit **only when `foundation_skills.add-e2e-test` is `enabled`**; when add-e2e-test is disabled (or absent), don't ship it and set `foundation_skills.create-qa-use-cases: disabled` in `.vinta-ai-workflows.yaml` to match.
61
61
 
62
62
  **E2E stripping in `plan-feature`.** `plan-feature` ships verbatim but carries e2e-only regions delimited by `<!-- e2e:start -->` / `<!-- e2e:end -->` lines. When copying it, run an e2e pass:
63
63
 
@@ -1,11 +1,15 @@
1
1
  ---
2
2
  name: deslop-comments
3
- description: Rewrite code comments and docs touched during the current conversation into Simple English, stripping AI-slop / AI-lingo vocabulary and negative framing. Use when the user says "deslop these comments", "clean up the comments we just wrote", "rewrite this in plain English", or after a review flags comments as convoluted/AI-sounding. Comment-only - never changes function names, APIs, or behavior.
3
+ description: Rewrite code comments and docs touched during the current conversation into Simple English, stripping AI-slop / AI-lingo vocabulary and negative framing, and delete the ones that should not be there at all. Use when the user says "deslop these comments", "clean up the comments we just wrote", "rewrite this in plain English", or after a review flags comments as convoluted/AI-sounding. Comment-only - never changes function names, APIs, or behavior.
4
4
  ---
5
5
 
6
6
  # Deslop comments
7
7
 
8
- Rewrites comments and doc blocks into short, direct Simple English. This is a comment-only pass: no renames, no logic changes, no behavior changes.
8
+ Rewrites comments and doc blocks into short, direct Simple English, and deletes the ones that should not be there. This is a comment-only pass: no renames, no logic changes, no behavior changes.
9
+
10
+ Prefer fewer and shorter comments. Deleting one is a normal result, not a last resort. Default to one line, and add more only when the extra lines say something the code cannot.
11
+
12
+ Some comments only need rewording. Others should not exist at all, whatever the wording - a list of call sites, another module's internals, a plan that never shipped. Delete those; making them shorter does not help.
9
13
 
10
14
  ## Scope
11
15
 
@@ -15,10 +19,10 @@ Do not expand scope to a file just because the agent `Read` it or reviewed it in
15
19
 
16
20
  ## What counts as slop
17
21
 
18
- Rewrite a comment if it has any of:
22
+ Rules 1-5 are usually reworded. Rules 6-12 are usually deleted.
19
23
 
20
- 1. **Dense, multi-clause sentences** that try to explain everything at once instead of one idea per sentence.
21
- 2. **Negative / before-state framing** - describing removed code, or leading with "not X" when X was never the point. Keep "not X, it's Y" only for a genuine edge case, a non-obvious gotcha, or bug-fix rationale worth flagging; otherwise state the current behavior directly.
24
+ 1. **Dense, multi-clause sentences** that try to explain everything at once instead of one idea per sentence. Split a sentence that adds a second thought.
25
+ 2. **Negative / before-state framing** - describing removed code, or leading with "not X" when X was never the point. Keep "not X, it's Y" only for a genuine edge case or a non-obvious gotcha.
22
26
  3. **AI buzzwords** in place of plain software engineering vocabulary. Common offenders and their replacements:
23
27
  - `gate` / `gates` / `gating` → "check", "decides whether to show...", "authentication check", "feature check"
24
28
  - `guard` → "check" / "prevent"
@@ -39,10 +43,35 @@ Rewrite a comment if it has any of:
39
43
  - `canonical` → "shared" / "standard"
40
44
  - `churn` → "unnecessary changes"
41
45
  - `invariant` → "rule" / "condition" / "constraint" / or something like "state that must stay true"
46
+ - `gloss` → "explanation" / "note"
47
+ - `roll-call` → "list of call sites"
48
+ - `signal` (meaning a hint) → "sign", or drop the word and say what to check
49
+ - `the tell` → "to tell them apart, ask whether..."
50
+ - `unpick` → "work out" / "read twice"
51
+ - `papering over` → "hiding", or name the real fix ("fix the type instead")
52
+ - `strictly worse` → "worse", and say why
53
+ - `earns its place` → say what to do instead: "delete it unless it explains X"
54
+ - "asks nicely" (for something that isn't enforced) → name the mechanism: "a comment does not fail when the code changes; a test does"
55
+ - "prefers X over Y" → "uses X, or Y when there is no X"
56
+ - `failure` for a wording problem → name the problem: "this is the part to fix"
42
57
 
43
58
  Do not apply this as a blind blacklist. Keep the original word when it is the precise domain term, especially for security, type-system, React, database, FHIR, or JavaScript runtime concepts. For example, `opaque` is a precise security/API term for an id, token, or string whose structure and meaning a caller must not depend on ("recipient is an opaque viewer-supplied string; never log it"); it is not on the replacement list above, so leave it — "generic" would drop that contract.
44
- 4. **Undocumented return shapes.** A function returning an object or union should say what each field/variant means, not just that it "returns a result."
45
- 5. **Any other AI-slop or AI-lingo words, framing, structure.**
59
+ 4. **Essay voice.** Write plain statements a reader can check, not persuasion. No sayings ("most do not clear that bar"), no "it looks like X but is really Y", no treating code as if it wants things ("the ideas want to be separate sentences"), and no metaphor where an instruction fits ("earn its place" → "delete it").
60
+ 5. **Undocumented return shapes.** A function returning an object or union should say what each field or variant means, not just that it "returns a result". TypeScript covers most of this, so raise a missing type in the conversation as a code fix instead of describing the shape in a comment. This is the one rule that can make a comment longer, so apply it only where the field names do not already answer it: `{ ready, missingSteps }` needs no explanation, `{ status, mode }` does.
61
+ 6. **Where-used lists.** Naming the call sites of a helper, type, or constant, or the features that use it. Call sites move, get added, and get deleted, and the comment does not follow them - so it is wrong the first time someone adds a second caller, and the editor already answers the question with "find all references". Say what the thing is and why it exists. Keeping a *reason* that happens to involve other code is fine ("shared, so the count and the list cannot disagree"); listing *which* code is not ("shared by the list, the count and the latest scan").
62
+ 7. **Another module's internals, explained from here.** A comment on A that explains how B works behind its own API. Nobody editing B will think to update it, so it goes stale silently. Say what this code needs, and let B's own comment explain B. The same applies downward: do not explain a caller's behaviour from inside the thing being called.
63
+ 8. **One decision explained in several places.** Three sites explaining the same design decision give three wordings that drift apart, and a reader cannot tell which is current. Which way to fix it depends on the code:
64
+ - **The code is duplicated too** (two apps with the same database setup). Repeat the comment in full at each copy. Each file has to be readable on its own, so do not shorten one copy to a link pointing at the other.
65
+ - **One decision shows up in several places.** Say a mapper leaves `documentUrl` out of the rows it returns, the row type leaves it out to match, and the app fills it in later. That is one decision seen from three files. Write the reason once at the place that makes it - the mapper that leaves the field out - and at the other two say only what a reader there needs. See the `documentUrl` example below.
66
+
67
+ A link is fine for extra depth ("more info in `x.ts`"). A link is not enough when the reader needs it to understand the line in front of them - inline that sentence instead.
68
+ 9. **Restating the code.** A comment that is just explaining what is already written in code: a visible fallback chain, the steps of a loop, or a parameter's name and its type in prose ("`reportsByOrderId` holds the report for each order, keyed by the order's id" for `reportsByOrderId: Map<string, DiagnosticReport>`). Comment the part that is *not* on screen: why this order, why this default, what breaks otherwise.
69
+ 10. **A comment standing in for a name.** When the comment exists to explain what a condition or a function means, the name is the fix: `shouldSendErrorToSentry(error)` instead of a note above a negated two-part condition. Renaming is out of scope for this pass, so report it and leave the comment alone - see "When the fix is not a comment edit".
70
+ 11. **Plans, deferred work, and requests.** "Once X is supported we can drop this", "temporary until Y", "as requested in review". None of it is verifiable from the code, most of it never happens, and it outlives the conversation that produced it. Real follow-up work belongs in a tracked ticket, and the comment should link that ticket or say nothing.
71
+ 12. **Paths not taken.** "Tried X, settled on Y", "started with a Map, an array was simpler". If X was never committed it was a draft, and drafts are not part of the code's history. If X was committed, git and the PR record it better than a comment can. Cut the alternative and keep what the code has to do now.
72
+
73
+ One thing this is not: a fact about an external system, even when you found it by trying the other thing first. `PUT Binary/<id>` returning 404 is Medplum's behaviour, so write it that way - a property of the API, with no author and no alternative in it.
74
+ 13. **Any other AI-slop or AI-lingo words, framing, structure.**
46
75
 
47
76
  ## What to leave alone
48
77
 
@@ -52,17 +81,115 @@ Rewrite a comment if it has any of:
52
81
  - Genuine security markers or contracts written as caps-negatives (`NEVER log PHI`, `Does NOT upsert`) - these are useful warnings, not slop.
53
82
  - User-facing UI copy (JSX text nodes rendered to the browser) - that's product copy, not a code comment.
54
83
  - Already-clear one-liners that don't exhibit any of the problems above. Don't rewrite for the sake of rewriting.
84
+ - **The reason a non-obvious line exists** - why this order, why this default, what an external system does, what breaks without the line. This pass removes noise, not knowledge, so keep the reason and shorten the wording instead.
85
+ - **A comment repeated because the code is repeated.** Two apps with the same setup step each keep their own copy. See rule 8.
55
86
 
56
87
  ## Process
57
88
 
58
89
  1. Build the file list per Scope above.
59
- 2. For each file, read every comment/doc block (`//`, `/* */`, `/** */`, `#`, `%`, depending on the language) and check it against "What counts as slop." Skip anything covered by "What to leave alone."
60
- 3. Rewrite in place with `Edit`. Prefer one idea per sentence. When a function's return type is a union or object with meaningful fields, spell out what each branch/field means using the comment body or JSDoc (or similar) if it's a project pattern.
61
- 4. After editing, confirm the diff is comment-only: `git diff -- <files>` should show no code-line changes, only comment text.
62
- 5. Run the project's lint/typecheck on touched files (e.g. `pnpm biome check <files>`, `pnpm --filter <pkg> typecheck`) to confirm nothing broke.
63
- 6. Summarize what changed in 1-2 sentences - don't restate every line edited.
90
+ 2. Read every comment/doc block in each file (`//`, `/* */`, `/** */`, `#`, `%`, depending on the language) and check it against "What counts as slop". Skip anything under "What to leave alone".
91
+ 3. Check every comment next to changed code for claims that are no longer true, including comments this task did not write. A comment goes wrong when the code under it moves, and nothing fails: no test, no type error, no lint rule. This is the one check a reader cannot make from the comment alone.
92
+ 4. Decide **delete or reword** before editing. Rules 1-5 are rewordings. Rules 6-12 are usually deletions - cut the comment, or cut the offending part and keep the rest.
93
+ 5. Find the duplicates across the whole file set before editing anything. Rule 8 needs the full set to decide which copy stays; one file at a time leaves the same reason in three places, each slightly reworded.
94
+ 6. Rewrite in place with `Edit`. One idea per sentence.
95
+ 7. Grep the edited files for every word listed in rule 3, including comments this pass just wrote. Reading for them does not work: they get written and then skimmed past, in the same file that lists them. A hit is not automatically wrong - rule 3 keeps the precise domain term, `signal` is right for `AbortSignal`, and a function genuinely named `isGated` matches - so check each one. This covers rule 3 only; the other rules need judgement.
96
+ 8. Confirm the diff is comment-only: `git diff -- <files>` should show no code-line changes.
97
+ 9. Run the project's lint/typecheck on touched files (e.g. `pnpm biome check <files>`, `pnpm --filter <pkg> typecheck`).
98
+ 10. Summarize in 1-2 sentences, with the comment-line count before and after. List separately, since neither is a style note: any comment that was **factually wrong**, and anything left in place per the next section.
99
+
100
+ ## When the fix is not a comment edit
101
+
102
+ This pass edits comments and nothing else. When the right fix is a test, a rename, or splitting a function, leave the comment alone and report it, naming the file and what has to exist first: "`use-lab-results.ts:44` - this note can go once a regression test covers the sort flip. Left in place; that test does not exist yet." Never delete a comment whose knowledge has nowhere else to live yet.
103
+
104
+ ## Examples: what a comment says
105
+
106
+ Bad (rule 6, a list of today's three callers):
107
+ ```ts
108
+ /** Shared by the list, the count and the latest scan, so the tile cannot count a row the list omits. */
109
+ const LAB_ORDER_SEARCH = { category: LAB_ORDER_CATEGORY }
110
+ ```
111
+
112
+ Good (keeps the reason, drops the list):
113
+ ```ts
114
+ /** Narrows a `ServiceRequest` search to lab orders. Every read here shares it, so none can disagree. */
115
+ const LAB_ORDER_SEARCH = { category: LAB_ORDER_CATEGORY }
116
+ ```
117
+
118
+ Bad (rule 8, first case - this file cannot be read without opening another app):
119
+ ```ts
120
+ // Remove sslmode from the URL so it does not conflict with the ssl object above.
121
+ // More info on shl-server/src/db.ts
122
+ ```
123
+
124
+ Good (the same explanation in full at each copy):
125
+ ```ts
126
+ // Remove sslmode from the URL so it does not conflict with the ssl object above: node-postgres
127
+ // rejects the connection when both are set.
128
+ ```
129
+
130
+ Bad (rule 8, second case - one decision about `documentUrl`, repeated in all three files):
131
+ ```ts
132
+ // types.ts
133
+ /** A mapped lab row. The mapper knows whether a document exists; only the app knows its url. */
134
+ export interface LabResultRow extends Omit<LabResultView, 'documentUrl'> { ... }
135
+
136
+ // mappers.ts
137
+ /** Maps lab orders to rows. No `documentUrl`: the mapper knows a document exists; only the app knows its url. */
138
+ export function mapLabResults(...)
139
+
140
+ // use-lab-results.ts
141
+ /** Adds `documentUrl`. The mapper knows whether a document exists; only the app knows its url. */
142
+ function withDocumentUrl(row: LabResultRow): LabResultView
143
+ ```
144
+
145
+ Good (the reason sits with the mapper, since that is what leaves the field out; the other two say only what a reader there needs):
146
+ ```ts
147
+ // types.ts
148
+ /** A mapped lab row, before the app fills in `documentUrl`. */
149
+ export interface LabResultRow extends Omit<LabResultView, 'documentUrl'> { ... }
150
+
151
+ // mappers.ts
152
+ /** Maps lab orders to rows. Leaves out `documentUrl` because the url depends on how the app serves binaries; each row carries `reportId` instead. */
153
+ export function mapLabResults(...)
154
+
155
+ // use-lab-results.ts
156
+ /** Serves the row's PDF through this app's `/api/binary` route. */
157
+ function withDocumentUrl(row: LabResultRow): LabResultView
158
+ ```
159
+
160
+ Bad (rule 2 - explains why a check that used to be here is gone; the comment never mentions the removal, so it reads as rationale):
161
+ ```ts
162
+ // Report only unexpected failures to Sentry, tagged with the resource. A missing session is
163
+ // routine, since cookies expire, so it stays out of Sentry. A not-provisioned patient is
164
+ // reported like any other unexpected error: login already rejects that state, so a signed-in
165
+ // patient who hits it had their membership revoked mid-session, which is worth seeing.
166
+ if (!isPatientSessionUnauthorizedError(error)) { ... }
167
+ ```
168
+
169
+ Good (what the line does now; the deleted check is not a reader's problem):
170
+ ```ts
171
+ // Report only unexpected failures to Sentry
172
+ if (!isPatientSessionUnauthorizedError(error)) { ... }
173
+ ```
64
174
 
65
- ## Example
175
+ Bad (rule 9, restating a fallback chain that is on screen):
176
+ ```ts
177
+ /**
178
+ * The badge for a report: its interpretation when it carries one ("Abnormal", flagged), otherwise its
179
+ * workflow status ("Preliminary"), otherwise a bare "Result". A workflow status never flags - only an
180
+ * out-of-range interpretation does.
181
+ */
182
+ function reportBadge(report: DiagnosticReport): LabBadge {
183
+ return interpretation ?? (statusMeta ? { ...statusMeta, flagged: false } : UNKNOWN_RESULT_BADGE)
184
+ }
185
+ ```
186
+
187
+ Good (the order is visible in the code; the flagging rule is not):
188
+ ```ts
189
+ /** The badge for a report. Only an out-of-range interpretation sets `flagged`. */
190
+ ```
191
+
192
+ ## Examples: how a comment is written
66
193
 
67
194
  Bad (dense, negative framing, buzzwords):
68
195
  ```
@@ -135,14 +262,13 @@ export async function getIntakeReadiness(headers: Headers): Promise<IntakeReadin
135
262
  Good:
136
263
  ```
137
264
  /**
138
- * Returns whether the patient can finalize their intake: every required step
139
- * must pass its schema, and one of the payment steps must have a saved
140
- * response. `completeIntake` enforces the same rule on the server, so a
141
- * client cannot complete a partial intake by skipping this check. A
142
- * Save-for-later draft counts as missing, which sends the patient back to
143
- * the earliest unfinished step (e.g. Demographics) instead of skipping past
144
- * it. In the returned object, `ready` tells whether the intake can be
145
- * completed, and `missingSteps` lists the keys of the steps still missing.
265
+ * Returns whether the patient can finalize their intake: every required step must pass its schema,
266
+ * and one of the payment steps must have a saved response.
267
+ *
268
+ * A Save-for-later draft counts as missing, so the patient goes back to the earliest unfinished step
269
+ * instead of past it. `completeIntake` applies the same rule on the server.
146
270
  */
147
271
  export async function getIntakeReadiness(headers: Headers): Promise<IntakeReadiness> { ... }
148
272
  ```
273
+
274
+ `ready` and `missingSteps` are not explained, because their names already say it - see rule 5.
@@ -51,7 +51,6 @@ tiers:
51
51
  models:
52
52
  anthropic:
53
53
  - id: claude-haiku-4-5
54
- note: with iteration; step up to claude-sonnet-5 if phase touches >3 files
55
54
  - id: claude-sonnet-5
56
55
  note: step-up target when phase touches >3 files
57
56
  openai:
@@ -84,7 +83,6 @@ tiers:
84
83
  models:
85
84
  anthropic:
86
85
  - id: claude-opus-4-8
87
- note: "[1m] 1M-context variant for large codebases"
88
86
  openai:
89
87
  - id: gpt-5.6-sol
90
88
  note: Frontier model for complex professional work
@@ -41,9 +41,11 @@ Two project-level signals decide the actual behavior:
41
41
 
42
42
  4. **Write the prs-context file** at {{PRS_CONTEXT_FILE_PATH}}, following [resources/prs-context-template.md](../../prs-context-template.md). Frontmatter: `plan_id`, `feature_name`, `phase_id`, `phase_title`, `branch`, `base`, `created_at`, `status: pending`, empty `pr_url`. **`base` is the branch the PR opens against — resolve it per the commit strategy, never default it to `<BASE_BRANCH>` blindly:** for stacked branches only the first executed phase bases on `<BASE_BRANCH>`; every subsequent phase bases on the **previous phase's branch** (see the PR-base rule under the {{BRANCH_PUSH_HEADING}} step above). For a single plan-level PR (modular / one-PR strategies) `base = <BASE_BRANCH>`. Body sections: `# Title` (single-line PR title), `# Description` (Markdown body — uses the project's PR template structure from step 2 when one exists), `# Comments` (YAML list of `{file, start_line, end_line?, side, body}` — empty list when comments are off).
43
43
 
44
- 5. **Confirm `.vinta-ai-workflows/prs-context/` is in `.gitignore`.** [vinta-install-ai-tools-setup](../../../vinta-install-ai-tools-setup/SKILL.md) runs the multi-vendor setup script which appends `.vinta-ai-workflows/prs-context/` on its first invocation. If an older bootstrap missed it, append it now.
44
+ 5. **Deslop the prose.** Run the `deslop-comments` skill ([deslop-comments](../deslop-comments/SKILL.md)) over the file you just wrote, passing {{PRS_CONTEXT_FILE_PATH}} as the explicit scope. `# Title`, the `# Description` body, and every `# Comments` `body` must read as Simple English: one idea per sentence, current behavior stated directly instead of "not X" framing, and no AI-slop vocabulary (`gate`, `guard`, `backstop`, `surface`, `leverage`, `plumb`, `canonical`, …). Keep precise domain terms, and keep the project's PR-template structure intact — section headings, `<!-- HTML comment -->` placeholders, and checklist lines stay exactly as step 2 wrote them. This pass rewrites prose only: it never touches the frontmatter, the section layout, or which file and lines a comment targets. Do it before `open-pr.sh` runs, since the published PR body and comments come straight from this file.
45
45
 
46
- 6. **Run `open-pr.sh`** (only when policy = agents create PRs). Detect a usable CLI (`gh` for GitHub, `glab` for GitLab) plus the script's other deps (`yq`, `jq`):
46
+ 6. **Confirm `.vinta-ai-workflows/prs-context/` is in `.gitignore`.** [vinta-install-ai-tools-setup](../../../vinta-install-ai-tools-setup/SKILL.md) runs the multi-vendor setup script which appends `.vinta-ai-workflows/prs-context/` on its first invocation. If an older bootstrap missed it, append it now.
47
+
48
+ 7. **Run `open-pr.sh`** (only when policy = agents create PRs). Detect a usable CLI (`gh` for GitHub, `glab` for GitLab) plus the script's other deps (`yq`, `jq`):
47
49
 
48
50
  ```bash
49
51
  bash ai-tools/skills/open-pr-from-context/scripts/open-pr.sh {{PRS_CONTEXT_FILE_PATH}}
@@ -57,5 +59,5 @@ Two project-level signals decide the actual behavior:
57
59
 
58
60
  When policy = "branches only": **don't run the script.** File stays `status: pending`.
59
61
 
60
- 7. **Skill wrapper** — [open-pr-from-context](../open-pr-from-context/SKILL.md) is available for ad-hoc invocation (after the run, on a different machine, etc.). The orchestrator can call the script directly here; the skill is for humans.
62
+ 8. **Skill wrapper** — [open-pr-from-context](../open-pr-from-context/SKILL.md) is available for ad-hoc invocation (after the run, on a different machine, etc.). The orchestrator can call the script directly here; the skill is for humans.
61
63
  <!-- block-end: PR_CONTEXT -->
@@ -289,6 +289,8 @@ For the rewritten branch, look for `.vinta-ai-workflows/prs-context/{feature-keb
289
289
  - Inline comments may now reference SHAs that no longer exist. They'll appear as "outdated" in the PR UI.
290
290
  - If the new diff has materially different comment-worthy spots, regenerate the `# Comments` block, set `status: pending`, and re-run [open-pr.sh](../open-pr-from-context/scripts/open-pr.sh) on the file. The script reuses the existing PR, posts new comments. Old "outdated" comments stay visible in the PR for audit; that's the platform's behavior.
291
291
 
292
+ Whenever you rewrite any of `# Title`, `# Description`, or `# Comments`, finish by running the `deslop-comments` skill ([deslop-comments](../deslop-comments/SKILL.md)) over the file with the file path as the explicit scope, so the rewritten text stays in Simple English same as [integrate-phase]({{INTEGRATE_PHASE_LINK}}) does when it first writes the file. Structure, frontmatter, and comment line targets stay untouched.
293
+
292
294
  When rewriting the `# Description` body, **honor `project.pr_template_paths`** from `.vinta-ai-workflows.yaml` — same rule as [integrate-phase]({{INTEGRATE_PHASE_LINK}})'s **Open PR via context file** step: follow the project's PR template structure, fill new sections with phase-specific content from the rewritten body, leave un-fillable placeholders untouched. If the prior file used a different template than the project now declares, prefer the current `pr_template_paths` choice — surface the change to the user when the body shape shifts visibly.
293
295
 
294
296
  Always include in the publish-log block at the bottom of the file:
@@ -12,6 +12,10 @@ status: pending # `pending` until published; `published
12
12
  pr_url: # set by open-pr-from-context after publishing
13
13
  ---
14
14
 
15
+ Everything below the frontmatter is prose a human reads on the PR, so write it in
16
+ **Simple English** — see [Write it in Simple English](#write-it-in-simple-english)
17
+ at the bottom of this file.
18
+
15
19
  # Title
16
20
 
17
21
  <single-line PR title — follow project commit style; keep ≤72 chars>
@@ -89,3 +93,24 @@ Plan sections have **names** (`Goals + Non-goals`, `Guiding Decisions`, `Data Mo
89
93
  - Boilerplate matching nearby files.
90
94
  - Standard patterns documented in AGENTS.md.
91
95
  - Test bodies whose names already describe the assertion.
96
+
97
+ ## Write it in Simple English
98
+
99
+ The title, the description, and every comment `body` are read by a human reviewer.
100
+ Write them the way the `deslop-comments` skill
101
+ (`ai-tools/skills/deslop-comments/SKILL.md`) asks for:
102
+
103
+ - One idea per sentence. Split the dense multi-clause sentence instead of nesting it.
104
+ - Say what the code does now. Lead with "not X" only when the reader would
105
+ otherwise get it wrong — a real gotcha, an edge case, or the reason behind a fix.
106
+ - Plain engineering words, not AI buzzwords: "check" instead of `gate` / `guard`,
107
+ "use" instead of `leverage` / `utilize`, "show" or "return" instead of `surface`,
108
+ "pass" or "connect" instead of `plumb` / `wire`, "created" instead of `mint`,
109
+ "rule" or "constraint" instead of `invariant`. Keep a word when it is the precise
110
+ domain term (`opaque` for a token, `idempotent` for a handler, framework and
111
+ database vocabulary).
112
+ - Keep proper nouns verbatim: plan and phase names, the plan's section names,
113
+ branch names, flag names, file paths, identifiers from the code.
114
+
115
+ `implement-plan` runs `deslop-comments` over this file before publishing it, so
116
+ anything that slips through gets rewritten there — but write it clean the first time.