@plinng/ai-code-assistant-tools 1.0.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 +100 -0
- package/claude-code/CLAUDE.md +28 -0
- package/claude-code/rules/architecture.md +60 -0
- package/claude-code/rules/branch-naming.md +45 -0
- package/claude-code/rules/clean-architecture.md +57 -0
- package/claude-code/rules/design-patterns.md +42 -0
- package/claude-code/rules/error-handling.md +61 -0
- package/claude-code/rules/solid-principles.md +44 -0
- package/cursor/rules/architecture.mdc +48 -0
- package/cursor/rules/clean-architecture.mdc +49 -0
- package/cursor/rules/design-patterns.mdc +43 -0
- package/cursor/rules/error-handling.mdc +45 -0
- package/cursor/rules/solid-principles.mdc +40 -0
- package/cursor/rules/universal.mdc +28 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @plinng/ai-code-assistant-tools
|
|
2
|
+
|
|
3
|
+
Versioned, modular AI coding standards for the Plinng platform. Provides
|
|
4
|
+
behavioral and architectural rules for Claude Code and Cursor that encode
|
|
5
|
+
what linters and formatters cannot: architectural intent, error philosophy,
|
|
6
|
+
and design discipline.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What this is
|
|
11
|
+
|
|
12
|
+
These are **not** linter rules. ESLint, Prettier, Ruff, and Biome handle
|
|
13
|
+
formatting and style deterministically at zero LLM token cost. These rules
|
|
14
|
+
encode the decisions that static analysis has no reach into:
|
|
15
|
+
|
|
16
|
+
- Which layer is responsible for what
|
|
17
|
+
- When and how to handle errors
|
|
18
|
+
- Which design patterns to prefer and why.
|
|
19
|
+
- SOLID principles and how to apply them in this codebase.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Rules included
|
|
24
|
+
|
|
25
|
+
| Rule file | Tool | Activated when | Enforces |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `CLAUDE.md` / `universal.mdc` | Both | Always | How to work, commits, task runner, error + architecture summary |
|
|
28
|
+
| `error-handling.md/mdc` | Both | `**/*.ts`, `**/*.py` | No silent catches; structured boundary logging; propagation discipline |
|
|
29
|
+
| `architecture.md/mdc` | Both | `**/handlers/**`, `**/resolvers/**`, `**/routes/**`, `**/app/**` | Layer ordering; no DB client in handlers; factory injection |
|
|
30
|
+
| `solid-principles.md/mdc` | Both | `**/*.ts`, `**/*.tsx` | SRP, OCP, LSP, ISP, DIP; don't refactor violations unless asked |
|
|
31
|
+
| `clean-architecture.md/mdc` | Both | `**/*.ts`, `**/*.py` | Four-layer model; domain types from repositories; coupling detector |
|
|
32
|
+
| `design-patterns.md/mdc` | Both | Always (Claude) / Agent Requested (Cursor) | Propose pattern + rationale before implementing |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
### Interactive CLI
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @plinng/ai-code-assistant-tools
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The installer will ask which AI assistant you use (Claude Code / Cursor / Both)
|
|
45
|
+
and, for Cursor, whether to install at project scope or user scope. Existing
|
|
46
|
+
files are backed up automatically as `<file>.backup-<timestamp>`
|
|
47
|
+
|
|
48
|
+
### Manual install — Claude Code
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cp claude-code/CLAUDE.md ~/.claude/CLAUDE.md
|
|
52
|
+
cp claude-code/rules/*.md ~/.claude/rules/
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Manual install — Cursor (project scope)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cp -r cursor/rules/ .cursor/rules/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Cursor scope note
|
|
64
|
+
|
|
65
|
+
**Project scope** (`.cursor/rules/`) is the reliably enforced path for teams.
|
|
66
|
+
Rules installed there are version-controlled and apply to every developer who
|
|
67
|
+
opens the project in Cursor.
|
|
68
|
+
|
|
69
|
+
**User scope** (`~/.cursor/rules/`) is a filesystem convention. Cursor reads
|
|
70
|
+
user rules from its Settings UI (plain text), not from a filesystem path
|
|
71
|
+
directly. To apply user rules from this install, paste the content of each
|
|
72
|
+
`.mdc` file into `Cursor Settings → General → Rules for AI`, or use Cursor
|
|
73
|
+
Team Rules (requires Cursor Team/Enterprise plan) for enforced team standards.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Updating
|
|
78
|
+
|
|
79
|
+
Re-run the installer to get the latest rules. Existing files will be backed up
|
|
80
|
+
before overwriting:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @plinng/ai-code-assistant-tools
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Contributing
|
|
89
|
+
|
|
90
|
+
Before adding or modifying a rule, verify it meets this checklist:
|
|
91
|
+
|
|
92
|
+
1. **Cannot be enforced by a linter** — if ESLint/Ruff can catch it, it belongs in tool config, not here
|
|
93
|
+
2. **Imperative with motivation** — "Use X because Y", not "Consider using X"
|
|
94
|
+
3. **Both `.md` and `.mdc` updated** — keep Claude Code and Cursor rule content in sync
|
|
95
|
+
4. **Line count passes** — run `moon run ai-code-assistant-tools:lint-rules`
|
|
96
|
+
5. **PR to platform team** — rules affect all engineers; review is required
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
moon run ai-code-assistant-tools:lint-rules
|
|
100
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Plinng Platform — AI Coding Standards
|
|
2
|
+
|
|
3
|
+
## How to work
|
|
4
|
+
- Only make changes directly requested. A bug fix does not need surrounding code cleaned up.
|
|
5
|
+
- Do not design for hypothetical future requirements. Three similar lines beat a premature abstraction.
|
|
6
|
+
- Never push to the remote repository without an explicit human request.
|
|
7
|
+
- Use Conventional Commits: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:`.
|
|
8
|
+
- Run `moon run <project>:build` and verify no type errors before committing.
|
|
9
|
+
- Do not add docstrings, comments, or type annotations to code you did not change.
|
|
10
|
+
|
|
11
|
+
## Error handling
|
|
12
|
+
- Never hide exceptions with fallback returns (`catch (e) { return null }`).
|
|
13
|
+
- At system boundaries (HTTP handlers, queue consumers, cron jobs): log structured metadata `{ operation, identifiers, err }` then re-throw.
|
|
14
|
+
- Inside service and domain layers: let errors propagate — do not catch and re-wrap without adding value.
|
|
15
|
+
- Never add try/catch inside a repository or service unless translating a third-party error into a domain error.
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
- Handler → Service → Repository → Data source. Never skip layers.
|
|
19
|
+
- Never import a database client (Prisma, Mongoose, pg) in a resolver, handler, or route file.
|
|
20
|
+
- Use the repository pattern: data access belongs in `*Repository` or `*Store` modules only.
|
|
21
|
+
- Inject dependencies via factory function arguments (`createHandler({ userRepo, logger })`), not global imports.
|
|
22
|
+
- Handlers are stateless coordinators — no business logic, no data transformation beyond shaping the HTTP response
|
|
23
|
+
|
|
24
|
+
## This monorepo
|
|
25
|
+
- Task runner: Moon (`moon run <project>:<task>`).
|
|
26
|
+
- Python services use `uv` for dependency management.
|
|
27
|
+
- Stack-specific rules load automatically from `~/.claude/rules/` when matching file paths are open.
|
|
28
|
+
- Never modify `moon.yml` task definitions without discussing with the platform team first.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: "**/resolvers/**,**/handlers/**,**/routes/**,**/app/**"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Architecture — handlers, resolvers, and routes
|
|
6
|
+
|
|
7
|
+
The dependency flow is strictly one direction:
|
|
8
|
+
Handler/Resolver → Service → Repository → Data source
|
|
9
|
+
|
|
10
|
+
Never skip or invert layers.
|
|
11
|
+
|
|
12
|
+
## Handlers are stateless coordinators
|
|
13
|
+
|
|
14
|
+
A handler's only responsibilities are:
|
|
15
|
+
1. Parse and validate the incoming request
|
|
16
|
+
2. Call the appropriate service method
|
|
17
|
+
3. Shape the service result into an HTTP response
|
|
18
|
+
|
|
19
|
+
Handlers must not contain business logic, domain rules, or data transformation
|
|
20
|
+
beyond mapping service output to response shape.
|
|
21
|
+
|
|
22
|
+
## Never access data sources from handlers or resolvers
|
|
23
|
+
|
|
24
|
+
Never import Prisma, Mongoose, pg, a Redis client, or any external service SDK
|
|
25
|
+
directly into a handler, resolver, or route file.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Violation — do not do this
|
|
29
|
+
import { prisma } from '../db/client';
|
|
30
|
+
|
|
31
|
+
export async function getOrderHandler(req: Request, res: Response) {
|
|
32
|
+
const order = await prisma.order.findUnique({ where: { id: req.params.id } });
|
|
33
|
+
res.json(order);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Correct — delegate to service, which delegates to repository
|
|
37
|
+
export function createOrderHandler({ orderService }: { orderService: OrderService }) {
|
|
38
|
+
return async (req: Request, res: Response) => {
|
|
39
|
+
const order = await orderService.getOrder(req.params.id);
|
|
40
|
+
res.json(order);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Inject dependencies via factory functions
|
|
46
|
+
|
|
47
|
+
Use the `create*` factory pattern to inject services and repositories. Do not
|
|
48
|
+
rely on module-level singletons or global imports for mutable dependencies.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Factory injection pattern
|
|
52
|
+
export function createUserHandler({ userService, logger }: UserHandlerDeps) {
|
|
53
|
+
return {
|
|
54
|
+
getUser: async (req: Request, res: Response) => { ... },
|
|
55
|
+
createUser: async (req: Request, res: Response) => { ... },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This makes handlers testable without module mocking.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Branch naming standard for git branch creation and rename in production-line-microservice
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Branch Naming
|
|
7
|
+
|
|
8
|
+
## Format
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
<prefix>/<short-description>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Use **kebab-case**, all **lowercase**, and aim for **3-6 words** in the description.
|
|
15
|
+
|
|
16
|
+
## Valid Prefixes
|
|
17
|
+
|
|
18
|
+
| Prefix | Purpose | Example |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `feat/` | New feature or capability | `feat/upsell-task-cleanup` |
|
|
21
|
+
| `fix/` | Bug fix | `fix/duplicate-task-assignment` |
|
|
22
|
+
| `refactor/` | Code restructuring (no behavior change) | `refactor/di-container-cleanup` |
|
|
23
|
+
| `docs/` | Documentation only | `docs/update-readme-setup` |
|
|
24
|
+
|
|
25
|
+
## Rules
|
|
26
|
+
|
|
27
|
+
- **Always use `feat/`** - not `feature/` (historical branches may use `feature/` but new ones must not).
|
|
28
|
+
- Keep descriptions concise but descriptive enough to identify the work.
|
|
29
|
+
- Avoid generic names like `feat/update` or `fix/bug`.
|
|
30
|
+
|
|
31
|
+
## Quick Validation
|
|
32
|
+
|
|
33
|
+
- Prefix is one of: `feat/`, `fix/`, `refactor/`, `docs/`
|
|
34
|
+
- Description is lowercase kebab-case
|
|
35
|
+
- Description has 3-6 meaningful words
|
|
36
|
+
- No `feature/` prefix in new branches
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
feat/webhook-retry-logic
|
|
42
|
+
fix/task-repository-query-race
|
|
43
|
+
refactor/use-case-error-handling
|
|
44
|
+
docs/api-versioning-guide
|
|
45
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: "**/*.ts,**/*.py"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Clean architecture — layering rules
|
|
6
|
+
|
|
7
|
+
The codebase follows a four-layer architecture. Dependencies point inward only.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Handler / Controller (outermost — HTTP, queues, cron)
|
|
11
|
+
↓
|
|
12
|
+
Service (business rules, orchestration)
|
|
13
|
+
↓
|
|
14
|
+
Repository (data access abstraction)
|
|
15
|
+
↓
|
|
16
|
+
Data source (Prisma, Mongoose, pg, Redis, external APIs)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Layer responsibilities
|
|
20
|
+
|
|
21
|
+
**Handler**: HTTP parsing, auth context extraction, input validation, response
|
|
22
|
+
shaping. No business logic. Delegates immediately to a service.
|
|
23
|
+
|
|
24
|
+
**Service**: Business rules, domain validation, orchestration across multiple
|
|
25
|
+
repositories. Receives and returns domain types — never raw DB documents.
|
|
26
|
+
|
|
27
|
+
**Repository**: Encapsulates all queries for a single aggregate. Returns typed
|
|
28
|
+
domain objects. Handles DB-specific errors and translates them to domain errors.
|
|
29
|
+
|
|
30
|
+
**Data source**: The actual client (Prisma, Mongoose). Only imported inside
|
|
31
|
+
repository files.
|
|
32
|
+
|
|
33
|
+
## Coupling violation detector
|
|
34
|
+
|
|
35
|
+
If a single file imports from both an HTTP framework (`express`, `fastapi`,
|
|
36
|
+
`hono`) and a database client (`prisma`, `mongoose`, `pg`, `motor`), it is a
|
|
37
|
+
coupling violation. Split it along layer boundaries.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Violation — handler importing DB client directly
|
|
41
|
+
import { Request, Response } from 'express'; // HTTP layer
|
|
42
|
+
import { prisma } from '../db/client'; // Data layer — not allowed here
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Services receive domain types, not raw DB documents
|
|
46
|
+
|
|
47
|
+
Repositories must map database results to domain types before returning them.
|
|
48
|
+
Services must never access raw Prisma/Mongoose document fields (`_id`,
|
|
49
|
+
`__v`, `createdAt` in raw object form) — only typed domain properties.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Repository — maps raw result to domain type
|
|
53
|
+
async findById(id: string): Promise<User> {
|
|
54
|
+
const doc = await prisma.user.findUniqueOrThrow({ where: { id } });
|
|
55
|
+
return { id: doc.id, email: doc.email, name: doc.name }; // domain type
|
|
56
|
+
}
|
|
57
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Design patterns
|
|
2
|
+
|
|
3
|
+
Before implementing a non-trivial solution, propose the applicable design
|
|
4
|
+
pattern with a one-sentence rationale explaining why it fits. State the
|
|
5
|
+
pattern name explicitly. Do not propose patterns for simple, single-purpose
|
|
6
|
+
functions — only for solutions involving multiple collaborators, varying
|
|
7
|
+
behavior, or extension points.
|
|
8
|
+
|
|
9
|
+
## Patterns to consider and when
|
|
10
|
+
|
|
11
|
+
**Repository** — When multiple parts of the codebase need to query the same
|
|
12
|
+
data source. Centralizes query logic and allows the data source to be swapped.
|
|
13
|
+
|
|
14
|
+
**Factory** — When object creation involves conditional logic, multiple
|
|
15
|
+
dependencies, or configuration. Use `createX({ dep1, dep2 })` functions
|
|
16
|
+
instead of constructor-heavy classes.
|
|
17
|
+
|
|
18
|
+
**Strategy** — When a behavior varies based on context (e.g., different
|
|
19
|
+
payment processors, notification channels, export formats). Pass the strategy
|
|
20
|
+
as a dependency rather than branching with `if/switch`.
|
|
21
|
+
|
|
22
|
+
**Observer / Event emitter** — When an action in one part of the system
|
|
23
|
+
should trigger reactions in decoupled parts. Prefer domain events over direct
|
|
24
|
+
coupling between services.
|
|
25
|
+
|
|
26
|
+
**Decorator** — When adding cross-cutting concerns (logging, caching, retry,
|
|
27
|
+
auth) to an existing interface without modifying the underlying implementation.
|
|
28
|
+
|
|
29
|
+
## Format for pattern proposals
|
|
30
|
+
|
|
31
|
+
Before writing implementation code, state:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Pattern: Repository
|
|
35
|
+
Why: Order data is accessed from three services; centralizing queries
|
|
36
|
+
prevents duplication and lets us swap the ORM in tests.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then proceed with implementation only after the pattern is clear.
|
|
40
|
+
|
|
41
|
+
Do not propose patterns speculatively — only when there is a concrete,
|
|
42
|
+
present need in the current task.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: "**/*.ts,**/*.py"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Error handling
|
|
6
|
+
|
|
7
|
+
Never catch an error and return null, undefined, or a default value. Silent
|
|
8
|
+
catches hide bugs and make debugging impossible. Always let the error surface.
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// Anti-pattern — never do this
|
|
12
|
+
async function getUser(id: string) {
|
|
13
|
+
try {
|
|
14
|
+
return await userRepo.findById(id);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return null; // caller can't distinguish "not found" from "DB exploded"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Correct — propagate at domain layer
|
|
21
|
+
async function getUser(id: string) {
|
|
22
|
+
return await userRepo.findById(id); // let errors propagate
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
At system boundaries (HTTP handlers, queue consumers, cron jobs), catch errors
|
|
27
|
+
once, log structured metadata, then re-throw or respond with an error status:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// HTTP handler boundary
|
|
31
|
+
async function handleGetUser(req: Request, res: Response) {
|
|
32
|
+
try {
|
|
33
|
+
const user = await userService.getUser(req.params.id);
|
|
34
|
+
res.json(user);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
logger.error({ operation: 'getUser', userId: req.params.id, err });
|
|
37
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Python equivalent — boundary catch with structured log, then re-raise or
|
|
43
|
+
return an error response:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# FastAPI route boundary
|
|
47
|
+
@router.get("/users/{user_id}")
|
|
48
|
+
async def get_user(user_id: str, service: UserService = Depends(get_user_service)):
|
|
49
|
+
try:
|
|
50
|
+
return await service.get_user(user_id)
|
|
51
|
+
except Exception as err:
|
|
52
|
+
logger.error({"operation": "get_user", "user_id": user_id, "error": str(err)})
|
|
53
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Do not add try/catch inside services or repositories unless you are
|
|
57
|
+
translating a third-party library error into a typed domain error. In that
|
|
58
|
+
case, wrap once at the outermost repository method and throw a domain error.
|
|
59
|
+
|
|
60
|
+
Only add error handling at system boundaries — one catch per entry point, not
|
|
61
|
+
one catch per function call.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: "**/*.ts,**/*.tsx"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# SOLID principles
|
|
6
|
+
|
|
7
|
+
## Single Responsibility Principle
|
|
8
|
+
A module has one reason to change. A class or function that handles HTTP
|
|
9
|
+
parsing, business logic, AND database access has three reasons to change —
|
|
10
|
+
split it. If you cannot describe a module's purpose without "and", it violates SRP.
|
|
11
|
+
|
|
12
|
+
## Open/Closed Principle
|
|
13
|
+
Extend behavior via composition, not by modifying existing code. Add new
|
|
14
|
+
capabilities by creating new modules or passing new dependencies — do not
|
|
15
|
+
reach into existing implementations and add branches. Prefer strategy objects
|
|
16
|
+
and dependency injection over `if (featureFlag)` conditions in core logic.
|
|
17
|
+
|
|
18
|
+
## Liskov Substitution Principle
|
|
19
|
+
Subtypes must honor the behavioral contract of the type they extend. If a
|
|
20
|
+
function accepts a `Repository` interface, every concrete implementation must
|
|
21
|
+
satisfy the same pre/postconditions. Never throw additional errors, accept
|
|
22
|
+
fewer inputs, or return narrower output types than the interface declares.
|
|
23
|
+
|
|
24
|
+
## Interface Segregation Principle
|
|
25
|
+
Define narrow, role-specific interfaces. A `UserRepository` interface should
|
|
26
|
+
not include email-sending methods. Callers should depend only on the interface
|
|
27
|
+
members they actually use — split large interfaces by caller role.
|
|
28
|
+
|
|
29
|
+
## Dependency Inversion Principle
|
|
30
|
+
High-level modules (services, domain logic) must depend on abstractions
|
|
31
|
+
(interfaces, type aliases), not on concrete implementations (Prisma client,
|
|
32
|
+
specific HTTP framework). Pass concrete implementations via factory function
|
|
33
|
+
arguments or constructor injection so they can be swapped in tests.
|
|
34
|
+
|
|
35
|
+
## Legacy code and SOLID violations
|
|
36
|
+
If you encounter existing code that violates SOLID principles, do NOT refactor
|
|
37
|
+
it unless explicitly asked to do so. Add a comment noting the violation and
|
|
38
|
+
continue with the requested change:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// TODO: SRP violation — this handler also contains business logic; refactor when scope permits
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Unsolicited refactoring introduces risk and scope creep.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: "**/resolvers/**,**/handlers/**,**/routes/**,**/app/**"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Architecture — handlers, resolvers, and routes
|
|
7
|
+
|
|
8
|
+
The dependency flow is strictly one direction:
|
|
9
|
+
Handler/Resolver → Service → Repository → Data source
|
|
10
|
+
|
|
11
|
+
Never skip or invert layers.
|
|
12
|
+
|
|
13
|
+
## Handlers are stateless coordinators
|
|
14
|
+
|
|
15
|
+
A handler's only responsibilities:
|
|
16
|
+
1. Parse and validate the incoming request
|
|
17
|
+
2. Call the appropriate service method
|
|
18
|
+
3. Shape the service result into an HTTP response
|
|
19
|
+
|
|
20
|
+
No business logic, domain rules, or data transformation beyond response shaping.
|
|
21
|
+
|
|
22
|
+
## Never access data sources from handlers or resolvers
|
|
23
|
+
|
|
24
|
+
Never import Prisma, Mongoose, pg, Redis, or any external service SDK directly
|
|
25
|
+
into a handler, resolver, or route file.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Violation — do not do this
|
|
29
|
+
import { prisma } from '../db/client';
|
|
30
|
+
export async function getOrderHandler(req, res) {
|
|
31
|
+
const order = await prisma.order.findUnique({ where: { id: req.params.id } });
|
|
32
|
+
res.json(order);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Correct — factory injection, delegate to service
|
|
36
|
+
export function createOrderHandler({ orderService }: { orderService: OrderService }) {
|
|
37
|
+
return async (req: Request, res: Response) => {
|
|
38
|
+
const order = await orderService.getOrder(req.params.id);
|
|
39
|
+
res.json(order);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Inject dependencies via factory functions
|
|
45
|
+
|
|
46
|
+
Use the `create*` factory pattern. Do not rely on module-level singletons or
|
|
47
|
+
global imports for mutable dependencies. This makes handlers testable without
|
|
48
|
+
module mocking.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: "**/*.ts,**/*.py"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Clean architecture — layering rules
|
|
7
|
+
|
|
8
|
+
Four layers, dependencies point inward only:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Handler / Controller (HTTP, queues, cron)
|
|
12
|
+
↓
|
|
13
|
+
Service (business rules, orchestration)
|
|
14
|
+
↓
|
|
15
|
+
Repository (data access abstraction)
|
|
16
|
+
↓
|
|
17
|
+
Data source (Prisma, Mongoose, pg, Redis, external APIs)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Layer responsibilities
|
|
21
|
+
|
|
22
|
+
**Handler**: HTTP parsing, auth extraction, input validation, response shaping.
|
|
23
|
+
No business logic. Delegates to a service immediately.
|
|
24
|
+
|
|
25
|
+
**Service**: Business rules, domain validation, orchestration across repositories.
|
|
26
|
+
Receives and returns domain types — never raw DB documents.
|
|
27
|
+
|
|
28
|
+
**Repository**: Encapsulates all queries for one aggregate. Returns typed domain
|
|
29
|
+
objects. Translates DB errors to domain errors.
|
|
30
|
+
|
|
31
|
+
**Data source**: The actual client. Only imported inside repository files.
|
|
32
|
+
|
|
33
|
+
## Coupling violation detector
|
|
34
|
+
|
|
35
|
+
If a file imports from both an HTTP framework (`express`, `fastapi`, `hono`)
|
|
36
|
+
and a database client (`prisma`, `mongoose`, `pg`, `motor`), it is a coupling
|
|
37
|
+
violation. Split it along layer boundaries.
|
|
38
|
+
|
|
39
|
+
## Services receive domain types, not raw DB documents
|
|
40
|
+
|
|
41
|
+
Repositories must map database results to domain types before returning them.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Repository — maps to domain type
|
|
45
|
+
async findById(id: string): Promise<User> {
|
|
46
|
+
const doc = await prisma.user.findUniqueOrThrow({ where: { id } });
|
|
47
|
+
return { id: doc.id, email: doc.email, name: doc.name };
|
|
48
|
+
}
|
|
49
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Design patterns — propose applicable patterns before implementing complex solutions involving multiple collaborators, varying behavior, or extension points"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Design patterns
|
|
7
|
+
|
|
8
|
+
Before implementing a non-trivial solution, propose the applicable design
|
|
9
|
+
pattern with a one-sentence rationale explaining why it fits. State the
|
|
10
|
+
pattern name explicitly. Do not propose patterns for simple, single-purpose
|
|
11
|
+
functions.
|
|
12
|
+
|
|
13
|
+
## Patterns to consider and when
|
|
14
|
+
|
|
15
|
+
**Repository** — Multiple parts of the codebase query the same data source.
|
|
16
|
+
Centralizes query logic and allows the data source to be swapped.
|
|
17
|
+
|
|
18
|
+
**Factory** — Object creation involves conditional logic, multiple dependencies,
|
|
19
|
+
or configuration. Use `createX({ dep1, dep2 })` functions.
|
|
20
|
+
|
|
21
|
+
**Strategy** — Behavior varies based on context (payment processors, notification
|
|
22
|
+
channels, export formats). Pass the strategy as a dependency.
|
|
23
|
+
|
|
24
|
+
**Observer / Event emitter** — An action should trigger reactions in decoupled
|
|
25
|
+
parts. Prefer domain events over direct coupling between services.
|
|
26
|
+
|
|
27
|
+
**Decorator** — Adding cross-cutting concerns (logging, caching, retry, auth)
|
|
28
|
+
to an existing interface without modifying the underlying implementation.
|
|
29
|
+
|
|
30
|
+
## Format for pattern proposals
|
|
31
|
+
|
|
32
|
+
Before writing implementation code, state:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Pattern: Repository
|
|
36
|
+
Why: Order data is accessed from three services; centralizing queries
|
|
37
|
+
prevents duplication and lets us swap the ORM in tests.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then proceed with implementation only after the pattern is clear.
|
|
41
|
+
|
|
42
|
+
Do not propose patterns speculatively — only when there is a concrete,
|
|
43
|
+
present need in the current task.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: "**/*.ts,**/*.py"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error handling
|
|
7
|
+
|
|
8
|
+
Never catch an error and return null, undefined, or a default value. Silent
|
|
9
|
+
catches hide bugs and make debugging impossible. Always let the error surface.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Anti-pattern — never do this
|
|
13
|
+
async function getUser(id: string) {
|
|
14
|
+
try {
|
|
15
|
+
return await userRepo.findById(id);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
return null; // caller can't distinguish "not found" from "DB exploded"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Correct — propagate at domain layer
|
|
22
|
+
async function getUser(id: string) {
|
|
23
|
+
return await userRepo.findById(id);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
At system boundaries (HTTP handlers, queue consumers, cron jobs), catch once,
|
|
28
|
+
log structured metadata `{ operation, identifiers, err }`, then re-throw or
|
|
29
|
+
respond with an error status.
|
|
30
|
+
|
|
31
|
+
Python boundary example:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
@router.get("/users/{user_id}")
|
|
35
|
+
async def get_user(user_id: str, service: UserService = Depends(get_user_service)):
|
|
36
|
+
try:
|
|
37
|
+
return await service.get_user(user_id)
|
|
38
|
+
except Exception as err:
|
|
39
|
+
logger.error({"operation": "get_user", "user_id": user_id, "error": str(err)})
|
|
40
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Only add error handling at system boundaries — one catch per entry point, not
|
|
44
|
+
one catch per function call. Do not add try/catch inside services or
|
|
45
|
+
repositories unless translating a third-party error into a domain error.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: "**/*.ts,**/*.tsx"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SOLID principles
|
|
7
|
+
|
|
8
|
+
## Single Responsibility Principle
|
|
9
|
+
A module has one reason to change. If you cannot describe a module's purpose
|
|
10
|
+
without "and", it violates SRP. Split handler, business logic, and data access
|
|
11
|
+
into separate layers.
|
|
12
|
+
|
|
13
|
+
## Open/Closed Principle
|
|
14
|
+
Extend behavior via composition, not by modifying existing code. Add new
|
|
15
|
+
capabilities with new modules or dependencies — do not add branches to core
|
|
16
|
+
logic. Prefer strategy objects and dependency injection over feature flags
|
|
17
|
+
inside implementations.
|
|
18
|
+
|
|
19
|
+
## Liskov Substitution Principle
|
|
20
|
+
Subtypes must honor the behavioral contract of the type they extend. Every
|
|
21
|
+
concrete implementation of a `Repository` interface must satisfy the same
|
|
22
|
+
pre/postconditions. Never throw additional errors or return narrower types
|
|
23
|
+
than the interface declares.
|
|
24
|
+
|
|
25
|
+
## Interface Segregation Principle
|
|
26
|
+
Define narrow, role-specific interfaces. Callers should depend only on the
|
|
27
|
+
interface members they actually use. Split large interfaces by caller role.
|
|
28
|
+
|
|
29
|
+
## Dependency Inversion Principle
|
|
30
|
+
High-level modules depend on abstractions (interfaces, type aliases), not on
|
|
31
|
+
concrete implementations. Pass concrete implementations via factory function
|
|
32
|
+
arguments so they can be swapped in tests.
|
|
33
|
+
|
|
34
|
+
## Legacy code and SOLID violations
|
|
35
|
+
If you encounter existing code that violates SOLID, do NOT refactor it unless
|
|
36
|
+
explicitly asked. Add a comment noting the violation and continue:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// TODO: SRP violation — handler contains business logic; refactor when scope permits
|
|
40
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Plinng Platform — Universal Rules
|
|
6
|
+
|
|
7
|
+
## How to work
|
|
8
|
+
- Only make changes directly requested. A bug fix does not need surrounding code cleaned up.
|
|
9
|
+
- Do not design for hypothetical future requirements. Three similar lines beat a premature abstraction.
|
|
10
|
+
- Never push to the remote repository without an explicit human request.
|
|
11
|
+
- Use Conventional Commits: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:`.
|
|
12
|
+
- Run `moon run <project>:build` and verify no type errors before committing.
|
|
13
|
+
- Do not add docstrings, comments, or type annotations to code you did not change.
|
|
14
|
+
|
|
15
|
+
## Task runner
|
|
16
|
+
- This monorepo uses Moon as the task runner: `moon run <project>:<task>`.
|
|
17
|
+
- Python services use `uv` for dependency management.
|
|
18
|
+
- Never modify `moon.yml` task definitions without discussing with the platform team first.
|
|
19
|
+
|
|
20
|
+
## Error handling summary
|
|
21
|
+
- Never hide exceptions with fallback returns (`catch (e) { return null }`).
|
|
22
|
+
- At system boundaries: log `{ operation, identifiers, err }` then re-throw.
|
|
23
|
+
- Inside service and domain layers: let errors propagate.
|
|
24
|
+
|
|
25
|
+
## Architecture summary
|
|
26
|
+
- Handler → Service → Repository → Data source. Never skip layers.
|
|
27
|
+
- Never import a database client in a handler, resolver, or route file.
|
|
28
|
+
- Use factory function injection: `createHandler({ userRepo, logger })`.
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plinng/ai-code-assistant-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI installer for Plinng platform AI coding standards",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ai-code-assistant-tools": "./bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json && node scripts/add-shebang.js",
|
|
10
|
+
"release": "npm run build && npm publish"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"claude-code/",
|
|
15
|
+
"cursor/"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"typescript": "^5.7.0"
|
|
26
|
+
}
|
|
27
|
+
}
|