ai-guard-plugins 1.1.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/.github/workflows/publish.yml +42 -0
- package/.github/workflows/release.yml +156 -0
- package/CONTRIBUTING.md +56 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/install.sh +129 -0
- package/install.yaml +49 -0
- package/llms.txt +33 -0
- package/package.json +24 -0
- package/plugins/auto-lint-fix.ts +39 -0
- package/plugins/auto-summarize-input.ts +45 -0
- package/plugins/billing-guard.ts +101 -0
- package/plugins/capture-insights.ts +215 -0
- package/plugins/cline/hooks/PostToolUse +73 -0
- package/plugins/cline/hooks/PreToolUse +200 -0
- package/plugins/cline/hooks/TaskComplete +134 -0
- package/plugins/cline/hooks/UserPromptSubmit +29 -0
- package/plugins/cursor/rules/auto-lint-fix.mdc +21 -0
- package/plugins/cursor/rules/auto-summarize-input.mdc +17 -0
- package/plugins/cursor/rules/billing-guard.mdc +26 -0
- package/plugins/cursor/rules/capture-insights.mdc +32 -0
- package/plugins/cursor/rules/code-standards.mdc +77 -0
- package/plugins/cursor/rules/dry-guard.mdc +31 -0
- package/plugins/cursorrules-enforcer.ts +124 -0
- package/plugins/dry-guard.ts +107 -0
- package/plugins/kilo/rules/auto-lint-fix.md +15 -0
- package/plugins/kilo/rules/auto-summarize-input.md +12 -0
- package/plugins/kilo/rules/billing-guard.md +21 -0
- package/plugins/kilo/rules/capture-insights.md +27 -0
- package/plugins/kilo/rules/code-standards.md +71 -0
- package/plugins/kilo/rules/dry-guard.md +25 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
|
+
|
|
3
|
+
const SKIP_PATHS = ['node_modules', 'dist', '.next', 'build', '.turbo', 'migrations'];
|
|
4
|
+
const CODE_EXTS = new Set(['ts', 'tsx', 'js', 'jsx', 'mts', 'mjs', 'cts', 'cjs']);
|
|
5
|
+
const DOC_FILES = new Set(['readme.md', 'readme', 'changelog.md', 'contributing.md']);
|
|
6
|
+
|
|
7
|
+
interface Violation {
|
|
8
|
+
rule: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function check(content: string, filePath: string): Violation[] {
|
|
13
|
+
const violations: Violation[] = [];
|
|
14
|
+
const basename = filePath.split('/').pop() ?? '';
|
|
15
|
+
const ext = basename.includes('.') ? (basename.split('.').pop()?.toLowerCase() ?? '') : '';
|
|
16
|
+
const isIndex = /^index\.(ts|tsx|js|jsx|mts|mjs|cts|cjs)$/i.test(basename);
|
|
17
|
+
|
|
18
|
+
if (DOC_FILES.has(basename.toLowerCase())) {
|
|
19
|
+
violations.push({
|
|
20
|
+
rule: 'DOCUMENTATION',
|
|
21
|
+
message: `Creating ${basename}. Do not create documentation files unless explicitly requested.`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (basename.startsWith('_')) {
|
|
26
|
+
violations.push({
|
|
27
|
+
rule: 'UNDERSCORE FILE',
|
|
28
|
+
message: `Filename starts with underscore (${basename}). Do not use underscore prefixes.`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!CODE_EXTS.has(ext)) return violations;
|
|
33
|
+
|
|
34
|
+
if (/\bas\s+(any|unknown)\b/.test(content)) {
|
|
35
|
+
violations.push({
|
|
36
|
+
rule: 'TYPE CAST',
|
|
37
|
+
message: "'as any' or 'as unknown' found. Use the most specific correct type.",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (/\bas\s+[A-Z][A-Za-z]+/.test(content)) {
|
|
42
|
+
violations.push({
|
|
43
|
+
rule: 'TYPE ASSERTION',
|
|
44
|
+
message: "'as Type' found. Avoid type assertions — use type narrowing or generics instead.",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (/\brequire\s*\(/.test(content)) {
|
|
49
|
+
violations.push({ rule: 'REQUIRE', message: 'require() found. Use static imports.' });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (/@ts-ignore|@ts-expect-error/.test(content)) {
|
|
53
|
+
violations.push({
|
|
54
|
+
rule: 'TS SUPPRESSION',
|
|
55
|
+
message: '@ts-ignore or @ts-expect-error found. Fix the type error properly.',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (/\bTODO\b|\bFIXME\b|\bHACK\b|\bplaceholder\b|\/\/\s*later\b/i.test(content)) {
|
|
60
|
+
violations.push({
|
|
61
|
+
rule: 'PLACEHOLDER',
|
|
62
|
+
message: 'TODO/FIXME/placeholder/later found. Production-ready code only.',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (/(const|let|var|function)\s+_[a-zA-Z]/.test(content)) {
|
|
67
|
+
violations.push({
|
|
68
|
+
rule: 'UNDERSCORE NAMING',
|
|
69
|
+
message: 'Variable/function starting with underscore. Do not use underscore prefixes.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isIndex) {
|
|
74
|
+
const lines = content
|
|
75
|
+
.split('\n')
|
|
76
|
+
.filter(
|
|
77
|
+
(l) =>
|
|
78
|
+
l.trim() &&
|
|
79
|
+
!/^\s*(export\s+[{*]|export\s+type|export\s+default|import\s|\/\/|\/\*)/.test(l),
|
|
80
|
+
);
|
|
81
|
+
if (lines.length > 2) {
|
|
82
|
+
violations.push({
|
|
83
|
+
rule: 'INDEX LOGIC',
|
|
84
|
+
message: 'Logic found in index file. Index files may only re-export modules.',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!isIndex) {
|
|
90
|
+
const reexports = (content.match(/export\s+\{[^}]*\}\s+from\s+/g) || []).length;
|
|
91
|
+
if (reexports > 2) {
|
|
92
|
+
violations.push({ rule: 'RE-EXPORTS', message: 'Multiple re-exports in non-index file.' });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return violations;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const CursorrulesEnforcerPlugin: Plugin = async () => {
|
|
100
|
+
return {
|
|
101
|
+
'tool.execute.before': async (input, output) => {
|
|
102
|
+
if (input.tool !== 'write' && input.tool !== 'edit') return;
|
|
103
|
+
|
|
104
|
+
const args = (output?.args ?? input.args) as Record<string, unknown> | undefined;
|
|
105
|
+
if (!args) return;
|
|
106
|
+
|
|
107
|
+
const filePath = (args.filePath ?? args.file_path) as string | undefined;
|
|
108
|
+
if (!filePath) return;
|
|
109
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
110
|
+
if (SKIP_PATHS.some((p) => normalizedPath.split('/').includes(p))) return;
|
|
111
|
+
|
|
112
|
+
const content = (input.tool === "edit" ? (args.newString ?? args.new_string ?? "") : (args.content ?? "")) as string;
|
|
113
|
+
if (!content) return;
|
|
114
|
+
|
|
115
|
+
const violations = check(content, filePath);
|
|
116
|
+
if (violations.length === 0) return;
|
|
117
|
+
|
|
118
|
+
const msg = violations.map((v) => `- ${v.rule}: ${v.message}`).join('\n');
|
|
119
|
+
throw new Error(
|
|
120
|
+
`CURSORRULES VIOLATIONS in ${filePath.split('/').pop()}:\n${msg}\n\nFix these before proceeding.`,
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
|
+
|
|
3
|
+
const CODE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
4
|
+
const SKIP_PATHS = ['node_modules', 'dist', '.next', 'build', '.turbo', 'migrations'];
|
|
5
|
+
const SAFE_TOKEN = /^[a-z0-9]+$/;
|
|
6
|
+
|
|
7
|
+
function getExt(filePath: string): string {
|
|
8
|
+
const dot = filePath.lastIndexOf('.');
|
|
9
|
+
return dot === -1 ? '' : filePath.slice(dot);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function tokenize(filename: string): string[] {
|
|
13
|
+
const noExt = filename.replace(/\.[^.]+$/, '');
|
|
14
|
+
return noExt
|
|
15
|
+
.split(/[-_.]/)
|
|
16
|
+
.map((t) => t.toLowerCase())
|
|
17
|
+
.filter((t) => t.length >= 3 && SAFE_TOKEN.test(t));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function toPascalCase(name: string): string {
|
|
21
|
+
return name
|
|
22
|
+
.replace(/\.[^.]+$/, '')
|
|
23
|
+
.split(/[-_.]/)
|
|
24
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
25
|
+
.join('');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const DryGuardPlugin: Plugin = async ({ $, directory }) => {
|
|
29
|
+
return {
|
|
30
|
+
'tool.execute.before': async (input, output) => {
|
|
31
|
+
if (input.tool !== 'write') return;
|
|
32
|
+
|
|
33
|
+
const args = (output?.args ?? input.args) as Record<string, unknown> | undefined;
|
|
34
|
+
if (!args) return;
|
|
35
|
+
|
|
36
|
+
const filePath = (args.filePath ?? args.file_path) as string | undefined;
|
|
37
|
+
if (!filePath) return;
|
|
38
|
+
|
|
39
|
+
if (!CODE_EXTS.has(getExt(filePath))) return;
|
|
40
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
41
|
+
if (SKIP_PATHS.some((p) => normalizedPath.split('/').includes(p))) return;
|
|
42
|
+
|
|
43
|
+
const basename = filePath.split('/').pop() ?? '';
|
|
44
|
+
const rawDirname = filePath.split('/').slice(0, -1).join('/');
|
|
45
|
+
const dirname = rawDirname.startsWith('/') ? rawDirname : `${directory}/${rawDirname}`;
|
|
46
|
+
const tokens = tokenize(basename);
|
|
47
|
+
const projectDir = directory;
|
|
48
|
+
|
|
49
|
+
const warnings: string[] = [];
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const similarFiles = new Set<string>();
|
|
53
|
+
for (const token of tokens) {
|
|
54
|
+
const result =
|
|
55
|
+
await $`find ${projectDir} -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) ! -path "*/node_modules/*" ! -path "*/dist/*" ! -path "*/.next/*" ! -path "*/.turbo/*" 2>/dev/null | grep -Fi -- ${token} | grep -Fv -- ${filePath} | head -8`
|
|
56
|
+
.quiet()
|
|
57
|
+
.nothrow();
|
|
58
|
+
const found = String(result.stdout).trim();
|
|
59
|
+
if (found) {
|
|
60
|
+
for (const f of found.split('\n')) {
|
|
61
|
+
if (f.trim()) similarFiles.add(f.trim());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (similarFiles.size > 0) {
|
|
67
|
+
const files = [...similarFiles].slice(0, 10).join('\n');
|
|
68
|
+
warnings.push(
|
|
69
|
+
`SIMILAR FILES (${similarFiles.size} found matching tokens from '${basename}'):\n${files}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (dirname) {
|
|
74
|
+
const existing =
|
|
75
|
+
await $`find ${dirname} -maxdepth 1 -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) 2>/dev/null`
|
|
76
|
+
.quiet()
|
|
77
|
+
.nothrow();
|
|
78
|
+
const existingFiles = String(existing.stdout).trim().split('\n').filter(Boolean);
|
|
79
|
+
if (existingFiles.length > 0) {
|
|
80
|
+
warnings.push(
|
|
81
|
+
`TARGET DIR already has ${existingFiles.length} file(s):\n${existingFiles.slice(0, 15).join('\n')}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const likelyExport = toPascalCase(basename);
|
|
87
|
+
if (likelyExport.length >= 3 && SAFE_TOKEN.test(likelyExport.toLowerCase())) {
|
|
88
|
+
const collisions =
|
|
89
|
+
await $`grep -rEl -- "export.*${likelyExport}" ${projectDir}/src ${projectDir}/packages ${projectDir}/lib ${projectDir}/app 2>/dev/null | grep -Fv -- ${filePath} | grep -v node_modules | head -5`
|
|
90
|
+
.quiet()
|
|
91
|
+
.nothrow();
|
|
92
|
+
const found = String(collisions.stdout).trim();
|
|
93
|
+
if (found) {
|
|
94
|
+
warnings.push(`SYMBOL COLLISION — '${likelyExport}' already exported from:\n${found}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (warnings.length === 0) return;
|
|
102
|
+
|
|
103
|
+
const message = `[DRY-GUARD] Before creating ${basename}:\n\n${warnings.join('\n\n')}\n\nConsider extending an existing file instead of creating a new one.`;
|
|
104
|
+
console.warn(message);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Auto Lint Fix
|
|
2
|
+
|
|
3
|
+
After EVERY file write or edit to a TypeScript/JavaScript file, run:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
"${BIOME_PATH:-biome}" check --fix --unsafe <filepath>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- Only run on code files: .ts, .tsx, .js, .jsx, .mts, .mjs, .cts, .cjs
|
|
12
|
+
- Skip files in: node_modules, dist, .next, build, .turbo
|
|
13
|
+
- If `BIOME_PATH` environment variable is set, use that path instead of `biome`
|
|
14
|
+
- If Biome is not installed, skip formatting (do not fail)
|
|
15
|
+
- Do not manually fix formatting issues that Biome handles automatically
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Auto Summarize Input
|
|
2
|
+
|
|
3
|
+
When receiving input that exceeds 10,000 characters, summarize it before processing.
|
|
4
|
+
|
|
5
|
+
## Summarization rules
|
|
6
|
+
|
|
7
|
+
- Preserve ALL code snippets verbatim
|
|
8
|
+
- Preserve ALL file paths verbatim
|
|
9
|
+
- Preserve ALL error messages verbatim
|
|
10
|
+
- Preserve ALL requirements and specifications verbatim
|
|
11
|
+
- Remove ONLY: redundancy, repetition, and filler text
|
|
12
|
+
- Note the original character count and reduction percentage
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Billing Guard
|
|
2
|
+
|
|
3
|
+
If ANY API call, tool execution, or command output contains text matching these patterns, IMMEDIATELY stop all operations:
|
|
4
|
+
|
|
5
|
+
- "no payment method" or "add a payment method"
|
|
6
|
+
- "payment required"
|
|
7
|
+
- "account is not active" with "billing"
|
|
8
|
+
- "billing details"
|
|
9
|
+
- "quota exceeded"
|
|
10
|
+
- "insufficient funds"
|
|
11
|
+
- "rate limit" with "billing"
|
|
12
|
+
- "plan expired"
|
|
13
|
+
- "subscription inactive"
|
|
14
|
+
|
|
15
|
+
## Required behavior
|
|
16
|
+
|
|
17
|
+
1. STOP all current operations immediately
|
|
18
|
+
2. Do NOT retry the failed operation
|
|
19
|
+
3. Do NOT delegate or spawn sub-tasks
|
|
20
|
+
4. Notify the user that billing needs to be fixed before continuing
|
|
21
|
+
5. Do NOT attempt workarounds or alternative approaches — the billing issue must be resolved first
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Capture Insights
|
|
2
|
+
|
|
3
|
+
Monitor your own outputs and actions for these recurring anti-patterns. When detected, apply the corresponding lesson immediately.
|
|
4
|
+
|
|
5
|
+
## Tracked patterns
|
|
6
|
+
|
|
7
|
+
| Pattern | Lesson |
|
|
8
|
+
|---------|--------|
|
|
9
|
+
| DRY violation ("already exists", "duplicate") | Check existing files before creating new ones. Extract shared utils for clear reuse. |
|
|
10
|
+
| Unused code (imports, variables, dead code) | Biome --fix handles unused imports/variables automatically. |
|
|
11
|
+
| Type safety bypass (as any, ts-ignore) | Never use as any or ts-ignore. Use the most specific correct type. |
|
|
12
|
+
| Scope creep (refactoring while fixing) | Make the minimal change needed. Do not refactor while fixing. |
|
|
13
|
+
| Circular dependencies | Restructure module boundaries to break circular dependencies. |
|
|
14
|
+
| Re-export overuse (barrel files with logic) | Index files may only re-export modules, never contain logic. |
|
|
15
|
+
| Path resolution (process.cwd assumptions) | Never assume process.cwd() equals project root in monorepos. |
|
|
16
|
+
| Migration issues | Verify schema and data integrity after migrations. Make migrations idempotent. |
|
|
17
|
+
| Workarounds and hacks | Document why the workaround was needed and plan a proper fix. |
|
|
18
|
+
| Performance guessing | Profile before optimizing. Measure, do not guess. |
|
|
19
|
+
| Security (secrets, credentials, tokens) | Review credentials handling. Never commit secrets. |
|
|
20
|
+
| Test mock overreach | Mock behavior (modules), not implementation details. |
|
|
21
|
+
|
|
22
|
+
## Required behavior
|
|
23
|
+
|
|
24
|
+
When you detect one of these patterns in your own work:
|
|
25
|
+
1. Stop and acknowledge the pattern
|
|
26
|
+
2. Apply the corresponding lesson
|
|
27
|
+
3. Correct the action before proceeding
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Code Standards Enforcer
|
|
2
|
+
|
|
3
|
+
These rules apply to every code file write and edit. Violations must be fixed before proceeding.
|
|
4
|
+
|
|
5
|
+
## Type safety
|
|
6
|
+
|
|
7
|
+
- Never use `as any` or `as unknown`. Use the most specific correct type.
|
|
8
|
+
- Never use `as Type` assertions. Use type narrowing, generics, or satisfies instead.
|
|
9
|
+
- Never use `@ts-ignore` or `@ts-expect-error`. Fix the underlying type error.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Bad
|
|
13
|
+
const data = response as any;
|
|
14
|
+
const user = getUser() as UserType;
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
brokenCall();
|
|
17
|
+
|
|
18
|
+
// Good
|
|
19
|
+
const data: ApiResponse = response;
|
|
20
|
+
const user = getUser(); // return type already UserType
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Import style
|
|
24
|
+
|
|
25
|
+
- Never use `require()`. Use static `import` statements.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Bad
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
|
|
31
|
+
// Good
|
|
32
|
+
import fs from 'node:fs';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## No placeholders
|
|
36
|
+
|
|
37
|
+
- Never leave `TODO`, `FIXME`, `HACK`, `placeholder`, or `// later` comments. All code must be production-ready.
|
|
38
|
+
|
|
39
|
+
## Naming
|
|
40
|
+
|
|
41
|
+
- Never prefix variables or functions with underscore (`_`).
|
|
42
|
+
- Never prefix filenames with underscore.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Bad
|
|
46
|
+
const _count = 0;
|
|
47
|
+
function _helper() {}
|
|
48
|
+
|
|
49
|
+
// Good
|
|
50
|
+
const count = 0;
|
|
51
|
+
function helper() {}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Index files
|
|
55
|
+
|
|
56
|
+
- Index/barrel files (`index.ts`, `index.js`) may ONLY contain re-exports. No business logic.
|
|
57
|
+
- Non-index files may have at most 2 re-export statements.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Good index.ts
|
|
61
|
+
export { UserService } from './user-service';
|
|
62
|
+
export { AuthService } from './auth-service';
|
|
63
|
+
|
|
64
|
+
// Bad index.ts
|
|
65
|
+
export { UserService } from './user-service';
|
|
66
|
+
const cache = new Map(); // logic does not belong here
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Documentation files
|
|
70
|
+
|
|
71
|
+
- Do not create README.md, CHANGELOG.md, or CONTRIBUTING.md unless explicitly requested.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# DRY Guard
|
|
2
|
+
|
|
3
|
+
Before creating ANY new code file (.ts, .tsx, .js, .jsx), perform these checks:
|
|
4
|
+
|
|
5
|
+
## 1. Similar file search
|
|
6
|
+
|
|
7
|
+
Tokenize the new filename by splitting on `-`, `_`, and `.` (ignore tokens shorter than 3 characters). Search the project for existing files whose names contain any of those tokens.
|
|
8
|
+
|
|
9
|
+
Skip directories: node_modules, dist, .next, build, .turbo, migrations
|
|
10
|
+
|
|
11
|
+
## 2. Target directory check
|
|
12
|
+
|
|
13
|
+
List existing code files in the target directory. If the directory already contains files, consider whether the new file's logic belongs in one of them.
|
|
14
|
+
|
|
15
|
+
## 3. Symbol collision check
|
|
16
|
+
|
|
17
|
+
Convert the filename to PascalCase (e.g., `format-date.ts` becomes `FormatDate`). Search `src/`, `packages/`, `lib/`, and `app/` for existing exports matching that name.
|
|
18
|
+
|
|
19
|
+
## Required behavior
|
|
20
|
+
|
|
21
|
+
- If similar files are found: review them before creating a new file
|
|
22
|
+
- If the symbol already exists: extend the existing file instead
|
|
23
|
+
- If the target directory has related files: consolidate rather than create
|
|
24
|
+
|
|
25
|
+
Do NOT create a new file when extending an existing one would be cleaner.
|