slash-do 2.11.0 → 2.12.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/README.md +1 -0
- package/commands/do/depfree.md +104 -4
- package/commands/do/help.md +1 -0
- package/commands/do/rpr.md +3 -3
- package/commands/do/scan.md +775 -0
- package/install.sh +2 -2
- package/lib/code-review-checklist.md +19 -1
- package/lib/copilot-review-loop.md +13 -6
- package/lib/review-cross-file-tracing.md +4 -0
- package/lib/review-security-audit.md +3 -1
- package/lib/review-surface-scan.md +14 -1
- package/package.json +1 -1
- package/uninstall.sh +2 -2
package/install.sh
CHANGED
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
- String accumulation via `+=` inside high-frequency loops (streaming frames, chunked I/O, per-event handlers) becomes O(n²) for long outputs and triggers React re-renders on growing payloads. Collect chunks into an array and `join('')` once at the end while still emitting per-chunk events to consumers
|
|
29
29
|
- Server-side string formatting (`toLocaleString`, `toLocaleDateString`, currency/number formatters) that depends on locale/timezone defaults produces non-deterministic outputs across deployments. For data that flows into prompts, logs, persisted records, or cross-system messages, format with explicit `Intl.DateTimeFormat({ timeZone: ... })` or ISO strings; reserve locale-aware formatting for user-visible UI layers where the user's locale is the explicit input
|
|
30
30
|
- Required-at-use-time config values (model name, API key, endpoint URL, default selection) that may be null/undefined in the source data must be validated at the boundary before invoking the downstream API or initialization. Otherwise the upstream API responds with an opaque error far from the user's actual intent. Emit a clear, actionable error (or fail fast at startup) when a required value is missing, identifying the specific field
|
|
31
|
+
- Child process `spawn()` calls without an `error` event handler — when the binary is missing or unexecutable, Node emits an `'error'` event and never `'close'`. Promise wrappers listening only for `'close'` hang forever; bare spawn calls with no listener crash the parent via uncaught exception. Always pair `proc.on('error', ...)` with `'close'`. SIGKILL escalation guards must check liveness via `proc.exitCode == null` (or a `closed` flag set in the close handler), not `proc.killed` — `.killed` becomes `true` immediately after `kill('SIGTERM')` is called, so guards using `if (!proc.killed)` never fire and hung children survive indefinitely. Single-process tracking ("BUSY guard", `activeProcess` global) must hold the reference until the `'close'` event fires, not until `kill()` is sent — clearing at SIGTERM opens a race window where a new job starts while the previous child is still alive
|
|
32
|
+
- `spawn`/`exec` env objects: setting a key to `undefined` may coerce to the literal string `"undefined"` instead of unsetting the variable — build the env, then `delete env.PYTHONPATH` (or set to `''` if you explicitly want it cleared)
|
|
33
|
+
- Caches that store negative/error results (`null`, "not found", probe failure) without a TTL or invalidation hook — when the missing dependency is installed mid-runtime, the cache reports "still missing" until process restart. Cache only successful lookups, OR use a short TTL for negatives, OR re-probe when the cached value is the negative sentinel
|
|
34
|
+
- Late-connecting clients to long-running async jobs (SSE, WebSocket subscribe-by-id) receive nothing if they connect after the terminal `complete`/`error` broadcast — the server emitted once and moved on. Persist the most-recent (or terminal) payload on the job and emit it immediately on attach, OR document that subscribers must connect before kicking off the job and update any "late connectors will get the final state" comments accordingly
|
|
35
|
+
- Server returning an empty success payload (`200` with `{ images: [] }`, `{ items: null }`, etc.) when an awaited operation succeeded but the artifact fetch failed — clients treat empty as "no work to show" and never surface the underlying error. After awaiting completion, a missing/unreadable artifact is an internal error: return non-2xx with a structured error, never an empty 200
|
|
36
|
+
- HTML `<button>` elements without an explicit `type="button"` attribute default to `type="submit"`. When the component is rendered (or could be rendered) inside a `<form>` ancestor, clicks trigger unintended form submission. Set `type="button"` on every non-submit button (close, cancel, expand, menu trigger)
|
|
31
37
|
|
|
32
38
|
**API & URL safety**
|
|
33
39
|
- User-supplied or system-generated values interpolated into URL paths, shell commands, file paths, subprocess arguments, or dynamically evaluated code (eval, CDP evaluate, new Function, template strings executed in browser/page context) without encoding/escaping — use `encodeURIComponent()` for URLs, regex allowlists for execution boundaries, and `JSON.stringify()` for values embedded in evaluated code strings. Generated identifiers used as URL path segments must be safe for your router/storage (no `/`, `?`, `#`; consider allowlisting characters and/or applying `encodeURIComponent()`). Identifiers derived from human-readable names (slugs) used for namespaced resources (git branches, directories) need a unique suffix (ID, hash) to prevent collisions between entities with the same or similar names
|
|
@@ -88,6 +94,10 @@
|
|
|
88
94
|
- External service calls without configurable timeouts — a hung downstream service blocks the caller indefinitely
|
|
89
95
|
- Missing fallback behavior when downstream services are unavailable (see also: retry without backoff in "Sync & replication")
|
|
90
96
|
- Route handlers that call a status/health probe before delegating to the main service when the service already handles the "not configured"/"unreachable" case — the pre-probe adds a redundant upstream round-trip on every request and can fail even when the intended operation would succeed. Let the service be the authoritative source and map its structured errors to the appropriate HTTP status at the route boundary
|
|
97
|
+
- Sync-shaped route handler wrapping a service that is async-by-design (returns a job handle, writes the artifact later) — the handler must subscribe to the completion event BEFORE calling the service (so a fast/cached job can't fire `complete` before the listener attaches) AND wait for the matching job id with a timeout. Common bug: the handler reads `result.filename` from disk immediately after the service returns, gets nothing, and replies with an empty success payload. If the service is callable both async (jobId-only) and sync (await artifact), expose the sync variant explicitly (`generateAndWait` / `generateSync`) rather than mixing modes
|
|
98
|
+
- Cross-module feature-flag detection drift — when multiple modules independently determine "is feature X active?" (HTTPS enabled, OAuth scope satisfied, dark mode, tier-gated capability) using divergent checks, behavior diverges and UX contradicts itself (UI advertises an `https://` URL while the server runs HTTP; one helper checks for `cert.pem`, another requires `cert.pem && key.pem`). Centralize the predicate in a single exported helper and have every caller import it; flag any module that re-derives the same boolean inline
|
|
99
|
+
- Cross-module error classification — a low-level wrapper rethrows errors with a different `name`/`code`/`message` shape than the original (e.g., custom fetch wrapper aborts with `new Error('Request aborted')` while a downstream classifier checks `err.name === 'AbortError'`). The classifier matches nothing and the timeout/cancel branch never fires. Either preserve `name`/`code`/`cause` through the wrapper, OR have the classifier accept the union of shapes the wrapper can emit
|
|
100
|
+
- Compatibility-shim end-to-end plumbing — when a route bridges to an external API standard (A1111-style image-gen, OpenAI, S3-compatible, etc.) every documented response field must be backed by a real value chain through the provider, intermediate service, and response builder. Common bug: response shape is correct but a field like `seed`, `progress`, `eta`, `model`, or `usage.tokens` is hardcoded to a default (`0`, `null`, the request input) because nothing in the chain actually returns it. Trace each declared response field from where it's set in the route → service return shape → underlying provider/process output, and confirm the value flows end-to-end. "Always returns 0 / always undefined / always empty array" patterns signal incomplete plumbing
|
|
91
101
|
|
|
92
102
|
**Resource management** _[applies when: code uses event listeners, timers, subscriptions, or useEffect]_
|
|
93
103
|
- Event listeners, socket handlers, subscriptions, timers, and useEffect side effects are cleaned up on unmount/teardown. `requestAnimationFrame` handles must be cancelled on unmount — pending frames invoke DOM operations or state updates on unmounted nodes. Blob/object URLs created via `URL.createObjectURL()` must be revoked on both item removal AND component unmount. ReadableStream / fetch readers consumed in a loop need `try/finally { reader.cancel() }` — exceptions in the loop otherwise leave the stream open; the `finally` block should catch its own errors so it doesn't mask the original exception
|
|
@@ -119,6 +129,9 @@
|
|
|
119
129
|
- Arrays of IDs (widget ids, tag ids, member ids) persisted, returned by API, or rendered with `key={x}` without container-level deduplication — element-level validation (type check, length cap) is not enough. Duplicates cause React key collisions, inflated operation counts, and inconsistent UI updates. Enforce uniqueness via schema refinement (`zod.refine(arr => new Set(arr).size === arr.length)`), dedupe during ingestion, AND dedupe during read-path sanitization so hand-edited or legacy data can't reintroduce collisions. Apply the same logic to arrays of records keyed by id at the container level (first-wins, not just element-level shape checks)
|
|
120
130
|
- Data loaded from files or persistent stores sanitized less strictly than the API accepts on write — hand-edited, migrated, or corrupted persisted state can introduce values (oversized names, non-kebab ids, duplicate entries, bypassed limits) the API would reject, producing oversized responses, unreachable records (client renders but API rejects on mutate), or invariant violations. Apply the same length caps, regex, uniqueness, and type guards in read-path sanitization as in request-schema validation; drop or truncate out-of-range values rather than passing them through
|
|
121
131
|
- Authority / privilege flags (builtIn, protected, owner, role, immutable) read directly from persisted records without re-derivation — flipping the flag in a hand-edited file or a tampered sync source grants unintended privileges (e.g., changing `builtIn: true → false` lets "protected" records be deleted). Derive authority from code (a constant set of built-in ids, session identity, server-side role lookup), not from the persisted representation
|
|
132
|
+
- Persisted-state filename/path fields (history JSON entries, settings.json paths, manifest entries) used as filesystem operands (`path.join(BASE, item.filename)` for `unlink`/`readFile`/`spawn` arg lists / ffmpeg-imagemagick concat manifests) without basename + path-resolve-prefix-check validation — corrupted or tampered persisted state can include `../` segments that escape the intended directory. Use a `safeUnder(base, candidate)` helper at every consumption site. For paths that further pass into exec arg strings or manifest files (e.g., ffmpeg concat-demuxer `file '...'` lines), basename validation is necessary but not sufficient — the consumer's parser has its own escaping rules: single quotes / newlines break ffmpeg manifests, backslashes on Windows are interpreted as escape characters in quoted strings, shell metacharacters break shell-quoted args. Either reject filenames containing parser-special characters at validation time, or apply consumer-specific escaping before writing the manifest/argv
|
|
133
|
+
- Allowlists gating user-provided identifiers must use the consumer's identifier namespace, not a sibling namespace. Common bug: an allowlist of import-module names (`cv2`, `PIL`) used to gate `pip install <name>` — pip's identifier space is package specs (`opencv-python`, `pillow`), so the allowlist permits installs of typosquatted/unintended packages. Same risk for command names vs aliases, OAuth scope strings vs role names, file extensions vs MIME types. Build the allowlist from the consumer's actual valid-input set, NOT from a related-but-different list, and include a unit test that asserts every allowlist entry is a valid input to the consumer
|
|
134
|
+
- API responses returning server-internal absolute filesystem paths (`/Users/.../data/...`, `C:\app\data\...`) leak server layout, OS, and install locations to clients and couple them to filesystem structure. Return basenames or relative identifiers (`/data/loras/<filename>`) and resolve/validate server-side at consumption time
|
|
122
135
|
- Cross-module constants kept in sync by comment ("must stay in sync with X", copy-pasted regex, duplicated event names or size limits) — the comment is not enforcement and drift is a silent failure. Any constant shared across module boundaries (client↔server, route↔service, component↔component, producer↔consumer) must be a single exported value imported at both sides: event names, regex patterns, numeric limits, path segments, feature-flag keys, error codes
|
|
123
136
|
- Generator/validator structural invariants — when a generator produces values with structural guarantees (sortability via fixed-width prefix, embedded checksum, encoded version), the validator regex (and any client-side mirror) must enforce the SAME shape. Broader regexes accept inputs the generator never emits, breaking invariants the rest of the system relies on (e.g., lexical sort == chronological sort breaks once a base36 timestamp grows by a digit). Verify generator + server validator + client mirror form a closed loop. Test fixtures should use IDs/payloads that match generator output, not contrived literals — fixtures with off-spec shapes hide regressions where the production format changes
|
|
124
137
|
- Schemas accepting paired/range fields (`startDate`/`endDate`, `min`/`max`, `from`/`to`) must add a cross-field refinement (`zod .refine()`, JSON Schema `dependentSchemas`) enforcing the relationship (start ≤ end). Without it, the schema accepts inconsistent ranges that the implementation may silently swap, ignore, or interpret ambiguously. Define deterministic rules when only one bound is supplied
|
|
@@ -193,6 +206,8 @@
|
|
|
193
206
|
- Subprocess output parsed from a single stream (stdout or stderr) to detect conditions (conflicts, errors, specific states) — the information may appear in the other stream or vary by tool version/config. Check both stdout and stderr, and verify the exit code, to reliably detect the condition
|
|
194
207
|
- Shell expansions (brace `{a,b}`, glob `*`, tilde `~`, variable `$VAR`) suppressed by quoting context — single quotes prevent all expansion, so patterns like `--include='*.{ts,js}'` pass the literal braces to the command instead of expanding. Use multiple flags, unquoted brace expansion (bash-only), or other command-specific syntax when expansion is required
|
|
195
208
|
- Arguments passed via process argv have OS-imposed length limits (notoriously low on Windows, ~32KB; ~128KB-2MB on most Unix). For variable-length payloads (prompts, JSON blobs, file contents) or anything potentially large, pipe via stdin instead of constructing a long argv. If argv must be used, enforce a strict cap and fail with a clear message before spawning
|
|
209
|
+
- PowerShell `$LASTEXITCODE` propagates from any external call and is read by the script's final exit. A step claiming to be "fail-soft" (a non-essential post-install hook, optional dashboard auto-open) that runs an external command without explicitly resetting `$LASTEXITCODE = 0` (or wrapping in try/catch with `$global:LASTEXITCODE = 0`) leaks a non-zero exit from the soft step into the parent script's overall exit status — breaking the fail-soft contract that callers depend on
|
|
210
|
+
- Outbound `fetch()` / HTTP calls in setup/install/update scripts without an `AbortController` per-request timeout — a hung server (accepts connection, never responds) blocks the parent shell process indefinitely, breaking "fail-soft" guarantees that the parent script depends on. Use the same timeout helper the rest of the codebase uses for outbound HTTP, and treat timeout as a skip with a clean exit code
|
|
196
211
|
|
|
197
212
|
**Search & navigation** _[applies when: code implements search results or deep-linking]_
|
|
198
213
|
- Search results linking to generic list pages instead of deep-linking to the specific record
|
|
@@ -204,6 +219,9 @@
|
|
|
204
219
|
|
|
205
220
|
**Streaming & real-time protocols** _[applies when: code implements SSE, WebSocket, ReadableStream, or event-driven APIs]_
|
|
206
221
|
- Wire protocol parsers that assume a simplified subset of the spec (e.g., `\n`-only line endings when the spec allows `\r\n`) — test against boundary representations the spec permits, not just the happy path. Parsers must (a) handle the spec's full set of separators (e.g., both `\n\n` and `\r\n\r\n` for SSE; multiple `data:` lines joined with `\n`); (b) flush remaining buffered content on EOF — otherwise the last frame is dropped when upstream closes mid-frame; (c) wrap per-frame deserialization (`JSON.parse`) so a single malformed frame doesn't terminate the entire stream
|
|
222
|
+
- Stateful parsers (multipart, MIME, framed protocols) must verify they reached the terminal state on `req.on('end')` / EOF — calling `finish()` while still in a body/header state accepts truncated input as success, silently corrupting partially-written uploads. Track the terminal-state transition (e.g., `STATE_DONE` after the closing `--boundary--`) and return a 400 error otherwise (and clean up any partial files). Per-part state (`currentFileMimetype`, accumulated headers, decoder state) must be reset at the start of each new part — otherwise a part with no `Content-Type` inherits the previous part's mimetype
|
|
223
|
+
- Refactoring a streaming parser to "buffer-then-process" (calling `readAllBytes()` / `Buffer.concat(chunks)` / `await req.text()` before parsing) defeats the streaming contract and re-introduces an OOM/DoS vector for large uploads — verify the new implementation still respects each caller's `maxSize`/body cap WHILE reading (stop collecting once bytes exceed the cap), or restore true streaming. Watch for header comments still claiming "streams" / "never buffers entire body in memory" after such refactors — they become a documentation lie
|
|
224
|
+
- Library wrappers advertising a multer/express-style contract `(req, file, cb)` must pass the real `req` through to filters/hooks, and must `await` async callbacks before continuing — treating the `cb` as synchronous when the contract permits async breaks any caller that supplied an async filter. Errors thrown from middleware/parser modules without `err.status` set are normalized to HTTP 500 — set `err.status = 400` (or 413 for size limits) and a stable `err.code` (`PAYLOAD_TOO_LARGE`, `INVALID_MULTIPART`, `VALIDATION_ERROR`) at the throw site, OR throw a typed `ServerError`/`ApiError`
|
|
207
225
|
- Event handlers or effects that fire on every high-frequency event (streaming deltas, scroll, resize, keydown) without throttle, debounce, or `requestAnimationFrame` batching — causes jank and excessive re-renders
|
|
208
226
|
|
|
209
227
|
**Accessibility** _[applies when: code modifies UI components or interactive elements]_
|
|
@@ -216,7 +234,7 @@
|
|
|
216
234
|
## Tier 4 — Always Check (Quality, Conventions, AI-Generated Code)
|
|
217
235
|
|
|
218
236
|
**Intent vs implementation**
|
|
219
|
-
- Labels, comments, status messages, or documentation that describe behavior the code doesn't implement — e.g., a map named "renamed" that only deletes, an action labeled "migrated" that never creates the target, or UI actions offered for entity states where the transition is invalid (e.g., a "Reject" button on already-rejected items). Also covers doc drift on concrete facts: file paths or extensions (`foo.js` referenced when the file is `foo.jsx`), item counts ("13 widgets" when there are 15), documented default entity names ("Default" vs the actual "Everything"), and route/response-shape comments that say `{ activeId }` when the handler returns `{ activeId, items }`. When reviewing, verify every factual claim in a comment or doc against the code it references
|
|
237
|
+
- Labels, comments, status messages, or documentation that describe behavior the code doesn't implement — e.g., a map named "renamed" that only deletes, an action labeled "migrated" that never creates the target, or UI actions offered for entity states where the transition is invalid (e.g., a "Reject" button on already-rejected items). Also covers doc drift on concrete facts: file paths or extensions (`foo.js` referenced when the file is `foo.jsx`), item counts ("13 widgets" when there are 15), test counts ("5 tests" when the file has 3), timeout values ("30s" when the code uses 90s), request content-type ("urlencoded" when the code sends multipart), "fail-soft" / "never crashes" claims that are belied by the actual error path, documented default entity names ("Default" vs the actual "Everything"), and route/response-shape comments that say `{ activeId }` when the handler returns `{ activeId, items }`. Test names are part of this contract too: a test called "rejects traversal X" that asserts a 200 (because the implementation strips and accepts) is lying about the security contract — rename to match what's actually tested. When reviewing, verify every factual claim in a comment, test name, changelog entry, or doc against the code it references
|
|
220
238
|
- Inline code examples, command templates, and query snippets that aren't syntactically valid as written — template placeholders must use a consistent format, queries must use correct syntax for their language (e.g., single `{}` in GraphQL, not `{{}}`)
|
|
221
239
|
- Cross-references between files (identifiers, parameter names, format conventions, version numbers, operational thresholds) that disagree — when one reference changes, trace all other files that reference the same entity and update them. This includes internal identifiers (route paths, file names, component names) that should be renamed when the concept they represent is renamed — a nav label saying "Rejected" pointing to `/admin/flagged` or a component named `FlaggedList` rendering rejected items creates maintenance confusion. For releases, verify version consistency across all versioned artifacts (package manifests, lockfiles, API specs, changelogs, PR metadata). Also applies to field-set enumerations: when an operation targets a set of entity fields, every predicate, filter expression, scan criteria, API doc, and UI conditional that enumerates those fields must stay in sync — an independently maintained list that omits a field causes silent skips or false positives
|
|
222
240
|
- Template/workflow variables referenced (`{VAR_NAME}`) but never assigned — trace each placeholder to a definition step; undefined variables cause silent failures or confusing instructions. Also check for colliding identifiers (two distinct concepts mapped to the same slug, key, or name)
|
|
@@ -25,10 +25,15 @@ blocking other PRs:
|
|
|
25
25
|
- Iteration 4: max wait 60 seconds
|
|
26
26
|
- Iteration 5+: max wait 45 seconds
|
|
27
27
|
When running a single-PR review (do:pr, do:release), use dynamic timing:
|
|
28
|
-
check the previous Copilot review duration on this PR
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
check the previous Copilot review duration on this PR. If no prior
|
|
29
|
+
review exists, default to 60 seconds. Set max wait to 3x the expected
|
|
30
|
+
duration (minimum 90 seconds, maximum 5 minutes); only large diffs
|
|
31
|
+
(200+ changed lines) should approach the max. Copilot reviews on small
|
|
32
|
+
diffs typically land in 30-90 seconds; large diffs may take longer.
|
|
33
|
+
Use progressive poll intervals: 5s, 5s, 10s, 10s, then 15s thereafter —
|
|
34
|
+
an early first check avoids burning a full minute on a review that's
|
|
35
|
+
already sitting in the API. For parallel PR reviews (do:better), use
|
|
36
|
+
the decreasing timeout schedule above with a 15-second poll interval.
|
|
32
37
|
|
|
33
38
|
Run the following loop until Copilot returns zero new comments:
|
|
34
39
|
|
|
@@ -51,8 +56,10 @@ Run the following loop until Copilot returns zero new comments:
|
|
|
51
56
|
- For parallel PR reviews (do:better): use the DECREASING TIMEOUT for
|
|
52
57
|
the current iteration number
|
|
53
58
|
- For single-PR reviews (do:pr, do:release): use dynamic timing based on
|
|
54
|
-
the previous Copilot review duration on this PR (
|
|
55
|
-
max
|
|
59
|
+
the previous Copilot review duration on this PR (3x that, min 90 sec,
|
|
60
|
+
max 5 min). If no prior review exists, default expected duration to
|
|
61
|
+
60 seconds. Use progressive poll intervals (5s, 5s, 10s, 10s, then
|
|
62
|
+
15s thereafter)
|
|
56
63
|
- Error detection: if the review body contains "Copilot encountered an
|
|
57
64
|
error" or "unable to review this pull request", re-request (step 1)
|
|
58
65
|
and resume polling. Max 3 error retries before reporting failure.
|
|
@@ -60,6 +60,10 @@ Apply the checklist as a prompt for attention, not an exhaustive specification.
|
|
|
60
60
|
- Raw `fetch()` failures (TypeError "Failed to fetch", DNS errors, ECONNREFUSED) at API client boundaries must be translated to a consistent message matching the project's established transport-error utility. Trace each new API client function against existing siblings (`apiCore.request`, `apiOpenClaw.streamMessage`) and verify the same wrapper is used; preserve `AbortError` so callers distinguish cancellation from failure
|
|
61
61
|
- SSE or event dispatchers that handle named event types but ignore the protocol's default/unnamed event — SSE streams that emit `data:` without `event:` produce type `'message'` (the SSE default), which a handler processing only named types will silently discard. Verify the default event type is either handled or explicitly excluded
|
|
62
62
|
- Route handlers that call a status/health probe before delegating to the main service when the service already handles the "not configured"/"unreachable" case — the pre-probe adds an extra upstream round-trip on every request and can fail even when the intended operation would succeed. Let the service be the authoritative source of truth and map its structured errors to the appropriate HTTP status at the route boundary
|
|
63
|
+
- Sync-shaped route handler (`POST /generate`, `POST /txt2img`) wrapping a service that is async-by-design (returns a job handle, writes the artifact later) — the handler must subscribe to the completion event BEFORE calling the service (so a fast/cached job can't fire `complete` before the listener attaches) AND wait for the matching job id with a timeout. Common bug: the handler reads `result.filename` from disk immediately after the service returns, gets nothing, and replies with an empty success payload. Trace from route → service → completion-event/file-watcher → response builder; verify the route awaits a real readiness signal, not just the service's job-handle return. If the service is callable both async (jobId-only) and sync (await artifact), expose the sync variant explicitly (`generateAndWait` / `generateSync`) rather than mixing modes
|
|
64
|
+
- Cross-module feature-flag detection drift — when multiple modules independently determine "is feature X active?" (HTTPS enabled, OAuth scopes, dark mode, a tier-gated capability) using divergent checks, behavior diverges and the user-visible UX contradicts itself. Examples: client UI checks one cert file while the server requires both; client hardcodes an `https://` scheme while the server is running plain HTTP; one helper checks `cert.pem` exists, another checks `cert.pem && key.pem`. Centralize the predicate in a single exported helper (`hasTailscaleCert()`, `isHttpsEnabled()`, `userHasScope(scope)`) and have every caller import it. Flag any module that re-derives the same boolean inline
|
|
65
|
+
- Cross-module error classification — a low-level wrapper rethrows errors with a different `name`/`code`/`message` shape than the original (e.g., a custom fetch wrapper aborts with `new Error('Request aborted')` while the classifier downstream checks `err.name === 'AbortError'`). The classifier matches nothing and the timeout/cancel branch never fires. Either preserve `name`/`code`/`cause` through the wrapper, OR have the classifier accept the union of shapes the wrapper can emit. Trace each error-classifying call site back to the wrapper(s) that produce its inputs and verify the contract holds
|
|
66
|
+
- Compatibility-shim end-to-end plumbing — when a route bridges to an external API standard (A1111 SD-API, OpenAI, S3-compatible, etc.) every documented response field must be backed by a real value chain through the provider, intermediate service, and response builder. Common bug: the response shape is correct but a field like `seed`, `progress`, `eta`, `model`, or `usage.tokens` is hardcoded to a default (`0`, `null`, the request input) because nothing in the chain actually returns it. Trace each declared response field from where it's set in the route → the service's return shape → the underlying provider/process output, and confirm the value flows end-to-end; placeholder fields ("we'll plumb it later") break clients that depend on the standard. Same trace applies to "always returns 0 / always undefined / always empty array" patterns in the response — they signal incomplete plumbing
|
|
63
67
|
|
|
64
68
|
### Resource Management
|
|
65
69
|
|
|
@@ -24,9 +24,10 @@ For each changed file:
|
|
|
24
24
|
|
|
25
25
|
### Trust Boundaries & Data Exposure
|
|
26
26
|
|
|
27
|
-
- API responses returning full objects with sensitive fields — destructure and omit across ALL paths (GET, PUT, POST, error, socket). Comments claiming data isn't exposed while the code does expose it
|
|
27
|
+
- API responses returning full objects with sensitive fields — destructure and omit across ALL paths (GET, PUT, POST, error, socket). Comments claiming data isn't exposed while the code does expose it. This includes server-internal absolute filesystem paths (`/Users/.../data/loras/foo.safetensors`, `C:\app\data\models\bar`) returned in catalog/list endpoints — they leak server layout, OS, and install locations to any UI user and couple the client to filesystem structure. Return basenames or relative identifiers (`/data/loras/<filename>`) and resolve/validate server-side at consumption time
|
|
28
28
|
- Server trusting client-provided computed/derived values (scores, totals, correctness flags, file metadata like MIME type and size) — strip and recompute server-side. Validate uploads via magic bytes and buffer length, not headers
|
|
29
29
|
- Server trusting persisted-state flags (builtIn, protected, role, owner, immutable) read from flat-file/JSON/DB records to make authorization or deletion decisions — hand-editing the file or tampered sync can flip the flag and bypass protection. Derive authority on every read from a trusted source: a code-level constant set of built-in ids, session identity, or a server-side role lookup. The persisted representation can cache the flag for display, but must not be the source of truth for security decisions
|
|
30
|
+
- Persisted-state filename/path fields (history JSON entries, settings.json paths, manifest entries) used as filesystem operands (`path.join(BASE, item.filename)` for `unlink`, `readFile`, `spawn` arg lists, ffmpeg/imagemagick concat manifests) without basename + path-resolve-prefix-check validation — corrupted, hand-edited, or tampered persisted state can include `../` segments that escape the intended directory and read/write/delete arbitrary files. Use a `safeUnder(base, candidate)` helper at every consumption site (delete, stitch, last-frame extract, batch ops, thumbnail). For paths that further pass into exec arg strings or manifest files (e.g., ffmpeg concat-demuxer `file '...'` lines), basename validation is necessary but not sufficient — the consumer's parser has its own escaping rules: single quotes / newlines break ffmpeg manifests, backslashes on Windows are interpreted as escape characters in quoted strings, shell metacharacters break shell-quoted args. Either reject filenames containing parser-special characters at validation time, or apply consumer-specific escaping (forward-slash normalization, quote escape, etc.) before writing the manifest/argv
|
|
30
31
|
- New endpoints under restricted paths (admin, internal) missing authorization — compare with sibling endpoints for same access gate (role check, scope validation). New OAuth scopes must be checked comprehensively — a check testing only one scope misses newly added scopes
|
|
31
32
|
- User-controlled objects merged via `Object.assign`/spread without sanitizing keys — `__proto__`, `constructor`, `prototype` enable prototype pollution. Use `Object.create(null)`, whitelist keys, use `hasOwnProperty` not `in`
|
|
32
33
|
- Push events (WebSocket, SSE, pub/sub) emitted without scoping to originating user/session — sensitive payloads leak to all connected clients. Scope via room/channel isolation or server-side correlation ID
|
|
@@ -36,6 +37,7 @@ For each changed file:
|
|
|
36
37
|
- Trimming values where whitespace is significant (API keys, tokens, passwords, base64) — only trim identifiers/names
|
|
37
38
|
- Endpoints accepting unbounded arrays without upper limits — enforce max size. Validate element types/format, deduplicate to prevent inflated counts/repeated side effects. Internal operations fanning out unbounded parallel I/O risk EMFILE — use concurrency limiters
|
|
38
39
|
- Security/sanitization functions handling only one input format when data arrives in multiple formats (JSON, shell env, URL-encoded, headers) — sensitive data leaks through unhandled format
|
|
40
|
+
- Allowlists gating user-provided identifiers must use the consumer's identifier namespace, not a sibling namespace. Common bug: an allowlist of import-module names (`cv2`, `PIL`) used to gate `pip install <name>` — pip's identifier space is package specs (`opencv-python`, `pillow`), so the allowlist permits installs of typosquatted/unintended packages. Same risk for: command names vs aliases, OAuth scope strings vs role names, file extensions vs MIME types, language identifiers vs runtime identifiers. Build the allowlist from the consumer's actual valid-input set (`REQUIRED_PACKAGES.map(pipNameFor)`), NOT from a related-but-different list, and include a unit test that asserts every allowlist entry is a valid input to the consumer
|
|
39
41
|
|
|
40
42
|
### Hand-rolled Validators
|
|
41
43
|
|
|
@@ -62,6 +62,11 @@ For each changed file, read the **ENTIRE file** (not just diff hunks). New code
|
|
|
62
62
|
- Loading flag covers only the primary fetch — a slow or failed secondary fetch renders a blank page with no indicator. Include every render-gating load in the loading state or provide explicit empty/error states
|
|
63
63
|
- Streaming UIs that clear the streaming buffer when a terminal `error` event arrives discard deltas the user already saw. Preserve accumulated content as a partial result with an error indicator so users don't lose what was visible
|
|
64
64
|
- Raw `fetch()` failures (TypeError "Failed to fetch", DNS errors, ECONNREFUSED) at API client boundaries must be translated to a consistent user-friendly message matching the project's established transport-error utility — preserve `AbortError` so callers can still distinguish cancellation from failure
|
|
65
|
+
- Child process `spawn()` calls without an `error` event handler — when the binary is missing or unexecutable, Node emits an `'error'` event and never emits `'close'`. Promise wrappers that only listen for `'close'` hang forever; bare spawn calls with no listener crash the parent process via uncaught exception. Always register `proc.on('error', ...)` alongside `'close'`. SIGKILL escalation timers must check liveness via `proc.exitCode == null` (or a `closed` flag set in the close handler) — `proc.killed` becomes `true` immediately after `kill('SIGTERM')` is called, so guards using `if (!proc.killed)` never fire and hung children survive indefinitely. Single-process tracking ("BUSY guard", `activeProcess` global) must hold the reference until the `'close'` event fires, not until `kill()` is sent — clearing the reference at SIGTERM opens a race window where a new job can start while the previous child is still alive
|
|
66
|
+
- `spawn`/`exec` env objects: setting a key to `undefined` may coerce to the literal string `"undefined"` instead of unsetting the variable — build the env, then `delete env.PYTHONPATH` (or set to `''` if you explicitly want it cleared). Same caveat applies to nullish/numeric values being coerced to strings inside the env map
|
|
67
|
+
- Caches that store negative/error results (`null`, "not found", probe failure) without a TTL or invalidation hook — when a user installs the missing dependency mid-runtime (ffmpeg, python venv, model file), the cache reports "still missing" until process restart. Cache only successful lookups, OR use a short TTL for negatives, OR re-probe on demand when the cached value is the negative sentinel
|
|
68
|
+
- Late-connecting clients to long-running async jobs (SSE, WebSocket subscribe-by-id) receive nothing if they connect after the terminal `complete`/`error` broadcast — the server emitted once and moved on. Persist the most-recent (or terminal) payload on the job and emit it immediately on attach, OR document that subscribers must connect before kicking off the job and update any "late connectors will get the final state" comments accordingly
|
|
69
|
+
- Server returning an empty success payload (`200` with `{ images: [] }`, `{ items: null }`, etc.) when an awaited operation succeeded but the artifact fetch failed — clients treat empty as "no work to show" and never surface the underlying error. After awaiting completion, a missing/unreadable artifact is an internal error: return non-2xx with a structured error, never an empty 200
|
|
65
70
|
|
|
66
71
|
**Streaming response handlers (server-side)**
|
|
67
72
|
- Long-lived streaming handlers (SSE, chunked HTTP) must register a client-disconnect listener (`req.on('close')`/`req.on('aborted')`), set an `aborted` flag, and check it before subsequent writes — otherwise the server keeps emitting frames and incurring `write-after-end` errors after the client navigates away
|
|
@@ -76,6 +81,8 @@ For each changed file, read the **ENTIRE file** (not just diff hunks). New code
|
|
|
76
81
|
- Error discrimination by string matching (`err.message.includes('not found')`, regex on error text) — localization, refactors, or wrapper rewrites silently change HTTP status / retry behavior. Use explicit error codes or typed classes
|
|
77
82
|
- Route handlers mapping any exception from a service into a single HTTP status (e.g., `catch { throw new NotFoundError() }`) — hides real server errors (file I/O, parse, write failures) as domain 404s. Map only known codes/classes; let unknown errors surface as 500
|
|
78
83
|
- Error wrappers that re-throw with only `{ status }` and drop `code`/`context`/`cause` — downstream consumers see generic `INTERNAL_ERROR` instead of the specific code. Preserve structured detail when wrapping
|
|
84
|
+
- Errors thrown from middleware/parser modules (multipart, body parsers, validators) without `err.status` set are normalized to HTTP 500 by the framework's default error handler — but they typically represent client payload issues (bad multipart, payload too large, file type rejected, missing boundary). Set `err.status = 400` (or 413 for size limits, etc.) and a stable `err.code` (`PAYLOAD_TOO_LARGE`, `INVALID_MULTIPART`, `VALIDATION_ERROR`) at the throw site, OR throw a typed `ServerError`/`ApiError`, so clients distinguish their bad input from real server failures
|
|
85
|
+
- Outbound `fetch()` / HTTP calls in setup, install, or update scripts (`scripts/*.js`, `setup.sh` invoked tools, post-install hooks) without an `AbortController` per-request timeout — a hung server (accepts connection, never responds) blocks the parent shell process indefinitely, breaking "fail-soft" guarantees that the parent script depends on. Use the same timeout helper the rest of the codebase uses for outbound HTTP, and treat timeout as a skip with a clean exit code
|
|
79
86
|
|
|
80
87
|
### Domain-Specific (check only when file type matches)
|
|
81
88
|
|
|
@@ -122,6 +129,7 @@ For each changed file, read the **ENTIRE file** (not just diff hunks). New code
|
|
|
122
129
|
- Setup/provisioning scripts invoked from hot paths (`npm start`, dev script, container entrypoint) that mutate credentials, privileges, or installed-package state (`ALTER USER`, password resets, brew installs) on every invocation — gate the heavy work behind a cheap readiness check, OR refactor each step to be idempotent and detect already-applied state
|
|
123
130
|
- Shell expansions suppressed by quoting — single quotes prevent all expansion
|
|
124
131
|
- Arguments passed via process argv have OS-imposed length limits (notoriously low on Windows, ~32KB). For variable-length payloads (prompts, JSON blobs, file contents), pipe via stdin instead of constructing a long argv. If argv must be used, enforce a strict cap and fail with a clear message before spawning
|
|
132
|
+
- PowerShell `$LASTEXITCODE` propagates from any external call and is read by the script's final exit. A step claiming to be "fail-soft" (e.g., a non-essential post-install hook) that runs an external command without explicitly resetting `$LASTEXITCODE = 0` (or wrapping in try/catch with `$global:LASTEXITCODE = 0`) leaks a non-zero exit code from the soft step into the parent script's overall exit status — breaking the fail-soft contract that callers depend on
|
|
125
133
|
|
|
126
134
|
**Search & navigation** _[search, deep-linking]_
|
|
127
135
|
- Search results linking to generic list pages instead of deep-linking to specific record
|
|
@@ -136,12 +144,17 @@ For each changed file, read the **ENTIRE file** (not just diff hunks). New code
|
|
|
136
144
|
- Nested inputs handling `Escape`/`Enter`/`ArrowUp`/`ArrowDown` inside a modal/form that also handles the key at the ancestor — the event bubbles and the ancestor fires too (closes modal, submits form). Call `e.stopPropagation()` (and usually `preventDefault()`) in the inner handler
|
|
137
145
|
- Custom toggles from non-semantic elements instead of native inputs
|
|
138
146
|
- Overlay layers with `pointer-events-auto` intercepting clicks beneath; `pointer-events-none` on parent killing child hover handlers
|
|
147
|
+
- HTML `<button>` elements without an explicit `type="button"` attribute default to `type="submit"`. When the component is rendered (or could be rendered) inside a `<form>` ancestor, clicks trigger unintended form submission. Set `type="button"` on every non-submit button (close, cancel, expand, menu trigger) — the cost is one attribute and the bug is silent until the component lands inside a form
|
|
139
148
|
|
|
140
149
|
**UI performance** _[UI components with streaming, scroll, or frequent updates]_
|
|
141
150
|
- Event handlers or `useEffect` callbacks firing on every high-frequency event (streaming deltas, scroll, resize, keydown) without throttle, debounce, or `requestAnimationFrame` batching — causes jank and excessive re-renders. Batch with rAF or a time-based limiter when the handler doesn't need to run on every tick
|
|
142
151
|
|
|
143
|
-
**Wire-protocol parsers** _[SSE/NDJSON/line-delimited frame parsers]_
|
|
152
|
+
**Wire-protocol parsers** _[SSE/NDJSON/line-delimited frame parsers, multipart, etc.]_
|
|
144
153
|
- Wire-protocol parsers must (a) handle the spec's full set of separators (e.g., both `\n\n` and `\r\n\r\n` for SSE; multiple `data:` lines joined with `\n`); (b) flush remaining buffered content on EOF — otherwise the last frame is dropped when upstream closes mid-frame; (c) wrap per-frame deserialization (`JSON.parse`) so a single malformed frame doesn't terminate the entire stream
|
|
154
|
+
- Stateful parsers (multipart, MIME, framed protocols) must verify they reached the terminal state on `req.on('end')` / EOF — calling `finish()` while still in `STATE_HEADERS`/`STATE_BODY` accepts truncated input as success, silently corrupting partially-written uploads or persisting half-written state. Track the terminal-state transition (e.g., `STATE_DONE` after the closing `--boundary--`) and return a 400 error otherwise (and clean up any partial files written)
|
|
155
|
+
- Per-part state in stateful parsers must be reset at part boundaries — fields like `currentFileMimetype`, accumulated headers, decoder state, and offsets that aren't cleared at the start of each new part will leak the previous part's value (e.g., a file part with no `Content-Type` inherits the previous part's mimetype). Reset per-part state at the top of the part-start handler
|
|
156
|
+
- Refactoring a streaming parser to "buffer-then-process" (calling `readAllBytes()` / `Buffer.concat(chunks)` / `await req.text()` before parsing) defeats the streaming contract and re-introduces an OOM/DoS vector for large uploads — verify the new implementation still respects each caller's `maxSize`/body cap WHILE reading (stop collecting once bytes exceed the cap), or restore true streaming. Watch for header comments still claiming "streams" / "never buffers entire body in memory" after such refactors — they become a documentation lie
|
|
157
|
+
- Library wrappers advertising a multer/express-style contract `(req, file, cb)` must pass the real `req` (not `null`) through to filters/hooks; treating the `cb` as synchronous breaks any caller that supplied an async filter (callback fires later, but the wrapper already read pre-callback state). Either enforce synchronous filters with a clear error and document, or `await` a Promise-wrapped callback before continuing
|
|
145
158
|
|
|
146
159
|
### Always Check — Quality & Conventions
|
|
147
160
|
|
package/package.json
CHANGED
package/uninstall.sh
CHANGED