# Solo PR Review Checklist

Project-grounded checks distilled from recurring review feedback. Walk this list during the **structural pass** (
workflow step 3 in `SKILL.md`). Each section names what to look for, what to say when you find it, and the PR(s) where
the pattern was previously flagged so the citation is traceable.

---

## 1. DRY — duplicated logic, parallel APIs, copy-paste

**What to look for**

- New method that overlaps an existing one (e.g., `installManifest` vs. existing `applyManifest`).
- Same 10+ line block appearing in two or more files or branches of the same file.
- Multiple call sites computing the same value with the same inputs.
- A new `if/elseif` chain that mirrors an existing one for a parallel concept (e.g., one per component type).
- Multiple GitHub workflows whose only difference is the matrix dimensions.

**How to respond**

- "this seems like a duplicate of `<existing method>`, perhaps you should just enhance that method if needed."
- "duplicate code fragment — could make more DRY. Consider extracting `<name>` and calling it from both sites."
- "I see this a couple of times, make it DRY and create a type."
- For workflows: "I'd prefer for it to be more DRY. We have the E2E matrix set. We have the Examples matrix. Perhaps
  another matrix could consolidate these. Less redundancy to maintain and manage."
- Prefer **fixing the existing method** (e.g., add a parameter to filter pods with a `deletionTimestamp`) over adding a
  sibling method. Multiple near-identical methods cause confusion about which to call.

**Prior precedent:** PR #4230 (`installManifest` vs `applyManifest`), PR #4363 (returning-truthy helper duplicated), PR
#3939 (`helm-values-helper.ts` 27-line duplicate, scheduled-job sprawl), PR #3546 (`getNewestReadyPod` vs fixing
`waitForReadyStatus`).

---

## 2. No exported functions — classes with static methods only

**What to look for**

- `export function foo(...)` at module scope in `src/`.
- `export async function foo(...)` likewise.
- New class whose only purpose is namespacing yet has a constructor with injected dependencies it doesn't use.

**How to respond**

- "we should use classes and static methods, we should not export functions."
- "no export functions, only classes with static methods."
- "if the only method is static, there is no reason for a constructor or to inject anything."
- Suggestion-block form:
  ```suggestion
  export class FooHelper {
    public static async resolveX(...): Promise<...> { ... }
  }
  ```

**Counter-balance:** §3.4.5 of the TS style guide says *don't* create container classes with static methods purely for
namespacing — `export constants and functions instead`. Reconcile: the Solo convention in practice is
class-with-static-methods for **behavior** (resolution, computation, orchestration). Pure data — constants, types,
simple factories — can stay as exports. When in doubt, follow the directory's existing pattern.

**Prior precedent:** PR #4230 (`resolveStorageClass` exported), PR #3870 (`GetSoloRemoteConfigMapTask` had unnecessary
constructor).

---

## 3. Helper methods belong as private class methods

**What to look for**

- A new top-level helper used only by one class.
- A free function defined in the same file as a class that calls it.
- Repeated arrow-function expressions inside a method body that could collapse into one private method called twice.

**How to respond**

- "Consider moving `<name>` to a private class method for consistency with the other extracted helpers and to keep
  `<caller>` a bit easier to scan."
- "consider making this a private method to be cleaner."
- "make private method and move into class."

**Prior precedent:** PR #4363 (`isExplicitFlag` should be private method), PR #3870 (`remote-config-collector.ts`).

---

## 4. CLI flag descriptions stay generic

**What to look for**

- A flag description that references a single command, sub-command, or component (e.g., "the consensus node version to
  deploy for **one-shot**").
- A flag description that contains environment-specific guidance ("for CI", "when running locally").

**How to respond — suggestion-block form**

````
```suggestion
        'Consensus node version to deploy (e.g. v0.73.0 or 0.73.0).',
```
keep generic, flags should be designed for the entire CLI
````

**Prior precedent:** PR #4363 (3 separate flags in `flags.ts`).

---

## 5. Use `flags.<flag>.constName` / `flags.<flag>.name`, never the string literal

**What to look for**

- `argv['deployment']`, `argv['nodeAliasesUnparsed']`, `'--deployment'`, etc. anywhere a `flags` constant exists.
- `config['deployment']` instead of `config[flags.deployment.constName]`.

**How to respond**

- "Use `flags.deployment.constName` (or `.name` for argv) instead of the string literal. Style guide §5.2.6."

**Prior precedent:** Documented in `typescript-code-style.md` §5.2.6.

---

## 6. Match existing error-handling patterns

**What to look for**

- New `try/catch` that throws `new Error(...)` instead of `SoloError`.
- New K8s client wrapper that doesn't go through `KubeApiResponse.throwError`.
- A new error path that doesn't wrap the underlying cause.

**How to respond**

- "match K8 error handling logic."
- "we need to wrap the error. Instead use `KubeApiResponse.throwError`."
- "Instead use `KubeApiResponse.throwError`."

**Prior precedent:** PR #4230 (storage class lister), PR #3390 (RBAC client).

---

## 7. Backwards compatibility — the default path must keep working

**What to look for**

- A new required flag that the user previously didn't need to set.
- A new default behavior that changes what happens when the flag is omitted.
- Removal of a flag, alias, or output line that scripts may depend on.

**How to respond**

- "We want to maintain backwards compatibility as much as possible. If the user does not supply `<flag>`, match the
  existing behavior of `<old behavior>`."
- Ask: "what happens for users who upgrade and run this without setting the new flag?"

**Prior precedent:** PR #4230 (storage class default behavior).

---

## 8. Defaults / cascading fallback chains must be complete

**What to look for**

- A resolver that handles N-1 of N reasonable inputs and falls off a cliff for the last one.
- A "user supplied or default" path with no auto-detection middle step.
- A new install/provision step that doesn't first check whether the thing already exists.

**How to respond**

- "I think you are missing to check for `<the default>` and if none exists set this one as the default."
- "if no `<X>` is passed, default to `<sensible value>`."
- Spell out the cascade explicitly when responding: user-supplied → cluster default → auto-detect → bootstrap.

**Prior precedent:** PR #4230 (storage class resolution order), PR #3546 (Taskfile defaults).

---

## 9. Naming — words that don't add information are noise

**What to look for**

- Names where one word repeats a property already encoded in the type (e.g., `getNewestPod` returning a single `Pod` — "
  Pod" is in the return type; `getNewest(...)` is clearer).
- Adjectives that aren't terms of art in the surrounding domain (e.g., `valid` for a K8s status — K8s uses `phase`,
  `status.conditions[].type`, etc.).
- Helpers prefixed with `do-` or `handle-` that don't describe the action.
- File names using banned abbreviations (`env-var`, `opts`, `cfg`) — see TS style guide §5.1.2.

**How to respond**

- "`newest` and `pod` implies singular, so no need to be redundant. `ready` implies stable. Suggest renaming to
  `getNewestReadyPod`."
- "I don't see that `valid` is adding any value here. It isn't a Kubernetes term (status/phase/etc.)."

**Prior precedent:** PR #3546 (`getNewestPodsForLabel` / `valid` parameter).

---

## 10. Comments that decay — no "now", "currently", "TODO later"

**What to look for**

- Comments anchored to a moment in time: `// now we use X`, `// currently the same as Y`.
- Block comments describing transient state ("about to be deleted", "this is temporary").
- Comments that re-state what the code already says.

**How to respond — suggestion-block form**

````
```suggestion
    // TSS wraps extraEnv is handled via generateExtraEnvironmentValuesFile()
```
Would be a bit odd if this comment survives for several years and it says 'now'.
````

**Prior precedent:** PR #3939 (3 occurrences of "now" comments), PR #4363 (`currently it is the same` comment).

---

## 11. Cross-platform — Windows must work

**What to look for**

- Shell-out via `sh`, `bash`, `/usr/bin/env`, `\`, `&&` patterns embedded in `Taskfile.yml`.
- Path manipulation with hard-coded `/`.
- `chmod`, `ln -s`, `find -exec` in scripts the user is expected to run.
- New `node:path` imports — should be `PathEx` from `src/business/utils/path-ex.ts`.

**How to respond**

- "this will not work on Windows. <pointer to alternative — e.g. `examples/consensus-node-jvm-parameters` solved this
  without this logic>."
- "Use `PathEx` instead of `node:path`. Style guide §3.3.5."

**Prior precedent:** PR #3390 (5 different Taskfile.yml files).

---

## 12. User experience comes first

**What to look for**

- Documentation or examples that say "run this from `<this specific directory>`".
- Required env vars the user must set before running an example.
- CI shortcuts (e.g., `npm run solo-test`) leaking into user-facing docs.

**How to respond**

- "The default should be to be able to run from any directory. We should enhance our CI to do a local build and set env
  variables as needed. User experience must come first over our Solo developer experience."

**Prior precedent:** PR #3390 (17 example READMEs — leave one consolidated comment, don't repeat it).

---

## 13. Question workarounds — find the root cause

**What to look for**

- New polling loops (`while sleep 1; do …`) against state the container should expose.
- `kubectl exec` chains that touch supervisor files (`/run/service/.../down`), package internals, or override init
  scripts.
- Application code that compensates for a misconfigured upstream image, chart, or service.
- Passwords or secrets passed on the command line.

**How to respond**

- "it seems like this is a workaround. Do we not have our `<upstream>` configured correctly? Should we be updating our
  `<upstream>` containers? This would be hard for our SREs and end-users to intuitively grasp if we don't have our
  `<upstream>` logic coded correctly."
- "are there other options that can avoid passing the password through the command line?"
- When suggesting an upstream fix, link to the upstream repo and propose a concrete change (image command, chart value,
  helm flag) — see PR #3546 s6-overlay example for the depth and shape.

**Prior precedent:** PR #3546 (s6-overlay lifecycle hacks), PR #3390 (haproxy ingress uninstall).

---

## 14. Question whether the code is needed at all

**What to look for**

- New code paths that exist "just in case".
- Unused parameters or callers.
- New nightly jobs whose value isn't articulated.
- Re-running tests against already-released code with no clear failure mode being guarded against.

**How to respond**

- "what is the use case scenario for running our examples against already tagged/released code?"
- "I could not figure out a reason why `<X>` would need to `<Y>`. In my PR I killed this."

**Prior precedent:** PR #3390 (mirror-node destroy loading node client, examples against released code).

---

## 15. Test strategy — push down the pyramid

**What to look for**

- New behavior covered only by an E2E or nightly test.
- A new scheduled workflow that runs on a cron the author hasn't justified.
- Tests that hit real clusters when a unit test against a mocked client would suffice.

**How to respond**

- "We should also be looking for opportunities for catching as much as possible inside of unit testing which can run far
  faster, more frequently, and with less costs."
- "I'm concerned about this pattern of creating all of these scheduled nightly jobs."

**Prior precedent:** PR #3939 (nightly extended tests).

---

## 16. TypeScript-only — no Python, no shell-language sprawl

**What to look for**

- New `.py`, `.sh` (beyond `.github/workflows/script/`), or other-language files in the source tree.
- `.github/` TypeScript files missing the SPDX header (the `.github` directory is excluded from ESLint, so the rules
  must be applied manually).

**How to respond**

- "we should not be adding python to solo repository."
- "We've excluded the `.github` directory from eslint. This file does not follow our coding conventions at all. The
  first line should be our license."
- For LLM-authored `.github` scripts: "Ask Copilot/Claude why it is not following `CLAUDE.md` and its reference to
  `docs/contributing/typescript-code-style.md`. Then update accordingly and include those changes in this PR."

**Prior precedent:** PR #3939 (jdwp_tester.py, jdwp-tester.ts missing license).

---

## 17. Use existing constants and registries

**What to look for**

- Inline option objects that duplicate `constants.LISTR_DEFAULT_OPTIONS.DEFAULT`.
- Direct `path.join(...)` / `path.resolve(...)` instead of `PathEx`.
- Inline default values that duplicate exported defaults in `src/core/constants.ts`.

**How to respond — suggestion-block form**

````
```suggestion
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
```
````

**Prior precedent:** PR #3390 (LISTR_DEFAULT_OPTIONS in 3 files).

---

## 18. New CLI flag registration is complete

When the diff adds a new `CommandFlag` in `src/commands/flags.ts`, verify all five steps in CLAUDE.md "Adding CLI
Flags":

1. Static field defined.
2. Added to `Flags.allFlags`.
3. Added to the command-specific list (e.g., `src/commands/node/flags.ts`).
4. Added to special registry if applicable (`nodeConfigFileFlags`, `integerFlags`).
5. `test/unit/commands/flags.test.ts` still passes.

Missing any of these = Critical (the flag is half-registered).

---

## 19. Env var docs are in sync

When the diff adds/removes/renames anything read via `getEnvironmentVariable()` in `src/**/*.ts` or `version.ts`, OR
adds/removes/renames an `@Expose()` field on `SoloConfigSchema` (or nested), verify `docs/site/content/en/docs/env.md`
was updated. See CLAUDE.md "Environment Variable Documentation" for the SOLO_* naming convention.

> Caveat: PR #4363 noted env.md is moving to `solo-docs` — but until that move lands, the in-repo file is still the
> source of truth.

---

## Quick decision aids

**"Should this be a class with statics or a module of functions?"**

- Has multiple methods that share state or call each other → class.
- Single static method, no shared state → still class per Solo convention; flag if exported as function.
- Pure data (constants, types, factories returning literals) → exports are fine.

**"Is this DRY enough?"**

- One occurrence: fine.
- Two occurrences: leave a note for the author to consider extracting.
- Three or more: insist on extraction.

**"Is this a workaround?"**
Ask: "If I describe the fix to an SRE who has never seen Solo, will they say 'why does Solo know about that?'" If yes —
push the fix upstream.
