-
Notifications
You must be signed in to change notification settings - Fork 2
test: add review guidelines skill for testing bot guideline adherence #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
541c7f5
7cd23cf
17e50ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Review Guidelines for droid-action | ||
|
|
||
| These are the repository-specific review guidelines. Reviewers MUST follow all rules below when evaluating pull requests. | ||
|
|
||
| ## Naming Conventions | ||
|
|
||
| - All boolean variables must be prefixed with `is`, `has`, `should`, or `can` (e.g., `isValid`, `hasPermission`). Flag any boolean that uses other prefixes or no prefix. | ||
| - Avoid single-letter variable names everywhere, including loop iterators. Use descriptive names like `index` or `item` instead of `i` or `x`. | ||
| - Constants must use SCREAMING_SNAKE_CASE. If a value is hardcoded and never reassigned, it should be extracted into a named constant. | ||
|
|
||
| ## Error Handling | ||
|
|
||
| - Never swallow errors silently. Every `catch` block must either re-throw, log with `console.error`, or return a meaningful error value. Empty catch blocks are always a bug. | ||
| - All async functions that interact with external systems (network, file system, GitHub API) must have explicit error handling -- do not rely on callers to catch. | ||
| - When logging errors, always include the original error object or message for debuggability. Logging a generic string like `"something went wrong"` without context is a bug. | ||
|
|
||
| ## TypeScript-Specific Rules | ||
|
|
||
| - Prefer `interface` over `type` for object shapes that could be extended. Use `type` only for unions, intersections, or mapped types. | ||
| - Never use `any`. If a type is truly unknown, use `unknown` and narrow appropriately. The only exception is test files where mocking requires it. | ||
| - All exported functions must have explicit return type annotations. Inferred return types are acceptable only for private/internal functions. | ||
| - Prefer `readonly` arrays and properties when mutation is not needed. | ||
|
|
||
| ## Code Structure | ||
|
|
||
| - Functions must not exceed 50 lines (excluding comments and blank lines). If a function is longer, it should be decomposed. | ||
| - No more than 3 levels of nesting (if/for/while). Prefer early returns to reduce nesting depth. | ||
| - Avoid `else` after `return` -- use early return pattern instead: | ||
| ```typescript | ||
| // Bad | ||
| if (condition) { | ||
| return x; | ||
| } else { | ||
| return y; | ||
| } | ||
|
|
||
| // Good | ||
| if (condition) { | ||
| return x; | ||
| } | ||
| return y; | ||
| ``` | ||
|
|
||
| ## Comments | ||
|
|
||
| - Every comment must start with a capital letter and end without a period. Comments ending with periods are too formal for code. | ||
| - TODO comments must include an author tag: `// TODO(username): description` | ||
| - Do not comment obvious code. A comment like `// increment counter` above `counter++` is noise and should be flagged. | ||
|
|
||
| ## Imports | ||
|
|
||
| - Imports must be organized in three groups separated by blank lines: (1) external/node modules, (2) internal project imports, (3) relative imports from the same module. | ||
| - Prefer named imports over default imports. Default imports make refactoring harder. | ||
| - Never import from a barrel file (index.ts) when you can import directly from the source module. | ||
|
|
||
| ## Security | ||
|
|
||
| - Never log sensitive data: tokens, keys, passwords, or full request/response bodies that might contain PII. | ||
| - All string interpolation into shell commands must use proper escaping or parameterized execution. Template literal concatenation into `execSync` is always a bug. | ||
| - Environment variable access must have fallback handling. Bare `process.env.X!` non-null assertions are not acceptable -- always provide a default or throw a descriptive error. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| /** | ||
| * This file intentionally contains violations of the review guidelines | ||
| * defined in .factory/skills/review-guidelines/SKILL.md for testing | ||
| * whether the review bot catches them. | ||
| */ | ||
|
|
||
| // VIOLATION: single-letter variable names (rule: no single-letter vars) | ||
| export function processItems(items: string[]) { | ||
| const result: string[] = []; | ||
| for (let i = 0; i < items.length; i++) { | ||
| const x = items[i]!.toUpperCase(); | ||
| result.push(x); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| // VIOLATION: boolean without is/has/should/can prefix | ||
| export function checkPermissions(user: { role: string }) { | ||
| const valid = user.role === "admin" || user.role === "editor"; | ||
| const enabled = true; | ||
| return valid && enabled; | ||
| } | ||
|
|
||
| // VIOLATION: silent catch (empty catch block) | ||
| export function loadConfig(path: string): Record<string, any> { | ||
| try { | ||
| const raw = require(path); | ||
| return raw; | ||
| } catch (e) { | ||
| // swallowed silently. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P0] Typecheck break: unused catch binding under noUnusedLocals This file is included by tsconfig ("include": ["src/**/*"]) and the repo enables "noUnusedLocals": true; the |
||
| } | ||
| return {}; | ||
| } | ||
|
|
||
| // VIOLATION: uses `any`, comments end with periods. | ||
| export function transformData(input: any) { | ||
| // normalize the input data. | ||
| const output = { ...input }; | ||
| // apply the default values. | ||
| output.timestamp = Date.now(); | ||
| return output; | ||
| } | ||
|
|
||
| // VIOLATION: else after return, deeply nested (>3 levels) | ||
| export function categorize( | ||
| value: number, | ||
| ranges: { min: number; max: number; label: string }[], | ||
| ) { | ||
| if (value >= 0) { | ||
| for (const range of ranges) { | ||
| if (value >= range.min) { | ||
| if (value <= range.max) { | ||
| if (range.label !== "") { | ||
| return range.label; | ||
| } else { | ||
| return "unlabeled"; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| return "negative"; | ||
| } | ||
| return "unknown"; | ||
| } | ||
|
|
||
| // VIOLATION: no explicit return type on exported function, | ||
| // bare process.env non-null assertion, hardcoded magic number | ||
| export function getTimeout() { | ||
| const base = parseInt(process.env.TIMEOUT_MS!, 10); | ||
| return base + 5000; | ||
| } | ||
|
|
||
| // VIOLATION: unescaped shell interpolation with template literal | ||
| import { execSync } from "child_process"; | ||
|
|
||
| export function runGitCommand(branch: string): string { | ||
| const output = execSync(`git checkout ${branch}`, { encoding: "utf8" }); | ||
| return output.trim(); | ||
| } | ||
|
|
||
| // VIOLATION: default import, not organized in 3 groups | ||
| import path from "path"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P0] Typecheck break: default import from Node builtin without esModuleInterop
|
||
| import { resolve } from "path"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P0] Typecheck break: default import from Node builtin without esModuleInterop
|
||
| import { readFileSync } from "fs"; | ||
|
|
||
| // VIOLATION: type used for extendable object shape instead of interface | ||
| export type UserConfig = { | ||
| name: string; | ||
| email: string; | ||
| settings: Record<string, unknown>; | ||
| }; | ||
|
|
||
| // VIOLATION: TODO without author tag | ||
| // TODO: refactor this later | ||
| export function readUserConfig(configPath: string): UserConfig { | ||
| const full = path.join(resolve("."), configPath); | ||
| const raw = readFileSync(full, "utf8"); | ||
| return JSON.parse(raw) as UserConfig; | ||
| } | ||
|
|
||
| // VIOLATION: logging sensitive data | ||
| export function authenticate(token: string): boolean { | ||
| console.log(`Authenticating with token: ${token}`); | ||
| return token.length > 0; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P0] Typecheck break: unused catch binding under noUnusedLocals
This file is included by tsconfig ("include": ["src/**/*"]) and the repo enables "noUnusedLocals": true; the
catch (e)binding is never read, sotsc --noEmitwill fail with an unused-local error. Fix by removing the binding (catch { ... }) or by using/renaming it (e.g.,catch (_error) { ... }).