slash-do 1.2.0 → 1.4.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/commands/do/help.md +1 -1
- package/commands/do/review.md +12 -0
- package/hooks/slashdo-statusline.js +20 -13
- package/install.sh +13 -4
- package/lib/code-review-checklist.md +46 -6
- package/package.json +1 -1
- package/src/installer.js +26 -12
- package/uninstall.sh +8 -1
package/commands/do/help.md
CHANGED
|
@@ -12,9 +12,9 @@ List all available `/do:*` commands with their descriptions.
|
|
|
12
12
|
|
|
13
13
|
| Command | Description |
|
|
14
14
|
|---|---|
|
|
15
|
+
| `/do:better` | Unified DevSecOps audit, remediation, per-category PRs, CI verification, and Copilot review loop |
|
|
15
16
|
| `/do:fpr` | Commit, push to fork, and open a PR against the upstream repo |
|
|
16
17
|
| `/do:goals` | Scan codebase to infer project goals, clarify with user, and generate GOALS.md |
|
|
17
|
-
| `/do:better` | Unified DevSecOps audit, remediation, per-category PRs, CI verification, and Copilot review loop |
|
|
18
18
|
| `/do:help` | List all available slashdo commands |
|
|
19
19
|
| `/do:omd` | Audit and optimize markdown files (CLAUDE.md, README.md, etc.) against best practices |
|
|
20
20
|
| `/do:pr` | Commit, push, and open a PR against the repo's default branch |
|
package/commands/do/review.md
CHANGED
|
@@ -75,10 +75,22 @@ Check every file against this checklist:
|
|
|
75
75
|
- Trace each error path end-to-end: does the error reach the user with a helpful message and correct HTTP status? Or does it get swallowed, logged silently, or surface as a generic 500?
|
|
76
76
|
- For multi-step operations (sync to N repos, batch updates): are per-item failures tracked separately from overall success? Does the status reflect partial failure accurately?
|
|
77
77
|
|
|
78
|
+
**Concurrency under user interaction**
|
|
79
|
+
- If a component performs optimistic updates with async operations, simulate what happens when the user triggers a second action while the first is in-flight — trace whether rollback/success handlers can clobber concurrent state changes or close over stale snapshots
|
|
80
|
+
|
|
81
|
+
**State ownership across component boundaries**
|
|
82
|
+
- If a child component maintains local state derived from a parent's data (e.g., optimistic UI copies), trace the ownership boundary: does the child propagate changes back to the parent? What happens on unmount/remount — does the parent's stale cache resurface?
|
|
83
|
+
|
|
78
84
|
**Data flow audit**
|
|
79
85
|
- For sensitive data (secrets, tokens): trace the value from input → storage → retrieval → response. Verify it is never leaked in ANY response path (GET, PUT, POST, error responses, socket events)
|
|
80
86
|
- For user input → URL/command interpolation: verify encoding/escaping at every boundary
|
|
81
87
|
|
|
88
|
+
**Access scope changes**
|
|
89
|
+
- If the PR widens access to an endpoint or resource (admin→public, internal→external), trace all shared dependencies the endpoint uses (rate limiters, queues, connection pools, external service quotas) and assess whether they were sized for the previous access level — in-memory/process-local limiters don't enforce limits across horizontally scaled instances
|
|
90
|
+
|
|
91
|
+
**Guard-before-cache ordering**
|
|
92
|
+
- If a handler performs a pre-flight guard check (rate limit, quota, feature flag) before a cache lookup or short-circuit path, verify the guard doesn't block operations that would be served from cache without touching the guarded resource — restructure so cache hits bypass the guard
|
|
93
|
+
|
|
82
94
|
## Fix Issues Found
|
|
83
95
|
|
|
84
96
|
For each issue found:
|
|
@@ -8,9 +8,13 @@ const os = require('os');
|
|
|
8
8
|
|
|
9
9
|
// Read JSON from stdin
|
|
10
10
|
let input = '';
|
|
11
|
+
// Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
|
|
12
|
+
// Windows/Git Bash), exit silently instead of hanging.
|
|
13
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
11
14
|
process.stdin.setEncoding('utf8');
|
|
12
15
|
process.stdin.on('data', chunk => input += chunk);
|
|
13
16
|
process.stdin.on('end', () => {
|
|
17
|
+
clearTimeout(stdinTimeout);
|
|
14
18
|
try {
|
|
15
19
|
const data = JSON.parse(input);
|
|
16
20
|
const model = data.model?.display_name || 'Claude';
|
|
@@ -18,14 +22,14 @@ process.stdin.on('end', () => {
|
|
|
18
22
|
const session = data.session_id || '';
|
|
19
23
|
const remaining = data.context_window?.remaining_percentage;
|
|
20
24
|
|
|
21
|
-
// Context window display (shows USED percentage scaled to
|
|
22
|
-
// Claude Code
|
|
25
|
+
// Context window display (shows USED percentage scaled to usable context)
|
|
26
|
+
// Claude Code reserves ~16.5% for autocompact buffer, so usable context
|
|
27
|
+
// is 83.5% of the total window. We normalize to show 100% at that point.
|
|
28
|
+
const AUTO_COMPACT_BUFFER_PCT = 16.5;
|
|
23
29
|
let ctx = '';
|
|
24
30
|
if (remaining != null) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
// Scale: 80% real usage = 100% displayed
|
|
28
|
-
const used = Math.min(100, Math.round((rawUsed / 80) * 100));
|
|
31
|
+
const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
|
|
32
|
+
const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
|
|
29
33
|
|
|
30
34
|
// Write context metrics to bridge file for context-monitor hooks
|
|
31
35
|
if (session) {
|
|
@@ -48,12 +52,12 @@ process.stdin.on('end', () => {
|
|
|
48
52
|
const filled = Math.floor(used / 10);
|
|
49
53
|
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
|
50
54
|
|
|
51
|
-
// Color based on
|
|
52
|
-
if (used <
|
|
55
|
+
// Color based on usable context thresholds
|
|
56
|
+
if (used < 50) {
|
|
53
57
|
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
|
54
|
-
} else if (used <
|
|
58
|
+
} else if (used < 65) {
|
|
55
59
|
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
|
56
|
-
} else if (used <
|
|
60
|
+
} else if (used < 80) {
|
|
57
61
|
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
|
58
62
|
} else {
|
|
59
63
|
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
|
@@ -63,7 +67,9 @@ process.stdin.on('end', () => {
|
|
|
63
67
|
// Current task from todos
|
|
64
68
|
let task = '';
|
|
65
69
|
const homeDir = os.homedir();
|
|
66
|
-
|
|
70
|
+
// Respect CLAUDE_CONFIG_DIR for custom config directory setups
|
|
71
|
+
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(homeDir, '.claude');
|
|
72
|
+
const todosDir = path.join(claudeDir, 'todos');
|
|
67
73
|
if (session && fs.existsSync(todosDir)) {
|
|
68
74
|
try {
|
|
69
75
|
const entries = fs.readdirSync(todosDir);
|
|
@@ -91,7 +97,8 @@ process.stdin.on('end', () => {
|
|
|
91
97
|
|
|
92
98
|
// Update notifications (GSD + slashdo)
|
|
93
99
|
let updates = '';
|
|
94
|
-
const
|
|
100
|
+
const cacheDir = path.join(claudeDir, 'cache');
|
|
101
|
+
const gsdCacheFile = path.join(cacheDir, 'gsd-update-check.json');
|
|
95
102
|
if (fs.existsSync(gsdCacheFile)) {
|
|
96
103
|
try {
|
|
97
104
|
const cache = JSON.parse(fs.readFileSync(gsdCacheFile, 'utf8'));
|
|
@@ -100,7 +107,7 @@ process.stdin.on('end', () => {
|
|
|
100
107
|
}
|
|
101
108
|
} catch (e) {}
|
|
102
109
|
}
|
|
103
|
-
const slashdoCacheFile = path.join(
|
|
110
|
+
const slashdoCacheFile = path.join(cacheDir, 'slashdo-update-check.json');
|
|
104
111
|
if (fs.existsSync(slashdoCacheFile)) {
|
|
105
112
|
try {
|
|
106
113
|
const cache = JSON.parse(fs.readFileSync(slashdoCacheFile, 'utf8'));
|
package/install.sh
CHANGED
|
@@ -31,6 +31,7 @@ COMMANDS=(
|
|
|
31
31
|
pr push release replan review rpr update
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
+
|
|
34
35
|
OLD_COMMANDS=(cam good makegoals makegood optimize-md)
|
|
35
36
|
|
|
36
37
|
LIBS=(
|
|
@@ -152,12 +153,19 @@ install_claude() {
|
|
|
152
153
|
modified = true;
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
// Statusline
|
|
156
|
+
// Statusline: upgrade gsd-statusline → slashdo-statusline (superset)
|
|
156
157
|
const statuslineHookPath = path.join(hooksDir, "slashdo-statusline.js");
|
|
157
|
-
if (
|
|
158
|
+
if (fs.existsSync(statuslineHookPath)) {
|
|
158
159
|
const slCmd = "node \"" + statuslineHookPath + "\"";
|
|
159
|
-
settings.statusLine
|
|
160
|
-
|
|
160
|
+
const currentCmd = (settings.statusLine && typeof settings.statusLine.command === "string") ? settings.statusLine.command : "";
|
|
161
|
+
if (!settings.statusLine) {
|
|
162
|
+
settings.statusLine = { type: "command", command: slCmd };
|
|
163
|
+
modified = true;
|
|
164
|
+
} else if (currentCmd.indexOf("gsd-statusline") !== -1) {
|
|
165
|
+
settings.statusLine = { type: "command", command: slCmd };
|
|
166
|
+
modified = true;
|
|
167
|
+
}
|
|
168
|
+
// slashdo-statusline already active or custom statusline → no change
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
if (modified) {
|
|
@@ -231,6 +239,7 @@ install_gemini() {
|
|
|
231
239
|
NR==1 && /^---$/ { in_fm=1; print "+++"; next }
|
|
232
240
|
in_fm && /^---$/ { in_fm=0; print "+++"; next }
|
|
233
241
|
in_fm && /^description:/ { sub(/^description: */, ""); gsub(/"/, ""); printf "description = \"%s\"\n", $0; next }
|
|
242
|
+
in_fm && /^argument-hint:/ { sub(/^argument-hint: */, ""); gsub(/"/, ""); printf "argument-hint = \"%s\"\n", $0; next }
|
|
234
243
|
in_fm && /^allowed-tools:/ { next }
|
|
235
244
|
in_fm { print; next }
|
|
236
245
|
{ gsub(/~\/.claude\/lib\//, "~/.gemini/lib/"); print }
|
|
@@ -12,10 +12,26 @@
|
|
|
12
12
|
- State/variables that are declared but never updated or only partially wired up (e.g. a state setter that's never called)
|
|
13
13
|
- Side effects during React render (setState, navigation, mutations outside useEffect)
|
|
14
14
|
- Off-by-one errors, null/undefined access without guards
|
|
15
|
-
- `JSON.parse` on user-editable files (config, settings, cache) without error handling — corrupted files will crash the process
|
|
15
|
+
- `JSON.parse` on user-editable or external files (config, settings, cache, package metadata) without error handling — corrupted files will crash the process. When the parsed data is optional enrichment (e.g., version info, display metadata), isolate the failure so it doesn't abort the main operation
|
|
16
16
|
- Accessing properties/methods on parsed JSON objects without verifying expected structure (e.g., `obj.arr.push()` when `arr` might not be an array)
|
|
17
17
|
- Iterating arrays from external/user-editable sources without guarding each element — a `null` or wrong-type entry throws `TypeError` when treated as an object
|
|
18
18
|
- Version/string comparisons using `!==` when semantic ordering matters — use proper semver comparison for version checks
|
|
19
|
+
- `Number('')` produces `0`, not empty — cleared numeric inputs must map to `undefined`/`null`, not `0`, which silently fails validation or sets wrong values
|
|
20
|
+
- Truthy checks on numeric values where `0` is valid (e.g., `days || 365` treats `0` as falsy) — use `!= null` or explicit undefined checks instead
|
|
21
|
+
- Functions that index into arrays (`arr[Math.floor(Math.random() * arr.length)]`) without guarding empty arrays — produces `undefined`/`NaN` when `arr.length === 0`
|
|
22
|
+
- Module-level default/config objects passed by reference to consumers — shared mutation across calls. Use `structuredClone()` or spread when handing out defaults
|
|
23
|
+
- `useCallback`/`useMemo` referencing a `const` declared later in the same function body — triggers temporal dead zone `ReferenceError`. Ensure dependency declarations appear before their dependents
|
|
24
|
+
- Object spread/merge followed by unconditional field assignment that clobbers spread values — e.g., `{...input.details, notes: notes || null}` silently overwrites `input.details.notes` even when `notes` is undefined. Only set fields when the overriding value is explicitly provided
|
|
25
|
+
|
|
26
|
+
**Async & UI state consistency**
|
|
27
|
+
- Optimistic UI state changes (view switches, navigation, success callbacks) before an async operation completes — if the operation fails or is cancelled (drag cancel, upload abort, form dismiss), the UI is stuck in the wrong state with no rollback. Handle both failure and cancellation paths to reset intermediate state
|
|
28
|
+
- `Promise.all` without try/catch — if any request rejects, the UI ends up partially loaded with an unhandled rejection. Wrap in try/catch with fallback/error state so the view remains usable
|
|
29
|
+
- Success callbacks (`onSaved()`, `onComplete()`) called unconditionally after an async call — check the return value or catch errors before calling the callback
|
|
30
|
+
- Debounced/cancelable async operations that don't reset loading state on all code paths (input cleared, stale response arrives, request fails) — loading spinners get stuck and stale results display. Use AbortController or request IDs to discard outdated responses and clear loading in every exit path (including early returns)
|
|
31
|
+
- Multiple UI state variables representing coupled data (coordinates + display name, selected item + dependent list) updated independently — actions that change one must update all related fields to prevent display/data mismatch
|
|
32
|
+
- Error notification at multiple layers (shared API client that auto-displays errors + component-level error handling) — verify exactly one layer is responsible for user-facing error messages to avoid duplicate toasts/alerts. Suppress lower-layer notifications when the caller handles its own error display
|
|
33
|
+
- Optimistic state updates using full-collection snapshots for rollback — if a second user action starts while the first is in-flight, rollback restores the snapshot and clobbers the second action's changes. Use per-item rollback and functional state updaters (`setState(prev => ...)`) after async gaps to avoid stale closures
|
|
34
|
+
- Child components maintaining local copies of parent-provided data for optimistic updates without propagating changes back — on unmount/remount the parent's stale cache is re-rendered. Sync optimistic changes to the parent via callback alongside local state, or trigger a data refetch on remount
|
|
19
35
|
|
|
20
36
|
**Resource management**
|
|
21
37
|
- Event listeners, socket handlers, subscriptions, and timers are cleaned up on unmount/teardown
|
|
@@ -28,23 +44,36 @@
|
|
|
28
44
|
**API & URL safety**
|
|
29
45
|
- User-supplied values interpolated into URL paths must use `encodeURIComponent()` — even if the UI restricts input, the API should be safe independently
|
|
30
46
|
- Route params (`:name`, `:id`) passed to services without validation — add format checks (regex, length limits) at the route level
|
|
47
|
+
- Data from external APIs or upstream services interpolated into shell commands, file paths, or subprocess arguments without validation — enforce expected format (e.g., regex allowlist) before passing to execution boundaries
|
|
48
|
+
- Path containment checks using string prefix comparison (`resolvedPath.startsWith(baseDir)`) without a path separator boundary — `baseDir + "evil/..."` passes the check. Use `path.relative()` (reject if starts with `..`) or append `path.sep` to the base
|
|
49
|
+
- Error/fallback responses that hardcode security headers (CORS, CSP) instead of using the centralized policy — error paths bypass security tightening applied to happy paths. Always reuse shared header middleware/constants
|
|
31
50
|
|
|
32
51
|
**Data exposure**
|
|
33
52
|
- API responses returning full objects that contain sensitive fields (secrets, tokens, passwords) — destructure and omit before sending. Check ALL response paths (GET, PUT, POST) not just one
|
|
34
53
|
- Comments/docs claiming data is never exposed while the code path does expose it
|
|
35
54
|
|
|
55
|
+
**Client/server trust boundary**
|
|
56
|
+
- Server trusting client-provided computed/derived values (scores, totals, correctness flags) when the server has the data to recompute them — strip client-provided scoring/summary fields and recompute server-side
|
|
57
|
+
- Validation schemas requiring clients to submit fields the server should own (e.g., `expected` answers, `correct` flags) — make these optional/omitted in submissions and derive them server-side
|
|
58
|
+
- API responses leaking answer keys or expected values that the client will later submit back — either strip before responding or use server-side nonce/seed verification
|
|
59
|
+
|
|
36
60
|
**Input handling**
|
|
37
61
|
- Trimming values where whitespace is significant (API keys, tokens, passwords, base64) — only trim identifiers/names, not secret values
|
|
38
62
|
- Swallowed errors (empty `.catch(() => {})`) that hide failures from users — at minimum surface a notification on failure
|
|
63
|
+
- Endpoints that accept unbounded arrays/collections without an upper limit — large payloads can exceed request timeouts, exhaust memory, or create DoS vectors. Enforce a max size and return 400 when exceeded, or move large operations to background jobs
|
|
39
64
|
|
|
40
65
|
**Validation & consistency**
|
|
41
66
|
- New endpoints/schemas match validation standards of similar existing endpoints (check for field limits, required fields, types)
|
|
42
67
|
- New API routes have the same error handling patterns as existing routes
|
|
43
68
|
- If validation exists on one endpoint for a param, the same param on other endpoints needs the same validation
|
|
44
69
|
- Schema fields that accept values the rest of the system can't handle (e.g., a field accepts any string but downstream code requires a specific format)
|
|
70
|
+
- Zod/schema stripping fields the service actually reads — when Zod uses `.strict()` or strips unknown keys, any field the service reads from the validated object must be declared in the schema, otherwise it's silently `undefined`
|
|
71
|
+
- Config values accepted by the API and persisted but silently ignored by the implementation — trace each config field through schema → service → generator/consumer to verify it's actually used (e.g., a `startRange` saved to config but the generator hardcodes a range)
|
|
72
|
+
- Handlers/functions that read properties from framework-provided objects (request, event, context) using a field name the framework doesn't populate — results in silent `undefined`. Verify the property name matches the caller's contract, not just the handler's assumption
|
|
45
73
|
- Numeric query params (`limit`, `offset`, `page`) parsed from strings without lower-bound clamping — `parseInt` can produce 0, negative, or `NaN` values that cause SQL errors or unexpected behavior. Always clamp to safe bounds (e.g., `Math.max(1, ...)`)
|
|
46
74
|
- Summary counters/accumulators that miss edge cases — if an item is removed, is the count updated? Are all branches counted?
|
|
47
75
|
- Silent operations in verbose sequences — when a series of operations each prints a status line, ensure all branches print consistent output
|
|
76
|
+
- UI elements hidden from navigation (filtered tabs, conditional menu items) but still accessible via direct URL — enforce access restrictions at the route/handler level, not just visibility
|
|
48
77
|
- Labels, comments, or status messages that describe behavior the code doesn't implement — e.g., a map named "renamed" that only deletes, or an action labeled "migrated" that never creates the target
|
|
49
78
|
- Registering references (config entries, settings pointers) to files or resources without verifying the resource actually exists — a failed download or missing file leaves dangling references that break later operations
|
|
50
79
|
- Error/catch handlers that exit cleanly (`exit 0`, `return`) without any user-visible output — makes failures look like successes; always print a skip/warning message explaining why the operation was skipped
|
|
@@ -57,11 +86,12 @@
|
|
|
57
86
|
|
|
58
87
|
**Search & navigation**
|
|
59
88
|
- Search results that link to generic list pages instead of deep-linking to the specific record — include the record type and ID in the URL
|
|
60
|
-
- Search or query code that hardcodes one backend's implementation when the system supports multiple backends — use the active backend's capabilities so results aren't stale after a backend switch
|
|
89
|
+
- Search or query code that hardcodes one backend's implementation when the system supports multiple backends — use the active backend's capabilities so results aren't stale after a backend switch. Also check that option/parameter names are mapped between backends (e.g., `ftsWeight` vs `bm25Weight`) so configuration isn't silently ignored
|
|
61
90
|
|
|
62
91
|
**Sync & replication**
|
|
63
92
|
- Upsert/`ON CONFLICT UPDATE` clauses that only update a subset of the fields exported by the corresponding "get changes" query — omitted fields cause replicas to diverge. Deliberately omit only fields that should stay local (e.g., access stats), and document the decision
|
|
64
93
|
- Pagination using `COUNT(*)` to compute `hasMore` — this forces a full table scan on large tables. Use the `limit + 1` pattern: fetch one extra row to detect more pages, return only `limit` rows
|
|
94
|
+
- Pagination endpoints that return a `next` token but don't accept one as input (or vice versa) — clients can't retrieve pages beyond the first. Also check that hard-capped query limits (e.g., `Limit: 100`) don't silently truncate results when offset exceeds the cap
|
|
65
95
|
|
|
66
96
|
**SQL & database**
|
|
67
97
|
- Parameterized query placeholder indices (`$1`, `$2`, ...) must match the actual parameter array positions — especially when multiple queries share a param builder or when the index is computed dynamically
|
|
@@ -71,33 +101,43 @@
|
|
|
71
101
|
- Full-text search with strict query parsers (`to_tsquery`) directly on user input — punctuation, quotes, and operators cause SQL errors. Use `websearch_to_tsquery` or `plainto_tsquery` for user-facing search
|
|
72
102
|
- Query results assigned to variables but never read — remove dead queries to avoid unnecessary database load
|
|
73
103
|
- N+1 query patterns inside transactions (SELECT + INSERT/UPDATE per row) — use batched upserts (`INSERT ... ON CONFLICT ... DO UPDATE`) to reduce round-trips and lock time
|
|
104
|
+
- `CREATE TABLE IF NOT EXISTS` used as the sole schema migration strategy — it won't add new columns, indexes, or triggers to existing tables on upgrade. Use `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` or a migration framework for schema evolution
|
|
105
|
+
- O(n²) algorithms (self-joins, all-pairs comparisons, nested loops over full tables) triggered per-request on data that grows over time — these become prohibitive at scale. Add caps, use indexed lookups, or move to background jobs
|
|
74
106
|
|
|
75
107
|
**Lazy initialization & module loading**
|
|
76
108
|
- Cached state getters that return `null`/`undefined` before the module is initialized — code that checks the cached value before triggering initialization will get incorrect results. Provide an async initializer or ensure-style function
|
|
77
109
|
- Re-exporting constants from heavy modules defeats lazy loading — define shared constants in a lightweight module or inline them
|
|
110
|
+
- Module-level side effects (file reads, JSON.parse, SDK client init) that run on import without error handling — a corrupted file or missing credential crashes the entire process before any request is served. Wrap module-level init in try/catch and degrade gracefully
|
|
78
111
|
|
|
79
112
|
**Data format portability**
|
|
80
113
|
- Values that cross serialization boundaries (JSON API → database, peer sync) may change format — e.g., arrays in JSON vs specialized string literals in the database. Convert consistently before writing to the target
|
|
114
|
+
- Database BIGINT/BIGSERIAL values parsed into JavaScript `Number` via `parseInt` or `Number()` — precision is lost past `Number.MAX_SAFE_INTEGER`, silently corrupting IDs, sequence cursors, or pagination tokens. Use string representation or `BigInt` for large integer columns
|
|
81
115
|
|
|
82
116
|
**Shell script safety**
|
|
83
|
-
- Subprocess calls in shell scripts under `set -e` — if the subprocess fails, the script aborts.
|
|
117
|
+
- Subprocess calls in shell scripts under `set -e` — if the subprocess fails, the script aborts. Also check non-critical writes (e.g., `echo` to stdout) which fail on broken pipes and trigger exit — use `|| true` for non-critical output
|
|
118
|
+
- Detached/background child processes spawned with piped stdio — if the parent exits (restart, crash), pipes close and writes cause SIGPIPE. Redirect stdio to log files or use `'ignore'` for children that must outlive the parent
|
|
84
119
|
- When the same data structure is manipulated in both application code and shell-inline scripts, apply identical guards in both places
|
|
85
120
|
|
|
86
121
|
**Cross-platform compatibility**
|
|
87
|
-
-
|
|
122
|
+
- Platform-specific execution assumptions — hardcoded shell interpreters (`bash`, `sh`), `path.join()` producing backslashes that break ESM `import()` or URL-based APIs on Windows, platform-gated scripts without fallback or clear error. Use `pathToFileURL()` for dynamic imports, check `process.platform` for shell dispatch
|
|
88
123
|
|
|
89
124
|
**Test coverage**
|
|
90
125
|
- New validation schemas, service functions, or business logic added without corresponding tests — especially when the project already has a test suite covering similar existing code
|
|
91
126
|
- New error paths (404, 400) that are untestable because the service throws generic errors instead of typed/status-coded ones
|
|
127
|
+
- Tests that re-implement the logic under test instead of importing real exports — these pass even when the real code regresses. Import and call the actual functions
|
|
128
|
+
- Missing tests for trust-boundary enforcement — if the server strips/recomputes client-provided fields, add a test that submits tampered values and verifies the server ignores them
|
|
129
|
+
- Tests that depend on real wall-clock time (`setTimeout`, `Date.now`, network delays) for rate limiters, debounce, or scheduling — slow under normal conditions and flaky under CI load. Use fake timers or time mocking
|
|
92
130
|
|
|
93
131
|
**Accessibility**
|
|
94
|
-
- Interactive elements (buttons, toggles, custom controls) missing accessible names, roles, or ARIA states
|
|
132
|
+
- Interactive elements (buttons, toggles, custom controls) missing accessible names, roles, or ARIA states — including programmatically disabled interactions that don't reflect the disabled state visually or via `aria-disabled` (e.g., drag handles that appear interactive but are inert during async operations)
|
|
95
133
|
- Custom toggle/switch UI built from `<button>` or `<div>` instead of native inputs with appropriate labeling
|
|
96
134
|
|
|
97
135
|
**Configuration & hardcoding**
|
|
98
136
|
- Hardcoded values (usernames, org names, limits) when a config field or env var already exists for that purpose
|
|
99
137
|
- Dead config fields that nothing reads — either wire them up or remove them
|
|
100
|
-
-
|
|
138
|
+
- Function parameters that are accepted but never used — creates a false API contract; remove unused params or implement the intended behavior
|
|
139
|
+
- Duplicated config/constants/utility helpers across modules — extract to a single shared module to prevent drift (watch for circular imports when choosing the shared location)
|
|
140
|
+
- CI pipelines that install dependencies without lockfile pinning (`npm install` instead of `npm ci`) or that ad-hoc install packages without version constraints — creates non-deterministic builds that can break unpredictably
|
|
101
141
|
|
|
102
142
|
**Style & conventions**
|
|
103
143
|
- Naming and patterns consistent with the rest of the codebase
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -146,18 +146,25 @@ function registerHooksInSettings(env, hookFiles, dryRun) {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// Configure statusline
|
|
149
|
+
// Configure statusline: upgrade gsd-statusline → slashdo-statusline (superset)
|
|
150
150
|
const statuslineHook = hookFiles.find(h => h.name === 'slashdo-statusline.js');
|
|
151
|
-
if (statuslineHook
|
|
151
|
+
if (statuslineHook) {
|
|
152
152
|
const statuslineCommand = `node "${path.join(env.hooksDir, statuslineHook.name)}"`;
|
|
153
|
-
settings.statusLine
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
const currentCmd = typeof settings.statusLine?.command === 'string' ? settings.statusLine.command : '';
|
|
154
|
+
|
|
155
|
+
if (!settings.statusLine) {
|
|
156
|
+
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
157
|
+
modified = true;
|
|
158
|
+
actions.push({ name: 'settings/statusLine', status: dryRun ? 'would configure' : 'configured' });
|
|
159
|
+
} else if (currentCmd.includes('gsd-statusline')) {
|
|
160
|
+
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
161
|
+
modified = true;
|
|
162
|
+
actions.push({ name: 'settings/statusLine', status: dryRun ? 'would upgrade (gsd→slashdo)' : 'upgraded (gsd→slashdo)' });
|
|
163
|
+
} else if (currentCmd.includes('slashdo-statusline')) {
|
|
164
|
+
actions.push({ name: 'settings/statusLine', status: 'already configured' });
|
|
165
|
+
} else {
|
|
166
|
+
actions.push({ name: 'settings/statusLine', status: 'existing statusline preserved' });
|
|
167
|
+
}
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
if (!dryRun && modified) {
|
|
@@ -214,9 +221,16 @@ function deregisterHooksFromSettings(env, dryRun) {
|
|
|
214
221
|
|
|
215
222
|
// Remove statusline if it references slashdo-statusline
|
|
216
223
|
if (settings.statusLine?.command?.includes('slashdo-statusline')) {
|
|
217
|
-
|
|
224
|
+
// Restore gsd-statusline if its hook file still exists
|
|
225
|
+
const gsdHookPath = path.join(env.hooksDir, 'gsd-statusline.js');
|
|
226
|
+
if (fs.existsSync(gsdHookPath)) {
|
|
227
|
+
settings.statusLine = { type: 'command', command: `node "${gsdHookPath}"` };
|
|
228
|
+
actions.push({ name: 'settings/statusLine', status: dryRun ? 'would downgrade (slashdo→gsd)' : 'downgraded (slashdo→gsd)' });
|
|
229
|
+
} else {
|
|
230
|
+
delete settings.statusLine;
|
|
231
|
+
actions.push({ name: 'settings/statusLine', status: dryRun ? 'would remove' : 'removed' });
|
|
232
|
+
}
|
|
218
233
|
modified = true;
|
|
219
|
-
actions.push({ name: 'settings/statusLine', status: dryRun ? 'would remove' : 'removed' });
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
if (!dryRun && modified) {
|
package/uninstall.sh
CHANGED
|
@@ -27,6 +27,7 @@ COMMANDS=(
|
|
|
27
27
|
pr push release replan review rpr update
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
|
|
30
31
|
OLD_COMMANDS=(cam good makegoals makegood optimize-md)
|
|
31
32
|
|
|
32
33
|
LIBS=(
|
|
@@ -135,7 +136,13 @@ uninstall_claude() {
|
|
|
135
136
|
|
|
136
137
|
if (settings.statusLine && settings.statusLine.command &&
|
|
137
138
|
settings.statusLine.command.indexOf("slashdo-statusline") !== -1) {
|
|
138
|
-
|
|
139
|
+
var hooksDir = path.join(home, ".claude", "hooks");
|
|
140
|
+
var gsdHookPath = path.join(hooksDir, "gsd-statusline.js");
|
|
141
|
+
if (fs.existsSync(gsdHookPath)) {
|
|
142
|
+
settings.statusLine = { type: "command", command: "node \"" + gsdHookPath + "\"" };
|
|
143
|
+
} else {
|
|
144
|
+
delete settings.statusLine;
|
|
145
|
+
}
|
|
139
146
|
modified = true;
|
|
140
147
|
}
|
|
141
148
|
|