@ai-support-agent/cli 0.1.32 → 0.1.33-beta.1

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.
Files changed (74) hide show
  1. package/dist/alert-processor.d.ts +0 -8
  2. package/dist/alert-processor.d.ts.map +1 -1
  3. package/dist/alert-processor.js +5 -73
  4. package/dist/alert-processor.js.map +1 -1
  5. package/dist/api-client.d.ts +0 -4
  6. package/dist/api-client.d.ts.map +1 -1
  7. package/dist/api-client.js +0 -4
  8. package/dist/api-client.js.map +1 -1
  9. package/dist/commands/claude-code-args.d.ts +1 -0
  10. package/dist/commands/claude-code-args.d.ts.map +1 -1
  11. package/dist/commands/claude-code-args.js +3 -0
  12. package/dist/commands/claude-code-args.js.map +1 -1
  13. package/dist/commands/claude-code-runner.d.ts.map +1 -1
  14. package/dist/commands/claude-code-runner.js +2 -1
  15. package/dist/commands/claude-code-runner.js.map +1 -1
  16. package/dist/commands/plugin-dir.d.ts +20 -0
  17. package/dist/commands/plugin-dir.d.ts.map +1 -0
  18. package/dist/commands/plugin-dir.js +71 -0
  19. package/dist/commands/plugin-dir.js.map +1 -0
  20. package/dist/constants.d.ts +0 -1
  21. package/dist/constants.d.ts.map +1 -1
  22. package/dist/constants.js +0 -1
  23. package/dist/constants.js.map +1 -1
  24. package/dist/plugin/.claude-plugin/plugin.json +9 -0
  25. package/dist/plugin/LICENSE +26 -0
  26. package/dist/plugin/README.md +86 -0
  27. package/dist/plugin/SYNC.md +113 -0
  28. package/dist/plugin/agents/build-error-resolver.md +159 -0
  29. package/dist/plugin/agents/code-reviewer.md +277 -0
  30. package/dist/plugin/agents/django-reviewer.md +159 -0
  31. package/dist/plugin/agents/infra-reviewer.md +172 -0
  32. package/dist/plugin/agents/investigator.md +75 -0
  33. package/dist/plugin/agents/nextjs-reviewer.md +191 -0
  34. package/dist/plugin/agents/php-reviewer.md +167 -0
  35. package/dist/plugin/agents/planner.md +184 -0
  36. package/dist/plugin/agents/python-reviewer.md +161 -0
  37. package/dist/plugin/agents/react-reviewer.md +150 -0
  38. package/dist/plugin/agents/silent-failure-hunter.md +158 -0
  39. package/dist/plugin/agents/typescript-reviewer.md +179 -0
  40. package/dist/plugin/agents/ui-reviewer.md +203 -0
  41. package/dist/plugin/commands/add-feature.md +301 -0
  42. package/dist/plugin/commands/build-fix.md +47 -0
  43. package/dist/plugin/commands/code-review.md +228 -0
  44. package/dist/plugin/commands/fix-defect.md +393 -0
  45. package/dist/plugin/commands/learn-eval.md +94 -0
  46. package/dist/plugin/commands/learn.md +84 -0
  47. package/dist/plugin/commands/plan.md +211 -0
  48. package/dist/plugin/commands/test-coverage.md +64 -0
  49. package/dist/plugin/commands/update-docs.md +98 -0
  50. package/dist/plugin/hooks/hooks.json +59 -0
  51. package/dist/plugin/hooks/scripts/auto-format.sh +63 -0
  52. package/dist/plugin/hooks/scripts/check-secrets-before-commit.sh +50 -0
  53. package/dist/plugin/hooks/scripts/guard-dangerous-commands.sh +55 -0
  54. package/dist/plugin/hooks/scripts/on-command-resume.sh +112 -0
  55. package/dist/plugin/hooks/scripts/on-command-stop.sh +200 -0
  56. package/dist/plugin/hooks/scripts/protect-sensitive-files.sh +58 -0
  57. package/dist/plugin/rules/common/coding-guidelines.md +73 -0
  58. package/dist/plugin/rules/documentation/api-docs.md +46 -0
  59. package/dist/plugin/rules/documentation/docs-site.md +60 -0
  60. package/dist/plugin/rules/documentation/source-docs.md +89 -0
  61. package/dist/plugin/rules/documentation/test-docs.md +39 -0
  62. package/dist/plugin/rules/logging/logging-rules.md +83 -0
  63. package/dist/plugin/rules/php/coding-rules.md +40 -0
  64. package/dist/plugin/rules/python/coding-rules.md +40 -0
  65. package/dist/plugin/rules/typescript/coding-rules.md +45 -0
  66. package/dist/plugin/skills/api-design/SKILL.md +269 -0
  67. package/dist/plugin/skills/backend-patterns/SKILL.md +312 -0
  68. package/dist/plugin/skills/database-migrations/SKILL.md +323 -0
  69. package/dist/plugin/skills/docker-patterns/SKILL.md +308 -0
  70. package/dist/plugin/skills/docs-site/SKILL.md +341 -0
  71. package/dist/plugin/skills/e2e-testing/SKILL.md +334 -0
  72. package/dist/plugin/skills/frontend-patterns/SKILL.md +318 -0
  73. package/dist/plugin/skills/integration-testing/SKILL.md +273 -0
  74. package/package.json +2 -2
@@ -0,0 +1,39 @@
1
+ # Test Documentation Conventions
2
+
3
+ Tests are a specification. The goal is that reading the list of test names alone gives you a list of the spec's requirements, and reading a test's body gives you the detail (what it covers and why).
4
+
5
+ ## Writing describe / it blocks
6
+
7
+ - **describe**: names the subject under test (class, function, or endpoint). Use nested describes to represent different scenarios ("when inventory is insufficient," "when the user has admin privileges").
8
+ - **it / test**: write a **complete specification sentence that includes an observable outcome**, in the project's designated language.
9
+
10
+ ```typescript
11
+ describe('EstimateApprovalService.approve', () => {
12
+ describe('when the estimate amount exceeds $10,000', () => {
13
+ it('does not become approved until director approval is complete', () => { ... });
14
+ it('throws PendingDirectorApprovalError when only the manager has approved', () => { ... });
15
+ });
16
+ });
17
+ ```
18
+
19
+ - Forbidden names: uninformative labels like "happy path," "error case 1," "test1," "works," "OK" — anything that doesn't convey the spec on its own.
20
+ - **Whatever outcome the test name claims must actually be asserted in the body.** A name like "throws an exception" with no assertion verifying the exception is a mismatch that the update-docs workflow and code review should catch.
21
+
22
+ ## Structuring the test body
23
+
24
+ - Separate Arrange / Act / Assert with blank lines so the flow is easy to follow.
25
+ - For complex setup, add a comment explaining what scenario is being constructed.
26
+ - Annotate meaningful test data with its rationale (e.g., `1_000_001 // one above the $10,000 threshold boundary`). Don't annotate values that have no particular significance.
27
+
28
+ ## Conventions by stack
29
+
30
+ | Stack | Test naming | Notes |
31
+ |---|---|---|
32
+ | Jest / Vitest / Playwright | describe + it (as above) | Nest with `test.describe` where helpful |
33
+ | pytest | Test function name (e.g., `test_requires_director_approval_above_threshold`, in the designated language if appropriate) | For complex cases, put the summary and intent in a docstring |
34
+ | PHPUnit | Test method name | The `@testdox` annotation can spell out the spec sentence; `#[TestDox('throws an exception when inventory is insufficient')]` is recommended |
35
+
36
+ ## Scope of application
37
+
38
+ - All new tests must follow this convention.
39
+ - Rename existing tests opportunistically, as part of a change that already touches them — a rename-only bulk change isn't worth the review cost on its own.
@@ -0,0 +1,83 @@
1
+ # Logging Conventions
2
+
3
+ Goal: make it possible for both humans and AI to complete debugging **by reading only the source comments and the logs**. The standard is that logs alone should let you reconstruct "which user's, which request's, which function, with what input, passed through and failed where."
4
+
5
+ ## Adoption and cost tuning
6
+
7
+ - Projects adopting this convention should add `logging` to the "adopted conventions" list in the "## Documentation conventions" section of their CLAUDE.md.
8
+ - Log volume translates directly into cost, so **control output level via an environment variable** and tune it per project (typical default: INFO in production, DEBUG in development/investigation as a debug mode).
9
+ - For high-traffic code paths, consider sampling DEBUG output (emit only a fraction of events). Document any such tuning in CLAUDE.md.
10
+
11
+ ## Basics
12
+
13
+ - **Structured logging (JSON) is the standard.** Emit a `message` plus fields — don't embed information via string concatenation.
14
+ - Field names are **full English words in snake_case, never abbreviated** (`request_id`, not `req_id`; `user_id`, not `uid`; also `duration_ms`, `function_name`).
15
+ - Write messages in the project's designated language (same as the documentation convention; default to English if undeclared). Field names are always in English.
16
+
17
+ ### Choosing a log level
18
+
19
+ | Level | Use |
20
+ |---|---|
21
+ | ERROR | A failure that requires action. Always include the exception, correlation ID, and the ID of the affected resource. |
22
+ | WARN | An anomaly that self-recovered, a retry, approaching a threshold — anything worth monitoring. |
23
+ | INFO | Business events (start/completion of order confirmation, invoice issuance, etc.; state transitions). The standard production level. |
24
+ | DEBUG | For investigation: function start/end, inputs/outputs, and the data behind branching decisions. |
25
+
26
+ ## Correlation IDs (end-to-end tracing)
27
+
28
+ To let you trace all logs from a single request across the system, **attach the following to every log entry automatically** — never pass them manually at each call site.
29
+
30
+ - `request_id`: capture the `X-Request-Id` header at the request entry point (generate one if absent), set it in the logging context, and echo it back in the response header.
31
+ - `user_id` (set after authentication), `tenant_code` (in multi-tenant systems, also useful for verifying tenant isolation).
32
+ - `trace_id`: if you have AWS X-Ray (or an equivalent tracing system) enabled, include the trace ID as well, so logs and traces can be cross-referenced (for Lambda, enabling active tracing propagates the header automatically).
33
+ - **Propagation into async work**: when enqueuing a job, include `request_id` in the message and restore it into the logging context on the worker side. For batch-originated processing, generate a `job_id` per execution unit.
34
+
35
+ ### Implementing context propagation, by stack
36
+
37
+ | Stack | Implementation |
38
+ |---|---|
39
+ | NestJS | nestjs-pino + AsyncLocalStorage (set request_id / user_id in middleware). On Lambda, AWS Lambda Powertools Logger + `injectLambdaContext`. |
40
+ | Django / Flask | structlog + contextvars. Bind in middleware; propagate to Celery etc. via headers. |
41
+ | Laravel | Set Log Context (`Context::add()`) in middleware. Propagate to queued jobs via Context dehydration. |
42
+ | CakePHP | Attach common fields via a Monolog Processor. |
43
+
44
+ ## Debug mode (DEBUG level)
45
+
46
+ - **Log the start and end of each function**: `function_name`, the input (INPUT) at start, and the output (OUTPUT) plus `duration_ms` at end.
47
+ - Apply this to public methods in the service/use-case layer by default; skip trivial getters and one-line utilities.
48
+ - Don't scatter this by hand — implement it cross-cuttingly (decorators, interceptors, etc.).
49
+
50
+ ```typescript
51
+ // NestJS example: a method decorator that logs start/end/input/output at DEBUG level
52
+ @LogExecution() // implementation lives in a shared project library
53
+ async approveEstimate(input: ApproveEstimateInput): Promise<EstimateResult> { ... }
54
+ // DEBUG: {"message":"function start","function_name":"approveEstimate","input":{...},"request_id":"..."}
55
+ // DEBUG: {"message":"function end","function_name":"approveEstimate","output":{...},"duration_ms":42,"request_id":"..."}
56
+ ```
57
+
58
+ ### Masking (required even at DEBUG level)
59
+
60
+ - Never log passwords, tokens, API keys, or personal information (name, email, address, etc., per your project's definition) — **even at DEBUG level**. Route these through a shared masking function.
61
+ - Truncate large payloads (file contents, large arrays) down to a count/size summary.
62
+
63
+ ## Recording errors
64
+
65
+ - Every ERROR log must include: the exception object (with stack trace), `request_id`, the ID of the affected resource (e.g., `order_id`), and what operation was being attempted.
66
+ - **If the project uses Sentry, capture unhandled exceptions there too.**
67
+ - Send them via a single global exception handler (a NestJS exception filter, or the equivalent in Django/Laravel) rather than manually capturing at each call site. (Rule of thumb: only manually capture "handled" exceptions — ones that don't propagate to the caller — when they're operationally worth knowing about.)
68
+ - Set `request_id` / `user_id` as Sentry tags/context so they can be cross-referenced with logs and traces.
69
+ - Don't report exceptions that were caught and handled as part of normal flow — that's just noise and duplicate alerting.
70
+ - The discipline of error handling itself (never swallow, preserve the cause) follows the standards in rules/common and the silent-failure-hunter criteria.
71
+
72
+ ## Checklist: logs an AI can debug from
73
+
74
+ Confirm new feature logging design satisfies the following:
75
+
76
+ - [ ] Every log for a given request (including async work) can be retrieved by `request_id`.
77
+ - [ ] The retrieved logs alone reveal the order of functions traversed and their inputs/outputs (at DEBUG level).
78
+ - [ ] On failure, the ERROR log has everything: where it happened, what was being attempted, what went wrong, and what resource was affected.
79
+ - [ ] Read together with the source comments (constraints, intent), you can form a root-cause hypothesis without running the code.
80
+
81
+ ## Naming and clarity (a prerequisite shared with logging)
82
+
83
+ Readable logs depend on readable naming in the implementation. Follow the naming rules in `rules/common/coding-guidelines.md` (**avoid abbreviations, use full English words; name functions so they read as a sentence describing what they do**). Ideally, `function_name` alone should read like a spec.
@@ -0,0 +1,40 @@
1
+ # PHP Coding Rules
2
+
3
+ For language-agnostic rules, see `rules/common/coding-guidelines.md`. This document covers only PHP-specific rules.
4
+
5
+ ## Language and types
6
+
7
+ - Add `declare(strict_types=1)` to every new file (except views such as Blade templates).
8
+ - Declare parameter and return types on public methods. Reserve `mixed` for cases that genuinely can't be expressed as a union type.
9
+ - Mark constructor-promoted properties `readonly` when they're never reassigned.
10
+ - Leave formatting to Pint / PHP CS Fixer (PSR-12) — don't debate formatting in review.
11
+ - Never commit `var_dump()` / `dd()` / `dump()` calls.
12
+
13
+ ## Error handling
14
+
15
+ - Empty `catch (\Exception $e) {}` blocks are forbidden. Log the error and handle it (propagate, retry, notify).
16
+ - When re-throwing, preserve the cause: `throw new XxxException('...', previous: $e)`.
17
+ - Don't ignore the return value of APIs that return `false` on failure (e.g., `save()`).
18
+
19
+ ## Laravel
20
+
21
+ - Define `$fillable` on every model. `$guarded = []` is forbidden.
22
+ - Don't pass `$request->all()` directly into a save. Define a FormRequest and use `$request->validated()`; implement `authorize()` too.
23
+ - Use `with()` / `load()` for eager loading whenever you access a relation in a loop.
24
+ - Any user input passed into `DB::raw()` / `whereRaw()` must use parameter binding.
25
+ - Never use Blade's `{!! !!}` on user-supplied data. If you must, comment the sanitization rationale.
26
+ - Implement `failed()` on queued jobs and configure `$tries` / `$backoff`. Write jobs to be idempotent.
27
+ - Authorize via `auth` middleware plus `Gate` / `Policy` — don't rely on hiding UI elements alone.
28
+ - Define `$casts` for date, JSON, and boolean columns.
29
+
30
+ ## CakePHP
31
+
32
+ - Never set `'*' => true` in an Entity's `$_accessible`. Explicitly list which fields allow mass assignment.
33
+ - Use `contain()` when accessing related data in a loop.
34
+ - Use `validationDefault()` for input format validation, and `buildRules()` for domain rules like uniqueness/existence checks — keep the two separate.
35
+ - Always check the return value of `save()`.
36
+ - Never interpolate variables directly into query conditions (`"field = '$value'"`). Use array conditions or parameter binding instead.
37
+
38
+ ## Documentation
39
+
40
+ - For PHPDoc format and comment language, see `rules/documentation/`.
@@ -0,0 +1,40 @@
1
+ # Python Coding Rules
2
+
3
+ For language-agnostic rules, see `rules/common/coding-guidelines.md`. This document covers only Python-specific rules.
4
+
5
+ ## Type hints and syntax
6
+
7
+ - Add type hints to public functions. Reserve `Any` for receiving data at external boundaries — narrow it to a concrete type internally.
8
+ - Make optionality explicit with `X | None` (3.10+) or `Optional[X]`.
9
+ - Mutable default arguments (`def f(items=[])`) are forbidden. Default to `None` and construct the value inside the function.
10
+ - Never compare against `None` with `==`. Use `is None` / `is not None`.
11
+ - Don't shadow builtin names (`list`, `dict`, `id`, `type`).
12
+ - Leave formatting and import ordering to ruff / black — don't debate them in review.
13
+
14
+ ## Error handling
15
+
16
+ - Bare `except:` and `except Exception: pass` are forbidden. Catch specific exceptions and handle them (log and propagate, retry, or notify).
17
+ - Manage files, connections, and locks with `with` (context managers).
18
+ - When re-raising, preserve the cause with `raise ... from e`.
19
+
20
+ ## Django
21
+
22
+ - Always use `select_related` (FK/OneToOne) or `prefetch_related` (M2M/reverse relations) when accessing related objects in a loop.
23
+ - Never do a read-increment-save cycle for counters; update atomically with an `F()` expression.
24
+ - Wrap multiple writes in `transaction.atomic()`.
25
+ - Every model change needs a migration; drop columns via a two-phase deploy (make nullable → remove references → drop the column in a later release).
26
+ - DRF: `fields = '__all__'` on a Serializer is forbidden. List fields explicitly and set `read_only_fields`. List endpoints must be paginated.
27
+ - Use `save(update_fields=[...])` for partial updates so you don't clobber concurrent writes.
28
+ - Don't use signals for intra-app model-to-model coordination; prefer explicit calls from the service layer.
29
+
30
+ ## Flask
31
+
32
+ - Use the application factory pattern (`create_app()`). Don't instantiate `app` at module level.
33
+ - Split routes into feature-scoped Blueprints.
34
+ - Validate `request.json` / `request.args` with marshmallow or pydantic before use.
35
+ - Keep request-scoped state in `g` and configuration in `app.config`. Don't share state through mutable module-level globals.
36
+ - Ensure `db.session.commit()` failures trigger a `rollback()` (via try/except/finally or a teardown handler).
37
+
38
+ ## Documentation
39
+
40
+ - For docstring format (Google style) and comment language, see `rules/documentation/`.
@@ -0,0 +1,45 @@
1
+ # TypeScript Coding Rules
2
+
3
+ For language-agnostic rules, see `rules/common/coding-guidelines.md`. This document covers only TypeScript-specific rules.
4
+
5
+ ## Types
6
+
7
+ - Assume `strict: true`. Any change that weakens tsconfig strictness needs justification in review.
8
+ - `any` is forbidden as a rule. Receive external data as `unknown` and narrow it. If you must use `any`, comment why.
9
+ - Give public/exported functions an explicit return type.
10
+ - Prefer type guards and early returns over the non-null assertion (`value!`).
11
+ - Don't silence a type error with an `as` cast — if a cast feels necessary, fix the type definition instead.
12
+ - Consider type aliases or branded types for domain values (avoid parameter lists that are all bare `string`).
13
+
14
+ ## Asynchronous code
15
+
16
+ - Always `await` a Promise, or mark an intentional fire-and-forget with a `void` prefix and a comment explaining why.
17
+ - `array.forEach(async ...)` is forbidden. Use `for...of` (sequential) or `Promise.all` (parallel).
18
+ - Don't sequentially `await` independent async operations — batch anything that can run in parallel with `Promise.all`.
19
+ - Set a timeout on external calls via `AbortController` or client configuration.
20
+
21
+ ## Modules and syntax
22
+
23
+ - Default to `const`; use `let` only when reassignment is required. Never use `var`.
24
+ - Use only `===` / `!==` for equality comparisons.
25
+ - Prefer a union type (`type Status = 'active' | 'inactive'`) or `as const` over `enum`.
26
+ - Don't overuse barrel files (re-exporting everything through `index.ts`) — they cause circular imports and bundle bloat.
27
+
28
+ ## NestJS
29
+
30
+ - Always receive input through a DTO with class-validator decorators. Set `whitelist: true` on the global `ValidationPipe`.
31
+ - Explicitly attach a Guard (authentication + authorization) to protected routes. Any use of `@Public()` needs justification in review.
32
+ - Direct access to `process.env` is forbidden. Use `ConfigService` with startup-time schema validation.
33
+ - If you find yourself needing `forwardRef()`, treat it as a signal to revisit the design (extract a shared module, or move to an event-driven approach instead).
34
+ - Convert exceptions via `HttpException` subclasses or an exception filter — never leak internal details into the response.
35
+
36
+ ## Next.js
37
+
38
+ - Place `"use client"` close to the interactive leaf component that needs it — not at the top of a page or layout.
39
+ - Treat Server Actions and Route Handlers as public endpoints: check the session, authorize, and validate the schema at the top of each one.
40
+ - Never put secrets in `NEXT_PUBLIC_*` variables.
41
+ - Use `next/image` for content images and `next/link` for internal navigation.
42
+
43
+ ## Documentation
44
+
45
+ - For TSDoc format, comment language, and the OpenAPI workflow (NestJS → Next.js type generation), see `rules/documentation/`.
@@ -0,0 +1,269 @@
1
+ ---
2
+ name: api-design
3
+ description: A pattern library for REST API design. Provides guidance on resource naming, choosing HTTP methods and status codes, error response formats, pagination, versioning, idempotency, authentication/authorization, and rate limiting. Use this when designing endpoints for a new API, reviewing the design of an existing API, or when you need decision criteria for a discussion about API conventions.
4
+ ---
5
+
6
+ # REST API Design Patterns
7
+
8
+ A collection of decision criteria and patterns for designing and implementing REST APIs. Reference this skill when adding new endpoints, revising an existing API, or doing a design review. Implementation examples favor NestJS, Django REST Framework, and Laravel.
9
+
10
+ This skill covers HTTP interface design — formats and conventions. For server-side implementation details of authorization, rate limiting, and error handling, see the backend-patterns skill.
11
+
12
+ ## 1. Resource Design and Naming
13
+
14
+ ### Basic Principles
15
+
16
+ - Represent resources as **plural nouns**. Don't use verbs.
17
+ - Good: `GET /users`, `POST /orders`, `GET /users/123`
18
+ - Bad: `GET /getUsers`, `POST /createOrder`
19
+ - Use lowercase kebab-case for paths (`/purchase-orders`). For query parameters, pick either snake_case or camelCase and stay consistent within the project.
20
+ - Represent IDs as path segments: `/users/{userId}`
21
+
22
+ ### Nesting Depth
23
+
24
+ Keep nesting to **at most two levels** (two resources).
25
+
26
+ ```
27
+ GET /users/123/orders # Acceptable: a user's orders
28
+ GET /users/123/orders/456/items/789 # Avoid: too deep
29
+ GET /order-items/789 # Preferred: reference directly if the ID is unique
30
+ ```
31
+
32
+ If a child resource can be uniquely identified without its parent, promote it to a top-level resource. Reserve nesting for expressing "belongs-to" relationships and narrowing list results.
33
+
34
+ ### Expressing Relationships
35
+
36
+ - Express a relationship lookup either as a sub-resource (`/users/123/orders`) or as a filter (`/orders?user_id=123`). If you offer both, make sure their behavior matches.
37
+ - Express many-to-many association/disassociation as POST/DELETE on a sub-resource.
38
+
39
+ ```
40
+ POST /articles/10/tags # body: {"tag_id": 5} to attach a tag
41
+ DELETE /articles/10/tags/5 # remove a tag
42
+ ```
43
+
44
+ - Keep embedded related data in responses to the bare minimum (roughly an ID and a display name), or only expand it when explicitly requested (e.g. via `?include=`). Embedding deep relations unconditionally is a common source of N+1 queries and bloated payloads.
45
+
46
+ ## 2. HTTP Methods and Status Codes
47
+
48
+ ### Choosing a Method
49
+
50
+ | Method | Purpose | Idempotent |
51
+ |---|---|---|
52
+ | GET | Retrieval. Must not have side effects | Yes |
53
+ | POST | Create, or execute a non-idempotent action | No |
54
+ | PUT | Replace the entire resource | Yes |
55
+ | PATCH | Partial update | Not guaranteed (depends on design) |
56
+ | DELETE | Delete | Yes |
57
+
58
+ ### When to Use Each Status Code
59
+
60
+ - **200 OK**: Successful retrieval or update. Response body present.
61
+ - **201 Created**: Successful creation. It's good practice to return the new resource's URL in the `Location` header.
62
+ - **204 No Content**: Success with no body. The standard response for a successful DELETE.
63
+ - **400 Bad Request**: The request is malformed syntactically (broken JSON, missing required parameters, etc.).
64
+ - **401 Unauthorized**: Not authenticated. Token missing, invalid, or expired.
65
+ - **403 Forbidden**: Authenticated, but not authorized for this action.
66
+ - **404 Not Found**: The resource doesn't exist.
67
+ - **409 Conflict**: A state conflict — unique constraint violation, optimistic lock failure, an operation that's already been processed, etc.
68
+ - **422 Unprocessable Entity**: Well-formed but semantically invalid (validation error). Decide once, per project, how you split this from 400.
69
+ - **500 Internal Server Error**: An unexpected server-side error. Log the details; never return them to the client.
70
+
71
+ ### Choosing Between 404 and 403
72
+
73
+ Return 404 when you don't want an unauthorized user to even know the resource exists (e.g. another tenant's data, an unpublished draft). Return 403 when the resource's existence is public knowledge and only the action is forbidden (e.g. no edit permission on a published article). The deciding question is: "would revealing existence itself be a data leak?" In multi-tenant APIs, default to 404.
74
+
75
+ ## 3. Error Responses
76
+
77
+ ### Use One Consistent Shape
78
+
79
+ Return the same error structure from every endpoint. At minimum, include a code, a message, and details.
80
+
81
+ ```json
82
+ {
83
+ "error": {
84
+ "code": "VALIDATION_ERROR",
85
+ "message": "There were errors in your input.",
86
+ "details": [
87
+ { "field": "email", "code": "invalid_format", "message": "Email address format is invalid." },
88
+ { "field": "age", "code": "out_of_range", "message": "Must be between 0 and 150." }
89
+ ]
90
+ }
91
+ }
92
+ ```
93
+
94
+ - `code` should be a machine-readable constant string. Clients should branch on the code, not on the message text.
95
+ - Return validation errors **per field** in a `details` array, so forms can display errors next to each field.
96
+ - Aligning with RFC 9457 (Problem Details, `application/problem+json`) is also a solid choice. Either way, standardize on one shape.
97
+
98
+ ### Don't Leak Internal Information
99
+
100
+ - Never include stack traces, raw SQL, file paths, or a library's raw exception message in a response.
101
+ - For 5xx errors, return only a generic message and a correlation ID (request ID); log the details server-side.
102
+ - On authentication failure, don't distinguish between "user doesn't exist" and "wrong password" in the response.
103
+
104
+ ### Implementation Example (NestJS)
105
+
106
+ ```typescript
107
+ // An exception filter that converts all errors into the common shape
108
+ @Catch()
109
+ export class AppExceptionFilter implements ExceptionFilter {
110
+ catch(exception: unknown, host: ArgumentsHost) {
111
+ const res = host.switchToHttp().getResponse();
112
+ if (exception instanceof HttpException) {
113
+ res.status(exception.getStatus()).json({ error: this.toErrorBody(exception) });
114
+ return;
115
+ }
116
+ // Unexpected exception: log the details, return a generic message to the client
117
+ this.logger.error(exception);
118
+ res.status(500).json({
119
+ error: { code: 'INTERNAL_ERROR', message: 'An internal server error occurred.' },
120
+ });
121
+ }
122
+ }
123
+ ```
124
+
125
+ In Django REST Framework, point the `EXCEPTION_HANDLER` setting at a custom handler that repacks `ValidationError.detail` into the common shape. In Laravel, customize `render` via `withExceptions` in `bootstrap/app.php` (or the exception Handler).
126
+
127
+ ## 4. Pagination
128
+
129
+ ### Choosing a Strategy
130
+
131
+ - **Offset-based** (`?page=2&per_page=20` / `?offset=20&limit=20`)
132
+ - Well suited to page-number UIs. Simple to implement.
133
+ - Downsides: gets slow at large offsets. Inserts/deletes mid-list cause duplicates or gaps.
134
+ - **Cursor-based** (`?cursor=eyJpZCI6MTIzfQ&limit=20`)
135
+ - Well suited to infinite scroll, frequently-updated lists, and large datasets.
136
+ - The cursor encodes an opaque key that uniquely identifies the last row returned (e.g. `created_at` + `id`).
137
+
138
+ Decision rule: if you need a total-count display and arbitrary page jumps, use offset-based. If the dataset is large, updated frequently, or performance-sensitive, use cursor-based.
139
+
140
+ ### Enforce a Limit Ceiling
141
+
142
+ The server must **always enforce an upper bound** on `limit`/`per_page` (e.g. default 20, max 100). Never use the client-supplied value as-is.
143
+
144
+ ```python
145
+ # Django REST Framework
146
+ class StandardPagination(PageNumberPagination):
147
+ page_size = 20
148
+ page_size_query_param = "per_page"
149
+ max_page_size = 100 # requests above this are clamped to 100
150
+ ```
151
+
152
+ Include metadata in the response: for offset-based, `total` / `page` / `per_page`; for cursor-based, `next_cursor` (null when there's no next page).
153
+
154
+ ## 5. Filtering, Sorting, and Search
155
+
156
+ - Express filters as query parameters: `GET /orders?status=shipped&user_id=123`
157
+ - Standardize a suffix convention for range conditions and stick to it: `?created_at_gte=2026-01-01&created_at_lt=2026-02-01` (or a `?created_at[gte]=...` style — pick one).
158
+ - For sorting, a comma-separated list with a `-` prefix for descending order is concise: `?sort=-created_at,name`.
159
+ - Route full-text/fuzzy search through `?q=keyword`, kept separate from structured filters.
160
+ - Enforce an **allowlist**. The server must explicitly enumerate which fields are filterable/sortable, and must never accept an arbitrary column name (this prevents SQL injection and unintended index scans).
161
+
162
+ ```php
163
+ // Laravel: restrict sortable fields with an allowlist
164
+ $sortable = ['created_at', 'name', 'price'];
165
+ $sort = $request->query('sort', '-created_at');
166
+ $direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
167
+ $column = ltrim($sort, '-');
168
+ abort_unless(in_array($column, $sortable, true), 400);
169
+ $query->orderBy($column, $direction);
170
+ ```
171
+
172
+ ## 6. Versioning
173
+
174
+ ### Policy
175
+
176
+ **Prefer URL-path versioning (`/v1/users`).** It's explicit, and it's easy to work with in routing, logging, caching, and documentation alike.
177
+
178
+ Options and decision criteria:
179
+
180
+ | Approach | Example | Characteristics |
181
+ |---|---|---|
182
+ | URL path | `/v1/users` | Most explicit. Recommended. Easy to inspect via browser or curl |
183
+ | Header | `Accept: application/vnd.api.v1+json` | Keeps the URL clean, but harder to debug and more costly to adopt |
184
+ | Query param | `/users?version=1` | Tends to complicate cache keys. Not recommended |
185
+
186
+ ### Operating Rules
187
+
188
+ - Version only the major number (`v1`, `v2`). Don't bump the version for backward-compatible changes (added fields, new endpoints).
189
+ - Only cut a new version for breaking changes (removed fields, type changes, making a field required, changed behavior).
190
+ - Announce a deprecation date for old versions and signal it via `Deprecation` / `Sunset` headers.
191
+ - For internal-only APIs, it's reasonable to skip versioning and deploy the client and server together. For public APIs, versioning is mandatory.
192
+
193
+ ## 7. Idempotency
194
+
195
+ - Implement **GET, PUT, and DELETE as idempotent**: sending the same request multiple times must leave the resource in the same state.
196
+ - A second DELETE may return 404 (the state — "doesn't exist" — is unchanged). If you'd rather keep the client logic simple, returning 204 again on the second call is also acceptable.
197
+ - **POST is not idempotent**. You need a defense against duplicate execution from network failures or retries (double charges, duplicate orders).
198
+
199
+ ### Idempotency Keys
200
+
201
+ The client sends a unique-per-request key (a UUID) in an `Idempotency-Key` header, and the server stores the key alongside the processing result for some retention window. On a retry with the same key, **return the stored response as-is**.
202
+
203
+ ```typescript
204
+ // NestJS: sketch of duplicate-POST protection via an idempotency key
205
+ @Post('payments')
206
+ async create(@Headers('idempotency-key') key: string, @Body() dto: CreatePaymentDto) {
207
+ if (!key) throw new BadRequestException('Idempotency-Key header is required');
208
+ const cached = await this.idempotencyStore.find(key);
209
+ if (cached) return cached.response; // Retry: return the stored result
210
+ const result = await this.payments.create(dto);
211
+ await this.idempotencyStore.save(key, result, { ttl: '24h' });
212
+ return result;
213
+ }
214
+ ```
215
+
216
+ - Store the key under a unique constraint, and prevent concurrent-execution races with a transaction or lock.
217
+ - This is mandatory for any POST where duplicate execution causes real harm — payments, orders, notification sends.
218
+
219
+ ## 8. Authentication and Authorization
220
+
221
+ ### Token Authentication Basics
222
+
223
+ - Send tokens via the `Authorization: Bearer <token>` header. Never put a token in a query parameter (it leaks into logs and referrers).
224
+ - Keep access tokens short-lived (minutes to an hour) and refresh them with a refresh token.
225
+ - 401 means "authentication failed"; 403 means "authenticated, but not authorized." Don't conflate them.
226
+
227
+ ### Per-Resource Authorization Checks
228
+
229
+ Keep authentication (who you are) separate from authorization (what you can do), and **always check ownership/permission against the specific resource being accessed**. Letting a request through on authentication alone, with direct object access by ID, is the textbook API vulnerability (IDOR / BOLA).
230
+
231
+ - Filter list endpoints at the query level. Filtering after fetching breaks pagination and result counts.
232
+ - When denying access to a single resource because the caller lacks permission, return 404 if you want to hide its existence (see section 2).
233
+ - For the authorization implementation mechanics and code examples (NestJS Guards, Laravel Policies, DRF `permission_classes`, etc.), see backend-patterns.
234
+
235
+ ## 9. Rate Limiting
236
+
237
+ - Limit request counts per user (or token, or IP). Apply stricter limits to unauthenticated endpoints.
238
+ - On exceeding the limit, return **429 Too Many Requests** with a `Retry-After` header telling the client how many seconds until it can retry.
239
+ - Report the current limit state in every response's headers.
240
+
241
+ ```
242
+ RateLimit-Limit: 100
243
+ RateLimit-Remaining: 42
244
+ RateLimit-Reset: 1718100000
245
+ Retry-After: 30 # only present on 429
246
+ ```
247
+
248
+ (The `X-RateLimit-*` header family is also widely used. Pick one convention per project.)
249
+
250
+ - Apply a separate, stricter limit to authentication-related endpoints (login, password reset) than to the rest of the API.
251
+ - For implementation mechanics (each framework's throttling middleware, etc.), see backend-patterns.
252
+
253
+ ## 10. Design Review Checklist
254
+
255
+ - [ ] Are paths plural nouns, with nesting no deeper than two levels?
256
+ - [ ] Does each endpoint's method and status codes (including 201/204/409/422) match its actual semantics?
257
+ - [ ] Do all endpoints return errors in the same shape? Are validation errors broken out per field?
258
+ - [ ] Do errors avoid leaking internal information (stack traces, SQL, file paths)?
259
+ - [ ] Does every list endpoint paginate, with the limit enforced server-side?
260
+ - [ ] Are filterable/sortable fields restricted to an allowlist?
261
+ - [ ] Is the versioning policy defined, including how breaking changes are handled?
262
+ - [ ] Do POSTs where duplicate execution causes real harm have idempotency-key protection?
263
+ - [ ] Is there object-level authorization on every resource (IDOR protection)? Is the 403/404 policy consistent?
264
+ - [ ] Is rate limiting configured, with 429 and RateLimit headers reported?
265
+
266
+ ## Related
267
+
268
+ - Use the `/code-review` command for post-implementation code review.
269
+ - Delegate detailed API implementation review criteria (security, performance, convention compliance) to code-reviewer subagents.